Die Herausforderung besteht darin, einen Löser für das klassische Bleistift- und Papierspiel Dots and Boxes zu schreiben . Ihr Code sollte zwei ganze Zahlen enthalten m
und n
als Eingabe dienen, die die Größe der Karte angibt.
Beginnend mit einem leeren Punktegitter wechseln sich die Spieler ab und fügen eine einzelne horizontale oder vertikale Linie zwischen zwei nicht verbundenen benachbarten Punkten ein. Ein Spieler, der die vierte Seite einer 1 × 1-Box abschließt, erhält einen Punkt und ist erneut an der Reihe. (Die Punkte werden in der Regel aufgezeichnet, indem eine Kennzeichnung des Spielers (z. B. eine Initiale) in das Feld eingefügt wird.) Das Spiel endet, wenn keine Linien mehr platziert werden können. Der Gewinner des Spiels ist der Spieler mit den meisten Punkten.
Sie können annehmen, dass entweder n = m
oder n = m - 1
und m
mindestens 2 ist.
Die Herausforderung besteht darin, solve
das größtmögliche Dots and Boxes-Spiel in weniger als einer Minute zu schaffen. Die Größe eines Spiels ist einfach n*m
. Der Ausgang des Codes sollte sein win
, draw
oder lose
die das Ergebnis für den ersten Spieler sein sollte , vorausgesetzt , beide Spieler optimal spielen.
Ihr Code muss mit leicht installierbaren und kostenlosen Tools auf Ubuntu kompilierbar / lauffähig sein. Bitte geben Sie Ihre Punktzahl als den größten Bereich an, den Sie in 1 Minute zusammen mit der Zeit auf Ihrem Computer lösen können. Ich teste dann den Code auf meinem Computer und erstelle eine nach Rang geordnete Tabelle mit Einträgen.
Im Falle eines Gleichstands ist der Gewinner der schnellste Code auf der größten Tafel, die er in weniger als einer Minute lösen kann.
Es wäre besser, wenn der ausgegebene Code nicht nur gewinnt oder verliert, sondern auch die tatsächliche Punktzahl. Dies führt zu einer Überprüfung der Richtigkeit.
Antworten:
C99 - 3x3 Platine in 0.084s
Bearbeiten: Ich habe meinen Code überarbeitet und die Ergebnisse eingehender analysiert.
Weitere Änderungen: Beschneiden nach Symmetrien hinzugefügt. Dies ermöglicht 4 Algorithmuskonfigurationen: mit oder ohne Symmetrien X mit oder ohne Alpha-Beta-Bereinigung
Weiteste Änderungen: Memoisierung mit einer Hash-Tabelle hinzugefügt, um endlich das Unmögliche zu erreichen: Lösen eines 3x3-Brettes!
Hauptmerkmale:
#define HASHTABLE_BITWIDTH
). Wenn diese Größe größer oder gleich der Anzahl der Wände ist, werden keine Kollisionen und O (1) -Updates garantiert. Kleinere Hashtabellen haben mehr Kollisionen und sind etwas langsamer.-DDEBUG
für AusdruckeMögliche Verbesserungen:
Behebung eines kleinen Speicherverlustsbei der ersten BearbeitungAlpha / Beta-Beschneidungin der 2. Bearbeitung hinzugefügtPrune Symmetrienin der 3. bearbeiten hinzugefügt (beachten Sie, dass Symmetrien sind nicht von memoization behandelt, so dass eine separate Optimierung bleibt.)Memoisierungin der 4. Bearbeitung hinzugefügtCode
Aufgrund mangelnder Organisation ist die Anzahl der Dateien unvorbereitet gewachsen. Der gesamte Code wurde in dieses Github-Repository verschoben . In der Memoization-Bearbeitung habe ich ein Makefile und ein Testskript hinzugefügt.
Ergebnisse
Hinweise zur Komplexität
Brute-Force-Ansätze für Punkte und Kästchen nehmen sehr schnell an Komplexität zu .
Betrachten Sie eine Tafel mit
R
Zeilen undC
Spalten. Es gibtR*C
Quadrate,R*(C+1)
vertikale Wände undC*(R+1)
horizontale Wände. Das ist insgesamtW = 2*R*C + R + C
.Da Lembik uns gebeten hat , das Spiel mit Minimax zu lösen , müssen wir zu den Blättern des Spielbaums gehen. Lassen Sie uns das Beschneiden zunächst ignorieren, denn es kommt auf Größenordnungen an.
Es gibt
W
Optionen für den ersten Zug. Für jede davon kann der nächste Spieler eine derW-1
verbleibenden Wände spielen, usw .. Das gibt uns einen Suchraum vonSS = W * (W-1) * (W-2) * ... * 1
, oderSS = W!
. Factorials sind riesig, aber das ist nur der Anfang.SS
ist die Anzahl der Blattknoten im Suchraum. Für unsere Analyse relevanter ist die Gesamtzahl der zu treffenden Entscheidungen (dh die Anzahl der ZweigeB
im Baum). Die erste Schicht von Zweigen hatW
Optionen. Für jeden von denen hat der nächste LevelW-1
usw.Schauen wir uns einige kleine Tischgrößen an:
Diese Zahlen werden lächerlich. Zumindest erklären sie, warum der Brute-Force-Code für immer auf einem 2x3-Board zu hängen scheint. Der Suchraum einer 2x3-Karte ist 742560-mal größer als 2x2 . Wenn die Ausführung von 2x2 20 Sekunden dauert, sagt eine konservative Extrapolation eine Ausführungszeit von über 100 Tagen für 2x3 voraus . Natürlich müssen wir beschneiden.
Schnittanalyse
Zunächst habe ich mit dem Alpha-Beta-Algorithmus einen sehr einfachen Schnitt hinzugefügt. Im Grunde hört es auf zu suchen, ob ein idealer Gegner ihm niemals seine gegenwärtigen Möglichkeiten geben würde. "Hey schau - ich gewinne um ein Vielfaches, wenn mein Gegner mich jedes Feld holen lässt!", Dachte keine KI.
edit Ich habe auch einen Schnitt hinzugefügt, der auf symmetrischen Brettern basiert.
Ich verwende keinen Memoisierungsansatz, nur für den Fall, dass ich eines Tages Memoisierung hinzufüge und diese Analyse getrennt halten möchte. Stattdessenfunktioniert es so: Die meisten Linien haben ein "symmetrisches Paar" an einer anderen Stelle im Raster. Es gibt bis zu 7 Symmetrien (horizontal, vertikal, 180-Drehung, 90-Drehung, 270-Drehung, Diagonale und die andere Diagonale). Alle 7 gelten für quadratische Bretter, die letzten 4 gelten jedoch nicht für nicht quadratische Bretter. Jede Wand hat für jede dieser Symmetrien einen Zeiger auf das "Paar". Wenn das Brett in einer Runde horizontal symmetrisch ist, muss nur eines von jedem horizontalen Paar gespielt werden.bearbeiten bearbeiten Memoization! Jede Wand erhält eine eindeutige ID, die ich bequem als Indikatorbit festgelegt habe. Die n-te Wand hat die ID
1 << n
. Der Hash eines Bretts ist also nur das ODER aller gespielten Wände. Dies wird bei jeder Verzweigung zu O (1) -Zeit aktualisiert. Die Größe der Hashtabelle wird in a festgelegt#define
. Alle Tests wurden mit Größe 2 ^ 12 durchgeführt, warum nicht? Wenn mehr Wände als Bits vorhanden sind, die die Hash-Tabelle indizieren (in diesem Fall 12 Bits), werden die niederwertigsten 12 maskiert und als Index verwendet. Kollisionen werden mit einer verknüpften Liste an jedem Hashtable-Index behandelt. Das folgende Diagramm zeigt, wie sich die Größe der Hashtabelle auf die Leistung auswirkt. Auf einem Computer mit unbegrenztem RAM haben wir die Größe des Tisches immer auf die Anzahl der Wände eingestellt. Eine 3x4-Karte hätte eine Hash-Tabelle mit einer Länge von 2 ^ 31. Leider haben wir diesen Luxus nicht.Ok, zurück zum Beschneiden. Indem wir die Suche hoch im Baum stoppen, können wir viel Zeit sparen, indem wir nicht zu den Blättern hinuntergehen. Der 'Pruning Factor' ist der Bruchteil aller möglichen Zweige, die wir besuchen mussten. Brute-Force hat einen Schnittfaktor von 1. Je kleiner es ist, desto besser.
quelle
rows columns
, die die Größe der TafelPython - 2x2 in 29s
Crossposting von Rätseln . Nicht besonders optimiert, aber möglicherweise ein nützlicher Ausgangspunkt für andere Teilnehmer.
quelle
Javascript - 1x2 Karte in 20ms
Online-Demo hier verfügbar (Warnung - sehr langsam, wenn größer als 1x2 mit voller Suchtiefe ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html
Wurde für die ursprünglichen Gewinnkriterien (Code Golf) und nicht für Geschwindigkeit entwickelt.
Getestet in Google Chrome V35 unter Windows 7.
quelle