Nach dem wirklichen Boom der Brettspiele Ende der 00er Jahre hinterlieĂ die Familie mehrere Kisten mit Spielen. Eines davon ist das Spiel âHare and Hedgehogâ in der deutschen Originalversion. Ein Spiel fĂŒr mehrere Spieler, bei dem das Element der ZufĂ€lligkeit minimiert wird und die nĂŒchterne Berechnung und die FĂ€higkeit des Spielers, in mehreren Schritten nach vorne zu schauen, gewinnt.
Meine hĂ€ufigen Niederlagen im Spiel fĂŒhrten dazu, dass ich Computer-Intelligenz schrieb, um den besten Zug zu wĂ€hlen. Intelligenz, idealerweise in der Lage, den GroĂmeister des Hasen und des Igels zu bekĂ€mpfen (und was, Tee, nicht Schach, das Spiel wird einfacher sein). Der Rest des Artikels beschreibt den Entwicklungsprozess, die KI-Logik und einen Link zur Quelle.
Spielregeln Hase und Igel
Auf dem Spielfeld von 65 Zellen gibt es mehrere Spielerchips von 2 bis 6 Teilnehmern (meine Zeichnung, nicht kanonisch, sieht natĂŒrlich so lala aus):

Abgesehen von den Zellen mit den Indizes 0 (Start) und 64 (Ende) kann in jeder Zelle nur ein Spieler platziert werden. Das Ziel eines jeden Spielers ist es, vor den Rivalen in die Zielzelle vorzudringen.
"Treibstoff" fĂŒr die Weiterentwicklung ist Karotte - die "
WÀhrung " des Spiels. Zu Beginn erhÀlt jeder Spieler 68 Karotten, die er gibt (und manchmal erhÀlt), wenn er sich bewegt.
ZusĂ€tzlich zu den Karotten erhĂ€lt der Spieler zu Beginn 3 Salatkarten. Salat ist ein spezielles âArtefaktâ, das ein Spieler vor dem Ziel
loswerden muss. Salat loswerden (und das kann nur mit einem speziellen SalatkÀfig gemacht werden, wie folgt:

) erhĂ€lt der Spieler einschlieĂlich zusĂ€tzliche Karotten. Vorher ĂŒberspringen Sie Ihren Zug. Je mehr Spieler vor dem Salat die Karte verlassen, desto mehr Karotten erhĂ€lt der Spieler: 10 x (die Position des Spielers auf dem Spielfeld im VerhĂ€ltnis zu anderen Spielern). Das heiĂt, der Spieler, der als Zweiter auf dem Feld steht, erhĂ€lt 20 Karotten und verlĂ€sst den KĂ€fig des Salats.
Zellen mit den Nummern 1 bis 4 können mehrere zehn Karotten bringen, wenn die Position des Spielers mit der Nummer auf der Zelle ĂŒbereinstimmt (1 bis 4, die Zelle mit der Nummer 1 ist auch fĂŒr die 4. und 6. Position auf dem Spielfeld geeignet), analog zur Salatzelle.
Der Spieler kann den Zug ĂŒberspringen, mit dem Bild der Karotten auf dem KĂ€fig bleiben und 10 Karotten fĂŒr diese Aktion erhalten oder geben. Warum sollte ein Spieler "Treibstoff" geben? Tatsache ist, dass ein Spieler nach seinem letzten Zug nur noch 10 Karotten haben kann (20, wenn Sie Zweiter werden, 30, wenn Sie Dritter werden usw.).
SchlieĂlich kann der Spieler 10 x N Karotten erhalten, indem er N Schritte auf dem
nÀchsten freien Igel macht (wenn der nÀchste Igel beschÀftigt ist, ist eine solche Bewegung unmöglich).
Die Kosten fĂŒr die Weiterentwicklung werden nicht proportional zur Anzahl der UmzĂŒge gemÀà der Formel (mit Aufrundung) berechnet:
,
Dabei ist N die Anzahl der Schritte vorwÀrts.
Um eine Zelle vorwĂ€rts zu bringen, gibt der Spieler 1 Karotte, 3 Karotten fĂŒr 2 Zellen, 6 Karotten fĂŒr 3 Zellen, 10 fĂŒr 4, ..., 210 Karotten, um 20 Zellen vorwĂ€rts zu bewegen.
Die letzte Zelle - die Zelle mit dem Bild eines Hasen - bringt ein zufĂ€lliges Element in das Spiel. Nachdem der Spieler mit einem Hasen auf einem KĂ€fig gestanden hat, zieht er eine spezielle Karte vom Stapel, woraufhin eine Aktion ausgefĂŒhrt wird. AbhĂ€ngig von der Karte und der Spielsituation kann der Spieler einige Karotten verlieren, zusĂ€tzliche Karotten erhalten oder eine Runde ĂŒberspringen. Es ist erwĂ€hnenswert, dass es unter den Karten mit âEffektenâ mehrere weitere negative Szenarien fĂŒr den Spieler gibt, die das Spiel dazu ermutigen, vorsichtig und kalkuliert zu sein.
Implementierung ohne KI
In der allerersten Implementierung, die dann die Grundlage fĂŒr die Entwicklung des Spiels âIntellektâ bilden wird, habe ich mich auf die Option beschrĂ€nkt, bei der jeder Spieler einen Zug macht - eine Person.
Ich habe beschlossen, das Spiel als Client zu implementieren - eine statische einseitige Website, deren âLogikâ auf dem JS und dem Server implementiert ist - der WEB-API-Anwendung. Der Server ist in .NET Core 2.1 geschrieben und erzeugt ein Assembly-Artefakt - eine DLL-Datei, die unter Windows / Linux / Mac OS ausgefĂŒhrt werden kann.
Die âLogikâ des Client-Teils wird minimiert (ebenso wie die UX, da die GUI rein zweckmĂ€Ăig ist). Beispielsweise fĂŒhrt der Webclient die ĂberprĂŒfung selbst nicht durch, um festzustellen, ob die vom Spieler angeforderten Regeln akzeptabel sind. Diese PrĂŒfung wird auf dem Server durchgefĂŒhrt. Der Server teilt dem Client mit, welche Bewegungen der Spieler von der aktuellen Spielposition aus ausfĂŒhren kann.
Der Server ist eine klassische
Moore-Maschine . In der Serverlogik fehlen Konzepte wie "verbundener Client", "Spielsitzung" usw.
Der Server verarbeitet lediglich den empfangenen Befehl (HTTP POST). Das
Befehlsmuster ist auf dem Server implementiert. Der Client kann die AusfĂŒhrung eines der folgenden Befehle anfordern:
- ein neues Spiel starten, d.h. Legen Sie Chips der angegebenen Anzahl von Spielern auf ein "sauberes" Brett
- FĂŒhren Sie die im Befehl angegebene Bewegung aus
FĂŒr das zweite Team sendet der Client dem Server die aktuelle Spielposition (Objekt der Disposition-Klasse), d. H. Eine Beschreibung der folgenden Form:
- Position, Anzahl der Karotten und Salat fĂŒr jeden Hasen sowie ein zusĂ€tzliches Boolesches Feld, das anzeigt, dass der Hase nicht an der Reihe ist
- Index des Hasen, der sich bewegt.
Der Server muss keine zusĂ€tzlichen Informationen senden, z. B. Informationen zum Spielfeld. Genau wie beim Aufzeichnen einer Schachskizze ist es nicht erforderlich, die Anordnung der schwarzen und weiĂen Zellen auf die Tafel zu malen - diese Information wird als Konstante genommen.
Als Antwort zeigt der Server an, ob der Befehl erfolgreich ausgefĂŒhrt wurde. Technisch kann ein Client beispielsweise einen ungĂŒltigen Umzug anfordern. Oder versuchen Sie, ein neues Spiel fĂŒr einen einzelnen Teilnehmer zu erstellen, was offensichtlich keinen Sinn ergibt.
Wenn das Team erfolgreich ist, enthÀlt die Antwort eine neue
Spielposition sowie eine Liste der ZĂŒge, die der nĂ€chste Spieler in der Warteschlange ausfĂŒhren kann (aktuell fĂŒr die neue Position).
DarĂŒber hinaus enthĂ€lt die Serverantwort einige Servicefelder. Zum Beispiel der Text einer Karte eines Hasen, der von einem Spieler bei einem Schritt auf dem entsprechenden KĂ€fig âherausgezogenâ wurde.
Spieler an der Reihe
Der Zug des Spielers wird als Ganzzahl codiert:
- 0, wenn der Spieler gezwungen ist, in der aktuellen Zelle zu bleiben,
1, 2, ... N fĂŒr 1, ... N tritt vor, - -1, -2, ... -M, um 1 ... M Zellen zurĂŒck zum nĂ€chsten freien Igel zu bewegen,
- 1001, 1002 - spezielle Codes fĂŒr den Spieler, der sich entschieden hat, in der Karottenzelle zu bleiben und dafĂŒr 10 Karotten zu erhalten (1001) oder zu geben (1002).
Software-Implementierung
Der Server empfĂ€ngt den JSON des angeforderten Befehls, analysiert ihn in eine der entsprechenden Anforderungsklassen und fĂŒhrt die angeforderte Aktion aus.
Wenn der Client (Spieler) von der an das Team ĂŒbertragenen Position (POS) einen Zug mit dem CMD-Code anfordert, fĂŒhrt der Server die folgenden Aktionen aus:
- prĂŒft, ob ein solcher Umzug möglich ist
- erstellt eine neue Position aus der aktuellen und nimmt entsprechende Ănderungen daran vor,
bekommt viele mögliche ZĂŒge fĂŒr eine neue Position. Ich möchte Sie daran erinnern, dass der Index des Spielers, der den Zug ausfĂŒhrt, bereits in dem Objekt enthalten ist, das die Position beschreibt. - gibt dem Kunden eine Antwort mit einer neuen Position, möglichen Bewegungen oder dem Erfolgsflag gleich false und einer Beschreibung des Fehlers zurĂŒck.
Die Logik, die ZulĂ€ssigkeit des angeforderten Umzugs (CMD) zu ĂŒberprĂŒfen und eine neue Position zu konstruieren, erwies sich als etwas enger miteinander verbunden, als wir es gerne hĂ€tten. Mit dieser Logik hat die Methode, akzeptable Bewegungen zu finden, etwas gemeinsam. All diese Funktionen werden von der TurnChecker-Klasse implementiert.
Bei der Eingabe der Check / Execute-Methoden erhĂ€lt TurnChecker ein Objekt der Klasse der Spielposition (Disposition). Das Disposition-Objekt enthĂ€lt ein Array mit Spielerdaten (Haze [] hazes), dem Index des Spielers, der den Zug ausfĂŒhrt, sowie einigen Serviceinformationen, die wĂ€hrend des Betriebs des TurnChecker-Objekts eingegeben wurden.
Das Spielfeld beschreibt die FieldMap-Klasse, die als
Singleton implementiert ist. Die Klasse enthÀlt ein Array von Zellen + einige Overhead-Informationen, die zur Vereinfachung / Beschleunigung nachfolgender Berechnungen verwendet werden.
LeistungsĂŒberlegungenBei der Implementierung der TurnChecker-Klasse habe ich versucht, Schleifen so weit wie möglich zu vermeiden. Tatsache ist, dass Verfahren zum Erhalten des Satzes zulĂ€ssiger Bewegungen / AusfĂŒhrung einer Bewegung anschlieĂend wĂ€hrend des Suchvorgangs fĂŒr eine quasi optimale Bewegung Tausende (Zehntausende) Mal aufgerufen werden.
So berechne ich zum Beispiel anhand der folgenden Formel, wie viele Zellen ein Spieler mit N Karotten vorwÀrts bringen kann:
return ((int)Math.Pow(8 * N + 1, 0.5) - 1) / 2;
Wenn ich ĂŒberprĂŒfe, ob Zelle i von einem der Spieler besetzt ist, gehe ich nicht die Liste der Spieler durch (da diese Aktion wahrscheinlich viele Male ausgefĂŒhrt werden muss), sondern wende mich dem Wörterbuch der Form
[cell_index, Busy_cage_ Flag] zu, das im Voraus ausgefĂŒllt wurde.
Bei der ĂberprĂŒfung, ob die angegebene
Igelzelle der aktuellen Zelle, die der Spieler belegt, am nÀchsten (dahinter) liegt, vergleiche ich auch die angeforderte Position mit einem Wert aus einem Wörterbuch der Form
[cell_index, next_back_dezh] _index] - statische Informationen.
Implementierung mit AI
Der Liste der vom Server verarbeiteten Befehle wird ein Befehl hinzugefĂŒgt: FĂŒhren Sie eine vom Programm ausgewĂ€hlte quasi-optimale Bewegung aus. Dieses Team ist eine kleine Modifikation des Befehls "Spieler bewegen", aus dem das Bewegungsfeld (
CMD ) entfernt wurde.
Die erste Entscheidung, die mir in den Sinn kommt, ist die Verwendung von Heuristiken, um den âbestmöglichenâ Zug auszuwĂ€hlen. In Analogie zum Schach können wir jede der mit unserem Zug erhaltenen Spielpositionen bewerten, indem wir eine Bewertung fĂŒr diese Position festlegen.
Heuristische Positionsbewertung
Zum Beispiel ist es im Schach ganz einfach, eine Bewertung vorzunehmen (ohne in die Wildnis der Ăffnungen zu kriechen): Zumindest können Sie die Gesamtkosten der Figuren berechnen, indem Sie den Ritter- / Bischofswert fĂŒr 3 Bauern, den Wert eines Turmes auf 5 Bauern, die Königin auf 9 und den König auf
int setzen .MaxValue Bauern. Es ist einfach, die SchĂ€tzung zu verbessern, indem Sie sie beispielsweise hinzufĂŒgen (mit einem Korrekturfaktor - Faktor / Exponent oder einer anderen Funktion):
- die Anzahl der möglichen Bewegungen von der aktuellen Position,
- VerhÀltnis von Bedrohungen zu feindlichen Figuren / Bedrohungen durch den Feind.
Die Position der Matte wird speziell bewertet:
int.MaxValue , wenn der Schachmatt den Computer platziert hat,
int.MinValue, wenn der Schachmatt die Person platziert hat.
Wenn Sie dem Schachprozessor befehlen, den nĂ€chsten Zug zu wĂ€hlen, der nur von einer solchen Bewertung geleitet wird, wird der Prozessor wahrscheinlich nicht die schlechtesten ZĂŒge wĂ€hlen. Insbesondere:
- Verpassen Sie nicht die Gelegenheit, ein gröĂeres StĂŒck oder Schachmatt zu nehmen.
- höchstwahrscheinlich wird es die Figuren in den Ecken nicht treiben,
- Die Figur wird nicht erneut angegriffen (angesichts der Anzahl der Bedrohungen in der Bewertung).
NatĂŒrlich lassen solche Bewegungen des Computers ihm keine Chance auf Erfolg mit einem Gegner, der seine Bewegungen im geringsten Sinne macht. Der Computer ignoriert alle Stecker. AuĂerdem zögert er wahrscheinlich nicht, die Königin gegen einen Bauern einzutauschen.
Trotzdem ist der Algorithmus zur heuristischen Bewertung der aktuellen Schachposition im Schach (ohne die Lorbeeren des Champion-Programms zu beanspruchen) ziemlich transparent. Ăber das Spiel Hare and Hedgehog kann man nichts sagen.
Im allgemeinen Fall funktioniert im Spiel Hase und Igel eine eher verschwommene Maxime: â
Es ist besser, mit mehr Karotten und weniger Salat weiter zu gehen â. Allerdings ist nicht alles so einfach. Wenn ein Spieler mitten im Spiel 1 Salatkarte hat, kann diese Option recht gut sein. Aber der Spieler, der mit einer Salatkarte an der Ziellinie steht, ist offensichtlich in einer Verlustsituation. Neben dem Bewertungsalgorithmus möchte ich auch einen Schritt weiter âguckenâ können, so wie Bedrohungen fĂŒr Figuren in einer heuristischen Bewertung einer Schachposition gezĂ€hlt werden können. Zum Beispiel lohnt es sich, den Bonus von Karotten zu berĂŒcksichtigen, die ein Spieler erhĂ€lt, der die Salatzelle / Positionszelle verlĂ€sst (1 ... 4), wobei die Anzahl der Spieler vor ihm zu berĂŒcksichtigen ist.
Ich habe die Abschlussnote als Funktion abgeleitet:
E = Ks · S + Kc · C + Kp · P,
Dabei sind S, C, P Noten, die mit Salatkarten (S) und Karotten © in den HĂ€nden des Spielers berechnet wurden. P sind die Noten, die dem Spieler fĂŒr die zurĂŒckgelegte Strecke gegeben wurden.
Ks, Kc, Kp sind die entsprechenden Korrekturfaktoren (sie werden spÀter diskutiert).
Am einfachsten ist es, die Markierung fĂŒr
den zurĂŒckgelegten Weg zu bestimmen:
P = i * 3, wobei i der Index der Zelle ist, auf der sich der Spieler befindet.
Die Einstufung C (Karotten) ist bereits schwieriger.
Um einen bestimmten C-Wert zu erhalten, wÀhle ich eine von 3 Funktionen
von einem Argument (die Anzahl der Karotten auf den HĂ€nden). Der Index der Funktion C ([0, 1, 2]) wird durch die relative Position des Spielers auf dem Spielfeld bestimmt:
- [0] Wenn der Spieler weniger als die HĂ€lfte des Spielfelds abgeschlossen hat,
- [2] wenn ein Spieler genug (mb, sogar reichlich) Karotten hat, um fertig zu werden,
- [1] in anderen FĂ€llen.

Die Funktionen 0 und 1 sind Ă€hnlich: Der âWertâ jeder Karotte in den HĂ€nden des Spielers nimmt mit zunehmender Anzahl von Karotten in den HĂ€nden allmĂ€hlich ab. Das Spiel ermutigt die Plyushkins selten. Im Fall 1 (die HĂ€lfte des Feldes passiert) nimmt der Wert der Karotten etwas schneller ab.
Funktion 2 (der Spieler kann fertig werden) verhĂ€ngt im Gegenteil eine hohe Geldstrafe (negativer Koeffizientenwert) gegen jede Karotte in den HĂ€nden des Spielers - je mehr Karotten, desto gröĂer der Strafkoeffizient. Da bei einem Ăberschuss an Karotten das Finish nach den Spielregeln verboten ist.
Vor der Berechnung der Karottenmenge auf den HĂ€nden des Spielers wird unter BerĂŒcksichtigung der Karotten pro Zug aus der Salatzelle / Zellnummer 1 ... 4 angegeben.
In Ă€hnlicher Weise wird eine â
Salat â -Sorte S abgeleitet. AbhĂ€ngig von der Menge an Salat auf den HĂ€nden des Spielers (0 ... 3) wird eine Funktion ausgewĂ€hlt
oder
. Funktionsargument
. - wieder der "relative" Pfad, den der Spieler zurĂŒckgelegt hat. Die Anzahl der Zellen, vor denen Salat verbleibt (relativ zu der vom Spieler belegten Zelle):

Die Kurve
- Bewertungsfunktion (S) fĂŒr die Anzahl der Salatzellen vor dem Spieler (0 ... 5) fĂŒr einen Spieler mit 0 verfĂŒgbaren Salatkarten;
die Kurve
- die gleiche Funktion fĂŒr einen Spieler mit einer Salatkarte usw.
Die Abschlussnote (E = Ks * S + Kc * C + Kp * P) berĂŒcksichtigt somit:
- die zusÀtzliche Menge an Karotten, die der Spieler unmittelbar vor seinem eigenen Zug erhÀlt,
- den Pfad des Spielers
- die Menge an Karotten und Salat auf den HĂ€nden, die die Punktzahl nichtlinear beeinflusst.
Und so spielt ein Computer und wÀhlt den nÀchsten Zug mit der maximalen heuristischen Punktzahl:

Im Prinzip ist das DebĂŒt nicht so schlecht. Von einer solchen KI sollte man jedoch kein gutes Spiel erwarten: In der Mitte des Spiels beginnt der grĂŒne âRoboterâ, wiederholte Bewegungen auszufĂŒhren, und am Ende fĂŒhrt er mehrere Iterationen von Bewegungen vorwĂ€rts - rĂŒckwĂ€rts zum Igel durch, bis er schlieĂlich beendet ist. Teilweise zufĂ€llig wird er hinter dem Spieler landen - eine Person mit einem Dutzend ZĂŒgen.
ImplementierungshinweiseDie Berechnung der Bewertung wird von einer speziellen Klasse verwaltet - EstimationCalculator. Die Funktionen zur Bewertung der Position relativ zu Karottensalatkarten werden im statischen Konstruktor der Rechnerklasse in Arrays geladen. Bei der Eingabe empfĂ€ngt die PositionsschĂ€tzungsmethode das Positionsobjekt selbst und den Index des Spielers, aus dessen âSichtâ die Position vom Algorithmus bewertet wird. Das heiĂt, dieselbe Spielposition kann mehrere unterschiedliche Bewertungen erhalten, je nachdem fĂŒr welchen Spieler die virtuellen Punkte berĂŒcksichtigt werden.
Entscheidungsbaum und Minimax-Algorithmus
Wir verwenden den Entscheidungsalgorithmus im antagonistischen Minimax-Spiel. Sehr gut, meiner Meinung nach ist der Algorithmus in
diesem Beitrag beschrieben (Ăbersetzung) .
Wir bringen dem Programm bei, ein paar Schritte vorauszuschauen. Angenommen, von der aktuellen Position aus (und der Hintergrund ist fĂŒr den Algorithmus nicht wichtig - wie wir uns erinnern, funktioniert das Programm wie
eine Moore-Maschine ), nummeriert durch die Nummer 1, kann das Programm zwei ZĂŒge ausfĂŒhren. Wir erhalten zwei mögliche Positionen, 2 und 3. Als nĂ€chstes ist der Spieler an der Reihe - die Person (im allgemeinen Fall der Feind). Ab der 2. Position hat der Gegner 3 ZĂŒge, ab der dritten nur noch zwei ZĂŒge. Als nĂ€chstes fĂ€llt die Runde, um wieder einen Zug zu machen, auf das Programm, das insgesamt 10 ZĂŒge von 5 möglichen Positionen machen kann:

Angenommen, nach dem zweiten Zug des Computers endet das Spiel und jede der empfangenen Positionen wird aus der Sicht des ersten und des zweiten Spielers bewertet. Und wir haben den Bewertungsalgorithmus bereits implementiert. Bewerten wir jede der Endpositionen (BlÀtter des Baumes 9 ... 18) in Form eines Vektors
,
wo
- Punktzahl berechnet fĂŒr den ersten Spieler,
- Punktzahl des zweiten Spielers:

Da der Computer den letzten Schritt macht, wĂ€hlt er offensichtlich die Optionen in jedem der TeilbĂ€ume aus ([9, 10], [11], [12, 13], [14, 15, 16], [17, 18]). das gibt ihm eine bessere Bewertung. Es stellt sich sofort die Frage: Nach welchem ââPrinzip sollte man die âbesteâ Position wĂ€hlen?
Zum Beispiel gibt es zwei ZĂŒge, nach denen wir Positionen mit Ratings haben [5; 5] und [2; 1]. Wertet den ersten Spieler aus. Zwei Alternativen liegen auf der Hand:
- Positionsauswahl mit dem maximalen Absolutwert der i-ten Punktzahl fĂŒr den i-ten Spieler. Mit anderen Worten, der edle Rennfahrer Leslie, der ohne RĂŒcksicht auf die Konkurrenten den Sieg anstrebt. In diesem Fall ist eine Position mit einer SchĂ€tzung von [5; 5].
- Die Wahl einer Position mit der maximalen Bewertung im VerhĂ€ltnis zu den SchĂ€tzungen der Wettbewerber ist der listige Professor Faith, der die Gelegenheit nicht verpasst, dem Feind schmutzige Tricks zuzufĂŒgen. Bleiben Sie beispielsweise absichtlich hinter einem Spieler zurĂŒck, der von der zweiten Position aus starten möchte. Ein Artikel mit Bewertung [2; 1].
In meiner Software-Implementierung habe ich den Notenauswahlalgorithmus (eine Funktion, die den Notenvektor einem Skalarwert fĂŒr den i-ten Spieler zuordnet) zu einem benutzerdefinierten Parameter gemacht. Weitere Tests zeigten zu meiner Ăberraschung die Ăberlegenheit der ersten Strategie - die Wahl der Position durch den maximalen absoluten Wert
.
Merkmale der Software-ImplementierungWenn die erste Option zur Auswahl der besten Note in den Einstellungen der KI (TurnMaker-Klasse) angegeben ist, hat der Code der entsprechenden Methode die Form:
int ContractEstimateByAbsMax(int[] estimationVector, int playerIndex) { return estimationVector[playerIndex]; }
Die zweite Methode - das Maximum im VerhÀltnis zu den Positionen der Wettbewerber - ist etwas komplizierter implementiert:
int ContractEstimateByRelativeNumber(int[]eVector, int player) { int? min = null; var pVal = eVector[player]; for (var i = 0; i < eVector.Length; i++) { if (i == player) continue; var val = pVal - eVector[i]; if (!min.HasValue || min.Value > val) min = val; } return min.Value; }
AusgewĂ€hlte SchĂ€tzungen (in der Abbildung unterstrichen) werden auf die Ebene nach oben ĂŒbertragen. Jetzt muss der Feind eine Position auswĂ€hlen und wissen, welche nachfolgende Position der Algorithmus wĂ€hlen wird:

Der Gegner wĂ€hlt offensichtlich die Position mit der besten Bewertung fĂŒr sich selbst (diejenige, an der die
zweite Koordinate des Vektors den gröĂten Wert annimmt). Diese SchĂ€tzungen sind in der Grafik erneut unterstrichen.
SchlieĂlich kehren wir zum ersten Schritt zurĂŒck. Der Computer wĂ€hlt aus und bevorzugt die Bewegung mit der gröĂten ersten Koordinate des Vektors:

Damit ist das Problem gelöst - es wird ein quasi optimaler Zug gefunden. Angenommen, eine heuristische Bewertung von 100% in Blattpositionen auf einem Baum zeigt einen zukĂŒnftigen Gewinner an. Dann wĂ€hlt unser Algorithmus eindeutig den bestmöglichen Zug.
Die heuristische Punktzahl ist jedoch nur dann 100% genau, wenn die
endgĂŒltige Position des Spiels bewertet wurde - ein (oder mehrere) Spieler sind fertig, der Gewinner wird ermittelt. Wenn Sie also die Möglichkeit haben, nach vorne zu schauen, können Sie den optimalen Zug wĂ€hlen. So viel Zeit erforderlich ist, um Rivalen mit gleicher StĂ€rke zu gewinnen.
Ein typisches Spiel fĂŒr 2 Spieler dauert durchschnittlich 30 bis 40 ZĂŒge (fĂŒr drei Spieler etwa 60 ZĂŒge usw.). Von jeder Position aus kann ein Spieler normalerweise ungefĂ€hr 8 ZĂŒge machen. Folglich besteht ein vollstĂ€ndiger Baum möglicher Positionen fĂŒr 30 ZĂŒge aus ungefĂ€hr
= 1237940039285380274899124224 Spitzen!
In der Praxis dauert das Erstellen und "Parsen" eines Baums mit ~ 100.000 Positionen auf meinem PC ungefĂ€hr 300 Millisekunden. Dies gibt uns eine Grenze fĂŒr die Tiefe des Baums in 7 - 8 Ebenen (Bewegungen), wenn wir eine Computerantwort von nicht mehr als einer Sekunde erwarten möchten.
Merkmale der Software-ImplementierungOffensichtlich ist eine rekursive Methode erforderlich, um einen Baum von Positionen zu erstellen und den besten Zug zu finden. Am Eingang der Methode - die aktuelle Position (an der, wie wir uns erinnern, der Spieler bereits einen Zug macht) und die aktuelle Baumstufe (Zugnummer). Sobald wir auf das von den Algorithmuseinstellungen maximal zulĂ€ssige Niveau absteigen, gibt die Funktion einen heuristischen PositionsschĂ€tzungsvektor aus der Sicht jedes Spielers zurĂŒck.
Ein wichtiger Zusatz : Der Abstieg unter dem Baum muss auch gestoppt werden, wenn der aktuelle Spieler fertig ist. Andernfalls (wenn der Algorithmus zur Auswahl der besten Position im VerhĂ€ltnis zu den Positionen anderer Spieler ausgewĂ€hlt ist) kann das Programm lange Zeit im Ziel âstampfenâ und den Gegner âverspottenâ. AuĂerdem werden wir auf diese Weise die GröĂe des Baums im Endspiel geringfĂŒgig reduzieren.
Wenn wir uns noch nicht auf der endgĂŒltigen Rekursionsstufe befinden, wĂ€hlen wir die möglichen ZĂŒge aus, erstellen fĂŒr jeden Zug eine neue Position und ĂŒbergeben sie an den rekursiven Aufruf der aktuellen Methode.
Warum ist Minimax?In der ursprĂŒnglichen Interpretation der Spieler sind immer zwei. Das Programm berechnet die Punktzahl ausschlieĂlich aus der Position des ersten Spielers. Dementsprechend sucht der Spieler mit Index 0 bei der Auswahl der âbestenâ Position nach einer Position mit einer maximalen Bewertung, der Spieler mit Index 1 nach einer minimalen Bewertung.
In unserem Fall sollte die Bewertung ein Vektor sein, damit jeder der N Spieler sie unter seinem âGesichtspunktâ bewerten kann, wenn die Bewertung im Baum steigt.
KI einstellen
Meine Praxis, gegen den Computer zu spielen, hat gezeigt, dass der Algorithmus nicht so schlecht ist, aber den Menschen immer noch unterlegen. Ich habe beschlossen, die KI auf zwei Arten zu verbessern:
- die Konstruktion / Durchquerung des Entscheidungsbaums optimieren,
- Heuristik verbessern.
Minimax-Algorithmusoptimierung
Im obigen Beispiel könnten wir uns weigern, Position 8 zu berĂŒcksichtigen und 2 - 3 Eckpunkte des Baums zu "speichern":

Wir gehen von oben nach unten um den Baum herum, von links nach rechts. Unter Umgehung des Teilbaums ab Position 2 haben wir die beste SchĂ€tzung fĂŒr Zug 1 -> 2 abgeleitet: [3, 2]. Unter Umgehung des Teilbaums mit der Wurzel in Position 7 haben wir die aktuelle Bewertung (am besten fĂŒr Zug 3 -> 7) ermittelt: [2, 4]. Aus Sicht des Computers (erster Spieler) ist die Punktzahl [2, 4] schlechter als die Punktzahl [3, 2]. Und da der Gegner des Computers unabhĂ€ngig von der Punktzahl fĂŒr Position 8 den Zug von Position 3 aus wĂ€hlt, ist die endgĂŒltige Punktzahl fĂŒr Position 3 a priori schlechter als die Punktzahl fĂŒr die dritte Position. Dementsprechend kann der Teilbaum mit der Wurzel in Position 8 nicht erstellt und nicht ausgewertet werden.
Die optimierte Version des Minimax-Algorithmus, mit der Sie ĂŒberschĂŒssige TeilbĂ€ume abschneiden können, wird als
Alpha-Beta-Clipping-Algorithmus bezeichnet . Die Implementierung dieses Algorithmus erfordert geringfĂŒgige Ănderungen am Quellcode.
Merkmale der Software-ImplementierungZwei ganzzahlige Parameter werden zusĂ€tzlich an die CalcEstimate-Methode der TurnMaker-Klasse ĂŒbergeben - alpha, die anfĂ€nglich int.MinValue entspricht, und beta, die int.MaxValue entspricht. Ferner wird nach Erhalt einer SchĂ€tzung der aktuellen betrachteten Bewegung ein Pseudocode des Formulars ausgefĂŒhrt:
e = _[0]
Ein wichtiges Merkmal der Software-ImplementierungDie Alpha-Beta-Clipping-Methode fĂŒhrt per Definition zu derselben Lösung wie der âsaubereâ Minimax-Algorithmus. Um zu ĂŒberprĂŒfen, ob sich die Logik der Entscheidungsfindung geĂ€ndert hat (oder besser gesagt, die Ergebnisse sind ZĂŒge), habe ich einen Komponententest geschrieben, bei dem der Roboter 8 ZĂŒge fĂŒr jeden von 2 Gegnern (insgesamt 16 ZĂŒge) machte und die resultierende Reihe von ZĂŒgen speicherte - mit Clipping-Option deaktiviert .
Dann wurde im gleichen Test der Vorgang mit aktivierter Clipping-Option wiederholt. Danach wurde die Reihenfolge der Bewegungen verglichen. Die Diskrepanz in den Bewegungen zeigt einen Fehler bei der Implementierung des Alpha-Beta-Clipping-Algorithmus an (der Test ist fehlgeschlagen).
Kleinere Optimierung des Alpha-Beta-Clipping
Bei aktivierter Beschneidungsoption in den AI-Einstellungen wurde die Anzahl der Scheitelpunkte im Positionsbaum um das Durchschnitt dreimal reduziert. Dieses Ergebnis kann etwas verbessert werden.
Im obigen Beispiel:

so erfolgreich âzusammenfielâ, dass wir vor dem Teilbaum mit dem Scheitelpunkt in Position 3 den Teilbaum mit dem Scheitelpunkt in Position 2 untersuchten. Wenn die Sequenz anders war, konnten wir mit dem âschlechtestenâ Teilbaum beginnen und nicht zu dem Schluss kommen, dass es keinen Sinn machte, die nĂ€chste Position in Betracht zu ziehen .
In der Regel erweist sich das Abschneiden eines Baums als âwirtschaftlicherâ. Die untergeordneten Eckpunkte auf derselben Ebene (dh alle möglichen Bewegungen von der i-Position) sind bereits nach der aktuellen PositionsschĂ€tzung (ohne tief in die Position zu schauen) sortiert. Mit anderen Worten, wir gehen davon aus, dass der beste Zug (aus heuristischer Sicht) eher eine bessere Abschlussnote erzielt. Daher sortieren wir den Baum mit einiger Wahrscheinlichkeit so, dass der âbesteâ Teilbaum vor dem âschlechtestenâ Teil betrachtet wird, wodurch wir mehr Optionen abschneiden können.
Die Beurteilung der aktuellen Position ist ein kostspieliges Verfahren. Wenn es frĂŒher ausreichte, nur die Endpositionen (BlĂ€tter) zu bewerten, wird jetzt die Bewertung fĂŒr alle Eckpunkte des Baums vorgenommen. Wie die Tests zeigten, war die Gesamtzahl der vorgenommenen SchĂ€tzungen jedoch immer noch etwas geringer als in der Variante ohne die anfĂ€ngliche Sortierung möglicher Bewegungen.
Merkmale der Software-ImplementierungDer Alpha-Beta-Clipping-Algorithmus gibt den gleichen Zug zurĂŒck wie der ursprĂŒngliche Minimax-Algorithmus. Dies ĂŒberprĂŒft den von uns geschriebenen Komponententest und vergleicht zwei Bewegungssequenzen (fĂŒr einen Algorithmus mit und ohne Clipping). Alpha-Beta-Clipping mit Sortierung kann im allgemeinen Fall auf eine andere Bewegung als eine quasi optimale hinweisen.
Um den korrekten Betrieb des modifizierten Algorithmus zu testen, benötigen Sie einen neuen Test. Trotz der Modifikation sollte der sortierte Algorithmus genau den gleichen endgĂŒltigen SchĂ€tzvektor ([3, 2] im Beispiel in der Abbildung) wie der nicht sortierte Algorithmus wie der ursprĂŒngliche Minimax-Algorithmus erzeugen.
Im Test habe ich eine Reihe von Testpositionen erstellt und aus jeder nach der âbestenâ Bewegung ausgewĂ€hlt, wobei die Sortieroption ein- und ausgeschaltet wurde. Dann verglich er den auf zwei Arten erhaltenen Bewertungsvektor.
Da die Positionen fĂŒr jede der möglichen Bewegungen im aktuellen Scheitelpunkt des Baums durch heuristische Auswertung sortiert sind, schlĂ€gt die Idee auĂerdem vor, einige der schlechtesten Optionen sofort zu verwerfen. Zum Beispiel kann ein Schachspieler einen Zug in Betracht ziehen, bei dem er seinen Turm durch einen Bauerntreffer ersetzt. Wenn er jedoch die Situation in der Tiefe um 3, 4 ... VorwĂ€rtsbewegungen "abrollt", wird er die Optionen sofort bemerken, wenn beispielsweise der Gegner den Bischof seiner Königin angreift.
In den KI-Einstellungen habe ich den Vektor "Abschneiden der schlechtesten Optionen" festgelegt. Zum Beispiel bedeutet ein Vektor der Form [0, 0, 8, 8, 4], dass:
- Wenn man einen [0] und zwei [0] Schritte vorwĂ€rts betrachtet, berĂŒcksichtigt das Programm alle möglichen Bewegungen, weil 0 ,
- [8] [8] , 8 ââ , , ,
- Mit fĂŒnf oder mehr Schritten nach vorne [4] bewertet das Programm nicht mehr als vier âbesteâ ZĂŒge.
Nachdem die Sortierung fĂŒr den Alpha-Beta-Clipping-Algorithmus und einen Ă€hnlichen Vektor in den Clipping-Einstellungen aktiviert war, verbrachte das Programm etwa 300 Millisekunden damit, eine quasi-optimale Bewegung auszuwĂ€hlen und 8 Schritte vorwĂ€rts âtiefer zu gehenâ.Heuristische Optimierung
Trotz der anstĂ€ndigen Anzahl von Scheitelpunktpositionen, die im Baum durchlaufen wurden und auf der Suche nach einer quasi optimalen Bewegung âtiefâ nach vorne blickten, hatte die KI mehrere SchwĂ€chen. Ich habe eine davon als â Kaninchenfalle â definiert.Hasenfalle. ( 8 â 10 15), . ââ ( !):
. :

54 (43), â 10 55 . AI, , (61), 28 . , 6 9 ( 10 ).
, ( ), , 4 â 6 . , , , AI ?
,
, . AI . , , . â â :
65 â ââ , , . , , , () .
Korrekturfaktoren
Unter Berufung auf die heuristische Formel zur Bewertung der aktuellen PositionE = Ks * S + Kc * C + Kp * P habeich bereits Korrekturfaktoren erwĂ€hnt, aber nicht beschrieben.Tatsache ist, dass sowohl die Formel selbst als auch die FunktionssĂ€tze wurden von mir intuitiv auf Basis des sogenannten abgeleitet "Gesunder Menschenverstand." Zumindest möchte ich solche Koeffizienten Ks, Kc und Kp auswĂ€hlen, damit die SchĂ€tzung so angemessen wie möglich ist. Wie ist die âAngemessenheitâ der Bewertung zu bewerten? Die Bewertung ist eine dimensionslose GröĂe und kann nur mit einer anderen SchĂ€tzung verglichen werden. Ich konnte die einzige Möglichkeit finden, die Korrekturkoeffizienten zu verfeinern: Ichhabe eine Reihe von âStudienâ in das Programm aufgenommen, die in einer CSV-Datei mit Daten des Formulars gespeichert sind 45;26;2;f;29;19;0;f;2 ...
Diese Zeile bedeutet wörtlich Folgendes:- Der erste Spieler ist auf Feld 45, er hat 26 Karottenkarten und 2 Salatkarten in der Hand, der Spieler verpasst keinen Zug (f = falsch). Das Bewegungsrecht ist immer der erste Spieler.
- Der zweite Spieler in Zelle 29 mit 19 Karottenkarten und ohne Salatkarten verpasst keinen Zug.
- Die zweite Zahl bedeutet, dass ich bei der âEntscheidungâ fĂŒr die Studie davon ausgegangen bin, dass sich der zweite Spieler in einer Gewinnsituation befindet.
Nachdem ich 20 Skizzen in das Programm aufgenommen hatte, lud ich sie in den Web-Client des Spiels herunter und sortierte dann jede skizzierte. Bei der Analyse der Skizze habe ich abwechselnd fĂŒr jeden Spieler ZĂŒge gemacht, bis ich mich fĂŒr den âGewinnerâ entschieden habe. Nachdem ich die Bewertung abgeschlossen hatte, schickte ich sie in einem speziellen Team an den Server.Nachdem ich 20 EtĂŒden ausgewertet hatte (natĂŒrlich lohnt es sich, mehr davon zu analysieren), bewertete ich jede der EtĂŒden durch das Programm. Bei der Auswertung werden die Werte der einzelnen Korrekturfaktoren von 0,5 bis 2 in Schritten von 0,1 - insgesamt angegeben = 4096 Varianten von Dreifachkoeffizienten. Wenn sich herausstellte, dass die Punktzahl fĂŒr den ersten Spieler höher war als die Punktzahl des zweiten Spielers, und eine Ă€hnliche Anweisung in der Zeile des EtĂŒden-Datensatzes gespeichert wurde (der letzte Wert der Zeile ist 1), wurde der âTrefferâ gezĂ€hlt. Ăhnliches gilt fĂŒr eine Spiegelsituation. Ansonsten wurde ein Beleg gezĂ€hlt.Infolgedessen habe ich die Tripel ausgewĂ€hlt, fĂŒr die der Prozentsatz der âTrefferâ maximal war (16 von 20). Es kamen ungefĂ€hr 250 von 4096 Vektoren heraus, von denen ich die "besten" wieder "per Auge" auswĂ€hlte und sie in den KI-Einstellungen installierte.Zusammenfassung
Als Ergebnis habe ich ein Arbeitsprogramm bekommen, das mich in der Regel in der Eins-zu-Eins-Version gegen den Computer schlĂ€gt. Ernste Statistiken ĂŒber Siege und Niederlagen fĂŒr die aktuelle Version des Programms wurden noch nicht gesammelt. Vielleicht macht die anschlieĂende einfache KI-Abstimmung meine Siege unmöglich. Oder fast unmöglich, da der Hasenzellenfaktor immer noch besteht.Zum Beispiel wĂŒrde ich in der Logik der Auswahl von Bewertungen (absolutes Maximum oder Maximum im VerhĂ€ltnis zu anderen Spielern) definitiv eine Zwischenoption versuchen. Wenn der absolute Wert der Punktzahl des i-ten Spielers gleich ist, ist es zumindest sinnvoll, einen Zug zu wĂ€hlen, der zu einer Position mit einem höheren relativen Wert der Punktzahl fĂŒhrt (eine Mischung aus dem edlen Leslie und dem tĂŒckischen Feith).Das Programm ist fĂŒr die Version mit 3 Spielern voll funktionsfĂ€hig. Es besteht jedoch der Verdacht, dass die âQualitĂ€tâ der KI-Bewegungen beim Spielen mit 3 Spielern geringer ist als beim Spielen mit zwei Spielern. WĂ€hrend des letzten Tests verlor ich jedoch gegen den Computer - vielleicht fahrlĂ€ssig, indem ich beilĂ€ufig die Menge der Karotten in meinen HĂ€nden auswertete und mit einem Ăberschuss an âKraftstoffâ ins Ziel kam.Bisher wird die Weiterentwicklung der KI durch das Fehlen einer Person behindert - eines âTestersâ, dh eines lebenden Gegners eines Computer- âGeniesâ. Ich selbst habe genug von Hare und Hedgehog gespielt, um Ăbelkeit zu bekommen, und war gezwungen, in der gegenwĂ€rtigen Phase zu unterbrechen.â Link zum Repository mit Quelle