Ich habe ein Tic-Tac-Toe-Spiel in Java geschrieben, und meine derzeitige Methode zur Bestimmung des Spielendes berücksichtigt die folgenden möglichen Szenarien für das Ende des Spiels:
- Das Brett ist voll und es wurde noch kein Gewinner bekannt gegeben: Das Spiel ist unentschieden.
- Cross hat gewonnen.
- Kreis hat gewonnen.
Leider liest es dazu einen vordefinierten Satz dieser Szenarien aus einer Tabelle. Dies ist nicht unbedingt schlecht, wenn man bedenkt, dass sich nur 9 Felder auf einem Brett befinden und der Tisch daher etwas klein ist. Gibt es jedoch eine bessere algorithmische Methode, um festzustellen, ob das Spiel beendet ist? Die Feststellung, ob jemand gewonnen hat oder nicht, ist das Kernstück des Problems, da die Überprüfung, ob 9 Felder voll sind, trivial ist.
Die Tabellenmethode könnte die Lösung sein, aber wenn nicht, was ist das? Was wäre, wenn das Board keine Größe hätte n=9
? Was ist, wenn es ein viel größeres Bord ist, sagt ihnen n=16
, n=25
und so weiter, die Anzahl der hintereinander abgestellte Gegenstände verursacht zu gewinnen zu sein x=4
, x=5
etc? Ein allgemeiner Algorithmus für alle n = { 9, 16, 25, 36 ... }
?
quelle
3
). Sie können also die Anzahl der einzelnen Titel verfolgen und nur dann nach Gewinnen suchen, wenn diese höher sind.Antworten:
Sie wissen, dass ein Gewinnzug nur stattfinden kann, nachdem X oder O den letzten Zug ausgeführt haben. Sie können also nur Zeilen / Spalten mit optionalem Diagramm durchsuchen, die in diesem Zug enthalten sind, um Ihren Suchraum zu begrenzen, wenn Sie versuchen, ein Gewinnbrett zu bestimmen. Da es in einem Draw-Tic-Tac-Toe-Spiel eine feste Anzahl von Zügen gibt, sobald der letzte Zug ausgeführt wurde, ist es standardmäßig ein Draw-Spiel, wenn es kein Gewinnzug war.
Bearbeiten: Dieser Code ist für ein n mal n Board mit n in einer Reihe zu gewinnen (3x3 Board erfordert 3 in einer Reihe usw.)
Bearbeiten: Code hinzugefügt, um Anti-Diag zu überprüfen. Ich konnte keinen Weg ohne Schleife finden, um festzustellen, ob sich der Punkt auf dem Anti-Diag befand. Deshalb fehlt dieser Schritt
quelle
Sie können ein magisches Quadrat verwenden http://mathworld.wolfram.com/MagicSquare.html Wenn eine Zeile, Spalte oder Diag 15 ergibt, hat ein Spieler gewonnen.
quelle
Wie wäre es mit diesem Pseudocode:
Nachdem ein Spieler eine Figur an Position (x, y) abgelegt hat:
Ich würde ein Array von char [n, n] mit O, X und Leerzeichen verwenden.
quelle
i==1
undn==3
,rdiag
muss überprüft werden(1, 3)
und(1, 3-1+1)
ist gleich den richtigen Koordinaten, aber(1, 3-(1+1))
nein.Dies ähnelt der Antwort von Osama ALASSIRY, tauscht jedoch konstanten Raum und lineare Zeit gegen linearen Raum und konstante Zeit. Das heißt, nach der Initialisierung gibt es keine Schleife.
Initialisieren Sie ein Paar
(0,0)
für jede Zeile, jede Spalte und die beiden Diagonalen (Diagonale und Antidiagonale). Diese Paare repräsentieren die Ansammlung(sum,sum)
der Teile in der entsprechenden Zeile, Spalte oder Diagonale, wobeiWenn ein Spieler eine Figur platziert, aktualisieren Sie das entsprechende Zeilen-, Spalten- und Diagonalpaar (falls auf den Diagonalen). Wenn ein neu aktualisiertes Zeilen-, Spalten- oder Diagonalpaar gleich
(n,0)
oder(0,n)
entweder A oder B gewonnen hat.Asymptotische Analyse:
Für die Speichernutzung verwenden Sie
4*(n+1)
Ganzzahlen.Übung: Können Sie sehen, wie Sie in O (1) Zeit pro Zug auf ein Unentschieden testen? In diesem Fall können Sie das Spiel frühzeitig mit einem Unentschieden beenden.
quelle
O(sqrt(n))
Zeit ist, aber nach jedem Zug gemacht werden muss, wobei n die Größe des Bretts ist. So enden Sie mitO(n^1.5)
. Für diese Lösung haben SieO(n)
insgesamt Zeit.Hier ist meine Lösung, die ich für ein Projekt geschrieben habe, an dem ich in Javascript arbeite. Wenn Ihnen die Speicherkosten einiger Arrays nichts ausmachen, ist dies wahrscheinlich die schnellste und einfachste Lösung, die Sie finden werden. Es wird davon ausgegangen, dass Sie die Position des letzten Zuges kennen.
quelle
Ich habe das gerade für meine C-Programmierklasse geschrieben.
Ich poste es, weil keines der anderen Beispiele hier mit einem rechteckigen Raster beliebiger Größe und einer beliebigen Anzahl N- in-einer Reihe aufeinanderfolgender Markierungen funktioniert , um zu gewinnen.
Sie finden meinen Algorithmus, so wie er ist, in der
checkWinner()
Funktion. Es werden keine magischen Zahlen oder irgendetwas Besonderes verwendet, um nach einem Gewinner zu suchen, es werden einfach vier für Schleifen verwendet - Der Code ist gut kommentiert, also werde ich ihn für sich selbst sprechen lassen, denke ich.quelle
Wenn die Tafel n × n ist, gibt es n Zeilen, n Spalten und 2 Diagonalen. Überprüfen Sie jedes dieser Elemente auf All-X oder All-O, um einen Gewinner zu finden.
Wenn es nur x < n aufeinanderfolgende Quadrate braucht , um zu gewinnen, ist es etwas komplizierter. Die naheliegendste Lösung besteht darin, jedes x × x- Quadrat auf einen Gewinner zu überprüfen . Hier ist ein Code, der das demonstriert.
(Habe ich nicht getestet tatsächlich diesen * hust *, aber es ist die Kompilierung auf dem ersten Versuch, yay me!)
quelle
Ich kenne Java nicht so gut, aber ich kenne C, also habe ich die magische Quadratidee von adk ausprobiert (zusammen mit der Suchbeschränkung von Hardwareguy ).
Es kompiliert und testet gut.
Das hat Spaß gemacht, danke!
Wenn Sie darüber nachdenken, brauchen Sie kein magisches Quadrat, nur eine Zählung für jede Zeile / Spalte / Diagonale. Dies ist etwas einfacher als das Verallgemeinern eines magischen Quadrats auf
n
×n
Matrizen, da Sie nur bis zählen müssenn
.quelle
Die gleiche Frage wurde mir in einem meiner Interviews gestellt. Meine Gedanken: Initialisieren Sie die Matrix mit 0. Behalten Sie 3 Arrays 1) sum_row (Größe n) 2) sum_column (Größe n) 3) diagonal (Größe 2)
Für jede Bewegung um (X) dekrementieren Sie den Feldwert um 1 und für jede Bewegung um (0) erhöhen Sie ihn um 1. Zu jedem Zeitpunkt, wenn die Zeile / Spalte / Diagonale, die in der aktuellen Bewegung geändert wurde, entweder -3 oder + summiert 3 bedeutet, dass jemand das Spiel gewonnen hat. Für eine Auslosung können wir den obigen Ansatz verwenden, um die moveCount-Variable beizubehalten.
Glaubst du, mir fehlt etwas?
Bearbeiten: Gleiches kann für die nxn-Matrix verwendet werden. Die Summe sollte gerade +3 oder -3 sein.
quelle
eine Methode ohne Schleife, um festzustellen, ob sich der Punkt auf dem Anti-Diag befand:
quelle
Ich bin spät dran, aber ich wollte auf einen Vorteil hinweisen, den ich bei der Verwendung eines magischen Quadrats festgestellt habe , nämlich dass es verwendet werden kann, um einen Verweis auf das Quadrat zu erhalten, das den Gewinn oder Verlust in der nächsten Runde verursachen würde, anstatt wird nur verwendet, um zu berechnen, wann ein Spiel vorbei ist.
Nimm dieses magische Quadrat:
Richten Sie zunächst ein
scores
Array ein, das bei jeder Verschiebung erhöht wird. Siehe diese Antwort für Details. Wenn wir nun illegal zweimal hintereinander X bei [0,0] und [0,1] spielen,scores
sieht das Array folgendermaßen aus:Und das Board sieht so aus:
Dann müssen wir nur noch Folgendes tun, um einen Hinweis darauf zu erhalten, auf welchem Quadrat wir gewinnen / blockieren sollen:
In der Realität erfordert die Implementierung einige zusätzliche Tricks, wie den Umgang mit nummerierten Schlüsseln (in JavaScript), aber ich fand es ziemlich einfach und genoss die Freizeitmathematik.
quelle
Ich habe einige Optimierungen in den Zeilen-, Spalten- und Diagonalprüfungen vorgenommen. Es wird hauptsächlich in der ersten verschachtelten Schleife entschieden, ob eine bestimmte Spalte oder Diagonale überprüft werden muss. So vermeiden wir die Überprüfung von Spalten oder Diagonalen, was Zeit spart. Dies hat große Auswirkungen, wenn die Platinengröße größer ist und eine erhebliche Anzahl der Zellen nicht gefüllt ist.
Hier ist der Java-Code dafür.
quelle
Ich mag diesen Algorithmus, da er eine 1x9 vs 3x3 Darstellung des Boards verwendet.
quelle
Eine weitere Option: Generieren Sie Ihre Tabelle mit Code. Bis zur Symmetrie gibt es nur drei Möglichkeiten, um zu gewinnen: Kantenreihe, mittlere Reihe oder Diagonale. Nehmen Sie diese drei und drehen Sie sie auf jede mögliche Weise:
Diese Symmetrien können in Ihrem Spielcode mehr Verwendung finden: Wenn Sie zu einem Brett gelangen, von dem Sie bereits eine gedrehte Version gesehen haben, können Sie einfach den zwischengespeicherten Wert oder den zwischengespeicherten besten Zug von diesem nehmen (und ihn zurückdrehen). Dies ist normalerweise viel schneller als die Auswertung des Spielunterbaums.
(Das Umdrehen nach links und rechts kann auf die gleiche Weise helfen. Dies wurde hier nicht benötigt, da die Rotationsmenge der Gewinnmuster spiegelsymmetrisch ist.)
quelle
Hier ist eine Lösung, die ich mir ausgedacht habe: Sie speichert die Symbole als Zeichen und verwendet den int-Wert des Zeichens, um herauszufinden, ob X oder O gewonnen hat (siehe Code des Schiedsrichters).
Auch hier sind meine Unit-Tests, um zu überprüfen, ob es tatsächlich funktioniert
Vollständige Lösung: https://github.com/nashjain/tictactoe/tree/master/java
quelle
Wie wäre es mit einem folgenden Ansatz für 9 Slots? Deklarieren Sie 9 ganzzahlige Variablen für eine 3x3-Matrix (a1, a2 .... a9), wobei a1, a2, a3 Zeile-1 darstellen und a1, a4, a7 Spalte-1 bilden würden (Sie haben die Idee). Verwenden Sie '1', um Spieler-1 anzuzeigen, und '2', um Spieler-2 anzuzeigen.
Es gibt 8 mögliche Gewinnkombinationen: Win-1: a1 + a2 + a3 (Antwort könnte 3 oder 6 sein, je nachdem welcher Spieler gewonnen hat) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7
Jetzt wissen wir, dass wenn Spieler 1 a1 kreuzt, wir die Summe von 3 Variablen neu bewerten müssen: Win-1, Win-4 und Win-7. Welches 'Win-?' Variablen erreicht 3 oder 6 zuerst gewinnt das Spiel. Wenn die Variable Win-1 zuerst 6 erreicht, gewinnt Spieler-2.
Ich verstehe, dass diese Lösung nicht einfach skalierbar ist.
quelle
Dies ist eine sehr einfache Möglichkeit, dies zu überprüfen.
}}
quelle
Wenn Sie beispielsweise das Grenzfeld 5 * 5 haben, habe ich die nächste Überprüfungsmethode verwendet:
Ich denke, es ist klarer, aber wahrscheinlich nicht der optimalste Weg.
quelle
Hier ist meine Lösung mit einem zweidimensionalen Array:
quelle
Konstante Zeitlösung, läuft in O (8).
Speichern Sie den Status der Karte als Binärzahl. Das kleinste Bit (2 ^ 0) ist die obere linke Reihe der Platine. Dann geht es nach rechts, dann nach unten.
IE
Jeder Spieler hat seine eigene Binärzahl, um den Zustand darzustellen (weil Tic-Tac-Toe) 3 Zustände hat (X, O & leer), so dass eine einzelne Binärzahl nicht funktioniert, um den Zustand des Bretts für mehrere Spieler darzustellen.
Zum Beispiel ein Board wie:
Beachten Sie, dass die Bits für Spieler X von den Bits für Spieler O getrennt sind. Dies ist offensichtlich, da X kein Stück dort platzieren kann, wo O ein Stück hat, und umgekehrt.
Um zu überprüfen, ob ein Spieler gewonnen hat, müssen wir alle von diesem Spieler abgedeckten Positionen mit einer Position vergleichen, von der wir wissen, dass es sich um eine Gewinnposition handelt. In diesem Fall wäre der einfachste Weg, dies zu tun, das UND-Gating der Spielerposition und der Gewinnposition und das Überprüfen, ob beide gleich sind.
z.B.
Hinweis:
X & W = W
X befindet sich also in einem Gewinnzustand.Dies ist eine Lösung mit konstanter Zeit, die nur von der Anzahl der Gewinnpositionen abhängt, da das Anwenden des UND-Gatters eine Operation mit konstanter Zeit ist und die Anzahl der Gewinnpositionen endlich ist.
Es vereinfacht auch die Aufgabe, alle gültigen Kartenzustände aufzulisten, wobei nur alle Zahlen durch 9 Bits darstellbar sind. Aber natürlich benötigen Sie eine zusätzliche Bedingung, um sicherzustellen, dass eine Nummer ein gültiger Board-Status ist (z. B.
0b111111111
eine gültige 9-Bit-Nummer, aber kein gültiger Board-Status, da X gerade alle Runden gemacht hat).Die Anzahl der möglichen Gewinnpositionen kann im laufenden Betrieb generiert werden, aber hier sind sie trotzdem.
Um alle Kartenpositionen aufzulisten, können Sie die folgende Schleife ausführen. Obwohl ich es einer anderen Person überlassen werde, zu bestimmen, ob eine Nummer ein gültiger Board-Status ist.
HINWEIS: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)
quelle
Ich bin mir nicht sicher, ob dieser Ansatz noch veröffentlicht wurde. Dies sollte für jedes m * n-Brett funktionieren und ein Spieler soll die aufeinanderfolgende Position " wonPos " besetzen . Die Idee basiert auf dem laufenden Fenster.
quelle
Ich habe dafür einmal im Rahmen eines Wissenschaftsprojekts einen Algorithmus entwickelt.
Grundsätzlich teilen Sie das Brett rekursiv in eine Reihe überlappender 2x2-Rechtecke auf und testen die verschiedenen möglichen Kombinationen, um auf einem 2x2-Quadrat zu gewinnen.
Es ist langsam, hat aber den Vorteil, dass es auf einer Karte jeder Größe mit ziemlich linearen Speicheranforderungen funktioniert.
Ich wünschte, ich könnte meine Implementierung finden
quelle