Entwickler-Ecke
Algorithmen, Optimierung und Assembler - Strategie-Spiel - wie dazu eine KI programmieren?
galagher - Mi 03.05.06 18:16
Titel: Strategie-Spiel - wie dazu eine KI programmieren?
Hallo Leute!
Ich möchte ein Spiel programmieren, bei dem beide Spieler abwechselnd, ähnlich wie bei Tic-Tac-Toe, auf eine Fläche (Panels) klicken, um einen Spielstein zu setzen. Das Spielbrett hat 25 Felder. Anfangs besitzt jeder Spieler 5 Steine, die man wie die Bauern beim Schach bewegt. Ziel ist es, einen Stein auf die gegnerische Grundlinie zu bringen. Hier ein Beispiel:
http://fuzzy.cs.uni-magdeburg.de/studium/swprak/ws9900.html#Streichholz
... nur dass ich eben 5x5 Felder möchte.
Nun gibt es eine Menge zum Thema KI, Tic-Tac-Toe usw. zu lesen, aber irgendwie kann ich es nicht umsetzen. Mir fehlt ein Anfang.
Wie bringe ich ein Programm hin, dass sinnvolle Züge berechnet und dabei auch die Spielsteine des Gegners berücksichtigt? Alles mit if oder case abzufragen würde irgendwann (vielleicht) halbwegs funktionieren, aber so will ich das nicht. Das wäre doch nur unprofessioneller Spaghetticode!
Kann mir jemand ein paar Ansätze und Denkhilfen geben?
Marco D. - Mi 03.05.06 18:36
Ich habe in diesem Zusammenghang von dem Minimax-Algorithmus gehört. Dieser rechnet alle mgölichen Züge durch und wählt den günstigsten aus. Aber von der Umsetzung habe ich keine Ahnung.
galagher - Mi 03.05.06 18:48
Marco D. hat folgendes geschrieben: |
Aber von der Umsetzung habe ich keine Ahnung. |
Ich auch nicht! Vielleicht könnte mir diesen Algorithmus jemand erkären!
zemy - Do 04.05.06 12:02
wiki roxx^^
Die Idee ist einfach, das du dir von deiner Spielposition auds (du bist jetzt der Computer) einen Spielbaum aufbaust, also alle möglichen Spielverläufe. Da gibt es prinzipiell 2 Varianten:
1.) Du hast einen vollständigen Spielbaum (von der Startposizion an alle Spielverläufe. Die Anzahl explodiert aber in den meisten Fällen (4 gewinnt war auf ner normalen Festplatte schon nicht mehr speicherbar) Müsste man durchrechnen ob das bei dir klappt.
2.) Du baust dir an deiner aktuellen Spielposition nen Spielbaum bis zur ner bestimmten Tiefe auf. Um ddas etwas eleganter zu gestalten, kommt der MinMax-Algorythmus. Du wirst immer den Zug machen, der dir am meisten Gewinn bringt -> MAX. Der Gegner wird versuchen, sein Ergebnis zu maximieren... deins also zu minimieren -> MIN. Wenn man da immer die 3 besten/schlechtesten nimmt, bleibt es noch im überschaubaren Rahmen. (die 3 ist willkürlich gewählt ;) )
Das Problem ist, das du bei der zweiten Variante eine Bewertungsfunktion brauchst. Du rechnest ja nicht bis zum Ende sondern brichst ab. Die Stellung, die da erreicht wurde, muss bewertet werden und geht in den MinMax-Algorythmus ein. Zu Bewerten währen unter anderen schon erreichte Punkte, strategische Stellung, etc.... Mit Büchern kann man da auch was machen. Einige (viele) Stellungen werden ausgibieg berrechnet und gespeichert. Wenn Später diese Situation auftritt, weiß man schon bescheid. Google mal nach WZebra, ist zwar Reversi, spart abr nicht an Informationen...
PS: Forensuche;)
MfG
galagher - Do 04.05.06 17:18
zemy hat folgendes geschrieben: |
Die Idee ist einfach, das du dir von deiner Spielposition auds (du bist jetzt der Computer) einen Spielbaum aufbaust, also alle möglichen Spielverläufe. Da gibt es prinzipiell 2 Varianten:
1.) Du hast einen vollständigen Spielbaum (von der Startposizion an alle Spielverläufe. Die Anzahl explodiert aber in den meisten Fällen (4 gewinnt war auf ner normalen Festplatte schon nicht mehr speicherbar) Müsste man durchrechnen ob das bei dir klappt. |
Jetzt bin ich so schlau wie vorher! Das mit dem Baum ist mir klar, aber WIE schreibe ich den? Was soll das für ein Baum sein, beinhaltet der Spielstellungen:
Delphi-Quelltext
1: 2: 3:
| if [...] then [...] else if then [...] else if then [...] |
Da gibt es so nette grafische Darstellungen über Bäume, aber was das ist, steht nirgendwo so genau!
zemy hat folgendes geschrieben: |
PS: Forensuche;)
|
Siehe oben "Jetzt bin ich so schlau wie vorher!"!
galagher - Do 04.05.06 20:29
Die Oberfläache, die Zug-Regeln (gerade ziehen, schräg schlagen), die Option Spieler/Computer beginnt und die Spielaufstellung habe ich. Der 1. Zug des Computers erfolgt mit Random. Also alles nur keine KI!
Werde mich mal mit dem Minimax-Thread näher beschäftigen und versuchen, die KI zu verstehen. Aber momentan stehe ich an...
zemy - Do 04.05.06 21:52
galagher hat folgendes geschrieben: |
zemy hat folgendes geschrieben: | Die Idee ist einfach, das du dir von deiner Spielposition auds (du bist jetzt der Computer) einen Spielbaum aufbaust, also alle möglichen Spielverläufe. Da gibt es prinzipiell 2 Varianten:
1.) Du hast einen vollständigen Spielbaum (von der Startposizion an alle Spielverläufe. Die Anzahl explodiert aber in den meisten Fällen (4 gewinnt war auf ner normalen Festplatte schon nicht mehr speicherbar) Müsste man durchrechnen ob das bei dir klappt. |
Jetzt bin ich so schlau wie vorher! Das mit dem Baum ist mir klar, aber WIE schreibe ich den? Was soll das für ein Baum sein, beinhaltet der Spielstellungen:
Delphi-Quelltext 1: 2: 3:
| if [...] then [...] else if then [...] else if then [...] |
Da gibt es so nette grafische Darstellungen über Bäume, aber was das ist, steht nirgendwo so genau!
|
Die Darstellung von Bäumen passt ziemlich genau. Der Unterschied ist, das sich auf deiner Festplatte kein Grünzeug befindet, sondern da nur ein paar Bytes rumliegen. Trotzdem gibts Wurzeln Äste und Blätter. Der ganze Trick dabei ist: Die Äste verweisen über Pointer zu den untergestellten Blättern oder Ästen ;)
galagher hat folgendes geschrieben: |
zemy hat folgendes geschrieben: |
PS: Forensuche;)
|
Siehe oben "Jetzt bin ich so schlau wie vorher!"! |
War nur, weil ich nen Thread "MinMax" gesehen habe, den du wohl auch schon gefunden hast ;)
MfG
galagher - Fr 05.05.06 16:38
Ich glaube, da habe ich mir zu viel vorgenommen! Wenn ich nicht wirklich alles mit if oder case abfragen will, komme ich nicht weiter. Ich habe keine Ahnung, wie ich einen Algorithmus schreiben soll.
Folgende Anforderungen muss er erfüllen (der Computer spielt immer von oben nach unten):
1. Das Feld unterhalb der jeweiligen Ausgangsstellung besetzen, wenn dieses leer ist
2. Die "Figur" auf dem Feld schräg links oder rechts unterhalb der jeweiligen Ausgangsstellung schlagen
3. Dabei noch strategisch vorgehen und die Stellung des Spielers berücksichtigen (seinen Sieg verhindern).
Hier mal ein Screenshot:
zemy - Fr 05.05.06 18:08
Kannst ja auch erst mal schritt für Schritt vorgehen und nicht gleich bei Punkt 27 Anfangen. Schreib doch erst mal ne "dumme" Ki:
Schritt 1: Dumme KI:
1) Wenn ein gegnerischer Stein schmeißbar, dann schmeiße ihn. (da brauchst du ifs und vieleicht ne For-Schleife^^)
2) Wenn mehrere Steine schmeißbar sind, suche dir zufällig einen aus
3) Wenn nichts zum schmeißen ist, dann zieh zufällig einen Stein vor (bei dem es möglich ist)
4) Wenn nichts möglich ist, stürze ab oder setze aus... je nach dem ;)
Diese KI ist dann zwar sehr einfach zu schlagen aber immerhin ein Anfang
Schritt 2: Schon KIiger
1. Geh ALLE Zugvarianten durch. Bewerte die Position, die sich daraus ergibt und speichere diese ab
2. Wähle die Variante aus, die am günstigsten ist. Wenn mehrere Variante den gleichen Zahlenwert haben, wähle zufällig eine
Wenn du das bewerten nur nach der Figurenzahl machst, hast du wieder Variante eins. Ist ganz gut beim testen (einfach mal ca. 100'000* durchspielen, 50:50 müsste rauskommen), bringt einen aber nicht viel weiter. Also muss die strategische Stellung mit rein. Eventuell, ob der Stein nach dem Zug nicht mehr geschmissen werden kann oder ob so ein gegnerischer Stein davon abgehalten werden kann, durchzubrechen. Spiel am besten das Spiel ne Weile gegen einen Menschen (oder gegen ein Programm, das das Spiel schon realisiert hat) und beobachte dich selbst: Worauf achtest du? Die Gewichte kannste erstmal nur schätzen (also wie stark sowas eingeht). Kann man testen gegen die Dumme KI aus (1)
Schritt 3: MinMax
Einfach bis zur ner bestimmten Tiefe vorrausrechen (siehe anderen Thread) Die Bewertungsfunktunktion hast du schon. Dadurch kannst du einen Zug nicht nur nach der nächsten Stellung bewerten, sondern auch nach den daraus vollgenden. Da müsste natürlich das Save-Bekommen der eigenen Steine extra bewertet werden um brauchbare Resultate zu bekommen
Schritt 4..27:
Verbessern der bewertungsfunktion über hinzufügen weiterer Parameter. Verbessern der Gewichte (eventuell was genetisches). Anlegen von Büchern. Vieleicht ein bischen mit neuronalen Netzen spielen, etc. Performance steigern, ...
Schritt 1 müsste auch für einen Anfänger zu machen sein. Solange man Arrays und For-Schleifen kennt, ist das kein großes Problem. Schritt 2 ist das komplizierteste die Bewertungsfunktion. Die ist schon etwas komplizierter zu programmieren aber man brauchs nicht gleich zu übertreiben ;) Schritt 3 ist nach (2) dann auch kein Problem mehr und danach kannst du dir überlegen ob du es darauf beruhen lässt oder mehr Energie reinsetzt. Weiß ja nicht, wie intensiv du das hier betreiben willst...
MfG
galagher - Mo 08.05.06 19:01
zemy hat folgendes geschrieben: |
Kannst ja auch erst mal schritt für Schritt vorgehen und nicht gleich bei Punkt 27 Anfangen. Schreib doch erst mal ne "dumme" Ki: |
Danke für deine Hilfe - manchmal sieht man den Wald vor lauter Bäumen nicht! Es spielt schon recht agressiv - aber nicht wirklich intelligent! :D
alzaimar - Mo 08.05.06 19:32
Hier mal ein Tic-Tac-Toe mit 'MiniMax'-Algorithmus. So hingerotzt.
galagher - Di 09.05.06 18:39
alzaimar hat folgendes geschrieben: |
Hier mal ein Tic-Tac-Toe mit 'MiniMax'-Algorithmus. So hingerotzt. |
Super, spielt sehr gut! Aber ich kann den MiniMax-Algorithmus einfach nicht umsetzen. Irgendwie kapiere ich das nicht...
F34r0fTh3D4rk - Di 09.05.06 19:29
warum ist dein bewertungsalgorithmus so wie er ist ? reicht es nicht zb -1 zurückzugeben wenn der pc gewonnen hat, 0 bei unentschieden und 1 wenn der spieler gewinnt ?
das lustige ist, dass die ki wenn sie die chance hat zu gewinnen, sie nicht nutzt, wenn sie bei ihrem nächsten zug ohnehin gewinnt (erzeugen einer zwickmühle), ich denke mal weil sie die ergebnisse tief unten von suchbaum nimmt.
alzaimar - Mi 10.05.06 08:32
Also,
@Fear: Stimmt, solange der Minimax einen Gewinnzug hervorbringt, ist es egal, wann die Maschine gewinnt. Dazu müsste man die Stellungs/Zugbewertung erweitern, um die Vorschautiefe zu berücksichtigen, in der eine Gewinnstellung erkannt wird. So kann man den Rechner auch dazu bringen, seine Gegner zu braten, wenn nämlich Gewinnzüge, die erst später zum Erfolg führen, höher bewertet werden, also die Züge, die unmittelbar zum Gewinn führen (Gesellschaftskritisch betrachtet ist also Sadismus nichts anderes als ein verdammt dummer Automatismus).
Die Funktion zur Bewertung einer Stellung ist etwas allgemeiner geraten. Sie zählt die Anzahl der 3er. Ist ja klar, das schon ein 3er zum Sieg reicht. Wenn Du nun einige Kleinigkeiten an der Implementierung änderst (Stellungsbewertung, nächsten Zugkandidaten suchen) und an z.B. Reversi, Dame, Schach etc. anpasst, funktioniert es genau so. Ich hab mal angefangen, das in eine Klasse zu packen (TBoard, TPlayer, TEngine). Dann müsste man nur einige Methoden ableiten (TBoard.GetAvailabelMoves, TBoard.Score) und man hätte eine allgemeingültige Minimax/NegaMax/NegaScout-Engine für beliebige rundenbasierte Nullsummen-Spiele mit vollständiger Information.
Der Kern ist immer die Bewertungsfunktion für die Stellung, sowie, falls der NegaScout-Algorithmus (Alpha-Beta-Pruning) verwendet wird, eine Bewertungsfunktion für den Zug. Beim Optimieren des Suchbaumes ist es von entscheidender Bedeutung, die vermutlich guten Züge zuerst auszuführen.
@galagher: Wo ist denn dein Problem? Da kann man bestimmt helfen. Der Minimax-Algorithmus sucht den Besten Zug aus Sicht des 'Spielers'. Der beste (MAX) Zug des Spielers ist der, der die schlechteste (MIN) Antwort des Gegners als Antwort hat. Der Algo ruft sich -mit umgekehrten Vorzeichen- selbst auf.
F34r0fTh3D4rk - Mi 10.05.06 15:23
ja bei XXO funzts aber auch wunderbar mit sieg niederlage unentschieden ;)
spart ein paar zeilen ;)
galagher - Mi 10.05.06 17:50
alzaimar hat folgendes geschrieben: |
@galagher: Wo ist denn dein Problem? Da kann man bestimmt helfen. Der Minimax-Algorithmus sucht den Besten Zug aus Sicht des 'Spielers'. Der beste (MAX) Zug des Spielers ist der, der die schlechteste (MIN) Antwort des Gegners als Antwort hat. Der Algo ruft sich -mit umgekehrten Vorzeichen- selbst auf. |
Die Umsetzung ist das Problem. Was ist, wenn es mehrere gleich gute Züge gibt? Im Grunde habe ich keine Ahnung, nicht mal ansatzweise, wie ich den Minimax-Algorithmus für mein Programm umsetzen soll!
Ich habe 25 Panels und im Moment ist die ganze "KI" eine Reihe von if's. Genau das wollte ich aber nicht - aber ich weiss nicht, wie ich es sonst machen kann.
Ich weiss einfach nicht, wie.
Praktisches Beispiel:
alzaimar hat folgendes geschrieben: |
Der Minimax-Algorithmus sucht den Besten Zug aus Sicht des 'Spielers' |
Wie?
//Edit: Ich meine nicht, "wie logisch", sondern "wie programmtechnisch". Wie schreibe ich das für ein Programm, das im Prinzip das Bauernspiel im Schach ist? Kannst du mir vielleicht Pseudocode o.ä. dazu geben?
F34r0fTh3D4rk - Mi 10.05.06 18:09
der code ist der gleiche, aber die bewertungsfunktion ist ein ganz eigenes problem, da gibts bei schach noch keine perfekte lösung, die wird es wohl erst geben wenn der komplette spielbaum analysiert werden kann ;) dann braucht man die auch nicht mehr
alzaimar - Mi 10.05.06 18:44
galagher hat folgendes geschrieben: |
Die Umsetzung ist das Problem. Was ist, wenn es mehrere gleich gute Züge gibt? Im Grunde habe ich keine Ahnung, nicht mal ansatzweise, wie ich den Minimax-Algorithmus für mein Programm umsetzen soll!
|
Klar, die Umsetzung ist immer das Problem (wie bei mir). Wenn es mehrere Gleiche Züge gibt, dann... kann man doch würfeln, welchen man denn nun nimmt. Oder: Man analysierte diese Züge nochmals, aber eingehender.
Ich hatte doch irgendwo mal einen Pseudocode für Minimax gepostet... Na egal, im Prinzip sieht es so aus:
Die Funktion 'FindeBestenZug' liefert den besten Zug sowie die 'Punktzahl' des Zuges.
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| FindeBestenZug (Spieler, Gegner, Brett, SpielTiefe, MaxTiefe, BesterZug) returns Integer; Begin Foreach Move M of Player 'Spieler' do // Für jeden möglichen Zug Board.DoMove(M); // Führe ihn aus If SpielTiefe = MaxTiefe Then // Max. Vorschautiefe erreicht? S := Board.BewerteStellung (Spieler); // Je höher S, desto besser für den 'Spieler' Else // Ansonsten liefert dieser Aufruf ja den besten Zug des Gegners S := -FindeBestenZug (Gegner, Spieler, Brett, SpielTiefe + 1, MaxTiefe, DummyZug); // wir wollen den Zug, If S > MaxS Then // auf den der Gegner die schlechteste Anwort parat hat, daher das '-FindeBesten....' MaxS := S BesterZug := M End; Board.UndoMove (M); // Mache den Zug abschließend wieder rückgängig, denn wir probieren ja nur End |
Hier ist natürlich noch nicht dabei, das der Spieler 'passen' muss, oder das die suche abgebrochen wird, wenn eine Gewinnstellung erreicht wurde. Dann ist die Bewertung der Spielstellung natürlich (z.B.) MaxInt. Wenn ich einen Zug gefunden habe, der zu einer Gewinnstellung führt, brauch ich gar nicht erst weiter zu suchen. Bei einem 'Passe' muss ich rekursiv absteigen. Passt dann auch der Gegner, ist eine 'Endstellung' erreicht, die -je nach Spiel- auch zu bewerten ist.
Ich habe mir wochenlang Einen abgebrochen, als ich mich mit Spieleprogrammierung beschäftigt habe. Das ist nun schon fast 30 Jahre her (sabber)... Aber ich war damals extra an der Uni (mit 15) und habe mir die Bücher und Artikel (Journal of the ACM) über Spieletheorie reingezogen. Die ersten Artikel darüber sind immerhin von 1959 (glaub ich zumindest).
Ich bin grad zu faul zum suchen, aber welches Spiel willst Du programmieren?
galagher - Mi 10.05.06 19:01
[quote="
alzaimar"]
galagher hat folgendes geschrieben: |
Ich bin grad zu faul zum suchen, aber welches Spiel willst Du programmieren? |
Im Grunde nichts anderes als das Bauernspiel beim Schach, müssen nicht unbedingt 16 Figuren sein; momentan sind es je 10. Die Idee habe ich von da:
http://fuzzy.cs.uni-magdeburg.de/studium/swprak/ws9900.html#Streichholz
Vielleicht so, dass der gewonnen hat, der die meisten "Figuren" in der gegnerischen Grundlinie hat. Das wäre interessanter, da man sich nun nicht mehr Figuren um den Preis des Weiterkommens schlagen lassen kann.
Aber mir fehlt es an Grips, das zu realisieren. Momentan spielt mein Programm auf Basis von if [...] then. Es prüft, ob mit dem nächsten Zug die gegnerische Grundlinie erreicht ist, ob eine Figur geschlagen werden kann usw. Aber es ist eben bloss if und while; mit exit und break, wenn's passt. Fazit: Ich kann es nicht...
Aber wenn du mir helfen willst, ein Bauernspiel zu basteln :mrgreen: - wäre toll!
F34r0fTh3D4rk - Mi 10.05.06 19:58
wenn du ein 3*3 oder 4*4 (nicht sooo viel größer) feld nimmst, dann solltest du mit einer einfachen unterscheidung von sieg und niederlage zum besten ziel gelangen, dann der suchbaum ist dann nicht ganz so groß, da jede figur nur eine bis 3 mögliche züge hat.(wenn überhaupt)
alzaimar - Mi 10.05.06 20:49
Ok galagher,
Soweit bist du bestimmt schon:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Const nPlayerCount = 8; Type TPlayer = (plHuman, plComputer); TBoard = Array [0..nPlayerCount -1, 0..nPlayerCount -1] of TPlayer; TChoord = Record cRow, cCol : Integer End; TMove = Record mPlayer : TPlayer; mFrom, mTo : TChoord; End; TMoveList = Array [0..2*nPlayerCount -1] Of TMove; |
Das war einfach. Wir können ja später eine Klasse basteln, aber für den Anfang ist mir das egal.
Wie ist die Anfangsstellung? Alle Bauern auf die Grundlinie? Oder in die 2.Reihe? Egal. Und bewegen und schlagen können sich die Bauern auch wie beim Schach? Gut.
Als Nächstes generieren wir eine Liste aller Züge, die ein Spieler 'aPlayer' auf einem Spieldfeld 'aBoard' ausführen kann.
Delphi-Quelltext
1: 2: 3: 4:
| Procedure CreateAllMoves (aPlayer : TPlayer; aBoard : TBoard; Var aMoves : TMoveList; Var aMoveCount : Integer); Begin ... End; |
So, nun noch eine Funktion, die die aktuelle Stellung bewertet. Hier benötigt man Experten für das jeweilige Spiel. Ich bin Keiner. Du? Na, mal sehen:
1.Erstmal ist es doch besser, wenn ich mehr Figuren habe, als Du.
2.Dann ist es besser, wenn ich mehr Figuren auf der Grundlinie habe, als Du (das ist schon mal die Gewinnbedingung).
3.Dann würde ich noch berücksichtigen, wie viele Züge ich brauchen würde, um alle meine Figuren auf die Grundlinie zu bekommen.
4.Schlecht ist es, wenn ich Figuren habe, die blockiert werden.
5.Dann gilt es noch, eigene Steine zu 'decken', speziell die, die angegriffen werden. Hier werden wieder diverse Koeffizienten, die die einzelnen Umstände bewerten, zum Einsatz kommen.
Ok: Sei N1 die Anzahl meiner Figuren, N2 die Anzahl Deiner Figuren.
Sei G1 die Anzahl meiner Figuren auf der gegnerischen Grundlinie, G2 entsprechende Deine.
Z1 ist die Gesamtanzahl der züge, die ich brauche, um alle meine Figuren auf die Grundlinie zu bekommen, Z2 wieder deine. Eine Figur, die blockiert ist, zählt X zusätzliche Züge (X ist ein KOEFFIZIENT, den wir später auch noch per Evolution optimieren können :mrgreen: )
B1 ist die Anzahl meiner Figuren, die blockiert sind.
D1 ist die Anzahl meiner gedeckten Figuren, A1 die der gedeckten Figuren, die angegriffen werden.
Eine Bewertungsfunktion FB könnte so aussehen:
FB = (Z1-Z2)*A + (G1-G2)*B + (Z1-Z2)*C + (B1-B2)*D + (D1-D2)*E + (A1-A2)*F
A-F sind wieder Koeffizienten, die wir erstmal willkürlich festlegen und später anpassen werden (Viva la Evolucion!). Da (B1-B2) die Gewinnbedingung ist (das primäre Ziel), wählen wir D ziemlich hoch. Den Rest nach Schnautze.
Das Resultat dieser Funktion ist umso höher, je BESSER der Spieler dasteht. Und, die Güte der Funktion ist entscheidend für die Spielstärke. Eine miserable Funktion lässt dein Programm spielen, wie einen rekonvaleszenten Rehpinscher. Die spielen bekanntlich nicht so gut.
Das wars. Beinahe. Wir müssen noch ein Patt und eine Gewinnsituation berücksichtigen (Keine Züge möglich). Das Spiel ist Zuende, wenn beide nicht mehr ziehen können, wenn also auf ein Patt von mir direkt ein Patt von Dir folgt.
Mal sehen:
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:
| FindeBestenZug (Spieler, Gegner, Brett, SpielTiefe, MaxTiefe, BesterZug, Patt) returns Integer; Begin MovePossible := False; CreateAllMoves (Sieler, Brett, M, N); For i:=0 to N-1 do Begin MovePossible := True; Board.DoMove(M[i]); If SpielTiefe = MaxTiefe Then S := Board.BewerteStellung (Spieler); Else S := -FindeBestenZug (Gegner, Spieler, Brett, SpielTiefe + 1, MaxTiefe, DummyZug, False); If S > MaxS Then MaxS := S BesterZug := M End; Board.UndoMove (M[i]); End
if Not MovePossible Then If Patt Then Result := Board.BewerteEndstellung (Spieler) else Result := -FindeBestenZug (Gegner, Spieler, Brett, SpielTiefe + 1, MaxTiefe, DummyZug, True); End |
(Die Variablen deklariere ich nicht, wozu auch, ist ja eh nur Pseudocode)
Fertig (wirklich!, bis auf meine Schussligkeits- und Denkfehler).
Fang mal so an:
1.Schreibe die Funktion, die alle Züge eines Spielers bei einer beliebigen Stellung erzeugt.
2.Schreibe die Funktion, die die Stellung strategisch bewertet.
3.Schreibe die Funktion, die die Stellung abschließend bewertet (Ich habe gewonnen: >+100000, Du hast gewonnen: <-100000).
4.Verwurste alles mit minimax.
5.Ändere Minimax in NegaScout (fast das Gleiche, siehe wikipedia)
6.Du bist ein Profi!
Fragen? Fragen!
galagher - Mi 10.05.06 21:26
alzaimar hat folgendes geschrieben: |
Fang mal so an:
1.Schreibe die Funktion, die alle Züge eines Spielers bei einer beliebigen Stellung erzeugt.
2.Schreibe die Funktion, die die Stellung strategisch bewertet.
3.Schreibe die Funktion, die die Stellung abschließend bewertet (Ich habe gewonnen: >+100000, Du hast gewonnen: <-100000).
4.Verwurste alles mit minimax.
5.Ändere Minimax in NegaScout (fast das Gleiche, siehe wikipedia)
6.Du bist ein Profi!
Fragen? Fragen! |
Zunächst einmal danke für deine Hilfe! Ich verstehe im Moment nur Bahnhof, aber ich werde ab morgen Schritt für Schritt anfangen, und, ja, Fragen werde ich sicher haben! Ich muss alles ausser die Oberfläche wegschmeissen, denn zB. die Zugregeln sind in der Prozedur, die die Züge durchführt. Ich stelle mir die Grundstellung mit je 2 Reihen "Figuren" vor, mit insgesamt 30 Feldern, alles Panels - geht das so oder soll ich eine andere Komponente verwenden?
alzaimar - Do 11.05.06 10:18
Ich würde für den Anfang ein blödes StringGrid nehmen, mit 'X' für deine und 'O' für die Steine des Gegners. Dann das Spiel programmieren, bis es richtig gut ist und zum Schluss eine schöne GUI drum herum basteln. Ich denke, du kannst das mit 4 Bitmaps erledigen (1x weisses Feld, 1x schwarzes Feld, 1x Bauer Weiss, 1x Bauer Schwarz). Das hübsch auf einen Canvas pinseln und fertig. Besser als 25 oder 64 panels jedenfalls.
galagher - Do 11.05.06 19:28
Erst mal vielen Dank für deine Hilfe, aber ich sage gleich, es wird sicher mühsam werden, denn ein Spiel zu programmieren habe ich mir leichter vorgestellt! Aber ich hoffe dennoch, ich kriege es mit deiner Hilfe hin - dafür kommt dein Name dann auch in die About-Box!
alzaimar hat folgendes geschrieben: |
Ok galagher,
Soweit bist du bestimmt schon:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Const nPlayerCount = 8; Type TPlayer = (plHuman, plComputer); TBoard = Array [0..nPlayerCount -1, 0..nPlayerCount -1] of TPlayer; TChoord = Record cRow, cCol : Integer End; TMove = Record mPlayer : TPlayer; mFrom, mTo : TChoord; End; TMoveList = Array [0..2*nPlayerCount -1] Of TMove; | |
Ok, das hab' ich jetzt im Programm, aber es tut noch nichts und ich verstehe es nicht!
alzaimar hat folgendes geschrieben: |
Wie ist die Anfangsstellung? Alle Bauern auf die Grundlinie? Oder in die 2.Reihe? Egal. Und bewegen und schlagen können sich die Bauern auch wie beim Schach? Gut. |
Alle Bauern sind jeweils in der 1. und 2. Reihe, es gibt also 32 Bauern. Kann ich aber noch ändern. Es gibt jetzt wie beim Schach 64 Felder - kann ich aber natürlich auch noch ändern. Die Bauern ziehen wie beim Schach, nur auf die 2 Felder auf einmal beim 1. Zug verzichte ich - und man kann sich selbst schlagen! Die Züge funktionieren schon - ich habe das in einer Unit "Steuerung" in einer eigenen Prozedur "Zug(ACol, ARow: Integer)".
Aufgerufen wird Zug in StringGrid1SelectCell - man kann also schon mit sich selbst spielen.
alzaimar hat folgendes geschrieben: |
Als Nächstes generieren wir eine Liste aller Züge, die ein Spieler 'aPlayer' auf einem Spieldfeld 'aBoard' ausführen kann.
Delphi-Quelltext 1: 2: 3: 4:
| Procedure CreateAllMoves (aPlayer : TPlayer; aBoard : TBoard; Var aMoves : TMoveList; Var aMoveCount : Integer); Begin ... End; | |
Und schon wieder verstehe ich etwas nicht. Was ist TPlayer und TBoard, wenn es doch mit x und o auf einem StringGrid gemacht wird? Was soll ich da wie machen und wozu? :nixweiss:
Weiter fragen kommt dann später! :mrgreen:
alzaimar - Do 11.05.06 21:42
Also:
Ein TPlayer ist der Wert, den ein Feld auf dem Brett annehmen kann. Dabei habe ich natürlich vergessen, das ein Brett auch leer sein kann.
Ich würde das StringGrid nur zur Darstellung verwenden, als interne Datenstruktur würde ich ein Array [0..7,0..7] of TPlayer oder ein Array [0..63] of TPlayer verwenden. Letzeres ist etwas schneller, aber nicht so trivial.
galagher - Fr 12.05.06 18:09
alzaimar hat folgendes geschrieben: |
Ich würde das StringGrid nur zur Darstellung verwenden, als interne Datenstruktur würde ich ein Array [0..7,0..7] of TPlayer oder ein Array [0..63] of TPlayer verwenden. Letzeres ist etwas schneller, aber nicht so trivial. |
Mir wäre es lieber, wenn wir einfach beim StringGrid bleiben könnten. Dann ist der Code zwar fix auf ein StringGrid festgelegt, aber ich glaube, so wird es einfacher.
Ich bin gerade dabei, eine Bewertungsfunktion zu schreiben; die Rückgabe ist ein Integerwert, und ich werde versuchen, die von dir aufgelisteten Kriterien zu berücksichtigen, aber:
alzaimar hat folgendes geschrieben: |
3.Dann würde ich noch berücksichtigen, wie viele Züge ich brauchen würde, um alle meine Figuren auf die Grundlinie zu bekommen.
4.Schlecht ist es, wenn ich Figuren habe, die blockiert werden.
5.Dann gilt es noch, eigene Steine zu 'decken', speziell die, die angegriffen werden. Hier werden wieder diverse Koeffizienten, die die einzelnen Umstände bewerten, zum Einsatz kommen.
Ok: Sei N1 die Anzahl meiner Figuren, N2 die Anzahl Deiner Figuren.
Sei G1 die Anzahl meiner Figuren auf der gegnerischen Grundlinie, G2 entsprechende Deine.
Z1 ist die Gesamtanzahl der züge, die ich brauche, um alle meine Figuren auf die Grundlinie zu bekommen, Z2 wieder deine. Eine Figur, die blockiert ist, zählt X zusätzliche Züge (X ist ein KOEFFIZIENT, den wir später auch noch per Evolution optimieren können :mrgreen: )
B1 ist die Anzahl meiner Figuren, die blockiert sind.
D1 ist die Anzahl meiner gedeckten Figuren, A1 die der gedeckten Figuren, die angegriffen werden.
Eine Bewertungsfunktion FB könnte so aussehen:
FB = (Z1-Z2)*A + (G1-G2)*B + (Z1-Z2)*C + (B1-B2)*D + (D1-D2)*E + (A1-A2)*F
A-F sind wieder Koeffizienten, die wir erstmal willkürlich festlegen und später anpassen werden (Viva la Evolucion!). Da (B1-B2) die Gewinnbedingung ist (das primäre Ziel), wählen wir D ziemlich hoch. Den Rest nach Schnautze.
Das Resultat dieser Funktion ist umso höher, je BESSER der Spieler dasteht. Und, die Güte der Funktion ist entscheidend für die Spielstärke. Eine miserable Funktion lässt dein Programm spielen, wie einen rekonvaleszenten Rehpinscher. Die spielen bekanntlich nicht so gut. |
Das bekomme ich nicht hin. Ich kann Felder und deren "Figur" und die Felder rundherum bewerten, kann prüfen, ob und wieviele Figuren auf den Grundlinien stehen und ob Figuren blockiert werden. Aber zB. berechnen, wie viele Züge zum Sieg fehlen - das ist von mehreren Faktoren abhängig: Wie viele Figuren hat der Spieler, wie viele der Computer auf der Grundlinie. Wer hat die bessere Stellung, wie schnell (in wie vielen Zügen, und welche Züge!) können dies ändern, und ...
Das wird also ein rekonvaleszenter Rehpinscher, weil ich keine Ahnung habe, wie ich eine solche komplexe Funktion schreiben soll. Ich habe mir den Minimax-Algo einfacher vorgestellt - ich meine, man nimmt ihn, setzt ihn ein und das ist es dann! Naja, nicht ganz so naiv, aber eben einfacher. Etwa so, wie man lernt, eine CheckListBox, ein RichEdit oder SynEdit zu benutzen.
Ich meine, das mit den Zügen im StringGrid habe ich ja noch hinbekommen - mit dem StringGrid kann man jetzt ein Bauernspiel spielen. Die Zugregeln werden eingehalten, aber ein solches Spiel zu schreiben übersteigt insgesamt mein Können. Das ist kein "normales" Programm!
Wenn du meinst, es reicht, eine Bewertung zu schreiben, die das kann, was ich erwähnt habe, dann mache ich es. Wenn du meinst, dass das dann der rekonvaleszente Rehpinscher wird, dann lieber kein Programm als ein schlechtes!
Wenn du noch Chancen für das Programm siehst, und mir noch weiter helfen möchtest - aber mehr kann ich nicht! Ich habe noch nie ein "intelligentes" Spiel geschrieben.
alzaimar - Fr 12.05.06 19:26
Die Bewertungsfunktion kann doch erstmal trivial sein. Mach einfach, soviel Du schaffst. Am aller Wichtigsten ist es doch, eine Gewinnstellung zu erkennen. Die Anzahl der Züge soll man natürlich nicht durchprobieren, sondern abschätzen. Eine andere Möglichkeit ist der Abstand der Figuren zum Ziel.
Eine Funktion, die eine Stellung bewertet, ist in der Tag etwas Schwierig. Bei einigen Spielen ist es einfach (4 gewinnt oder Reversi), bei anderen hingegen schwer (Dame, Backgammon, Schach, Go(!)). Wenn Du nur ein Spiel *haben* willst, dann lass es. Wenn Du aber ein Spiel *programmieren* willst, dann beiß dich durch. Muste ich doch auch machen. Außerdem ist noch kein Meister vom Himmel gefallen.
Wenn Du den Suchbaum komplett durchrechnen kannst (schneller Rechner, guter Algorithmus, nicht so komplexes Spiel), dann ist die Bewertungsfunktion sehr einfach: +1 für 'Spieler gewinnt', -1 für 'Gegner gewinnt'.
Wenn man nicht bis zum Schluss durchrechnen kann (z.B. Schach), dann benötigt man eine solche Funktion. Die muss verschiede Kriterien erfüllen:
1. Sie muss schnell sein.
2. Das Resultat muß die strategischen Vor- und Nachteile wiederspiegeln.
(1) vergessen wir mal. Bei (2) kommt der Appetit mit dem Essen. Wenn Du erstmal die einfachen Dinge ermittelt hast, dann kommen peu-a-peu weitere hinzu.
Zu deinem Stringgrid sag ich so viel: NEE! Nimm ein Array [0..7,0..7] Of Char, die Spieler sind 'X', 'O' und ' '. Du ziehst in dem Array und rufst nach jedem Zug einfach eine Routine auf, die dein Spielfeld visualisiert. Und DAFÜR nimmst Du das Stringgrid.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| Procedure TMyForm.Showboard (aBoard : TBoard); Var i,j : Integer;
Begin For i:=0 To 7 do For j:=0 to 7 do MyStringGrid.Cells[i,j] := aBoard[i,j]; End; |
Das ist doch nicht so schwer, oder?
Aber was Grundsätzliches zum Schluss. Ein Spiel zu programmieren, das nicht bescheuert spielt, ist natürlich nicht so einfach. Man muss abstrahieren können und Rekursivität beherrschen. Ohne das wirds Nichts. Meine ersten Spielprogramme (Dame und Reversi) waren unglaublich aufgebläht, verdammt langsam und - spielten schlecht. Na und? Ich habe weiter gemacht und weiter gemacht und irgendwann hatte ich ein Backgammon (Reversi wurde langweilig) Programm, das nicht schlecht spielte. Zumindest hatte es eine Eröffnungsbibliothek, hielt sich an die gängigen Tricks und schlug sich achtbar.
Wenn Du so ein Spiel mit Minimax, Stellungsfunktion etc. mal richtig programmiert hast, hast Du mehr gelernt als in 2 Semestern IT-Studium!
Aber wenn Du dir das nicht zutraust, dann über erstmal einfachere Programme und versuche es später einfach nochmal. Ich brauch manchmal auch ettliche Anläufe, bis ich ein für mich komplexes Programm richtig zum Laufen bekomme. Aber ohne erfolglose Versuche, Rückschläge, ausgerissene Haare, suizidales Kopf-auf-den-Schreibtisch-knallen, verzeifeltes "Maaamaaaaaa" schreien usw. hätte ich es nie geschafft.
Blöder Spruch zum Schluss: "Nimm dir immer eine Aufgabe vor, die dir etwas zu komplex erscheint"!
galagher - Fr 12.05.06 21:25
Dann mache ich mich morgen mal an die Bewertung! Mal sehen, was das dann wird. Wenn möglich, versuche ich auch gleich, den Computer spielen zu lassen, wobei wird schon beim Array wären:
alzaimar hat folgendes geschrieben: |
Zu deinem Stringgrid sag ich so viel: NEE! Nimm ein Array [0..7,0..7] Of Char, die Spieler sind 'X', 'O' und ' '. Du ziehst in dem Array und rufst nach jedem Zug einfach eine Routine auf, die dein Spielfeld visualisiert. Und DAFÜR nimmst Du das Stringgrid.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| Procedure TMyForm.Showboard (aBoard : TBoard); Var i,j : Integer;
Begin For i:=0 To 7 do For j:=0 to 7 do MyStringGrid.Cells[i,j] := aBoard[i,j]; End; |
Das ist doch nicht so schwer, oder? |
Ich werde versuchen, das umzusetzen.
alzaimar hat folgendes geschrieben: |
Aber wenn Du dir das nicht zutraust, dann über erstmal einfachere Programme und versuche es später einfach nochmal. |
Zutrauen schon, nur fürchte ich, es wird eine halbe Sache - ein Programm, das nicht sinnvoll spielen kann. Das ist Mist. Schade um den Speicher, den es belegt!
Aber wie gesagt - mit deiner Hilfe versuche ich es schon! :D
//Edit: Verstehe ich das mit dem Array richtig: Der Spieler setzt seinen Zug direkt am StringGrid, das Array kennt die Stellung, berechnet wird mittels Array, der Computerzug erfolgt wie in deinem Beispiel wiederum im StringGrid?
galagher - Sa 13.05.06 06:29
alzaimar hat folgendes geschrieben: |
Zu deinem Stringgrid sag ich so viel: NEE! Nimm ein Array [0..7,0..7] Of Char, die Spieler sind 'X', 'O' und ' '. Du ziehst in dem Array und rufst nach jedem Zug einfach eine Routine auf, die dein Spielfeld visualisiert. Und DAFÜR nimmst Du das Stringgrid. |
Ich kriege die Umsetzung des Arrays einfach nicht hin. Wenn ich im Array ziehen kann, dann ist es also ein Abbild des Spielfeldes, oder? Und warum also dann nicht gleich das Spielfeld, warum erst dieser Umweg und dann die Ausgabe auf dem StringGrid?
Wenn du mir da mal auf die Sprünge helfen könntest? Konkret: Man klickt auf das StringGrid, und dann passiert - was? Im Moment sieht das bei mir so aus:
Delphi-Quelltext
1: 2: 3: 4: 5:
| procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); begin Zug(ACol, ARow); end; |
alzaimar - Sa 13.05.06 08:31
Mojn,
Weil das StringGrid zu langsam ist, deshalb. Aber der Hauptgrund ist das 'Softwaredesign'. Man trennt nunmal die Visualisierung von der Funktion. Ok, wenn du mal eben ein kleines Progrämmchen hinfrickelst, dann natürlich nicht. Aber hier würde ich es schon machen. Wenn Du nachher ein schickes Schachbrett mit tollen Bauernfiguren verwenden willst, musst du dann nur ein paar Routinen ändern, und ziehst nicht dieses Pferdefuß von TStringGird mit.
F34r0fTh3D4rk - Sa 13.05.06 08:58
über die visualisierung mache ich mir auch erst später gedanken, bzw entwickle das parallel, man sollte das möglichst trennen und dann eine zeichenfunktion bauen, die man dann ja anpassen kann.
galagher - Sa 13.05.06 09:52
Also:
Ich hab das Ganze jetzt in einem Array und der Prozedur ist es jetzt egal, ob es von einem StringGrid oder sonstwas kommt:
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:
| var TBoard: Array [0..7, 0..7] of String[1]; [...]
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); var B, N: Byte; begin for B := 0 to StringGrid1.ColCount-1 do begin for N := 1 to StringGrid1.RowCount-1 do TBoard[B, N] := StringGrid1.Cells[B, N]; end;
Zug(ACol, ARow);
for B := 0 to 7 do begin for N := 1 to 7 do StringGrid1.Cells[B, N] := TBoard[B, N]; end; |
Es geht sicher eleganter, als immer alles neu zu zeichnen, aber erstmal ist es das, was du meinst, oder?
//Edit: geändert:
Delphi-Quelltext
1: 2: 3: 4: 5:
| const nPlayerCount = 8;
var TBoard: array [0..nPlayerCount-1, 0..nPlayerCount-1] of String[1]; |
galagher - Mo 15.05.06 18:45
Hallo!
Ich habe jetzt eine Bewertungsfunktion
Delphi-Quelltext
1:
| function Bewertung(Player: TPlayer): Integer; |
die folgende Überprüfungen durchführt:
1. Zählen, wie viele eigene Figuren auf der gegnerischen Grundlinie sind - Inc(Result)
2. Zählen, wer mehr Figuren hat - eigene: Inc(Result), sonst Dec(Result)
3. Prüfen, ob sich der Spieler/Computer selbst blockiert;
das ist anfangs immer so (je 2 Reihen Figuren) - Dec(Result)
4. Prüfen, ob der Spieler/Computer eine oder zwei gegnerische Figuren schlagen könnte - je Inc(Result)
Ok, einfach, aber es ist eine Bewertung. Du hast ein Beispiel gebracht:
alzaimar hat folgendes geschrieben: |
Eine Bewertungsfunktion FB könnte so aussehen:
FB = (Z1-Z2)*A + (G1-G2)*B + (Z1-Z2)*C + (B1-B2)*D + (D1-D2)*E + (A1-A2)*F
A-F sind wieder Koeffizienten, die wir erstmal willkürlich festlegen und später anpassen werden (Viva la Evolucion!). Da (B1-B2) die Gewinnbedingung ist (das primäre Ziel), wählen wir D ziemlich hoch. Den Rest nach Schnautze. |
Da sind Welten dazwischen...
Nun ja:
alzaimar hat folgendes geschrieben: |
Fragen? Fragen! |
Das tue ich hiermit:
@alzaimar: Wenn ich nicht mit lauter IF arbeiten will (will ich definitiv nicht!), brauche ich jetzt einen sinnvollen Algorithmus, der die Bewertung verarbeitet und dann den Zug setzt. (Und eine bessere, genauere Bewertung). Nun, das kann ich einfach nicht (alleine).
F34r0fTh3D4rk - Mo 15.05.06 18:50
minimax, davon sprechen wir doch die ganze zeit, der code steht hier doch auch schon mehrfach drin :lol:
alzaimar - Mo 15.05.06 22:51
galagher hat folgendes geschrieben: |
Hallo! |
Hallo zurück
galagher hat folgendes geschrieben: |
Ich habe jetzt eine Bewertungsfunktion
Delphi-Quelltext 1:
| function Bewertung(Player: TPlayer): Integer; |
die folgende Überprüfungen durchführt:
1. Zählen, wie viele eigene Figuren auf der gegnerischen Grundlinie sind - Inc(Result)
2. Zählen, wer mehr Figuren hat - eigene: Inc(Result), sonst Dec(Result)
3. Prüfen, ob sich der Spieler/Computer selbst blockiert;
das ist anfangs immer so (je 2 Reihen Figuren) - Dec(Result)
4. Prüfen, ob der Spieler/Computer eine oder zwei gegnerische Figuren schlagen könnte - je Inc(Result)
Ok, einfach, aber es ist eine Bewertung. Du hast ein Beispiel gebracht:
alzaimar hat folgendes geschrieben: | Eine Bewertungsfunktion FB könnte so aussehen:
FB = (Z1-Z2)*A + (G1-G2)*B + (Z1-Z2)*C + (B1-B2)*D + (D1-D2)*E + (A1-A2)*F
A-F sind wieder Koeffizienten, die wir erstmal willkürlich festlegen und später anpassen werden (Viva la Evolucion!). Da (B1-B2) die Gewinnbedingung ist (das primäre Ziel), wählen wir D ziemlich hoch. Den Rest nach Schnautze. |
Da sind Welten dazwischen...
|
Nein, nur ein paar Koeffizienten. Du machst fast genau das, was ich vorschlug. Aber: Die Anzahl der eigenen Figuren ist doch nicht so wichtig wie, sagen wir, die Anzahl der Steine, die in der 7.Reihe sind (denn die sind potentielle Gewinnsteine!). Ebenso ist es wichtiger, Steine zu haben die Andere schlagen können oder Steine, die gedeckt sind.
Du solltest nicht einfach Inc/Dec Result machen, denn dann ist alles gleich wichtig, isses aber nicht. Du solltest viel mehr die einzelnen Eigenschaften der Stellung (Anzahl der Steine, wie viele sind geblockt etc. ) in eigenen Variablen zunächst zählen und dann mit den unterschiedlichen Gewichten multiplizieren. Beim Schach ist es doch so, das ein Turm 3x so viel 'Wert' ist wie ein Bauer (oder?). Also ist die Stellungsfunktion schon mal so:
Du bist wirklich schon sehr weit mit deiner Bewertungsfunktion. Jetzt fehlen nur noch die Gewichtungen, also das, was ich 'Koeffizienten' nenne. Um wieviel wichtiger nun die Anzahl der nicht geblockten Steine gegenüber der Anzahl der Schlagmöglichkeiten ist, weiss ich auch nicht. Das ist ja gerade die Kunst! Aber du kannst erstmal mit groben Werten experimentieren, das ist jetzt auch nicht so wichtig, denn die Koeffizienten lässt man sich später per evolutionärem Verfahren einfach -schwupps- mal eben vom Programm selbst optimieren! Das ist total einfach und unglaublich verblüffend: Du bekommst ein lernendes System, das sich -geschickt programmiert- sogar dem jeweiligen Gegner anpasst und lernt! Das ist dann mal eine echte KI!
Also: Eigenschaften getrennt zählen, jeweils mit unterschiedlichen Faktoren multiplizieren und zum Schluss addieren
Denk immer dran, das die Bewertungsfunktion nur aus Sicht des übergebenen Players zählt. Wenn der "Player" besser dasteht, ist das Ergebnis positiv, sonst negativ.
galagher - Di 16.05.06 19:19
alzaimar hat folgendes geschrieben: |
Nein, nur ein paar Koeffizienten. Du machst fast genau das, was ich vorschlug. Aber: Die Anzahl der eigenen Figuren ist doch nicht so wichtig wie, sagen wir, die Anzahl der Steine, die in der 7.Reihe sind (denn die sind potentielle Gewinnsteine!). Ebenso ist es wichtiger, Steine zu haben die Andere schlagen können oder Steine, die gedeckt sind. |
Ok, ist jetzt berücksichtigt.
alzaimar hat folgendes geschrieben: |
Du solltest nicht einfach Inc/Dec Result machen, denn dann ist alles gleich wichtig, isses aber nicht. Du solltest viel mehr die einzelnen Eigenschaften der Stellung (Anzahl der Steine, wie viele sind geblockt etc. ) in eigenen Variablen zunächst zählen und dann mit den unterschiedlichen Gewichten multiplizieren. Beim Schach ist es doch so, das ein Turm 3x so viel 'Wert' ist wie ein Bauer (oder?). Also ist die Stellungsfunktion schon mal so:
|
Mit den Gewichten meinst du die Wertigkeit (höher oder geringer) einer Eigenschaft, zB. "Figur ist einfach gedeckt"? ZB. könnte man einer einfach gedeckten Figur die Wertigkeit 2 zuordnen, während einer Figur auf der gegnerischen Grundlinie ein viel höherer Wert zukommt, zB 10. Meinst du das so? Die Formel wäre demnach für 3 einfach gedeckte Figuren, 1 doppelt gedeckten Figur und 2 auf der Gegner-Grundlinie 3*2, 1*4, 2*10
Zum Schluss die einzelnen Ergebnisse addieren. Oder verstehe ich das falsch? Und wie bestimmt man die Werte richtig? Ist 2 relativ zu 10 wirklich korrekt, oder ist es nicht doch eher 3 ? Also, da raucht einem ja der Kopf!
alzaimar hat folgendes geschrieben: |
Du bist wirklich schon sehr weit mit deiner Bewertungsfunktion. Jetzt fehlen nur noch die Gewichtungen, also das, was ich 'Koeffizienten' nenne. Um wieviel wichtiger nun die Anzahl der nicht geblockten Steine gegenüber der Anzahl der Schlagmöglichkeiten ist, weiss ich auch nicht. Das ist ja gerade die Kunst! Aber du kannst erstmal mit groben Werten experimentieren, das ist jetzt auch nicht so wichtig, denn die Koeffizienten lässt man sich später per evolutionärem Verfahren einfach -schwupps- mal eben vom Programm selbst optimieren! Das ist total einfach und unglaublich verblüffend: Du bekommst ein lernendes System, das sich -geschickt programmiert- sogar dem jeweiligen Gegner anpasst und lernt! Das ist dann mal eine echte KI! |
Also hier steig ich erstmal aus und später wieder ein, wenn die Bewertung steht! :eyes:
alzaimar hat folgendes geschrieben: |
Also: Eigenschaften getrennt zählen, jeweils mit unterschiedlichen Faktoren multiplizieren und zum Schluss addieren |
Stimmt demnach mein Beispiel oben mit 3*2, 1*4, 2*10 ?
alzaimar - Di 16.05.06 20:08
Langer Beitrag, kurze Antwort: Ja, Alles richtig.
Und die Gewichtung ist wirklich eine Sache des Ausprobierens. Wir fangen erstmal mit linearen Gewichtungen an: Zwei gedeckte Figuren sind doppelt so gut, wie eine gedeckte Figur. Es kann aber sein, das das so nicht stimmt: Dann sind z.B. zwei gedeckte Figuren 3x so gut, wie Eine... Das sind aber alles Dinge, um die man sich später kümmern kann. Jetzt erstmal die Bewertungsfunktion sauber und einigermaßen schnell hinbekommen.
Danach bauen wir noch die Routine, die die Züge generiert und dann.... ja dann sind wir fertig :mrgreen:
[edit] Und wenn Du dann noch Lust hast, lassen wir uns die Bewertungsfunktion vom Computer optimieren :dance2: [/edit]
galagher - Di 16.05.06 21:11
alzaimar hat folgendes geschrieben: |
Danach bauen wir noch die Routine, die die Züge generiert und dann.... ja dann sind wir fertig :mrgreen:
[edit] Und wenn Du dann noch Lust hast, lassen wir uns die Bewertungsfunktion vom Computer optimieren :dance2: [/edit] |
Klar möchte ich das, danke! Werde schauen, ob ich bis morgen die Bewertung fertig bekomme - komme nur abends zum Programmieren - und melde mich dann wieder! :D
galagher - Mi 17.05.06 17:32
Hallo!
Ich habe jetzt soweit die Bewertung und die Werte der Eigenschaften fertig:
Wie viele Figuren sind insgesamt im Spiel: 1
Wie viele eigene Figuren sind im Spiel: 1
Wie viele eigene Figuren auf der Gegner-Grundlinie: 10
Wie viele eigene Figuren auf der Linie vor der Gegner-Grundlinie: 8
Wer hat mehr Figuren: 3
Ist eine eigene Figur einfach gedeckt: 2
Ist eine eigene Figur doppelt gedeckt: 4
Blockiert sich eine eigene Figur selbst: -4
Kann eine gegnerische Figur geschlagen werden: 5
Die Werte sind erstmal willkürlich und ich bin gespannt, wie man sie korrigiert und optimiert! Diese Bewertungen werden jeweils nur für den angegebenen Spieler vorgenommen - die Funktion erwartet plHuman oder plComputer; der Rückgabewert ist:
Delphi-Quelltext
1: 2: 3:
| Result := gesamt + eigene + auf_GegnerGrund + auf_vor_GegnerGrund + mehr_Spieler + mehr_Computer + einfach_gedeckt + doppelt_gedeckt + blockiert + kann_schlagen; |
//Edit: Nach den Multiplikationen (zB. gesamt := gesamt * gesamt_Value) natürlich!
Ich hoffe, ich habe nichts Wichtiges in der Bewertung vergessen! Jetzt brauche ich noch eine Prozedur, die auf Basis der Bewertung einen Zug macht.
alzaimar - Mi 17.05.06 23:16
Dafür nimmst Du nun den Minimax / NegaMax Algorithmus. Hast Du schon eine Routine, die Dir eine Liste aller möglichen Züge eines Spielers generiert? Wenn das fertig ist, verwendest Du einfach meinen Pseudocode, oder passt mein TTT (Tic-Tac-Toe) an.
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: 31:
| Function FindeBestenZug (Spieler, Gegner : TPlayer; Brett : TBoard; SpielTiefe, MaxTiefe : Integer; Var BesterZug : TMove; Patt : Boolean) : Integer; Var MovePossible : Boolean; M : TMoveList; MaxS, S, N, i : Integer; DummyZug : TMove;
Begin MovePossible := False; CreateAllMoves (Spieler, Brett, M, N); For i:=0 to N-1 do Begin MovePossible := True; Board.DoMove(M[i]); If SpielTiefe = MaxTiefe Then S := Board.BewerteStellung (Spieler); Else S := -FindeBestenZug (Gegner, Spieler, Brett, SpielTiefe + 1, MaxTiefe, DummyZug, False); If S > MaxS Then MaxS := S BesterZug := M End; Board.UndoMove (M[i]); End
if Not MovePossible Then If Patt Then Result := Board.BewerteEndstellung (Spieler) else Result := -FindeBestenZug (Gegner, Spieler, Brett, SpielTiefe + 1, MaxTiefe, DummyZug, True); End; |
Das Prinzip ist einfach: Führe einfach jeden möglichen Zug nacheinander aus und schaue nach, was die beste Antwort des Gegners wird. Der beste eigene Zug ist nun einfach der, der den aus Sicht des Gegners schlechtesten Gegenzug zulässt. Das Ganze wird nun rekursiv aufgerufen, bis die maximale Vorschautiefe (MaxLevel) erreicht wird. Hier wird die Stellung bewertet (wenn nicht schon vorher eine Gewinnstellung erreicht wurde). In der tiefsten Ebene wird also der Zug gewählt, der die besten Stellungsbewertung 'verursacht'.
Das Teil rufst Du einfach so auf:
Delphi-Quelltext
1:
| S := FindeBestenZug (plHuman, plComputer, MyBoard, 0, MaxLevel, M, False); |
S ist die Bewertung des Zuges 'M'. Und 'M' ist eben der beste Zug. Ganz einfach.
Ach ja: MaxLevel ist sozusagen die Spielstärke von deinem Programm. Ein hoher Wert bedeutet eben, das der Suchbaum, also die Kombination aller möglichen Züge und Gegenzüge sehr weit durchlaufen wird. Das führt zu einer sehr guten Spielweise, kann aber eben auch ewig dauern.
Wenn es z.B. bei Deinem Spiel im Mittel pro Zug 10 Möglichkeiten gibt, dann muss man bei MaxLevel = 1 schon 100 (10*10) Stellungen durchsuchen, bei MaxLevel = 2 sind das schon 1000 usw. Irgendwann werden es so viele Kombinationen, das man zu lange warten müsste. Da aber die meisten Zugmöglichkeiten sowieso Blödsinn sind, könnte man das auch ganz gewaltig optimieren. Das machen wir dann im nächsten Schritt. Wenn das Programm funktioniert.
galagher - Do 18.05.06 17:20
alzaimar hat folgendes geschrieben: |
Hast Du schon eine Routine, die Dir eine Liste aller möglichen Züge eines Spielers generiert? |
Ist vielleicht eine blöde Frage: alle generell möglichen Züge, also nur einmal, oder alle Züge, die aus der momentanen Stellung heraus möglich sind, also eine immer aktualisierte Liste? Wirklich eine Liste, zB. TStringList?
//Edit: hätte lesen sollen: TMoveList. :oops:
Was den Pseudocode betrifft, werde ich mich schon durchbeissen (und fragen wahrscheinlich auch)!
alzaimar - Do 18.05.06 17:42
Es gibt keine blöden Fragen. Nur blöde Antworten.
Der Minimax spielt ja alle möglichen Zugkombinationen durch. Die Routine 'FindAllMoves' muss zu einer bestimmten Spielstellung, die in 'aBoard' gespeichert ist, für einen Spieler 'aPlayer' alle legalen Züge ermitteln.
Anschließend führt Minimax nacheinander den Zug aus, und prüft, wie der Gegner antworten könnte. Anschließend wird der Zug wieder rückgängig gemacht.
galagher - Do 18.05.06 17:44
Bevor ich umsonst weitermache: meinst du so in etwa:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| procedure CreateMoveList(Player: TPlayer); var B, N: Byte; begin Form1.ListBox1.Clear;
for B := 0 to nPlayerCount-1 do begin for N := 0 to nPlayerCount-1 do if (TBoard[B, N] = Human) and (TBoard[B, N-1] = '') then Form1.ListBox1.Items.Add(IntToStr(B)+' '+IntToStr(N-1)); end; end; |
ListBox1 verwende ich nur im Moment, damit ich auch sehen kann, was der Code macht. Wird, wenn es funktioniert, natürlich gegen TStringList ausgetauscht! Aber: Was ist eine TMoveList, und warum gerade das?
alzaimar - Do 18.05.06 17:55
So in etwa, aber Du kannst damit ja nur die 'Human' Züge erzeugen. Du brauchst aber auch (wenn aPlayer = plComputer) die Computerzüge. Da gehst du dann rückwärts. Und wenn du einen gegnerischen Bauern schlagen kannst, prüfst Du ja schräg nach vorne(bzw. hinten) rechts/links. Du kannst also pro Spielstein maximal 3 Züge erzeugen: Vorwärts ziehen, und schräg links und rechts schlagen.
Eine Listbox ist wieder zu langsam und ein GUI-Element.
Eine TMoveList ist einfach eine Datenstruktur, nämlich eine Liste von Zügen.
Ich würde das so machen (schon weiter oben erklärt)
Erstmal eine Position (auf dem Brett) definieren:
Delphi-Quelltext
1: 2: 3: 4: 5:
| Type TPosition = Record pRow, pCol : Integer; End; |
Dann einen Zug. Der fängt irgendwo an und hört irgendwo auf. Vielleicht schlägt er den gegnerischen Stein ja auch.
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| Type TMove = Record mFrom, mTo : TPosition; mCapture : Boolean; End; |
Und eine TMoveList ist dann einfach ein
Delphi-Quelltext
1: 2:
| TMoveList = Array [0..ccMaxMoves] Of TMove; |
ccMaxMoves ist eine Konstante, die die maximal möglichen Züge beschreibt. Wenn man max. 16 Steine auf dem Brett hat, und jeder Stein kann maximal 3 Züge ausführen, wird man nie mehr als 48 Züge erzeugen können.
galagher - Do 18.05.06 18:36
alzaimar hat folgendes geschrieben: |
So in etwa, aber Du kannst damit ja nur die 'Human' Züge erzeugen. Du brauchst aber auch (wenn aPlayer = plComputer) die Computerzüge. Da gehst du dann rückwärts. Und wenn du einen gegnerischen Bauern schlagen kannst, prüfst Du ja schräg nach vorne(bzw. hinten) rechts/links. Du kannst also pro Spielstein maximal 3 Züge erzeugen: Vorwärts ziehen, und schräg links und rechts schlagen. |
Ich habe ja auch vor, beide Spieler mit allen ihnen möglichen Zügen zu berücksichtigen!
alzaimar hat folgendes geschrieben: |
Eine Listbox ist wieder zu langsam und ein GUI-Element.
|
Die ListBox wird später gegen TStringList ausgetauscht.
alzaimar hat folgendes geschrieben: |
Eine TMoveList ist einfach eine Datenstruktur, nämlich eine Liste von Zügen.
|
Vielleicht bin ich zu dämlich, aber es wird immer abstrakter! Ich bewege "Figuren" in einem Array mit in einem Record berechneten Zügen... Ich dachte mir das so: Erstelle eine Liste aller möglichen Züge und gehe diese von 0 bis List.Count-1 durch, stelle das Array TBoard dabei immer auf die Stellung zurück, die beim Aufruf der Prozedur, die die Liste abarbeitet, aktuell war, mache den jeweiligen Zug und merke dir die Werte der Züge. Dann ermittle den besten Wert, mache schliesslich den Zug, der zu diesem Wert führte (wie, weiss ich noch nicht) und verlasse die Schleife. Und je besser dabei die Bewertungsfunktion ist, desto besser wird das Programm spielen. Soweit ist mir das alles ja mittlerweile dank deiner Hilfe klar.
Ich sage dir ehrlich, ich steige da nicht durch mit TPosition = Record und TMove = Record. Ich wil ja keine sichtbaren Komponenten verwenden (nur zum Testen), also geht das nicht auch mit einer einfachen TStringList? Ein paar Millisekunden Zeitersparnis sind mir also wirklich egal, zumal ich sowieso ein Memo einbauen möchte, das die Berechnungsschritte anzeigt!
Wenn du aber auf deine Version bestehst - das wird sicher mindestens Tage dauern, bis ich da etwas hinbekomme! Aber es wäre schade, wenn das Projekt jetzt stirbt, nur weil ich da etwas nicht recht kapiere. :(
alzaimar - Do 18.05.06 18:42
Du kannst es doch so machen, wie Du willst... Ich hab doch nur geschrieben, wie ich das machen würde.
Das mit dem 'Zurückstellen' des spielfeldes geht einfacher, indem Du zwei routinen schreibst: DoMove und UndoMove. Die eine führt den Zug aus, die Andere nimmt ihn wieder zurück. Mit meinen Datenstrukturen geht das recht einfach:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| Procedure DoMove (aBoard : TBoard; aPlayer : TPlayer; aMove : TMove); Begin With aMove do Begin aBoard[mFrom.pRow, mFrom.pCol] := plEmpty; aBoard[mTo.pRow, ,mTo.pCol] := aPlayer; End; End;
Procedure UndoMove (aBoard : TBoard; aPlayer, aOpponent : TPlayer; aMove : TMove); Begin With aMove do Begin aBoard[mFrom.pRow, mFrom.pCol] := aPlayer; If mCapture Then aBoard[mTo.pRow, ,mTo.pCol] := aOpponent Else aBoard[mTo.pRow, ,mTo.pCol] := plEmtpy; End; End; |
galagher - Do 18.05.06 19:28
alzaimar hat folgendes geschrieben: |
Du kannst es doch so machen, wie Du willst... Ich hab doch nur geschrieben, wie ich das machen würde.[/delphi] |
Ok, die Zugliste ist fertig und funktioniert korrekt. Mein Programm kann:
1. Keine eigenen Züge machen. Alle Züge, auch die des Programms, kann im Moment nur ich machen
2. Alle Züge den Bauern-Zugregeln beim Schach entsprechend in einem Array ausführen
3. Das Array auf das StringGrid anwenden
4. Alle Züge bewerten
5. Die Bewertung erfolgt im Moment noch ohne Sinn: Caption := IntTostr(Bewertung(plComputer)); (oder plHuman)
6. Eine Liste der in der aktuellen Stellung möglichen Züge erstellen, zB. CreateMoveList(plComputer);
Dies ist eine einfache Liste mit je 2 Werten, wobei der 1. Col, der 2. Row des Arrays beschreibt.
Das Programm besteht also nun aus einigen Teilen, die aber noch nicht zusammenarbeiten.
Ich komme mit "primitiven" Mitteln wie StringList einfach besser zurecht; das verstehe ich auf Anhieb, die einfache Struktur mit den zwei Werten für Col und Row habe ich geschrieben und kenne sie daher. Aber wie ich sie nun anwenden soll, wie ich die Teile des Programms zusammenfüge, wie eine Computer-Zug-Prozedur aussieht und wie man das Ganze optimiert - keine Ahnung!
Also - wenn du mir weiter helfen willst, nur zu! :D
galagher - Do 18.05.06 20:22
alzaimar hat folgendes geschrieben: |
Das Prinzip ist einfach: Führe einfach jeden möglichen Zug nacheinander aus und schaue nach, was die beste Antwort des Gegners wird. Der beste eigene Zug ist nun einfach der, der den aus Sicht des Gegners schlechtesten Gegenzug zulässt. Das Ganze wird nun rekursiv aufgerufen, bis die maximale Vorschautiefe (MaxLevel) erreicht wird. Hier wird die Stellung bewertet (wenn nicht schon vorher eine Gewinnstellung erreicht wurde). In der tiefsten Ebene wird also der Zug gewählt, der die besten Stellungsbewertung 'verursacht'. |
Ich komme nicht klar. Alles, was ich bis jetzt habe, ist die Bewertungsfunktion und das Ausführen des besten Zuges, der für den Computer ermittelt wurde: Das Programm erstellt eine Liste aller ihm möglichen Züge, ermittelt den besten und führt ihn aus. Das ist alles. Ich kriege weder die Rekursion hin noch komme ich mit deinem Pseudocode klar.
Ich habe alles bis auf eine Routine, die sinnvolle Züge berechnet. Das "Herz" des Programs also fehlt noch. Und ich muss zugeben, ich kann es nicht.
Ich habe, um mich optisch orientieren zu können, dzt. alle Zugkoordinaten in StringList's. Das erste "Wort" ist die Bewertung, ggf. mit Nullen voran, um sortieren zu können, dann folgen die Zugkoordinaten: Quell-Col Quell-Row Ziel-Col Ziel-Row. In der sortierten Liste ist somit das letzte Item der beste Zug - hat die höchste Bewertung. Aber das ist nicht ganz dasselbe, was gemeint war.
@alzaimar: Nicht, dass ich will, dass du mir den Code schreibst, aber ich weiss nicht, wie ich weiter machen soll... :(
galagher - Fr 19.05.06 21:56
Ich habe eine Prozedur FindeBestenZug, die auf Basis der Bewertung arbeitet, aber ich kann sie nicht rekursiv aufrufen - das nützt nichts, da sie immer wieder dem tatsächlichen Spielstand angeglichen werden muss, schliesslich ist dieser ja die Ausgangslage. Ohne diese Angleichnung werden mehrere Züge auf einmal ausgeführt.
Das Prinzip: Ich erstelle eine Liste aller möglichen Züge, führe diese nacheinander im Array TBoard aus, bewerte sie und sortiere sie in einer Liste. Der Zug mit der höchsten Bewertung wird dann tatsächlich ausgeführt; wenn es mehrere gleichwertige Züge gibt, entscheidet Random. Wie gesagt, nicht rekursiv, nur aus der momentanen Situation heraus und im Moment noch nur für den Computer.
Ich weiss nicht, wie ich das besser und vor allem wie ich es rekursiv hinbekommen soll.
Das Programm schlägt kaum und spielt entsetzlich unsinnig, aber es spielt jetzt immerhin!
galagher - Sa 20.05.06 16:46
Aus irgendeinem Grund wird mein letzter Beitrag, den ich heute (20.5.) geschrieben habe, als am 18.5. geschrieben gereiht. :nixweiss: Also wiederhole ich ihn hier mal einfach (und hoffe, dass es jetzt klappt).
alzaimar hat folgendes geschrieben: |
Das Prinzip ist einfach: Führe einfach jeden möglichen Zug nacheinander aus und schaue nach, was die beste Antwort des Gegners wird. Der beste eigene Zug ist nun einfach der, der den aus Sicht des Gegners schlechtesten Gegenzug zulässt. Das Ganze wird nun rekursiv aufgerufen, bis die maximale Vorschautiefe (MaxLevel) erreicht wird. Hier wird die Stellung bewertet (wenn nicht schon vorher eine Gewinnstellung erreicht wurde). In der tiefsten Ebene wird also der Zug gewählt, der die besten Stellungsbewertung 'verursacht'. |
Ich komme nicht klar. Alles, was ich bis jetzt habe, ist die Bewertungsfunktion und das Ausführen des besten Zuges, der für den Computer ermittelt wurde: Das Programm erstellt eine Liste aller ihm möglichen Züge, ermittelt den besten und führt ihn aus. Das ist alles. Ich kriege weder die Rekursion hin noch komme ich mit deinem Pseudocode klar.
Ich habe alles bis auf eine Routine, die sinnvolle Züge berechnet. Das "Herz" des Programs also fehlt noch. Und ich muss zugeben, ich kann es nicht.
Ich habe, um mich optisch orientieren zu können, dzt. alle Zugkoordinaten in StringList's. Das erste "Wort" ist die Bewertung, ggf. mit Nullen voran, um sortieren zu können, dann folgen die Zugkoordinaten: Quell-Col Quell-Row Ziel-Col Ziel-Row. In der sortierten Liste ist somit das letzte Item der beste Zug - hat die höchste Bewertung. Aber das ist nicht ganz dasselbe, was gemeint war.
@alzaimar: Nicht, dass ich will, dass du mir den Code schreibst, aber ich weiss nicht, wie ich weiter machen soll... :(
galagher - Do 25.05.06 20:44
alzaimar hat folgendes geschrieben: |
Das Prinzip ist einfach: Führe einfach jeden möglichen Zug nacheinander aus und schaue nach, was die beste Antwort des Gegners wird. |
Das klappt jetzt: Das Programm prüft bei jedem ihm möglichen Zug, was der beste Gegenzug des Spielers ist. Aber das ist es auch schon. Ich schaffe es nicht, den Computer einen Zug machen zu lassen, den er auf die selbe Weise berechnet hat. Und rekursiv läuft das Ganze auch nicht.
alzaimar - Sa 27.05.06 00:53
Hier mal eine Rohversion, wie es klappen könnte.
Die GUI ist grauenhaft, die Bewertungsfunktion ziemlich simpel, aber es spielt ganz nett.
Vielleicht hat ja jemand Lust,das aufzupeppen.
Objektorientiert könnte das auch mal werden.
Na ja, spielt aber.
Spieler = 'X'. Zum ziehen auf einen Stein klicken und dann auf die Zielposition.
Horst_H - Sa 27.05.06 09:07
Hallo,
gibt es soetwas wie eine Pattsituation?
Ich habe es geschafft, keinen Zug mehr machen zu koennen, bevor ein Bauer die gegnerische Grundlinie erreicht hat.
Wer gewinnt oder verliert dann?
Gruss Horst
alzaimar - Sa 27.05.06 09:16
Äh... Das zu erkennen, hab ich gar nicht eingebaut. Der Minimax bricht zwar ab, wenn Spieler und Computer direkt hintereinander nicht ziehen können, aber den Rest nicht. Gewonnen hat dann entweder einfach der mit den meisten Steinen, oder eben Keiner (ein Unentschieden).
Kann ich ja noch einbauen...
galagher - So 28.05.06 10:32
alzaimar hat folgendes geschrieben: |
Vielleicht hat ja jemand Lust,das aufzupeppen. |
Hab's mir schon angesehen, ist super! Zum Thema aufpeppen: Habe erstmal Spieler und Computer umgedreht, der Spieler spielt jetzt von unten nach oben. Für die ersten, sagen wir, 4 Züge möchte ich eine Random-Funktion einbauen. Weiters möchte ich, dass das Programm weiterspielt, bis keine Figurenbewegungen mehr möglich sind und dann prüft, wer mehr Figuren auf der gegnerischen Grundlinie hat.
Dennoch: das "Herz", die KI, ist von dir geschrieben, und wenn ich das Programm so hinbekomme, wie ich das möchte, werde ich es in einer About-Box und im Quelltext auch vermerken. Dein Ansatz ist völlig anders als meiner es war, und ich muss erstmal versuchen, die Bewertung zu verstehen. Du sagst, sie ist grauenhaft, naja, mal sehen, was man da noch verbessern kann, vielleicht schaffe ich das ja!
Ich habe versucht, während des Berechnens des besten Zuges die jeweils ermittelten Züge in einem zweiten Memo auszugeben, sodass der Benutzer sieht, was das Programm macht. Das geht zwar, aber das Spiel wird um vieles langsamer, ab Levelstufe 6 dauert es einfach zu lange. Wie machen das Schachprogramme bei der Daueranalyse?
Du hast es so programmiert:
Delphi-Quelltext
1:
| Log(Format('L=%d, S=%d', [tbLevel.Position, l])); |
Ich möchte mehr Informationen anzeigen:
Delphi-Quelltext
1:
| Log(Format('Rechentiefe=%d, Bester Zug=%d', [tbLevel.Position, l])); |
Stimmen die bezeichnungen "Rechentiefe" und "Bester Zug"?
alzaimar - So 28.05.06 11:16
Hi
'l' ist hier die Bewertung des besten Zuges, und tbLevel.Position die eingestellte Rechentiefe.
Wenn Du ein wenig 'Action' in das Spiel bringen willst, dann solltest Du nur die ersten Level anzeigen. Übrigens kann ich das Spiel bei Level 4 schon nicht mehr besiegen, aber vielleicht bin ich auch einfach nur zu blöd. Level 6 dauert mir einfach zu lang.
Ich habe auch ein wenig an der GUI weitergebastelt und stelle das hier mal rein. Derzeit wird ein 'Patt' als unentschieden gewertet. Ich markiere die Stelle im Code, an der ich das einstelle und zeige, wie alternativ die Anzahl der Steine als Kriterium hinhalten kann (suche mal nach 'FinalScore').
Das Programm bemerkt mittlerweile, wer gewonnen hat und das man nicht ziehen kann. Ein Patt sollte mittlerweile erkannt werden.
Da ich das Programm hingerotzt habe, sind die Erweiterungen (Computer spielt gegen sich selbst, Spieloptimierung, Lernen etc.) nur noch schwer umzusetzen. Also, es geht schon noch, aber man will ja auch Vorbild sein :dunce: ...
Ich versuche bei Gelegenheit, die Engine als OOP zu implementieren.
[edit]Bitmaps gepimpt[/edit]
galagher - So 28.05.06 11:22
alzaimar hat folgendes geschrieben: |
Übrigens kann ich das Spiel bei Level 4 schon nicht mehr besiegen, aber vielleicht bin ich auch einfach nur zu blöd. Level 6 dauert mir einfach zu lang. |
Ich habe bei Level 4 auch noch nicht gewonnen, und Level 6 dauert wirklich lang.
galagher - So 28.05.06 11:25
alzaimar hat folgendes geschrieben: |
Ich habe auch ein wenig an der GUI weitergebastelt |
Toll! Sieht sehr gut aus!
galagher - So 28.05.06 11:50
Ich schaffe es nicht, das Programm dahingehend zu ändern, dass es spielt, bis keine Figuren mehr bewegt werden können. Ich habe das zunächst einmal auskommentiert; dann findet aber offenbar keine "FindeBestenZug"-Berechnung mehr statt, das Programm spielt dann sehr schnelle Züge:
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| if l >= sWinScore then if GameOver then begin ShowMessage(szIWin); StartGame; Exit; end; |
galagher - Mo 29.05.06 20:24
alzaimar hat folgendes geschrieben: |
[edit] Und wenn Du dann noch Lust hast, lassen wir uns die Bewertungsfunktion vom Computer optimieren :dance2: [/edit] |
Also das würde mich nun wirklich interessieren, wie man das macht! Aber zunächst: Ich habe der Bewertung eine "Stelle" hinzugefügt, 1 Feld vor der Spieler-Grundlinie mit der Gewichtung 70:
Delphi-Quelltext
1:
| if aBoard[r1, 7] = plEmpty then inc(v); |
Bringt das etwas, ist etwas falsch daran oder bin ich so auf dem richtigen Weg? Wenn ja, kann ich ja noch überlegen, welche Massnahmen noch sinnvoll wären!
//Edit: So ist das falsch...
alzaimar - Mo 29.05.06 20:47
Hi,
Wenn ein Stein in der vorletzten (nicht unbedingt der 7.) Reihe ist, dann ist das nicht unbedingt erstrebenswert, denn es kommt drauf an, was da sonst noch in der Gegend ist. Wenn Du solche speziellen 'Muster' in die Stellungsfunktion einbaust, dann musst Du das genau machen.
Eigentlich wird ja nicht die Bewertungsfunktion optimiert, sondern die Koeffizienten. Man lässt einfach den Compute gegeneinander spielen, und zwar mit unterschiedlichen Koeffizienten, und zwar ungefähr so ('evolutionär'):
Sei A der Satz von Koeffizienten.
1. Erstelle eine Kopie (B) von A.
2. Mutiere B, indem Du irgendeinen Koeffizienten veränderst.
3. Lass A gegen B spielen (10x) und dann B gegen A (auch 10x).
4. Wenn B öfter gewonnen hat, als A, dann ist wohl der veränderte Koeffizientensatz besser. Ersetze A mit B und mach bei 1 weiter.
5. Wenn B öfter verloren hat, war B wohl keine gute Idee, mach bei 1 weiter.
Anmerkung zu 2: Die Veränderung sollte nach der Gaußschen Verteilung geschehen. Ich meine, in der Math-Unit ist so eine Random-Funktion.
Man merkt sich aber die letzten 100 Koeffizientensätze. Zwischendurch lässt man den aktuell besten Koeffizientensatz noch gegen eine ältere Version antreten, zur Kontrolle. Es kann nämlich sein, das die 'Evolution' in eine Sackgasse führt (siehe Dinosaurier ;-)).
galagher - Mo 29.05.06 21:12
alzaimar hat folgendes geschrieben: |
Wenn ein Stein in der vorletzten (nicht unbedingt der 7.) Reihe ist, dann ist das nicht unbedingt erstrebenswert, denn es kommt drauf an, was da sonst noch in der Gegend ist. |
Ich dachte nur, wenn das Feld auf der Grundlinie des Spielers frei ist, und der Computer das erkennt, sollte er diesen Zug ausführen, denn damit gewinnt er das Spiel.
Was die "Evolution" angeht: Das Prinzip verstehe ich, aber wie immer hapert's an der Umsetzung!
alzaimar - Di 30.05.06 08:19
Mojn,
Is mir schon klar, was Du willst. Der Computer führt diesen Zug auch so aus, denn er gewinnt ja damit (das ist ja die maximale Punktzahl). Ich weiss jetzt nicht, welche Version du hast, aber mit ist Folgendes aufgefallen: Nehmen wir an, der Computer hat einen Stein in der 7.Reihe und könnte im nächsten Zug gewinnen. Er kann aber auch im übernächsten Zug gewinnen, denn er kann ja zunächst irgend einen blöden Zug machen, und den Gewinnzug erst im nächsten Zug. Für den Minimax-Algorithmus ist das egal, denn beide Züge ergeben eine Gewinnstellung, die mit 100000 Punkten bewertet wird. Um den Computer dazu zu bringen, *sofort* den Gewinnzug auszuführen, muss die Gewinnstellung umso mehr Punkte erhalten, desto 'eher' dieser Zug in der Vorausschau ausgeführt wird. Der Parameter 'Level' wird ja pro Zugtiefe erhöht. Eine mögliche Bewertung bei einer Gewinnstellung wäre also z.B. : 100000 + 100 - Level. Je kleiner Level, desto höher die Bewertung. Wenn Du es umgekehrt machst (100000 + Level), dann wird der Computer dich 'zappeln' lassen, also seinen Gewinnzug zum spätestmöglichen Zeitpunkt ausführen.
Noch ein wichtiger Effekt:
Bei der Vorausschau kommt es aber zu sog. Horizonteffekten: Wenn die maximale Zugtiefe erreicht ist, dann wird ja eine Stellungsbewertung durchgeführt, egal, was der Gegner dann noch ziehen kann. Und wenn dann die Stellung für Spieler A sehr gut ist, aber B im nächsten Zug gewinnen würde, dann sieht das die Stellungsfunktion nicht. Ich denke, deine Erweiterung würde diesen Effekt um 2 Halbzüge 'nach hinten' verschieben. Mit anderen Worten: Wenn Du korrekt prüfst, hast Du die Stellungsfunktion in der Tat verbessert!
Man sollte im Endspiel (also z.B. wenn es mindestens einen Stein gibt, der in der 6. oder 7. Reihe steht) die maximale Zugtiefe erhöhen, damit die Spiele bis zum bitteren Ende analysiert werden. Ich habe hier einen Laptop mit 1.5GHz M, und da können ohne Probleme 1 Mio Stellungen geprüft werden (1-2 Sekunden).
Anfangs sollte das Programm strategisch spielen, um die Spielstellung zu verbessern, später dann jedoch mit mehr 'Schmalz', also höheren 'MaxLevel'.
Mittlerweile finde ich dieses Spiel ziemlich interessant, weil es so obersimpel ist, aber dennoch viele der Probleme eines komplexeren Spiels (a.k.a. Schach) schon aufzeigt (Horizonteffekt, Eröffnung, Endspiel, etc.)
Besser als der Minimax- ist ohnehin der Negascout-Algorithmus, der allerdings eine Vorsortierung der Zugliste voraussetzt, sodaß vermeindlich gute Züge zuerst ausgeführt werden. Damit verringert sich dann die Anzahl der zu prüfenden Stellungen sehr stark (ohne das die Spielstärke leidet). Das bedeutet, das die Zugtiefe erhöht werden kann, ohne die Wartezeit nennenswert zu erhöhen.
Um mit der Evolution voranzukommen, schreibe mal eine Prozedur, die den Computer gegen sich selbst spielen lässt (bis zum Ende) und dann zurückliefert, ob plComputer, plHuman oder gar keiner gewonnen hat.
galagher - Di 30.05.06 18:24
alzaimar hat folgendes geschrieben: |
Noch ein wichtiger Effekt:
Bei der Vorausschau kommt es aber zu sog. Horizonteffekten: Wenn die maximale Zugtiefe erreicht ist, dann wird ja eine Stellungsbewertung durchgeführt, egal, was der Gegner dann noch ziehen kann. Und wenn dann die Stellung für Spieler A sehr gut ist, aber B im nächsten Zug gewinnen würde, dann sieht das die Stellungsfunktion nicht. Ich denke, deine Erweiterung würde diesen Effekt um 2 Halbzüge 'nach hinten' verschieben. Mit anderen Worten: Wenn Du korrekt prüfst, hast Du die Stellungsfunktion in der Tat verbessert! |
Darum werde ich mich später kümmern; muss erst herausfinden, wie man das genau programmiert, denn bei der Bewertung blicke ich nicht wirklich durch (ok, beim Minimax-Algorithmus auch nicht!) :mrgreen:
alzaimar hat folgendes geschrieben: |
Man sollte im Endspiel (also z.B. wenn es mindestens einen Stein gibt, der in der 6. oder 7. Reihe steht) die maximale Zugtiefe erhöhen, damit die Spiele bis zum bitteren Ende analysiert werden. Ich habe hier einen Laptop mit 1.5GHz M, und da können ohne Probleme 1 Mio Stellungen geprüft werden (1-2 Sekunden). |
Meiner braucht für 3,5 Mio Züge ca. 16 Sekunden, ist ein 1.2 GHz-Rechner...
alzaimar hat folgendes geschrieben: |
Anfangs sollte das Programm strategisch spielen, um die Spielstellung zu verbessern, später dann jedoch mit mehr 'Schmalz', also höheren 'MaxLevel'. |
Auf Anhieb fällt mir zu "später" nur ein, die Steine zu zählen und ab einer gewissen Anzahl dann den Wert zu erhöhen. Meine Frau sagt jedenfalls, sie könne im höchsten Level (Level 6) fast immer gewinnen. Ich hab's noch nie versucht. Aber da kommen wir dann gleich schon zur Evolution.
alzaimar hat folgendes geschrieben: |
Um mit der Evolution voranzukommen, schreibe mal eine Prozedur, die den Computer gegen sich selbst spielen lässt (bis zum Ende) und dann zurückliefert, ob plComputer, plHuman oder gar keiner gewonnen hat. |
Ist fertig. Dazu habe ich einfach die Prozedur MakeComputerMove umgeschrieben:
Delphi-Quelltext
1: 2: 3: 4:
| procedure MakeComputerMove; procedure MakeComputerMove(Player1, Player2: TPlayer); |
Wenn "rot" gewinnt, setzt ich eine Byte-Variable Winner einfach auf 1, bei Patt auf 0, bei "blau" hat gewonnen auf 2. Geht vielleicht auch besser, aber so läuft das jetzt erst mal.
Ich dachte mir das so: Ich erstelle eine Kopie der Funktion Score und vom Array coeffs (meinetwegen coeffs1) und ändere dort einen oder mehrere Wert(e), aber welche und um wieviel?
"Rot" soll mit der Kopie spielen. Wie ich das jetzt genau hinbekomme - daran arbeite ich noch.
Erst mal zum besseren Verständnis einmal meine Version des Spiels:
galagher - Di 30.05.06 19:06
galagher hat folgendes geschrieben: |
Ich dachte mir das so: Ich erstelle eine Kopie der Funktion Score und vom Array coeffs (meinetwegen coeffs1) und ändere dort einen oder mehrere Wert(e), aber welche und um wieviel?
"Rot" soll mit der Kopie spielen. Wie ich das jetzt genau hinbekomme - daran arbeite ich noch. |
Das habe ich jetzt, aber was ich ich auch verändere, es gewinnt immer jene Farbe, die den 1. Zug macht. Ich habe es jetzt allerdings so programmiert, dass "Blau" mit der Kopie spielt.
Balmung der blaue Gott - Di 30.05.06 21:10
Hi,
Ein Dickes Lob an euch beide für das geile Spiel. Hab es jetzt schon zum 10 mal gespielt ind auf sehr stark gewonnen :)
Also zur Verbesserung solltet ihr euch unbedingt die Anfangsstellung neu überlegen, weil die KI bei mir immer erst die erste Reihe nach vorne zieht und das lässt sich taktisch ausnutzen.
Bei meinem zweiten Spiel auf sehr stark ist mir auch das Problem aufgefallen, dass der Gegner nicht den schnellsten Sieg wählt, wenn ich ihm innerhalb dieser Züge sowieso nichts anhaben kann,womit ich ihn fast gekriegt hätte, hätte ich noch einen Zug mehr gehabt ;)
So jetzt wird erst mal das nächste lvl ausprobiert :)
galagher - Di 30.05.06 21:33
[quote="[user]Ein Dickes Lob an euch beide für das geile Spiel. Hab es jetzt schon zum 10 mal gespielt ind auf sehr stark gewonnen :)[/quote]
Danke! Aber das "Herz" des Spiels (bewertung, KI usw.) hat alzaimar allein programmiert. Ich hab's ein wenig umgeschrieben, so, wie ich die Oberfläche will. Ist aber noch lange nicht fertig!
@alzaimar: Es spielt jetzt zwar gegen sich selbst, aber es kommt dabei vor, dass es die Meldung "Ich passe" oder "Du musst passen" ausgibt, und zwar immer 1 Zug vor dem Sieg, also auf einem vorletzten gegnerischen Feld, obwohl beide Farben noch ziehen könnten. (??)
alzaimar - Di 30.05.06 21:43
galagher hat folgendes geschrieben: |
... Meine Frau sagt jedenfalls, sie könne im höchsten Level (Level 6) fast immer gewinnen... |
6 Halbzüge in die Tiefe ist aber auch nicht die Welt. Ich fürchte, wir müssen zu drastischeren Mitteln greifen (Zugliste sortieren und Negascout-Algorithmus). Mal sehen was das bringt.
Was die nicht funktionierende Evolution anbelangt, würde ich mal annehmen, das ....
... die Änderungen im Koeffizientenarray nicht drastisch genug sind, um eine positive Änderung des Spielverhaltens herbeizuführen,
... die Stellungsbewertung nicht 'filigran' genug ist
oder schlicht und ergreifend
... die Koeffizienten nicht weiter zu verbessern sind.
Wenn Deine Frau so gut ist, das Programm ständig zu schlagen, dann hat sie bestimmt eine Strategie (außer gutes Nachdenken :wink: ). Die kann man analysieren und das in die Bewertungsfunktion einfließen lassen.
Ich habe in den nächsten Tagen leider nicht genug Zeit, dir KI weiter zu verbessern. Eine Kleinigkeit habe ich aber: Das Programm ist 'feige'. Wenn es merkt, das es verliert, sagt es fälschlicherweise:'Ich passe'. Da ist ein kleiner Fehler in der KI, den ich verbessert habe. Die Parameterliste der Routine FindBestMove habe ich etwas verkürzt. Das kleine blinkende Rechteck ist nun besser sichtbar, und ein zweites Spielbrett hab ich noch gebastelt.
Ich poste mal meine Version, die kannst Du bei dir ja einbauen. (Das Menü ist beim 'Computer'-Label, einfach raufklicken)
@Balmung: Die Anfangsaufstellung ist wirklich langweilig: Ich habe beide Reihen jeweils um eins nach vorne gezogen. Die Software ist immer noch blöd und zieht die Reihe nach vorne. Aber so kommt man schneller zur Sache. Man darf nicht vergessen, das die Stellungsbewertung, also das Herz des Programms, noch sehr schlicht ist.
Die Tatsache, das das Programm dich zappeln lässt, liegt daran, das es der KI egal ist (war), wann es gewinnt ;-)
Probiere mal die neue Version.
Ach ja: Ich poste keine kompletten EXEN mehr, sondern nur noch Verbesserungen in der KI, schließlich wollte galagher das Spiel, also schreibt und verwaltet er es auch.
Balmung der blaue Gott - Di 30.05.06 21:50
Juhu, letzte Stufe gemeistert :D)
Macht weiter so! :)
galagher - Di 30.05.06 22:19
Und warum gewinnt immer die Farbe, die den 1. Zug hatte bei Computer gegen Computer?
alzaimar - Di 30.05.06 23:36
galagher hat folgendes geschrieben: |
Und warum gewinnt immer die Farbe, die den 1. Zug hatte bei Computer gegen Computer? |
Vermutlich, weil es einfach schlecht ist, anzufangen. Lass mal Computer gegen sich selbst spielen, einmal mit Stufe 2 und einmal mit 4, dann siehst Du, das das höhere Level schon etwas besser ist...
Ich experimentiere gerade (ja, ja, kaum Zeit, aber das Drecksteil macht *süchtig* :twisted: ) mit einer Verbesserung der Stellungsfunktion: Für jede Spalte, die mit mindestens einem Stein belegt ist (und damit ist sie ja unpassierbar) gibts auch noch einen Bonus. Und für zwei nebeneinanderliegende Spalten noch mehr. Umgekehrt bedeutet das, daß die KI es vermeidet, offene Spalten zu erzeugen, durch die man einfach durchmarschieren kann. Klappt ganz gut, aber beim Testen gibt es den Effekt der 'Self fullfilling prophecies', also wenn ich denke, das das Programm gut ist, dann spiele ich unbewusst entsprechend schwach. Deshalb ist es so wichtig, das unparteiische Spieler das Teil testen.
Morgen mehr. Jetzt gehts ins Kooooma.
galagher - Mi 31.05.06 17:58
alzaimar hat folgendes geschrieben: |
Vermutlich, weil es einfach schlecht ist, anzufangen. Lass mal Computer gegen sich selbst spielen, einmal mit Stufe 2 und einmal mit 4, dann siehst Du, das das höhere Level schon etwas besser ist... |
Aber der Computer fängt doch an! Mit beiden Farben kann ich das "Computer-geben-Computer"-Spiel durchführen; im Moment noch per Buttonklick, später sicher anders. Man klickt, und das Programm fängt an.
Aber zunächst mal vielen Dank, dass du dich so einsetzt!
//Edit: Verstehe schon, was du meinst!
galagher - Mi 31.05.06 19:41
galagher hat folgendes geschrieben: |
Und warum gewinnt immer die Farbe, die den 1. Zug hatte bei Computer gegen Computer? |
Jetzt ist es genau umgekehrt: Es gewinnt, bei gleicher Spielstärke (Variable Level) für beide Farben, immer die Farbe, die NICHT den 1. Zug machte!
alzaimar hat folgendes geschrieben: |
Vermutlich, weil es einfach schlecht ist, anzufangen. Lass mal Computer gegen sich selbst spielen, einmal mit Stufe 2 und einmal mit 4, dann siehst Du, das das höhere Level schon etwas besser ist... |
Das klappt jetzt: Rot=2, Blau=4: Blau gewinnt und umgekehrt; wie es zu erwarten ist, gewinnt die Farbe mit dem höheren Level. Wenn aber eine Farbe 1 hat, spielt die andere Farbe gar nicht, sondern gibt immer "Ich passe" aus... Mit allen anderen Werten dürfte es gehen :eyes:
Ich habe das einfach so gemacht:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| while not SpielEnde do begin Level := 1; makeComputerMove(plHuman, plComputer); Level := 4; makeComputerMove(plComputer, plHuman); end; |
Bei der "Evolution" hänge ich auch noch ziemlich ergebnislos rum!
Und noch eine Frage (eine von vielen): Ich möchte prüfen, ob das Feld geradeaus in der Gegner-Grundlinie frei ist und ob das Feld davor von plPlayer besetzt ist. Ich mache das so:
Delphi-Quelltext
1: 2: 3: 4:
| Function Score(Const aBoard: TBoard; aPlayer, aOpponent: TPlayer): Integer; Function _Score(aPlayer, aOpponent: TPlayer): Integer; If (aBoard[7, j] = plEmpty) and (aBoard[6, j] = aPlayer) Then inc(v); |
Stimmt das so oder übersehe ich da etwas? Ich kann es ja nicht per Haltepunkt nachprüfen, da das Programm ja ständig diesen "Zustand erzeugt", wenn es rechnet!
alzaimar - Mi 31.05.06 23:14
Hi
bei der _Score-Funktion musst Du bedenken, das es die Bewertung immer aus Sicht eines beliebigen Spielers geschieht. Es gibt also keine 7. Reihe. Dafür ist das Feld 'iRows' gedacht. In der _Score-Funktion laufe ich mit 'i' die Zeilen durch und entnehme aus iRows die entsprechende Zeile in 'r', sowie die darüberliegende (aus Sicht des Spielers) in r1. Die eigene Grundlinie ist in iRows[aPlayer,0], die Grundlinie des Gegners entsprechend in iRows[aPlayer,7].
Mittlerweile habe ich die KI ein wenig umgestrickt, schau mal rein: Ich zähle noch mit, ob eine Spalte mit keinem eigenen Stein belegt ist und ob zwei nebeneinanderliegende Spalten unbelegt sind. Weiterhin schaue ich nach, ob ein Stein durchmarschieren kann. Das ist aber nicht ganz korrekt, was ich da fabriziert habe, aber jetzt geht mir die Puste aus.
Hier mal eine Version, die den Evolutionsmechanismus rudimentär implementiert: Es verändert wahllos einen der Koeffizienten und spielt 500 mal gegen sich selbst. Wenn der veränderte Koeffizientensatz mehr als 55% der Spiele gewonnen hat, isser wohl besser.
Alle 50 Versuche wird gegen ein ältere Version gespielt, um zu verifizieren, das wirklich eine Verbesserung stattgefunden hat. Alle 500 Versuche wird gegen den Originalsatz gespielt, um sicherzustellen, das das Programm wirklich besser ist. Ich hab keine Ahnung, was passiert, wenn man das die Nacht über laufen lässt, aber kannste ja mal testen.
Ach, ich wollte ja eigentlich keine EXE per posten, aber egal. Die 'Evolution' startest du mit diesem häßlichen kleinen Button oben.
Horst_H - Do 01.06.06 14:51
Hallo,
die Evolution oder die KI ist suboptimal
Quelltext
1: 2: 3:
| 10292 // count Siege alt =0, neu =100 CoEffs = 6546 -17 -34 -62 -135 97 -336 43 |
Wenn der erste Koeffizient sehr gross wird, gewinnt diese Version scheinbar immer, oder irgendwas laeuft schief.
Gruss Horst
Edit:
Ich habe die Koeffizienten eingetragen und schon hatte der Komputer ohne Spielzug gewonnen ;-)
alzaimar - Do 01.06.06 15:31
Muuuuhaaaa!
Alles klar, so ein Hirnriss von mir: Die Bewertung für 'Gewonnen' ist 100.000... Irgendwie klar, wenn ein Stein so viel Wert ist, dann ist die erste Bewertung schon so hoch, das der Computer denkt:"Och, schon gewonnen?"... Na ja...
Hotfix geht so:
Delphi-Quelltext
1: 2: 3:
| Const sWinScore = 1000000000; sPassScore = -sWinscore - 1; |
Weiterhin sollte man offensichtlich noch die Koeffizienten 'deckeln', also eine einigermaßen sinnvolle Obergrenze wählen....
Das kommt davon, wenn man frickelt ;-)
F34r0fTh3D4rk - Do 01.06.06 15:49
-winscore - 1 ist -1000000001, oder möchtest du -(winscore - 1) bzw (winscore) - 1 * (-1)
das ist ein unterschied ;)
galagher - Do 01.06.06 17:10
@alzaimar, @Horst_H: Danke für eure Mühen! Hab' leider jetzt keine Zeit, mir die neue Version anzusehen (es gibt abseits vom Computer ja auch noch ein Leben, jaja, Tatsache! :mrgreen: ), aber ich werde das schon nachholen!
alzaimar - Do 01.06.06 23:16
So, jetzt habe ich mal Alpha-Beta-Pruning eingebaut, das reduziert die Anzahl der zu prüfenden Stellungen drastisch. Mittlerweile kann man mit Level 8-12 spielen (8 am Anfang, 12 gegen Ende). Allerdings ist das noch nicht zuende getestet...
Die Fehler, die Horst_H entdeckt hat, sind hoffentlich behoben.
Testet doch mal.
galagher - Fr 02.06.06 16:52
Noch besser und schneller! Super! Ich komme mir vor wie ein Dieb, der fremden Code stiehlt und einbaut, denn wirklich verstehen kann ich ihn nicht. :oops: Fast alle meine Ansätze und Überlegungen waren falsch bzw. falsch umgesetzt... Das ist etwas ganz anderes als alles, was ich bisher kannte. Am besten finde ich die selbstlernende KI!
In der neuen Version erhalte ich allerdings die Fehlermeldung "Access violation ...", wenn ich sie aufrufe.
Noch eine Frage: Wie machst du die Schatten der "Figuren" so perfekt? Wirklich durch manuelles Zeichnen oder hast du da ein bestimmtes Grafikprogramm? Ich meine, man kann sie ja nicht mit ihrem Schatten einfach ausschneiden und auf ein neues Feld legen, der Schatten passt dann nicht.
Ich habe einmal eine Prozedur geschrieben, die aus einer Datei beliebig viele Bitmaps in eine ImageList einlesen kann. Dazu speichert man zuerst jedes Bitmap als Datei, und, vorausgesetzt, alle Bitmaps haben die selbe Dateigrösse, kann man sie alle zusammen in einer Datei abspeichern - man hängt die einzelnen Dateien an die Zieldatei einfach an. So könnte man ja mehrere Brett-Sätze anlegen und zur Laufzeit laden.
alzaimar - Fr 02.06.06 17:36
galagher hat folgendes geschrieben: |
Ich komme mir vor wie ein Dieb, der fremden Code stiehlt und einbaut,... |
Nö, Geschenk ist Geschenk. Step doch einfach mal durch... Ich habe ja den einfachen Minimax dringelassen, der ist dann einfacher zu verstehen.
galagher hat folgendes geschrieben: |
... die selbstlernende KI! In der neuen Version erhalte ich allerdings die Fehlermeldung "Access violation ...", wenn ich sie aufrufe. |
Check mal den Aufruf... Da wird bestimmt nicht die Form mit dem Memo korrekt instantiiert. Alternativ kannst Du die Form auch beim Programmbeginn erzeugen.
galagher hat folgendes geschrieben: |
Noch eine Frage: Wie machst du die Schatten der "Figuren" |
CorelDraw. Ich habe 6 Bitmaps: 2 leere Felder, 2 Felder mit roten und 2 Felder mit blauen 'Steinen'.
galagher hat folgendes geschrieben: |
...So könnte man ja mehrere Brett-Sätze anlegen und zur Laufzeit laden. |
Die Bitmaps sind in einer Imagelist (je 6 Bitmaps in der Reihenfolge: 2xBlau, 2xLeer, 2xRot). Du kannst beliebig viele solcher 6er Sätze in die Imagelist hinten ran beppen und dann über den Menüpunkt 'Spiefeld' ein neues Layut ranhängen. Die Property 'Layout' ist einfach eine Zahl >= 0.
Bei Gelegenheit prüfe ich mal die Fehler und verbessere vielleicht noch mal die KI oder baue noch ein Layout ein. Ich glaube aber, mit einem Alpha-Beta-Pruning und dem Lernen sind wir ganz schön weit gekommen...
Studiere ruhig den Quelltext.
galagher - Fr 02.06.06 22:31
alzaimar hat folgendes geschrieben: |
Nö, Geschenk ist Geschenk. |
Also - dann danke nochmal! Nach dem Wochendende stelle ich mal meine aktuelle Version hier rein!
Balmung der blaue Gott - Di 06.06.06 15:09
alzaimar hat folgendes geschrieben: |
Testet doch mal. |
hmm.. ok die KI ist besser geworden, aber sie behauptet einfach schon gewonnen zu haben, ohne dabei einzuberechnen, dass ich schneller gewinnen kann als sie.
//EDIT:
.. weitergetestet ... die KI ist doch nicht so gut :) war nur Zufall, dass sie gewonnen hat (ich vermute das liegt an der Evolution *ichdepp*)
und leider gehen die Schwierigkeitsstufen 9-12 nicht.
alzaimar - Di 06.06.06 15:17
:gruebel: Ist mir auch schon aufgefallen... Leider ist das ziemlich schwer nachzuvollziehen, ich habe das dumpfe gefühl, das da ein wenig zu viel wegoptimiert wird. Außerdem hat die KI munter weiterprobiert, auch wenn beim Vorausberechnen eine Gewinnstellung erreicht wurde.
Ich habe in der neuesten Version, die ich heute Abend mal reinstellen wollte, einige Veränderungen vorgenommen. Die Wichtigste ist eine Funktion, die eine Stellung einlesen kann. Damit sollte man auch nachträglich eventuelle Fehler nachvollziehen können.
Bis später.
(Danke für den Hinweis)
galagher - Di 06.06.06 20:24
Hallo!
Mir ist aufgefallen, dass die KI manchmal erst nach ein paar Zügen merkt, dass sie gewonnen hat; in der Zwischenzeit kann man normal weiterspielen!
Nachdem alzaimar an dem Problem arbeitet (ich kann's ja nicht :( ), warte ich noch ab und poste die neue Version erst dann. Inzwischen habe ich die Oberfläche komplett neu gestaltet.
alzaimar - Di 06.06.06 23:22
Sooo..... Macht Spass, so ein Programm.
Hier die nächste Version. Ich weiss nicht, ob das Problem weiterhin besteht, ich vermute, das die Vorausberechnung Mist gebaut hat, weil sie munter weitersucht, auch wenn eine der Parteien in der Vorausberechnung gewonnen hat. Das kann nicht gut gehen. Das ist behoben
Aber das Programm kann jetzt mehr: Es zeichnet alle Züge auf und speichert sie bei Spielende als 'LastGamexxx.TXT' ab (xxx ist eine laufende Nummer). Sollte so ein Schwachsinnsfehler nochmal auftreten, spielt bitte bis zum Ende und schickt die Datei an mich. Alternativ könnt ihr auch (eben eingebaut :mrgreen: ) den Spielstand sofort abspeichern.
Die Zugliste kann man über das Menü (unter "Computer") sichtbar machen. Die Züge lassen sich Schritt für Schritt rückgängig machen. Mit dem 'GO' Button manifestiert man seine Feigheit :wink: kann dann weiter spielen.
Die Evolution wurde optisch etwas aufgepeppt. Jetzt kann man die Anzahl der Durchgänge sowie die Spielstärke einstellen. Wenn man sie beendet, kann man die Koeffizienten in einer Datei ('COEFFS.TXT') abspeichern. Diese Datei wird dann, wenn vorhanden, beim nächsten Programmstart geladen. So kann man mit den Koeffizienten ein wenig rumspielen.
Leider bin ich zu faul, die Modi 'Computer spielt gegen sich selbst', oder 'Farbe wechseln' oder 'Computer schlägt einen Zug vor' zu implementieren. Vielleicht erbarmt sich ja jemand.
Viel Spass.
[edit]Uses Liste erweitert[/edit]
[edit]KI noch "etwas" optimiert... [/edit]
galagher - Mi 07.06.06 19:15
@alzaimar: Ich verstehe die Evolution absolut nicht! Da werden doch lauter Nullen verwendet anstatt der aktuellen Koeffizinenten! Wenn man deinen Code zum Speichern der Koeffizionten korrigiert (der hat nämlich einen Bug, hähä, ich kann ja doch was! :mrgreen: ) werden diese ja auch abgespeichert und dann beim nächsten Programmstart geladen. Also, ich meine, mit 0 solche Berechnungen anstellen - da stimmt etwas nicht, oder?
Ich mache das einfach jetzt so:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| Procedure SaveCoeffs (C : TCoeffArray); var fn: string; s: TStringList; i: Integer; begin fn := ExtractFilePath(ParamStr(0)) + 'Coeffs.TXT'; s := TStringlist.create; try for i := 0 to Length(c)-1 do s.Add(IntToStr(c[i])); s.saveToFile (Fn); finally s.free; end; end; |
Ich habe deine "neue" Evolution nicht mit übernommen, sondern klammere noch an der alten Version mit dem Memo.
Dann noch eine Frage: Darf man den Quelltext einer Komponente, die man irgendwann einmal downgeloadet hat, verändern, wenn man die Änderungen im Quelltext vermerkt und die Komponente nicht als eigene ausgibt?
alzaimar - Mi 07.06.06 19:50
Die Evolution fängt bewusst bei 0 an, so wie das Leben for 4 Mrd Jahren ja auch. Nach einigen 100 Durchgängen kristallisiert sich doch eine Tendenz heraus. Nach 500 Durchläufen spielt diese Demo dann gegen die aktuell gespeicherten Koeffizienten, nur um sicherzustellen, das sich der Algorithmus nicht irgendwie verrennt.
Ich werde aber noch eine Auswahl einbauen, um Startwerte für die Koeffizienten einzugeben.
Vergiss bitte nicht, das es sich um eine Demo handelt, die einfach nur zeigt, mit wie wenig Mitteln man eine selbstoptimierende KI schreiben kann. Das ist natürlich noch längst nicht das Ende der Fahnenstange, denn:
- I.d.R. verändert sich nicht ein Koeffizient, sondern mehrere
- erfolgt die zufällige Veränderung nach der Gaußschen Normalverteilung
- müsste man eine Population von vielen 100 Spielen erzeugen, die ALLE mutieren, um dann in einem 'Turnier des Lebens' das 'Survival of the Fittest' Prinzip nachzubilden. Gewinner 'paaren' sich, vermischen also ihre erwiesenermaßen überlegenen Koeffizienten
Wenn man ein wirklich ernsthaften Versuch starten möchte, ein gutes Spiel zu entwickeln, dann müsste man die Stellungsbewertung gewaltig aufbohren. Man müsste bestimmte Muster klassifizieren und, wie immer, mit Koeffizienten versehen. Wenn man so ca. 100-200 Koeffizienten hat, dann wirft man die Evolution an und lässt das System auf einer Rechner-Farm mit mehreren 100 PC einige Wochen rumrechnen. Jeder PC verwalten dann eine Insel mit einigen 100 Populationen. Zwischendurch treten die PC dann gegeneinander an und ermitteln, wer denn nun besser ist. Der Gewinner vererbt seine Koeffizienten an den Verlierer-PC.
Soviel erstmal zur Evolution.
Dieser blöde Fehler beim Speichern :oops: , ich weiss auch nicht, wie der da rein gekommen ist... Äh,... muss meine Frau äh.. gewesen sein. Oder der Hund. :bawling: Oh, wir haben ja gar keinen...
Sind noch mehr Korken drin?
... Ah, offensichtlich: Der blaue Gott ..: Ich habe da eine kleine Optimierung drin:
Ich messe am Anfang, wie viele Stellungen in 1,5 Sekunden analysiert werden können (=MAXSCAN).
Bei Level 8+, 10+ und 12+ passiert Folgendes:
Solange die Anzahl der analyiserten Stellungen < MAXSCAN erhöhe Level um 2 und wiederhole die Suche.
Nun, manchmal (gegen Ende) gibt es nicht mehr genug Stellungen, die man analysieren kann. Dann haben wir dann eine Endlosschleife...Das ist mittlerweile behoben.
Nun nochmal zum Vorschlag vom blauen Gott: Diese Idee wurde bei einem der ersten lernenden Spiele auf ähnliche Weise implementiert, soweit ich mich erinnere, war das ein Dame-spiel auf einem IBM Grossrechner. Hier befürchte ich aber, die Stellungsfunktion ist noch zu grob. Wie ich oben schon erwähnte, müsste man noch mehr Muster und Analysen einfließen lassen. Schließlich muss eine Stellungsfunktion ja irgendwie bewerten, das eine Stellung 'in Zukunft' zu einem Gewinn führen könnte. die paar Parameter (8 Stück) reichen einfach nicht aus.
So, genug für heute.
galagher: Ich probiere mal die ungeraden Level, ich ahne schon, woran das liegt...
Balmung der blaue Gott - Mi 07.06.06 19:57
Hi,
Respekt, das Programm wird ja immer besser!
Ich hätte garnicht gedacht, das sich dieses einfache Spiel so gut entwickeln könnte. :D
Ich werde mir sobald ich endlich mal etwas mehr Zeit aufwenden kann den Quelltext ansehen um die Algorithmen der KI zu verstehen.
Nur ein kleiner Bug, von dem ich vermute, dass er schon in den vorherigen Versionen vorhanden war:
Zitat: |
Sollte so ein Schwachsinnsfehler nochmal auftreten, spielt bitte bis zum Ende und schickt die Datei an mich. Alternativ könnt ihr auch (eben eingebaut :mrgreen: ) den Spielstand sofort abspeichern. |
Ich kann ja wenn das Spiel abgestürtzt ist nicht zuende spielen. Du solltest nach jedem Zug speichern!
Naja, mir ist der Bug aber eigentlich relativ, weil sich das Spiel trotzdem spielen lässt. Schließlich tritt der Fehler erst am Ende auf.
Ich mach mich dann mal wieder auf in die Sphären der letzten Levels,
cu
galagher - Mi 07.06.06 20:08
Ich habe (mit "meiner" Version) immer noch das Problem, dass das Programm seinen Sieg weder vorraussieht (szIWillWin) noch den Sieg anzeigt (szIWin) - es spielt einfach weiter.
Ich lade mal die aktuelle Version hoch - wenn gewünscht, ersetze ich die Buttons auch durch Standard-Componenten.
Im Grunde ist es ja nur eine veränderte Oberfläche, die Funktionen kommen allesamt von alzaimar...
Trotzdem - wo liegt der Fehler in meinem Programm? Bei alzaimar's Programm tritt er nicht auf?! Bitte um Hilfe!
Balmung der blaue Gott - Mi 07.06.06 20:09
@alzaimar: Ach ja, noch ein Gedanke zu den Koeffizienten.. Ich weis zwar nicht wie du es machst, aber man könnte eine Bewertungsfunktion der Endstellung, also die Stellung, bei der bereits feststeht wer gewinnt, schreiben. Je schlechter das Ergebnis ausfiele, desto stärker die Veränderung der Koeffizienten. .... nicht das ich jetzt unbedingt will, dass du das einbaust. War mehr so ein spontaner Einfall :)
galagher - Mi 07.06.06 20:29
Seltsam, das Nichterkennen tritt bei Level 4 nicht auf, bei level 5 schon. Muss noch weiter testen!
An dieser Stelle - ich kann es nicht oft genug sagen - ohne alzaimar gäbe es mein Programm gar nicht! Und hier ein Nachschlag an erforderlichen Dateien:
//Edit: Bei geraden Levels klappt es, bei ungeraden nicht... *später darum kümmer und versuch herauszufinden wieso*
alzaimar - Mi 07.06.06 21:43
Oh... alle Antworten in dem umfangreichen Post weiter oben... :oops:
Testen, testen, testen. Danke!
galagher - Fr 09.06.06 16:50
Ich habe alzaimar's Evolution so umgeschrieben (die Version mit dem Memo), dass sie nun nicht mehr in einem eigenen Fenster läuft, sondern im Hauptprogramm-Fenster. Nun habe ich den seltsamen Effekt, dass nach Abbruch der Evolution das Spiel nicht mehr normal funktioniert. Image1 reagiert zunächst gar nicht auf Mausklicks und zeigt dann nach einigen Mausklickes den Spielstand, der beim Abbruch der Evolution war, an.
Um weiterspielen zu können, muss man "Neues Spiel" anklicken. Der OnClick-Aufruf des "Neues Spiel"-Buttons bewirkt nichts! Man muss manuell mit der Maus darauf klicken! So etwas hatte ich ja noch nie! :eyecrazy: Ich meine, mehr als Click geht nicht, und wer es auslöst (der User oder das Programm selbst) müsste doch egal sein - ist es aber nicht!
Ok, man kann das so machen, das man den User zwingt, auf "Neues Spiel" zu klicken, indem man Image1.Enabled := False setzt oder es unsichtbar macht. Aber das ist ja eher die Holzhammer-Methode!
Was kann das denn sein?
/Edit: Und nein, das Programm "evolutioniert" nicht weiter. Der Abbruch der Evolution ist definitiv, aber der Zustand des Programms ist irgendwie nicht wie erwartet! Und noch etwas: Wenn das Programm den Spielstand zeigt und man nochmal auf "Evolution" klickt, werden die Züge dann auch tatsächlich in Image1 angezeigt...
//Edit: Behoben! Es ist nicht egal, an welcher Stelle der Abbruch erfolgt!
galagher - Mo 12.06.06 18:27
Ich kriege keine sinnvolle Funktion Zugvorschlag hin! Ich stelle mal die neue Version hier rein, muss aber sagen, ihr müsst dafür folgende Komponenten installieren:
ColorTrackBar, IAeverButton, ColorProgressbar, BigIni
Ich packe diese in eine extra Zip-Datei und lade sie hoch; weiss nicht mehr, woher ich sie habe und ob es sie überhaupt noch gibt. (BigIni gibt's noch).
Ich würde gerne mehrere Spielbretter erstellen, aber mit meinen Grafikprogrammen kriege ich das nicht hin, so wie alzaimar mit den Schatteneffekten - versucht das mal manuell zu zeichnen! In verschiedenen Farbtönen, passend zum Hintergrund, ohne scharfe Übergänge - da wirst du alt und es wird doch nichts!
Die Prozedur InitCoeffs funktioniert nicht, wenn sie aus dem initialization-Abschnitt aufgerufen wird. Daher hab ich sie kurzerhand deaktiviert und lade die Koeffizienten nun aus der Create-Prozedur.
Naja, und da ist noch das Problem mit den ungeraden Zahlen bei der Spielstärke und dem Erkennen, dass das Programm gewonnen hat...
//Edit:
Das Interesse ist ja nicht gereade extrem...
Trotzdem - hat jamend Abhilfe bei dem ENORMEN DoubleClick-Bug (Zielfeld) ?
//Edit:
Das Problem mit den ungeraden Zahlen ist behelfsmässig behoben, der Klick-Bug sollte vollständig behoben sein!
galagher - Di 20.06.06 12:48
Hallo!
Hier die aktuelle Version. Es ist ein Zugvorschlag eingebaut, der allerdings ab Level 5 zu lange braucht - hängt aber sicherlich vom Computer ab. Ist derzeit auf maximal Level 4 eingestellt.
Ausserdem kann man den Spielstand selbst setzen und die Farben tauschen.
Die Evolution läuft gerade mal auf Level 1 in akzeptabler Geschwindigkeit, ab Level 2 wird's elendig lansam. Ist daher auf Level 1 fix eingestellt.
Zugrücknahme hab ich (noch) nicht hingekriegt!
ToDo: Bessere Stellungsbewertung, schnellere Evolution und Zugvorschlag, den Bug mit den ungeraden Levels beheben.
@alzaimar: Könnte ein wenig Hilfe dringend und gut gebrauchen, was diese ToDo's betrifft!
galagher - Do 22.06.06 19:50
Und hier auch schon die neue Version mit (vorläufigen) Bugfixes (ausser das Problem mit den ungeraden Levels - das kapiere ich einfach nicht!) - und ***Laut ruf nach alzaimar oder nach sonstwem der's kann*** eine verbesserte Stellungsbewertung, denn:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| function Score(const aBoard: TBoard; aPlayer, aOpponent: TPlayer): Integer;
function _Score(aPlayer, aOpponent: TPlayer): Integer; var |
//Edit: Der "Zufalls"zug als 1. Zug musste wieder 'raus - dadurch stimmte die gesamte Rechentiefe nicht mehr...
Zugrücknahme ist nun eingebaut.
galagher - Mo 26.06.06 19:58
Hallo!
Nachdem sich ja doch zumindest 3 Leute für das Programm interessieren, hier die neue Version.
Neu ist u.a.:
-> Der Zufallszug als 1. Zug ist wieder drin.
-> Das Spielstand-Speichern wurde korrigiert, allerdings ist es nun nicht mehr möglich, nach dem Laden einer Partie den Computer den 1. Zug machen zu lassen. Kommt aber vielliecht wieder (wenn ich's hinkriege!).
-> Zugvorschlag wurde korrigiert.
-> Pause-Funktion eingebaut.
-> Zugrücknahme korrigiert.
-> Den Bug mit den ungeraden Levels konnte ich nicht beheben, also gibt's eben nur gerade ausser 1: 2=2, 3=4, 4=6 usw.
-> Es gibt nun 3 Koeffizienten mehr: Nächster Zug wird Gegner bedrohen, Nächster Zug wird eigenen Stein decken,
2 Felder vor Grundlinie bei freiem, ungefährdetem Zug.
-> Ich habe versucht, die maximale Rechendauer auf ca. 5 min. zu begrenzen. Ich habe vor, dass man den Wert selbst einstellen können wird. Vielleicht kommt noch ein "Einstellungen"-Button.
Wichtig: Für diese Version muss in Bauernspiel.ini (im User-Ordner) der Abschnitt Coeffs entweder um 3 Werte erweitert oder entfernt werden!
Bitte testen und kritisieren (aber konstruktiv, wenn ich bitten darf :mrgreen: )!
//Edit:
Es gibt nun 4 Koeffizienten mehr: Nächster Zug wird Gegner bedrohen, Nächster Zug wird eigenen Stein decken,
2 Felder vor Grundlinie bei freiem, ungefährdeten Zug und blockierte Linien.
Die max. Rechendauer und einige andere Dinge kann man nun selbst einstellen.
Der Abschnitt Coeffs in Bauernspiel.ini muss angepasst/gelöscht werden!
//Edit:
Auf wundersame Weise hat sich der Bug mit den ungeraden Levels von selbst erledigt(?!) - es funktioniert! Diverse Bugs wurden behoben, die Einstellungen-Option erweitert und das Programm ist nun wieder schneller. Es interessiert sich nur kein Schw... dafür.
galagher - Mo 03.07.06 21:39
Hallo!
Ich habe jetzt zusätzlich einen 13. Koeffizienten eingebaut, der prüft, ob die nächsten 3 Felder links oder rechts frei sind, wenn dann eine eigene Figur folgt. Das soll dazu dienen, Lücken in der Stellung zu erkennen, durch die der Gegner hindurch kann. Das ist mir bei 2 Spielen gelungen - das Programm ignoriert solche Lücken offenbar.
Rein logischerweise stimmt der Code, aber ich weiss nicht, ob das etwas nützt bzw. sogar das Programm verschlechtert. Vielleicht sollte man die Bedingung, dass nach 3 leeren Feldern eine eigene Figur (aPlayer) folgt, weg lassen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
|
if (aBoard[r, j-4] = aPlayer) then if (aBoard[r, j-1] = plEmpty) and (aBoard[r, j-2] = plEmpty) and (aBoard[r, j-3] = plEmpty) then inc(c);
if (aBoard[r, j+4] = aPlayer) then if (aBoard[r, j+1] = plEmpty) and (aBoard[r, j+2] = plEmpty) and (aBoard[r, j+3] = plEmpty) then inc(c); |
Für Meinungen, Tipps und Hinweise etc. wäre ich echt dankbar! :flehan:
//Edit: Code auf "Prüfung auf 3 leere Felder" korrigiert
galagher - Fr 07.07.06 19:44
Version 1.0.0.28 !
-> Weiter optimiert mit 14 Koeffizienten
-> Die Vorgabe-Koeffizienten wurden ermittlelt per "Lernen per Evolution" (Code von alzaimar):
1000x / Level 0
200x / Level 1
41000x / Level 0
100000x / Level 0
2x / Level 5
127x / Level 3
aber ...
... es fehlt sehr dringend eine Stellungs-Muster-Erkennung!
//Edit: Test auf Level 4: Programm erkennt nicht, dass der Spieler in zwei Zügen gewinnen wird und schlägt nicht den Gewinn-Stein, sondern macht einen anderen Zug. Der Spieler kann nun auf 1 Feld vor der Grundlinie vorrücken, ist somit unschlagbar, das Programm macht einen Zug, der Spieler gewinnt mit dem nächsten Zug.
Siehe oben: es fehlt sehr dringend eine Stellungs-Muster-Erkennung!
galagher - Mi 12.07.06 18:13
Hallo!
Hat jemand vielliecht einen Tipp, wie ich die Spielstellung sozusagen umkehren kann? Ich meine, was zB. zuerst auf b5 stand soll dann auf g5 stehen, und was auf c2 stand auf f2 usw. Ich komme nicht hin. Zunächst lese ich das Spielfeld in ein Board aus, und aus diesem soll dann die Stellung umgekehrt aufgebaut werden:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| var aBoard: TBoard; X1, Y1, X2, Y2: Byte; aPlayer: TPlayer; begin for Y1 := 0 to 7 do for X1 := 0 to 7 do begin aPlayer := Board[Y1, X1]; SetBoard(aBoard, aPlayer, X1, Y1); end;
|
//Edit: ist erledigt.
galagher - Mi 02.08.06 17:54
Hallo Delphi-Leute!
Version 1.0.0.30 - lang erwartet, heiss ersehnt :mrgreen: :wink: ist da! Hier ein kleiner Auszug der Verbesserungen (hoffentlich sind's auch wirklich welche!):
-> 20 Koeffizienten
-> CPU-Geschwindigkeit wird ermittelt und beim Bewerten der Spielstellung verwendet anstatt wie bisher fix 1500 (mein PC ist langsamer!)
-> Brett umdrehen ist möglich
-> Bei nur einer Figur oder nur einem möglichen Zug rechnet das Programm nicht weiter, sondern macht diesen Zug
Wichtig: Man muss den Abschnitt [Coeffs] in Bauernspiel.ini entweder auf 20 Koeffizienten erweitern oder löschen!
Grosse Bitte an euch: Kann mir bitte jemand helfen, was Mustererkennung bei bestimmten Stellungen angeht? alzaimar hat das mal angesprochen, aber ich habe leider keinen Ansatz dafür! :gruebel: DAS fehlt noch, dann wäre es, denke ich, praktisch fertig. Schon mal Danke im Voraus!
//Edit: Hier noch die Boards - ich habe einfach die einzelnen Bitmaps in die *.brd-Zieldatei kopiert und dabei jeweils angehängt.
galagher - Fr 04.08.06 18:45
Hallo!
Die Mustererkennung (=Auswertung einer bestimmten Stellung) kriege ich ja sicherlich hin, aber was soll das Programm mit dieser "Erkenntnis" dann tun, wenn das Ganze eine Mustererkennung sein soll?
Hat denn wirklich niemand eine Idee? :(
//Edit: Beispiel - das Programm führt derzeit den Zug e6-e5 aus - ein fataler Fehler, denn der Spieler gewinnt dann! Wie also könnte eine Mustererkennung aussehen?
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!