Autor |
Beitrag |
Jetstream
      
Beiträge: 222
|
Verfasst: Do 21.12.06 03:13
Ich steh grade voll aufm Schlauch ...
hab den Algorithmus mehr oder weniger ausm Buch kopiert, er funzt aber nicht.
Zur Erklärung:
Ich schreibe grade an einer KI für ne Art Schach. Der Alpha-Beta-Algorithmus soll eine Spielstellung bewerten, indem er von einer gegebenen Brettstellung aus alle sinnvollen Möglichkeiten bildet, wie das Spielfeld in X Zügen aussehen könnte, und diese Möglichkeiten dann auswertet. Zurückgegeben wird ein Integer-Wert, der angibt, wie gut es grade für Weiss steht. Positive Werte bedeuten einen Vorteil für Weiss, negative für Schwarz. Genug Gelaber, hier der Code:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
| function TSpielfeld.alphaBeta(tiefe,alpha,beta:integer;Farbe:TSpielsteinfarbe):integer; var a,i,wert:integer; Zugliste:TMovelist; begin a := endposition; if a = 1 then Result := 10000 else if a = 2 then Result := -10000 else if (tiefe=0) then Result := evaluate(Farbe) else begin Zugliste:=generateMoves(Farbe); for a:=1 to 32 do if Zugliste[a] <> nil then begin doMove(Zugliste[a]); wert := -alphaBeta(tiefe-1,-beta,-alpha,switchcolor(Farbe)); undoMove(Zugliste[a]); if wert >= beta then begin Result:= beta; for i:=1 to 32 do FreeandNil(Zugliste[i]); exit; end; if wert > alpha then alpha :=wert; end; Result := alpha; for i:=1 to 32 do FreeandNil(Zugliste[i]); end; end; |
Der Algorithmus funktioniert solange, bis ich ihn mit einer Tiefe größer Null aufrufe
Dann wird nur noch Müll ausgegeben (Alle Züge haben die gleiche Bewertung etc. )
Sieht jemand den Fehler ?
// Edit: So, alles funzt. Zu bestaunen unter www.flechtmann.net/Sushi116.rar
// Version 1.0: Alpha-Beta funktioniert, Tiefe fest auf drei eingestellt.
// Version 1.1: Gewinnstellung wird erkannt, neu starten wird angeboten
// Version 1.11: Man kann nicht mehr mit jedem Stein alles vom Gegner schlagen (dummer Fehler)
// Version 1.12: Wenn man Schwarz wählt, zieht die KI zuerst (vorher vergessen zu implementieren)
// Version 1.13: Access Violation behoben
// Version 1.14: Bei gleich guten Zügen zieht er jetzt zufällig, das sorgt für Abwechslung.
// Version 1.15: Einen dummen Bug gefixt
// Version 1.16: Logo gebastelt, Neustart-Button, Rand verschöndert
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
Zuletzt bearbeitet von Jetstream am Do 22.03.07 16:55, insgesamt 4-mal bearbeitet
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 09:02
Vielleicht hilft dir eine NegaMax-(Besseres Alpha-Beta + Minimax) Implementierung weiter?
www.delphi-forum.de/...ieren_59658,100.html
Nach kurzem Studium deiner Implementierung vermisse ich die Angabe, welcher Zug denn nun der Beste ist...
Normalerweise würde ich die Funktion ja 'FindeDenBestenZug (.... Var BesterZug : TZug) : TBewertung' nennen (also sinngemäß).
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 11:06
alzaimar hat folgendes geschrieben: | Nach kurzem Studium deiner Implementierung vermisse ich die Angabe, welcher Zug denn nun der Beste ist... |
Das wiederum erledigt eine andere Prozedur, die das ganze aufruft: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
| function TSpielfeld.getBestMove(Tiefe:integer;Farbe:TSpielsteinfarbe;movelistImage:TImage):TMove; var a,alpha,beta,wert,auswahl:integer; Zugliste:TMoveList; Intliste:TIntlist; begin alpha := -20000; beta := 20000; auswahl := 0; for a:=1 to 32 do Intliste[a]:=0; Zugliste := generateMoves(Farbe); for a:=1 to 32 do if Zugliste[a] <> nil then begin doMove(Zugliste[a]); if Farbe = Weiss then wert := alphaBeta(Tiefe,alpha,beta,Schwarz) else wert := -alphaBeta(Tiefe,alpha,beta,Weiss); undoMove(Zugliste[a]); Intliste[a] := wert; if wert >= beta then begin Result := Zugliste[a]; if movelistImage <> nil then movelistZeichnen(Zugliste,Intliste,movelistImage); exit; end; if wert > alpha then begin alpha :=wert; auswahl:=a; end; end; if auswahl = 0 then Result := TMove.create(1,1,1,1) else Result := Zugliste[auswahl]; if movelistImage <> nil then movelistZeichnen(Zugliste,Intliste,movelistImage); end; | Funtioniert tadellos, man könnte aber bestimmt noch beide Funktionen zusammenschreiben ...
Zum NegaMax: Ich hatte eigentlich gelesen, dass Alpha-Beta eine Verbesserung des NegaMax darstellt.
NegaMax ist eine Verbesserung des MiniMax und macht aus zwei Prozeduren (Max und Min) eine einzige. (Unter der Annahme, dass man statt "Minimum" auch einfach "Maximum der Negativa" verstehen kann. Alpha-Beta beherrscht zusätzlich noch den Beta-Abbruch, um Rechenzeit zu sparen.
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 12:04
Jetstream hat folgendes geschrieben: | Funtioniert tadellos, man könnte aber bestimmt noch beide Funktionen zusammenschreiben ... |
Ich dachtem, es funktioniert doch nur bei Zugtiefe = 0 tadellos
Du solltest die AlphaBeta-Routine so erweitern, das sie auch den besten Zug zurückliefert.
Jetstream hat folgendes geschrieben: | ...Ich hatte eigentlich gelesen, dass Alpha-Beta eine Verbesserung des NegaMax darstellt. NegaMax ist eine Verbesserung des MiniMax und macht aus zwei Prozeduren (Max und Min) eine Einzige... |
Stimmt, hatte ich verwechselt: Vielleicht übernimmst Du einfach die Ermittlung des besten Zuges aus meinem Algorithmus (zumindest als Anhaltspunkt, weil Du bestimmt eine eigene Implementierung haben willst):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60:
| Type TMove = record mPlayer, mPrev: TPlayer; mFrom, mTo: TPoint; mScore: Integer; end;
Function AlphaBeta(Level: Integer; aPlayer, aOpponent: TPlayer; Var aBoard: TBoard; Var aBestMove: TMove; Alpha, Beta: Integer; aPatt: Boolean): Integer; Var aMoves: TMoveList; I, N, S: Integer;
Begin Result := sPassScore; aBestMove.mPlayer := plEmpty; CreateAllMoves(aPlayer, aOpponent, aBoard, aMoves, N); if level>0 Then SortMoveList(aBoard, aMoves, Level,aPlayer, aOpponent); If aMoves.Count = 0 Then If aPatt Then Result := 0 Else Result := -AlphaBeta(Level, aOpponent, aPlayer, aBoard, M, -beta, -alpha, True) Else Begin inc(iTotal); For i := 0 To aMoves.Count - 1 Do Begin MakeMove(aBoard, aMoves[i]); if (Level = 0) or (amoves[i].mScore >= sWinScore) then S := aMoves[i].mScore Else S := -AlphaBeta(Level - 1, aOpponent, aPlayer, aBoard, M, -beta, -alpha, False); UndoMove(aBoard, aMoves[i]); If (s >= beta) Then Begin aBestMove := aMoves[i]; Result := Beta; Exit; End; If (S > alpha) Then Begin Alpha := S; aBestMove := aMoves[i]; End End; Result := Alpha; End; End;
function FindBestMove(aLevel: Integer; aPlayer, aOpponent: TPlayer; var aBoard: TBoard; var aBestMove: TMove): Integer; begin Result := AlphaBeta(aLevel, aPlayer, aOpponent, aBoard, aBestMove, -maxint, maxint); end; |
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 12:31
alzaimar hat folgendes geschrieben: | Jetstream hat folgendes geschrieben: | Funtioniert tadellos, man könnte aber bestimmt noch beide Funktionen zusammenschreiben ... |
Ich dachtem, es funktioniert doch nur bei Zugtiefe = 0 tadellos  |
Das war auch nur auf die getBestMove-Funktion begrenzt. Was nicht funzt, ist alphaBeta.
Ich sehe jetzt nur keinen großen Unterschied zwischen unseren Funktionen.
Gut, du hast die Bewertung eines Zuges gleich in den Zug gesteckt, ich hab dafür ne eigene Prozedur ... deins is da eleganter. Trotzdem müsste meins doch auch funktionieren.
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 12:43
Ich glaube, der Fehler ist Folgender:
Verlfleichen wir meine Routine:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| ... S := -AlphaBeta(Level - 1, aOpponent, aPlayer, aBoard, M, -beta, -alpha, False); UndoMove(aBoard, aMoves[i]); ... If (S > alpha) Then Begin Alpha := S; aBestMove := aMoves[i]; End; ... |
Mit GetBestMove von Dir:
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| ... if Farbe = Weiss then wert := alphaBeta(Tiefe,alpha,beta,Schwarz) else wert := -alphaBeta(Tiefe,alpha,beta,Weiss); ... if wert > alpha then begin alpha :=wert; auswahl:=a; end; ... |
Das ist ja (fast das gleiche), aber ...
Du rufst alphaBeta mit 'alpha,beta' auf, es müsste aber doch '-beta, -alpha' heißen, schließlich suchst Du dann den besten Zug des GEGNERS...
[edit] Sortierst Du deine Zugliste? [/edit]
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 13:55
Nein, ich sortiere nicht, habe aber auch gelesen, dass es die Performance erhöhen soll.
Du betrachtest grade die Funktion, die funktioniert. Was nicht funktioniert, ist die Funktion aus dem ersten Post.
Vergleichen wir die mal:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7:
| meins: wert := -alphaBeta(tiefe-1,-beta,-alpha,switchcolor(Farbe)); undoMove(Zugliste[a]); deins: S := -AlphaBeta(Level - 1, aOpponent, aPlayer, aBoard, M, -beta, -alpha, False); UndoMove(aBoard, aMoves[i]); |
Kein Unterschied zu entdecken ...
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 14:02
Jetstream hat folgendes geschrieben: | Nein, ich sortiere nicht, habe aber auch gelesen, dass es die Performance erhöhen soll. |
Es erhöht die Performance, weil das Pruning (Abschnippeln des Suchbaumes) viel früher ansetzt.
Jetstream hat folgendes geschrieben: | Du betrachtest grade die Funktion, die funktioniert. Was nicht funktioniert, ist die Funktion aus dem ersten Post....
Kein Unterschied zu entdecken ... |
Wenn da keiner wäre, müsste es ja laufen.
Deine GetBestMove-Routine ruft AlphaBeta falsch auf. Innerhalb des Alpha-Beta machst du Alles richtig. Versuch's doch einfach mal.
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 14:16
alzaimar hat folgendes geschrieben: | Deine GetBestMove-Routine ruft AlphaBeta falsch auf. |
Wenn ich daraus aus alpha,beta einfach -beta,-alpha mache, ruft er die routine mit genau den gleichen werten auf ... -20000 und 20000
Wie gesagt, getBestMove(0,Weiss,nil) funzt bestens.
getBestMove(1,Weiss,nil) geht leider gar nicht. (Produziert nur Quark, die Steine rennen manisch hin und her).
Gleiches bei allen höheren Tiefen.
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
Zuletzt bearbeitet von Jetstream am Do 21.12.06 14:22, insgesamt 1-mal bearbeitet
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 14:19
Aber du veränderst doch Alpha .... und dann sieht die Welt doch anders aus
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 14:26
Oh, du hast Recht ... ist mir gar nicht aufgefallen.
Da muss aber irgendwo noch ein Fehler sein, jetzt verändert sich nur das Verhalten von Schwarz auf Tiefe Eins: Schwarz spielt auf einmal gut !
Auf höheren Ebenen weiterhin Chaos.
//Edit: Nein, immernoch überall Chaos. Muss an alphaBeta liegen.
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 14:31
Um 'mein' AlphaBeta zu testen, habe ich mal AB und NegaMax parallel aufgerufen (2x Delphi offen) und bin dann Schritt-für-Schritt durchgegangen. Dabei findet man i.a. die Stelle, an der es hakt. Sortiere am Besten die Züge, damit A/B vorhersagbar abbricht.
Ich vermute, das es irgend eine blöde Stelle ist: Ich sehe nämlich auch beim 2. Hinsehen keinen Fehler ...
Ich hack' nochmal auf GetBestMove (und dem Aufruf von AlphaBeta) herum: Deine Funktion, die die Spielstellung bewertet, tut das doch hoffentlich immer aus der Sicht eines Spielers, oder?
Meine Funktion hat folgenden Kopf:
Delphi-Quelltext 1:
| Function ScoreBoard (aBoard : TBoard; aPlayer : TPlayer) : Integer; |
Sie liefert immer einen hohen Wert, wenn die Stellung für den aPlayer gut ist, egal welche Farbe aPlayer hat!
_________________ Na denn, dann. Bis dann, denn.
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 17:55
Öhm, evaluate liefert einen absoluten Wert, ist das falsch ?
D.h. wenn Weiss besser steht, liefert evaluate einen positiven Wert, wenn Schwarz besser steht einen negativen.
evaluate bekommt aber als Parameter die Farbe übergeben, die am Zug ist.
//Edit: Du hattest absolut Recht !
Die evaluate-Funktion muss für gute Stellungen der Farbe, die am Zug ist, immer POSITIVE Werte liefern
Ich hatte positive für Weiss und negative für Schwarz.
Jetzt funktioniert alles, wie es soll ... 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61:
| function TSpielfeld.alphaBeta(tiefe,alpha,beta:integer;Farbe:TSpielsteinfarbe):integer; var a,i,wert:integer; Zugliste:TMovelist; begin a := endposition; if (a = 1) and (Farbe = Weiss) then Result := 10000 else if (a = 2) and (Farbe = Weiss) then Result := -10000 else if (a = 1) and (Farbe = Schwarz) then Result := -10000 else if (a = 2) and (Farbe = Schwarz) then Result := 10000 else if (tiefe=0) then Result := evaluate(Farbe) else begin Zugliste:=generateMoves(Farbe); for a:=1 to 32 do if Zugliste[a] <> nil then begin doMove(Zugliste[a]); wert := -alphaBeta(tiefe-1,-beta,-alpha,switchcolor(Farbe)); undoMove(Zugliste[a]); if wert >= beta then begin Result:= beta; for i:=1 to 32 do FreeandNil(Zugliste[i]); exit; end; if wert > alpha then alpha :=wert; end; Result := alpha; for i:=1 to 32 do FreeandNil(Zugliste[i]); end; end;
function TSpielfeld.getBestMove(Tiefe:integer;Farbe:TSpielsteinfarbe;movelistImage:TImage):TMove; var a,alpha,beta,wert,auswahl:integer; Zugliste:TMoveList; Intliste:TIntlist; begin alpha := -20000; beta := 20000; auswahl := 0; for a:=1 to 32 do Intliste[a]:=0; Zugliste := generateMoves(Farbe); for a:=1 to 32 do if Zugliste[a] <> nil then begin doMove(Zugliste[a]); wert := -alphaBeta(Tiefe,-beta,-alpha,switchcolor(Farbe)); undoMove(Zugliste[a]); Intliste[a] := wert; if wert >= beta then begin Result := Zugliste[a]; if movelistImage <> nil then movelistZeichnen(Zugliste,Intliste,movelistImage); exit; end; if wert > alpha then begin alpha :=wert; auswahl:=a; end; end; if auswahl = 0 then Result := TMove.create(1,1,1,1) else Result := Zugliste[auswahl]; if movelistImage <> nil then movelistZeichnen(Zugliste,Intliste,movelistImage); end; | Thx alzaimar !!!
Neue Version ist geupdated, www.flechtmann.net/Sushi.exe
Jetzt muss ich nur noch die Züge sortieren 
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
Zuletzt bearbeitet von Jetstream am Do 21.12.06 20:30, insgesamt 1-mal bearbeitet
|
|
alzaimar
      
Beiträge: 2889
Erhaltene Danke: 13
W2000, XP
D6E, BDS2006A, DevExpress
|
Verfasst: Do 21.12.06 19:08
Hi, hab mir eben das Spiel angeschaut:
Was für eine köstlich dämliche Idee für so ein göttlich geniales Spielchen. Lass Dir diese Spielidee (wenn Sie denn von Dir ist) sofort(!) patentieren, sonst tu ich es 
_________________ Na denn, dann. Bis dann, denn.
|
|
Christian S.
      
Beiträge: 20451
Erhaltene Danke: 2264
Win 10
C# (VS 2019)
|
Verfasst: Do 21.12.06 19:31
 Das ist ja mal eine witzige Idee 
_________________ Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: Do 21.12.06 19:38
Ahem... kommt mir irgendwie von ICQ bekannt vor....
Aber trotzdem schöne Umsetzung!
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
|
|
Jetstream 
      
Beiträge: 222
|
Verfasst: Do 21.12.06 19:46
Das Spiel hat sich der große Bruder meine Freundin ausgedacht, gibts schon bei Amazon.
Hab grade nochmal ne neue Version hochgeladen, man konnte bisher mit jedem Stein jeden gegnerischen schlagen 
_________________ Die folgenden Klangbeispiele sind Ergänzungen zum methodischen Aufbau der Textbeilage und dürfen nicht losgelöst von dieser behandelt werden.
|
|
wolke
      
Beiträge: 240
|
Verfasst: Do 21.12.06 21:30
nettes spiel. kann man auch gewinnen?
bin wohl zu blöd...
|
|
|