Forum: Compiler & IDEs Vier gewinnt


von Peter Z. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,
ich fange an, ein vier gewinnt Spiel zu programmieren.
Das Spielfeld ist 7 Spalten mal 6 Zeilen gross.
Die Abfrage ob 4 Steine einer Farbe waagerecht in einer Reihe sind mache 
ich so:
1
if(((Zeile1 & 0x78)==0x78)||((Zeile1 & 0x3C)==0x3C)||((Zeile1 & 0x1E)==0x1E)||((Zeile1 & 0x0F)==0x0F))return(vier_in_reihe);
2
if(((Zeile2 & 0x78)==0x78)||((Zeile2 & 0x3C)==0x3C)||((Zeile2 & 0x1E)==0x1E)||((Zeile2 & 0x0F)==0x0F))return(vier_in_reihe);
3
if(((Zeile3 & 0x78)==0x78)||((Zeile3 & 0x3C)==0x3C)||((Zeile3 & 0x1E)==0x1E)||((Zeile3 & 0x0F)==0x0F))return(vier_in_reihe);
4
if(((Zeile4 & 0x78)==0x78)||((Zeile4 & 0x3C)==0x3C)||((Zeile4 & 0x1E)==0x1E)||((Zeile4 & 0x0F)==0x0F))return(vier_in_reihe);
5
if(((Zeile5 & 0x78)==0x78)||((Zeile5 & 0x3C)==0x3C)||((Zeile5 & 0x1E)==0x1E)||((Zeile5 & 0x0F)==0x0F))return(vier_in_reihe);
6
if(((Zeile6 & 0x78)==0x78)||((Zeile6 & 0x3C)==0x3C)||((Zeile6 & 0x1E)==0x1E)||((Zeile6 & 0x0F)==0x0F))return(vier_in_reihe);
Ist das schon ganz gut, oder kann man das eleganter lösen?
Es kommt mir dabei besonders auf die Geschwindigkeit des Programmes an.

von asdf (Gast)


Lesenswert?

guck dir arrays an (Felder)

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Es kommt mir dabei besonders auf die Geschwindigkeit des Programmes an.

willst du computer gegen computer spielen lassen? Wenn noch ein Mensch 
daran beteiligt ist, brauchst du dir wirklich keine Gedanken wegen der 
geschwindigkeit zu machen.

> oder kann man das eleganter lösen?
auf jeden Fall. Stell dir mal vor du sollst dein Programm mal schnell 
auf 8 spalten umschreiben.

von Roger (Gast)


Lesenswert?

Peter Zz schrieb:
> if(((Zeile1 & 0x78)==0x78)||((Zeile1 & 0x3C)==0x3C)||((Zeile1 &
> 0x1E)==0x1E)||((Zeile1 & 0x0F)==0x0F))return(vier_in_reihe);

Ungetestet aus dem Ärmel:
1
for(i = 0; i < 6; i++) {
2
    for(j = 0; j < 4; j++) {
3
        if((zeile[i] & (0x0F << j)) == (0x0F << j)) {
4
            return vier_in_reihe;
5
        }
6
    }
7
}

von Karl H. (kbuchegg)


Lesenswert?

asdf schrieb:
> guck dir arrays an (Felder)


Exakt.
Vor allen Dingen auch deshalb, weil diese Abfrage ja sowieso nur die 
Viertel-Miete ist. 4 gewinnt ist ja nicht ausschliesslich dann zu Ende, 
wenn 4 Steine in einer Zeile zu finden sind.

ABgesehen davon.
4 gewinnt ist ein 2 Personen Spiel. In einer derartigen Abfrage würde 
ich irgendwie - nun ja - die Farbe der Steine erwarten. 4 Steine 
nebeneinander reicht ja nicht. Sie müssen auch von derselben Farbe sein.

von Peter Z. (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> In einer derartigen Abfrage würde
> ich irgendwie - nun ja - die Farbe der Steine erwarten.
1
#define vier_in_reihe 1
2
#define nicht_4_in_reihe 0
3
#define rot 0
4
#define gelb 1
5
unsigned char Spielfeld[84];
6
7
8
9
//*********************************************************************
10
unsigned char Gewinn_test(unsigned char Feldnummer,unsigned char Farbe)
11
{
12
unsigned char Zeiger = (Feldnummer * 12)+(Farbe * 6);
13
unsigned char Zeile1 = Spielfeld[Zeiger + 0];
14
unsigned char Zeile2 = Spielfeld[Zeiger + 1];
15
unsigned char Zeile3 = Spielfeld[Zeiger + 2];
16
unsigned char Zeile4 = Spielfeld[Zeiger + 3];
17
unsigned char Zeile5 = Spielfeld[Zeiger + 4];
18
unsigned char Zeile6 = Spielfeld[Zeiger + 5];

von Karl H. (kbuchegg)


Lesenswert?

Peter Zz schrieb:

> //*********************************************************************
> unsigned char Gewinn_test(unsigned char Feldnummer,unsigned char Farbe)
> {
> unsigned char Zeiger = (Feldnummer * 12)+(Farbe * 6);

d.h. du hast 2 Arrays, für jede Farbe eine?
Ja, kann man machen.
Aber warum so kompliziert?

Wenn du konzeptionell 2 Arrays hast, dann mach doch gleich 2 Arrays. Was 
bringt es dir, wenn du wegen jedem Krempel da jedesmal auseinander 
dividieren musst? Ausser das sich alles verkompliziert, sehe ich da 
keinen wirklichen Vorteil.

Dann kannst du dir diese ganze Auseinanderklamüserei in die Zeilen 
sparen. Wo du doch so auf Speed erpicht bist.

von Karl H. (kbuchegg)


Lesenswert?

Moment:
Wie entstehen eigentlich die 12 da drinnen?

d.h. du hast eigentlich pro Spieler 7 Arrays?

Denn für eine komplette Darstellung des Spielfeldes mittels Bits 
brauchst du 6 Bytes.
84 / 6 = 14 Spielfelder. Es gibt 2 Spieler, daher 7 gespeicherte 
Spielfelder pro Spieler.

wärs da nicht einfacher
1
typedef unsigned char SpielFeld[6];
2
3
struct Spieler_
4
{
5
  SpielFeld Spielfelder[7];
6
};
zu machen, also erst mal ein bischen in Datenstrukturen zu investieren, 
und dann
1
unsigned char Gewinn_test( struct Spieler_* player, unsigned char Feldnummer )
2
{
3
  return Gewinn_test_Feld( player->Spielfelder[Feldnummer] );
4
}
5
6
unsigned char Gewinn_test_Feld( SpielFeld Feld )
7
{
8
  // untersuche ob in Feld eine Gewinnsituation vorliegt
9
}


Einfach alles unstrukturiert in ein großes Array zu stopfen, ist auch 
nicht immer der Weisheit letzter Schluss. Dann verplempert man einen 
Haufen Zeit damit, immer wieder Array-Indizes auszurechnen, für nichts 
und wieder nichts.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Ich hab mal vor Jahren (erfolglos) versucht, 4 gewinnt mit neuronalen 
Netzwerken zu programmieren.

Ein "Abfallprodukt" davon waren recht schnelle C-Datenstrukturen und 
Algorithmen für Gewinner-Abfragen. Allerdings keine leichte Kost... (so 
long long spielereien, war noch 32bit damals)

von Peter Z. (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wie entstehen eigentlich die 12 da drinnen?

Das Array ist so aufgeteilt:
6 char für rot   Feld 0
6 char für gelb  Feld 0
6 char für rot   Feld 1
6 char für gelb  Feld 1
6 char für rot   Feld 2
6 char für gelb  Feld 2
6 char für rot   Feld 3
6 char für gelb  Feld 3
usw.
Der µC muss ja mehrere z.B. 7 Spielzüge vorausberechnen (Brute Force),
deshalb so viele Spielfelder und der Wunsch das das Programm schnell 
sein soll.


Ich habe die 3 Funktionen:
1
unsigned char Gewinn_test(unsigned char Feldnummer,unsigned char Farbe)
2
unsigned char Stein_setzen(unsigned char Feldnummer,unsigned char Spalte,unsigned char Farbe)
3
void Feld_kopieren(unsigned char Feldnummer)

von Karl H. (kbuchegg)


Lesenswert?

Peter Zz schrieb:

> Der µC muss ja mehrere z.B. 7 Spielzüge vorausberechnen (Brute Force),
> deshalb so viele Spielfelder

So was ähnliches hab ich mir schon gedacht

> und der Wunsch das das Programm schnell
> sein soll.

Dann erst recht.
Wozu jedesmal durch diese ganze Indexrechnerei durch, um aus dem großen 
Komplett-Array genau die Teile rauszuholen, die in der jeweiligen 
Situation relevant sind, wenn du dir das ganze auch so organisieren 
kannst, dass du diese Aufteilerei überhaupt nicht brauchst, weil sie 
schon durch die Datenstruktur vorab vom Compiler erledigt wurde?

von Peter Z. (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wozu jedesmal durch diese ganze Indexrechnerei durch

Das sehe ich im Moment gar nicht als sooo grosse Belastung an.
Ich denke meine Gewinnabfrage ist so schon ganz gut. Oder?
1
//*********************************************************************
2
unsigned char Gewinn_test(unsigned char Feldnummer,unsigned char Farbe)
3
{
4
unsigned char Zeiger = (Feldnummer * 12)+(Farbe * 6);
5
unsigned char Zeile1 = Spielfeld[Zeiger++];
6
unsigned char Zeile2 = Spielfeld[Zeiger++];
7
unsigned char Zeile3 = Spielfeld[Zeiger++];
8
unsigned char Zeile4 = Spielfeld[Zeiger++];
9
unsigned char Zeile5 = Spielfeld[Zeiger++];
10
unsigned char Zeile6 = Spielfeld[Zeiger++];
11
//Test ob waagerecht 4 Steine einer Farbe nebeneinander liegen
12
if(((Zeile6 & 0x78)==0x78)||((Zeile6 & 0x3C)==0x3C)||((Zeile6 & 0x1E)==0x1E)||((Zeile6 & 0x0F)==0x0F))return(vier_in_reihe);
13
if(((Zeile5 & 0x78)==0x78)||((Zeile5 & 0x3C)==0x3C)||((Zeile5 & 0x1E)==0x1E)||((Zeile5 & 0x0F)==0x0F))return(vier_in_reihe);
14
if(((Zeile4 & 0x78)==0x78)||((Zeile4 & 0x3C)==0x3C)||((Zeile4 & 0x1E)==0x1E)||((Zeile4 & 0x0F)==0x0F))return(vier_in_reihe);
15
if(((Zeile3 & 0x78)==0x78)||((Zeile3 & 0x3C)==0x3C)||((Zeile3 & 0x1E)==0x1E)||((Zeile3 & 0x0F)==0x0F))return(vier_in_reihe);
16
if(((Zeile2 & 0x78)==0x78)||((Zeile2 & 0x3C)==0x3C)||((Zeile2 & 0x1E)==0x1E)||((Zeile2 & 0x0F)==0x0F))return(vier_in_reihe);
17
if(((Zeile1 & 0x78)==0x78)||((Zeile1 & 0x3C)==0x3C)||((Zeile1 & 0x1E)==0x1E)||((Zeile1 & 0x0F)==0x0F))return(vier_in_reihe);
18
//Test ob senkrecht 4 Steine einer Farbe übereinander sind
19
if(Zeile3 & Zeile4 & Zeile5 & Zeile6)return(vier_in_reihe);
20
if(Zeile2 & Zeile3 & Zeile4 & Zeile5)return(vier_in_reihe);
21
if(Zeile1 & Zeile2 & Zeile3 & Zeile4)return(vier_in_reihe);
22
//Test ob in der einen Diagonalen 4 Steine einer Farbe sind
23
if(Zeile3 & (Zeile4 << 1) & (Zeile5 << 2) & (Zeile6 << 3))return(vier_in_reihe);
24
if(Zeile2 & (Zeile3 << 1) & (Zeile4 << 2) & (Zeile5 << 3))return(vier_in_reihe);
25
if(Zeile1 & (Zeile2 << 1) & (Zeile3 << 2) & (Zeile4 << 3))return(vier_in_reihe);
26
//Test ob in der anderen Diagonalen 4 Steine einer Farbe sind
27
if(Zeile3 & (Zeile4 >> 1) & (Zeile5 >> 2) & (Zeile6 >> 3))return(vier_in_reihe);
28
if(Zeile2 & (Zeile3 >> 1) & (Zeile4 >> 2) & (Zeile5 >> 3))return(vier_in_reihe);
29
if(Zeile1 & (Zeile2 >> 1) & (Zeile3 >> 2) & (Zeile4 >> 3))return(vier_in_reihe);
30
31
return(nicht_4_in_reihe);
32
}  
33
//*********************************************************************

von Mirko (Gast)


Lesenswert?

Peter Zz schrieb:
> Ich denke meine Gewinnabfrage ist so schon ganz gut. Oder?

Finde ich nicht. Aus meiner Sicht sehr unelegant, viel zu umständlich 
und sobald mehr Zeilen oder Spalten hinzukommen muss man viel Arbeit 
investieren um den Code anzupassen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Peter Zz schrieb:
> Ich denke meine Gewinnabfrage ist so schon ganz gut. Oder?

Für das oder schreibt man in Der Regel einen Unit-Test.
Ansonsten: Wie soll man mit vertretbarem Aufwand das aus deinem 
Zahlen, schiebe und Magic-Numbers gewusel das in Sinnvoller Zeit 
bewerten?
Schaff da doch erst mal eine sinnvolle Datenstruktur dann kann man das 
auch besser bewerten.

Ein Algorithmus hat zuerst mal korrekt zu sein, und dann kann man (in 
95% der Fälle durch Optimierung des Algorithmus) sich Gedanken über die 
Geschwindigkeit machen.

Peter Zz schrieb:
> Der µC muss ja [...] (Brute Force),

Bei Speilen, welche Regeln folgen nutzt man idr nicht BruteForce 
sondern Backtracking und ggf. Zugtabellen.

Karl Heinz Buchegger schrieb:
> Denn für eine komplette Darstellung des Spielfeldes mittels
> Bits  brauchst du 6 Bytes. 84 / 6 = 14 Spielfelder

Ich zähl auf dem Bild 6x7 = 42 (oh oh...) mögliche Positionen, wieso 
nicht einfach ein Array[6][7] und dann 0 = unbelegt, 1 = rot, 2 = 
gelb...
Wegen der paar Bytes und Speicherzugriffe ist es dann auch nicht schade, 
das ganze Rechnen und maskieren verbrät auch Zeit.

von Jens (Gast)


Lesenswert?

Hi Peter

Ich stimme Läubi vollkommen zu.

Läubi .. schrieb:
> Schaff da doch erst mal eine sinnvolle Datenstruktur dann kann man das
> auch besser bewerten.

Für einen schnellen Algorithmus solltest du dir überlegen, wann man 
gewonnen hat. Bei 4-Gewinnt ist es so, dass man nur gewinnen kann wenn 
man vier Steine senkrecht in einer Spalte hat, oder einen Stein in der 
Mitte besitzt!

Diese Überlegung gibt auch die optimale Gewinnstrategie vor. Bei 
4-Gewinnt wirft man IMMER in die mittlere Spalte es sei denn der andere 
hat 3 in einer Reihe und würde sonst gewinnen.

Gruß
Jens

von Detlev T. (detlevt)


Lesenswert?

Ich würde das so machen:

Es gibt für jedes Feld ja nur drei Zustände: kein Stein, rot oder gelb. 
Das kodiere ich in 2 Bits 00 = leer, 01 = rot,10 = gelb.
Jede Zeile fasse ich zu einem 16-Bit-Wert zusammen. Ich muss dann diesen 
Wert nur noch dreimal mit sich selbst UND verknüpfen, vobei ich den 
zweiten Wert jedesmal vorher um 2 Bit nach links shifte. Kommt da am 
Ende 0x0000 heraus, gibt es noch keine 4er Reihe.

Schneller dürfte es kaum gehen.

von Peter II (Gast)


Lesenswert?

Detlev T. schrieb:
> Schneller dürfte es kaum gehen.

hast du es getestet? Ich würde auch je feld ein byte machen. Da shiften 
ist für einen Atmel auch nicht so einfach, er kann nur jeweil am 1 
schiften. 2 links schifts sind also auch 2 enweisungen.

von avr (Gast)


Lesenswert?

Peter II schrieb:
> Da shiften
> ist für einen Atmel auch nicht so einfach, er kann nur jeweil am 1
> schiften. 2 links schifts sind also auch 2 enweisungen.

Das shiften ist hier kein Problem, da man dreimal shiften muss und jedes 
Zwischenergebnis benötigt.

von Peter II (Gast)


Lesenswert?

avr schrieb:
> Das shiften ist hier kein Problem, da man dreimal shiften muss und jedes
> Zwischenergebnis benötigt.

aber nur wenn genug register zum Merken vorhanden sind. Im schlimmsten 
fall macht es es mehrfach.

> if(Zeile3 & (Zeile4 >> 1) & (Zeile5 >> 2) & (Zeile6 >> 3))return(vier_in_reihe);

hier wird ganz schön viel umgeschiftet.

von avr (Gast)


Lesenswert?

Jens schrieb:
> Diese Überlegung gibt auch die optimale Gewinnstrategie vor. Bei
> 4-Gewinnt wirft man IMMER in die mittlere Spalte es sei denn der andere
> hat 3 in einer Reihe und würde sonst gewinnen.

Das ist Schwachsinn. Lese dich mal in 4-Gewinnt Strategien ein. Bei 
4-Gewinnt spielt man auf Zugzwang. Die mittlere Reihe ist wichtig, aber 
viel wichtiger ist es 3 Reihen an der richtigen Position aufzubauen und 
natürlich zu verhindern das der Gegner diese "richtigen" Reihen baut. So 
schlägt man übrigens schon die meisten menschlichen Spieler, selbst mit 
geringer Suchtiefe.

von avr (Gast)


Lesenswert?

Peter II schrieb:
> aber nur wenn genug register zum Merken vorhanden sind. Im schlimmsten
> fall macht es es mehrfach.

Deswegen würde ich bei einer solchen Optimierung gleich zu Assembler 
greifen. Mit Makros wird das ganze maximal doppelt so lang.

von Peter II (Gast)


Lesenswert?

avr schrieb:
> Deswegen würde ich bei einer solchen Optimierung gleich zu Assembler
> greifen. Mit Makros wird das ganze maximal doppelt so lang.

auch mit ASM hast dir nicht unendlich viele Register frei. Ich sehe hier 
für die paar Felder überhaupt kein Zeitproblem. Das erkennen ob jemand 
gewonnen hat lässt sich sehr gut optimieren. Man braucht z.b. die 
unteren reihen nicht zu prüfen wenn sich dort nichts ändert. Und das 
lässt dich mit C einfacher machen als mit ASM.

von Peter II (Gast)


Lesenswert?

Nachtrag:

wenn man darüber nachdenkt, dann würde ich sogar nur an der Stelle 
prüfen wo der letzte Stein abelegt wurde. Dann nur dort kann es einen 
gewinner geben. Von dem letzten stein aus kann es nur 7 richtungen geben 
wo ein gewinn möglich ist.

von Abstauber (Gast)


Lesenswert?

Wieso prüfst du immer das kommplette Spielfeld? Es würde doch reichen 
erst einmal die Umgebung des letzten eingeworfennen Steines zu Prüfen.

von Abstauber (Gast)


Lesenswert?

Peter II schrieb:
> Nachtrag:
>
> wenn man darüber nachdenkt, dann würde ich sogar nur an der Stelle
> prüfen wo der letzte Stein abelegt wurde. Dann nur dort kann es einen
> gewinner geben. Von dem letzten stein aus kann es nur 7 richtungen geben
> wo ein gewinn möglich ist.

Da war woll jemand schneller ^^. Es gibt aber 8 mögliche Richtungen

von Peter II (Gast)


Lesenswert?

Abstauber schrieb:
> Es gibt aber 8 mögliche Richtungen

nö, dann über den Stein kann ja nichts liegen.

von Abstauber (Gast)


Angehängte Dateien:

Lesenswert?

Peter II schrieb:
> Abstauber schrieb:
>> Es gibt aber 8 mögliche Richtungen
>
> nö, dann über den Stein kann ja nichts liegen.

Na klar stimmt ;)

Hab mir mal n paar gedanken gemacht...
Es gibt um jeden stein 7 möglichkeiten für einen derselben Farbe. Wenn 
du mit einem zweidimensionalen Array arbeitest könntest du dir sozusagen 
die Richtiguns Vektoren definieren. Dan die Position des Steins + 
Vektor*Iteration = zu Prüfendes Feld. Ist dort keine gleiche Farbe kann 
man diese Richtung vergessen und bei der nächsten "Umgebungs Ebene" 
nicht mehr mit einbeziehen.
Bei der nächsten Ebene musst du nur noch den Vektor mal zwei nehmen und 
hast die Position des Nächsten Steins und kannst ihn Prüfen. Wenn nach 
drei Iterationen noch ein Vektor übrig ist hast du deine Gewinn 
Situation.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Abstauber schrieb:
> Wieso prüfst du immer das kommplette Spielfeld

Weil vor lauter "Optimierungszwang" der TE halt schon den Blick für das 
tatsächliche Problem verloren hat...

von Peter Z. (Gast)


Lesenswert?

Läubi .. schrieb:
> Abstauber schrieb:
>> Wieso prüfst du immer das kommplette Spielfeld
>
> Weil vor lauter "Optimierungszwang" der TE halt schon den Blick für das
> tatsächliche Problem verloren hat...

Ich versuche mal, meine Lösung zu verteidigen:
Die Funktion Gewinn_test soll testen,
ob nach einem realen oder einem simulierten Zug die soeben 
eingeworfene Farbe gewinnt.
Wenn also z.B. soeben eine rote Scheibe eingeworfen wurde, wird getestet 
ob Rot jetzt irgendwo im Spielfeld vier Steine in einer Reihe hat.
Ein Test, ob Gelb vier Steine in Reihe hat, ist erst notwendig, 
nachdem Gelb einen Stein eingeworfen hat.
Da dieser Test nur aus einer absehbaren Anzahl boolscher Operationen 
und Schiebeoperationen besteht, ist der Test vermutlich gar nicht so 
ineffizient.

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Da dieser Test nur aus einer absehbaren Anzahl boolscher Operationen
> und Schiebeoperationen besteht, ist der Test vermutlich gar nicht so
> ineffizient.

doch wenn er über das gesamt spielfeld sucht. Nur weil es es bool 
liefert heist das noch lange nicht das es schnell ist.

Extrem beispiel:

Wenn der 1. Stein abgelegt wird, bräuchte mal überhaupt nicht suchen.

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> Extrem beispiel:
>
> Wenn der 1. Stein abgelegt wird, bräuchte man überhaupt nicht suchen.

Da hast du genau recht.
Und wenn der 2. Stein abgelegt wird brauche ich auch nicht zu suchen.
Und so könnte es 1000 Ausnahmen und Regeln geben, die man alle suchen 
und dann programmieren müsste. Mein Ansatz, den ich verfolge, hat nur 
diese eine Gewinn_test Routine die immer wieder aufgerufen wird.

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Mein Ansatz, den ich verfolge, hat nur
> diese eine Gewinn_test Routine die immer wieder aufgerufen wird.

dann mache doch einen Ansatz und du wirst merken das sie im durchschnitt 
mindestens 10mal langsamer ist als ein Ansatz wo nur die aktuelle 
Position bewertet wird.

Deine Routine ist einfach nicht wartbar, lesbar und dann auch noch 
schwer optimierbar. Aber sammle ruhig deine eigenen Erfahrungen.

von Karl H. (kbuchegg)


Lesenswert?

Peter Zz schrieb:

> Und so könnte es 1000 Ausnahmen und Regeln geben, die man alle suchen
> und dann programmieren müsste. Mein Ansatz, den ich verfolge, hat nur
> diese eine Gewinn_test Routine die immer wieder aufgerufen wird.

Ich finde den ganzen Ansatz mit den Spieler-Bitmaps eigentlich gar nicht 
so schlecht. Das interssante dabei ist, dass dieser Code das komplette 
Spielfeld auf irgendeine Gewinnsituation mit relativ wenigen Befehlen 
abklappern kann. Eine Abfrage in einer klassischen 2D-Matrix, ausgehend 
von einer bestimmten Position dürfte auch nicht scheller zu realisieren 
sein.

Ein paar Dinge würde ich anders machen. Die Sache mit der 
Datenorganisation hab ich ja schon angesprochen.
Aber auch solche Sachen
1
if(Zeile3 & (Zeile4 << 1) & (Zeile5 << 2) & (Zeile6 << 3))return(vier_in_reihe);

Die Shifts kann man auch anders anordnen (Horner-Schema)
1
if( ((((((Zeile6) << 1) & Zeile5) << 1) & Zeile4) << 1) & Zeile3 )
2
  return vier_in_reihe;

und ist damit von 6 Shifts auf 3 Shifts runtergekommen.

von Peter II (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Eine Abfrage in einer klassischen 2D-Matrix, ausgehend
> von einer bestimmten Position dürfte auch nicht scheller zu realisieren
> sein.

ich denke schon, denn wenn keine gewinn Situation vorherrst muss er alle 
vergleiche durchgehen. Wenn man von der letzen position ausgeht, dann 
kann man viel schneller festellen das kein Gewinn möglich ist. Wenn man 
jetzt davon ausgeht das ein Gewinn seltener ist als ein nicht Gewinn 
dann sollte man so programmieren das der "nicht gewinn" schneller 
erkannt wird.

von Karl H. (kbuchegg)


Lesenswert?

Peter II schrieb:
> Karl Heinz Buchegger schrieb:
>> Eine Abfrage in einer klassischen 2D-Matrix, ausgehend
>> von einer bestimmten Position dürfte auch nicht scheller zu realisieren
>> sein.
>
> ich denke schon, denn wenn keine gewinn Situation vorherrst muss er alle
> vergleiche durchgehen. Wenn man von der letzen position ausgeht, dann
> kann man viel schneller festellen das kein Gewinn möglich ist. Wenn man
> jetzt davon ausgeht das ein Gewinn seltener ist als ein nicht Gewinn
> dann sollte man so programmieren das der "nicht gewinn" schneller
> erkannt wird.

Deine Indexrechnerei um in der Matrix die 3 darüber liegenden, 3 
darunter liegenden, 3 Diagonal linksoben, 3 Diagonal rechtsoben, 3 
diagonal linksunten, 3 diagonal rechtsunten liegenden Felder zu finden 
und jeweils zu summieren, wenn sie die richtige Farbe haben, kosten dir 
auch was.

Man müsste die Alternativen durchprobieren und in realen Spielen 
durchprobieren was in Summe gesehen tatsächlich schneller ist.

Denn sowas
1
if(Zeile3 & Zeile4 & Zeile5 & Zeile6)return(vier_in_reihe);
2
if(Zeile2 & Zeile3 & Zeile4 & Zeile5)return(vier_in_reihe);
3
if(Zeile1 & Zeile2 & Zeile3 & Zeile4)return(vier_in_reihe);
ist ziemlich schwer zu schlagen. Wenn der Compiler es schafft die Werte 
in Registern vorzuhalten, dann sind das 3 Blöcke mit jeweils 3 
Und-Operationen, und einem bedingten Sprung.
Diagonalen sind fast ähnlich unaufwändig.

Nicht zu vergessen: Seine Methode ist recht speichersparend. Und das 
kann bei Rekursionstiefen von 14 (2*7) schon mit ein Argument sein.

von Peter II (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Nicht zu vergessen: Seine Methode ist recht speichersparend. Und das
> kann bei Rekursionstiefen von 14 (2*7) schon mit ein Argument sein.

warum sollte der speicherverbauch abhängig von der Rekursionstiefen 
sein? (ok ein paar stack variablen, aber doch bitte nicht das Array 
kopieren!)

in der Rekusion wird einfach der Stein gesetzt, dann getestet und dann 
der stein wieder weggenommen. Dann an der nächste stelle der stein 
probiert.

Damit spart man sich das umkopieren das arrays.

von Karl H. (kbuchegg)


Lesenswert?

Peter II schrieb:
> Karl Heinz Buchegger schrieb:
>> Nicht zu vergessen: Seine Methode ist recht speichersparend. Und das
>> kann bei Rekursionstiefen von 14 (2*7) schon mit ein Argument sein.
>
> warum sollte der speicherverbauch abhängig von der Rekursionstiefen
> sein? (ok ein paar stack variablen, aber doch bitte nicht das Array
> kopieren!)

da hast du recht. Da hab ich jetzt nicht nachgedacht.

von Karl H. (kbuchegg)


Lesenswert?

Wobei ich mich gerade frage
(Man merkt schon, mit 4 gewinnt hab ich mich noch nie beschäftigt)

Gibt es eigentlich bei 4 gewinnt Stellungs-Bewertungsregeln? Also 
ähnlich wie im Schach, bei dem man ja auch nicht aufs blaue hinein 
einfach alle Zweige durchrechnet, sondern eine Stellungsbewertung macht 
um rauszufinden, welche Zweige des Baumes gut sind und welche nicht.

Oder geht hier tatsächlich nur: rekursiv das Spielfeld anfüllen und der 
'beste' Zug ist der, mit dem man am Ende in die meisten 
Gewinnsituationen kommt?

von Peter II (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Gibt es eigentlich bei 4 gewinnt Stellungs-Bewertungsregeln?

keine Ahnung, man könnte versuchen die anzahl der Möglichen 
gewinnsitiuatioen zu bestimmen. Wenn man also 2 x 3 Steine senkrecht 
hat, dann ist es viel besser als wenn man sie nicht hat. Also die Anzahl 
der zusammenhängenden steine zählen wo noch ein gewinn möglich ist.

von Walter S. (avatar)


Lesenswert?

Detlev T. schrieb:
> Ich würde das so machen:

hast Du auch an Spalten und Diagonalen gedacht?

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Peter Zz schrieb:
> ist der Test vermutlich
> so könnte

Karl Heinz Buchegger schrieb:
> Wenn der Compiler

Ja wenn, vermutlich und könnte ... ;-)

Das Ziel bei so einem BackTracking Problem ist immer Zweige möglichst 
schnell auszuschließen, davon hängt alles ab. Und dafür muss man das 
Problem Profilen und nicht mutmaßen, und vor allem muss das ganze in 
erster Linie korrekt sein.

Peter Zz schrieb:
> es 1000 Ausnahmen und Regeln geben, die man alle suchen
> und dann programmieren müsste

Das ist doch Quatsch. Du musst nur strukturiert das obige Muster prüfen 
(es wurden dir ja schon netterweise fast fertige Algorithmen genannt), 
dann ergibt sich das automatisch.

Karl Heinz Buchegger schrieb:
> ist ziemlich schwer zu schlagen

Kommt immer drauf an, wenn in 90% der Fälle kein Gewinn vorliegt kann 
der "richtige" Algorithmus da ggf. deutlich besser sein und vorzeitig 
abbrechen, während der "falsche" ständig alles durchnudelt. Sobald dann 
nicht mehr zufällig alles in ein Byte passt wird auch interessant...

von avr (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Gibt es eigentlich bei 4 gewinnt Stellungs-Bewertungsregeln?

Hab ich schon weiter oben geschrieben. Dreier-Reihen bringern fast 
nichts wenn sie an der falschen Position sind, da die meisten Spiele im 
Zugzwang enden. Der Spieler der seine Reihen klug positioniert hat, der 
gewinnt durch Zugzwang (was von vielen Spieler als "Glück" bezeichnet 
wird).

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Hier gibt es übrigens einen "perfekten Spieler" als C, lauffähig auf 
386, besser 486: http://www.ce.unipr.it/~gbe/velena.html

> Instead the theory used here made possible to reduce the search
> tree to an opening book of about 60,000 positions, which is
> stored in a file that is less than one Megabyte

Hier gibt es noch das PDF dazu: 
http://www.informatik.uni-trier.de/~fernau/DSL0607/Masterthesis-Viergewinnt.pdf

von Lukas T. (tapy)


Lesenswert?

Ich würde:

- Das Spielfeld als Integerarray vorhalten. 0, 1, 2 = unbesetzt, blau, 
gelb.
- Aus Performancegründen ein 1D-Array nehmen (xmax*ymax) Position: 
(y*maxX+x)
- Ab dem vierten Stein pro Farbe nach jedem Zug einmal die umliegenden 
Steine checken.
- wenn einer der umliegenden Steine identisch ist in die Richtung und 
Gegenrichtung weiter suchen. Abbruch bei x = xmax oder y = ymax oder 
andere Farbe. Geht schön mit der selben Funktion und 'nem 
Richtungsparameter.

Das ist beliebig erweiterbar (Feldgröße, 4 gewinnt, 5 gewinnt, 
wasweißich)
Aus Jux könnte man dann noch den Algo im Betrieb umstellen und die 
Zeiten messen. Hey, schon fast ne Jugend-Forscht-Arbeit. Muss ich mal 
meinem ehemaligen Informatiklehrer schicken :-).

von Oliver (Gast)


Lesenswert?

Lukas T. schrieb:
> - Aus Performancegründen ein 1D-Array nehmen (xmax*ymax) Position:
> (y*maxX+x)

Wenn der Compiler ein zweidimensionales Array indiziert, ist das 
natürlich viel komplizierter, und dauert daher länger ;)

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Lukas T. schrieb:

> - Aus Performancegründen ein 1D-Array nehmen (xmax*ymax) Position:
> (y*maxX+x)

Das bringt dir genau gar nichts, ausser Schreibarbeit und der 
Möglichkeit blödsinnige Fehler zu machen.

Was glaubst du, wie ein Compiler 2-Dimensionale Arrays implementiert. 
Implementieren muss, weil es anders eh nicht geht.

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> aber doch bitte nicht das Array
> kopieren!)
>
> in der Rekusion wird einfach der Stein gesetzt, dann getestet und dann
> der stein wieder weggenommen. Dann an der nächste stelle der stein
> probiert.

Ich denke, mal aus dem Bauch heraus, ist das kopieren von 12 Byte 
einfacher, als den Spielzug wieder zurückzunehmen?!
1
void Feld_kopieren(unsigned char Feldnummer)
2
{
3
    unsigned char Zeiger_quelle = Feldnummer * 12;
4
    unsigned char Zeiger_ziel = Zeiger_quelle + 12;
5
    // hier keine for-schleife um Zeit zu sparen!
6
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //1
7
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //2
8
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //3
9
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //4
10
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //5
11
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //6
12
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //7
13
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //8
14
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //9
15
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //10
16
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //11
17
    Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //12
18
    // oder besser mit Pointer?!
19
}

von Kurt (Gast)


Lesenswert?

Peter Zz schrieb:
> Ich denke, mal aus dem Bauch heraus, ist das kopieren von 12 Byte
> einfacher, als den Spielzug wieder zurückzunehmen?!void
> Feld_kopieren(unsigned char Feldnummer)
> {
>     unsigned char Zeiger_quelle = Feldnummer * 12;
>     unsigned char Zeiger_ziel = Zeiger_quelle + 12;
>     // hier keine for-schleife um Zeit zu sparen!
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //1
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //2
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //3
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //4
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //5
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //6
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //7
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //8
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //9
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //10
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //11
>     Spielfeld[Zeiger_ziel++] = Spielfeld[Zeiger_quelle++]; //12
>     // oder besser mit Pointer?!
> }

Guck mal du hast gerade 12 mal das selbe geschrieben.
Don't repeat yourself!
das könnte man mit einer Schleife viel schöner machen und das wäre in 
meinen Augen auch noch deutlich angehmer zu lesen..

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Ich denke, mal aus dem Bauch heraus, ist das kopieren von 12 Byte
> einfacher, als den Spielzug wieder zurückzunehmen?!

org = feld[1,2];
feld[1,2] = ROT;
recursion();
feld[1,1] = org;

wobei man sich auch org auch sparen kann, denn das feld muss ja leer 
gewesen sein.

von Peter D. (peda)


Lesenswert?

Also der Code mit den & und | sieht nur groß aus, sollte aber am 
schnellsten gehen.
Die Schiebungen lassen sich etwas optimieren, aber das wars dann.

Der AVR hats nicht so mit indirekter Adressierung.
Der muß 2 Register mit der Baseadresse laden, 2 * den Index addieren, 
ehe er mit LD endlich zugreifen kann. Und bei Elementen > 1 Byte muß er 
erst noch den Index mit der Anzahl multiplizieren.
Das kostet viel mehr Zeit, als einfach direkt mit LDS ins Register.

Eine verkettete Liste mag vielleicht weniger Vergleiche enthalten, 
braucht aber erheblich mehr Adreßberechnungen.

von Peter II (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Also der Code mit den & und | sieht nur groß aus, sollte aber am
> schnellsten gehen.

aber das sieht doch einfach schrecklich aus

if(((Zeile6 & 0x78)==0x78)||((Zeile6 & 0x3C)==0x3C)||((Zeile6 & 
0x1E)==0x1E)||((Zeile6 & 0x0F)==0x0F))return(vier_in_reihe);


das ist doch einfach unschön.

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> Peter Zz schrieb:
>> Ich denke, mal aus dem Bauch heraus, ist das kopieren von 12 Byte
>> einfacher, als den Spielzug wieder zurückzunehmen?!
>
> org = feld[1,2];
> feld[1,2] = ROT;
> recursion();
> feld[1,1] = org;
>
> wobei man sich auch org auch sparen kann, denn das feld muss ja leer
> gewesen sein.

Und wieder versuche ich meinen Ansatz zu verteidigen:
die Funktion:
1
unsigned char Stein_setzen(unsigned char Feldnummer,unsigned char Spalte,unsigned char Farbe)
liefert ja nicht zurück, wo genau der Stein gesetzt wurde.
Der Funktion wird ja lediglich:
1. die Feldnummer (wo ist die Rekursion im Moment)
2. die Spalte
3. die Farbe (rot/gelb)
übergeben. Die Funktion schaut dann, wie tief der Spielstein nach 
unten fällt. Ist die Spalte voll, gibt die Funktion eine Fehlermeldung 
zurück.
1
unsigned char Maske = 1 << (7 - Spalte);
2
3
if((Spielfeld[Zeiger + 0] & Maske) || (Spielfeld[Zeiger + 6] & Maske)) return(Spalte_voll);
4
// usw.
Alternativ zu Feld_kopieren bräuchte ich dann eine Funktion 
Stein_setzen_zurücknehmen

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> if(((Zeile6 & 0x78)==0x78)||((Zeile6 & 0x3C)==0x3C)||((Zeile6 &
> 0x1E)==0x1E)||((Zeile6 & 0x0F)==0x0F))return(vier_in_reihe);
>
> das ist doch einfach unschön.

0x78 = 0b01111000
0x3C = 0b00111100
0x1E = 0b00011110
0x0F = 0b00001111

was ist daran unschön?
Naja, die Schönheit entsteht im Auge des Betrachters.

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Alternativ zu Feld_kopieren bräuchte ich dann eine Funktion
> Stein_setzen_zurücknehmen

feld kopieren würde ich auf jeden Fall vermeiden. Das begrenzt dir auf 
jeden Fall die rekursion wegen speicher überlauf. Und das ist wohl auf 
jeden Fall langsamer als wieder ein bit zu löschen.

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> feld kopieren würde ich auf jeden Fall vermeiden. Das begrenzt dir auf
> jeden Fall die rekursion wegen speicher überlauf.

Die Rekursiontiefe ist eher durch die Rechenzeit begrenzt, ich denke 
mehr als eine halbe Minute bis maximal eine Minute Rechenzeit ist 
unzumutbar.

> Und das ist wohl auf
> jeden Fall langsamer als wieder ein bit zu löschen.

Das sagst du nur so aus dem Bauch heraus, getestet hast du das auch 
nicht, oder?

von Peter II (Gast)


Lesenswert?

Peter Zz schrieb:
> Das sagst du nur so aus dem Bauch heraus, getestet hast du das auch
> nicht, oder?

also 6 byte kopieren oder 1 bit wieder zu löschen - was wird wohl 
schneller sein?
Somal auch nocht die übergabe von array dazukommt. Die man dann nicht 
bräuchte weil es nur ein array gibt. Damit kann auch der Compieler 
besser optimieren weil alle Adresse fest stehen.

von Peter Z. (Gast)


Lesenswert?

Peter II schrieb:
> Somal auch nocht die übergabe von array dazukommt. Die man dann nicht
> bräuchte weil es nur ein array gibt. Damit kann auch der Compieler
> besser optimieren weil alle Adresse fest stehen.

Diese Argumentation ist gerade dabei, mich zu überzeugen.

von Marcel K. (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe vor einer Zeit auch mal angefangen ein 4-Gewinnt mit KI zu 
programmieren. Man kann gegen KI spielen aber das Programm ist immer 
noch eine Baustelle. Das Projekt ruht seit längerem. Villeicht ist etwas 
sinnvolles dabei. Ich hätte übrigens für interessierte noch 2 Platinen 
übrig.

von Peter Z. (Gast)


Angehängte Dateien:

Lesenswert?

besten dank.
Leider kann ich die .sch und brd Datei nicht öffnen, ist wohl nicht 
eagle 5.6 kompatibel?
Meine angestrebte Hardware sieht so aus, siehe Bild im Anhang.
Es gibt 3 rot/gelb Zweifarb-LED's
Mit zwei Tastern (Select & Enter) wird dem µC die Position mitgeteilt, 
wo der Gegner seinen Stein (Gelb) eingeworfen hat.
Der µC antwortet dann nach einiger Zeit :-( und teilt im Binär-Code
001 = Spalte 1
010 = Spalte 2
011 = Spalte 3
100 = Spalte 4
101 = Spalte 5
110 = Spalte 6
111 = Spalte 7
mit, wo er seinen Spielstein (Rot) einwerfen würde.
Ich strebe ein möglichst kleines Volumen an, so das ich das Ding in der 
Hand verbergen kann.

von Marcel K. (Gast)


Angehängte Dateien:

Lesenswert?

Dann hier der Schaltplan noch als PNG. Das eigentliche Spiel und die KI 
sind in einer Klasse gekapselt. Es müsste leicht sein die Klasse mit 
deiner Hardware zu benutzen.

von Michael S. (msb)


Lesenswert?

Hier eine Idee wenn man etwas mehr Speicher spendiert:
Man könnte einmalig für jedes Feld berechnen an welchen möglichen 
Gewinnvierern es beteiligt ist. Das sollten maximal 16 sein (am Rand 
weniger). Jedem dieser Vierer ordnet man eine Nummer zu. Diese Nummer 
ist Index in eine Zählertabelle pro Farbe. Erreicht ein Zähler 4 ist der 
Gewinn da.
Die feste Datenstruktur könnte man sogar einmalig als Quellcode 
generieren.

Ich denke das ist deutlich schneller, als mit allen notwendigen 
Zusatzabfragen in 7 Himmelsrichtungen zu suchen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Michael S1. schrieb:
> Ich denke

Es zählen bei so was aber nur Fakten oder Beweise...

von Michael S. (msb)


Lesenswert?

Sollte nur eine Idee sein, kein Beweis.  Und mit einer Idee fängt es 
doch an, oder? Übrigens, mit "ich denke" wollte ich sagen, "ich gehe 
nach meinen Erfahrungen davon aus".

von Floh (Gast)


Lesenswert?

Also ich würde das so probieren:
Stein wird eingeworfen.
1. Sind unter dem Stein drei weitere gleichfarbige -> Sieg.
2a. Steine der Gleichen Farbe nach links zählen.
2b. Steine der gleichen Farbe nach rechts zählen.
2c. Zusammenzählen, wenn größer 2 -> sieg.
3a. Steine diagonal nach links unten zählen.
3b. Steine diagonal nach rechts oben zählen.
3c. Zusammenzählen, wenn größer 2 -> Sieg.
4. Dasselbe mit rechts unten und links oben.

Das erschlägt alle möglichen Siegkombinationen ausgehend vom 
eingeworfenen Stein.
:-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.