Schnellster Spieler für Punkte und Boxen

16

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 mund nals 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.

Bildbeschreibung hier eingeben

Sie können annehmen, dass entweder n = moder n = m - 1und mmindestens 2 ist.

Die Herausforderung besteht darin, solvedas 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, drawoder losedie 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.


quelle
2
Müssen wir Minimax verwenden?
Qwr
@qwr Kannst du mir mitteilen, welche andere Option du dir vorgestellt hast?
Warten Sie, es gibt einen vorhersehbaren Gewinner in diesem Spiel, der ausschließlich auf der Rastergröße basiert.
Nicht dass Charles
@Charles Ja, wenn beide Spieler optimal spielen.
1
@PeterTaylor Ich denke, du bekommst zwei Punkte, aber nur einen zusätzlichen Zug.

Antworten:

15

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:

  • Einfache Implementierung von Minimax mit Alpha-Beta-Bereinigung
  • sehr wenig Speicherverwaltung (behält DLL der gültigen Bewegungen bei; O (1) Aktualisierungen pro Zweig in der Baumsuche)
  • zweite Datei mit Beschneiden nach Symmetrien. Erreicht weiterhin O (1) Aktualisierungen pro Zweig (technisch O (S), wobei S die Anzahl der Symmetrien ist. Dies ist 7 für quadratische Karten und 3 für nicht quadratische Karten)
  • Die dritte und vierte Datei fügen Memoization hinzu. Sie haben die Kontrolle über die Größe der Hashtabelle ( #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.
  • Kompilieren Sie mit -DDEBUGfür Ausdrucke

Mögliche Verbesserungen:

  • Behebung eines kleinen Speicherverlusts bei der ersten Bearbeitung
  • Alpha / Beta-Beschneidung in der 2. Bearbeitung hinzugefügt
  • Prune Symmetrien in der 3. bearbeiten hinzugefügt (beachten Sie, dass Symmetrien sind nicht von memoization behandelt, so dass eine separate Optimierung bleibt.)
  • Memoisierung in der 4. Bearbeitung hinzugefügt
  • Derzeit verwendet Memoization ein Anzeigebit für jede Wand. Ein 3x4-Board hat 31 Wände, sodass diese Methode trotz zeitlicher Einschränkungen keine 4x4-Boards verarbeiten kann. Die Verbesserung wäre, X-Bit-Ganzzahlen zu emulieren, wobei X mindestens so groß ist wie die Anzahl der Wände.

Code

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

Protokollieren Sie die Ausführungszeiten

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 RZeilen und CSpalten. Es gibt R*CQuadrate, R*(C+1)vertikale Wände und C*(R+1)horizontale Wände. Das ist insgesamt W = 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 WOptionen für den ersten Zug. Für jede davon kann der nächste Spieler eine der W-1verbleibenden Wände spielen, usw .. Das gibt uns einen Suchraum von SS = W * (W-1) * (W-2) * ... * 1, oder SS = W!. Factorials sind riesig, aber das ist nur der Anfang. SSist die Anzahl der Blattknoten im Suchraum. Für unsere Analyse relevanter ist die Gesamtzahl der zu treffenden Entscheidungen (dh die Anzahl der Zweige B im Baum). Die erste Schicht von Zweigen hat WOptionen. Für jeden von denen hat der nächste Level W-1usw.

B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!

B = SUM W!/(W-k)!
  k=0..W-1

Schauen wir uns einige kleine Tischgrößen an:

Board Size  Walls  Leaves (SS)      Branches (B)
---------------------------------------------------
1x1         04     24               64
1x2         07     5040             13699
2x2         12     479001600        1302061344
2x3         17     355687428096000  966858672404689

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. Stattdessen funktioniert 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.

Auswirkungen der Hashtable-Größe

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.

Protokolldiagramm der genommenen Zweige

Protokollierung der Schnittfaktoren

wrongu
quelle
23s scheint für eine schnelle Sprache wie C auffallend langsam zu sein.
QWR
Brute Force mit ein wenig Beschneiden von Alpha Beta. Ich bin damit einverstanden , dass 23s ist verdächtig, aber ich sehe keinen Grund , in meinem Code , dass es unvereinbar wäre .. Mit anderen Worten, es ist ein Geheimnis
wrongu
1
Die Eingabe wird wie in der Frage angegeben formatiert. Zwei durch Leerzeichen getrennte Ganzzahlen rows columns, die die Größe der Tafel
angeben
1
@Lembik Ich glaube nicht, dass noch etwas zu tun ist. Ich bin mit diesem verrückten Projekt fertig!
Falsch
1
Ich denke, Ihre Antwort verdient einen besonderen Platz. Ich habe es nachgeschlagen und 3 mal 3 ist die größte Problemgröße, die jemals gelöst wurde, und Ihr Code ist fast augenblicklich dafür. Wenn du 3 mal 4 oder 4 mal 4 lösen kannst, kannst du das Ergebnis der Wiki-Seite hinzufügen und berühmt sein :)
4

Python - 2x2 in 29s

Crossposting von Rätseln . Nicht besonders optimiert, aber möglicherweise ein nützlicher Ausgangspunkt für andere Teilnehmer.

from collections import defaultdict

VERTICAL, HORIZONTAL = 0, 1

#represents a single line segment that can be drawn on the board.
class Line(object):
    def __init__(self, x, y, orientation):
        self.x = x
        self.y = y
        self.orientation = orientation
    def __hash__(self):
        return hash((self.x, self.y, self.orientation))
    def __eq__(self, other):
        if not isinstance(other, Line): return False
        return self.x == other.x and self.y == other.y and self.orientation == other.orientation
    def __repr__(self):
        return "Line({}, {}, {})".format(self.x, self.y, "HORIZONTAL" if self.orientation == HORIZONTAL else "VERTICAL")

class State(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.whose_turn = 0
        self.scores = {0:0, 1:0}
        self.lines = set()
    def copy(self):
        ret = State(self.width, self.height)
        ret.whose_turn = self.whose_turn
        ret.scores = self.scores.copy()
        ret.lines = self.lines.copy()
        return ret
    #iterate through all lines that can be placed on a blank board.
    def iter_all_lines(self):
        #horizontal lines
        for x in range(self.width):
            for y in range(self.height+1):
                yield Line(x, y, HORIZONTAL)
        #vertical lines
        for x in range(self.width+1):
            for y in range(self.height):
                yield Line(x, y, VERTICAL)
    #iterate through all lines that can be placed on this board, 
    #that haven't already been placed.
    def iter_available_lines(self):
        for line in self.iter_all_lines():
            if line not in self.lines:
                yield line

    #returns the number of points that would be earned by a player placing the line.
    def value(self, line):
        assert line not in self.lines
        all_placed = lambda seq: all(l in self.lines for l in seq)
        if line.orientation == HORIZONTAL:
            #lines composing the box above the line
            lines_above = [
                Line(line.x,   line.y+1, HORIZONTAL), #top
                Line(line.x,   line.y,   VERTICAL),   #left
                Line(line.x+1, line.y,   VERTICAL),   #right
            ]
            #lines composing the box below the line
            lines_below = [
                Line(line.x,   line.y-1, HORIZONTAL), #bottom
                Line(line.x,   line.y-1, VERTICAL),   #left
                Line(line.x+1, line.y-1, VERTICAL),   #right
            ]
            return all_placed(lines_above) + all_placed(lines_below)
        else:
            #lines composing the box to the left of the line
            lines_left = [
                Line(line.x-1, line.y+1, HORIZONTAL), #top
                Line(line.x-1, line.y,   HORIZONTAL), #bottom
                Line(line.x-1, line.y,   VERTICAL),   #left
            ]
            #lines composing the box to the right of the line
            lines_right = [
                Line(line.x,   line.y+1, HORIZONTAL), #top
                Line(line.x,   line.y,   HORIZONTAL), #bottom
                Line(line.x+1, line.y,   VERTICAL),   #right
            ]
            return all_placed(lines_left) + all_placed(lines_right)

    def is_game_over(self):
        #the game is over when no more moves can be made.
        return len(list(self.iter_available_lines())) == 0

    #iterates through all possible moves the current player could make.
    #Because scoring a point lets a player go again, a move can consist of a collection of multiple lines.
    def possible_moves(self):
        for line in self.iter_available_lines():
            if self.value(line) > 0:
                #this line would give us an extra turn.
                #so we create a hypothetical future state with this line already placed, and see what other moves can be made.
                future = self.copy()
                future.lines.add(line)
                if future.is_game_over(): 
                    yield [line]
                else:
                    for future_move in future.possible_moves():
                        yield [line] + future_move
            else:
                yield [line]

    def make_move(self, move):
        for line in move:
            self.scores[self.whose_turn] += self.value(line)
            self.lines.add(line)
        self.whose_turn = 1 - self.whose_turn

    def tuple(self):
        return (tuple(self.lines), tuple(self.scores.items()), self.whose_turn)
    def __hash__(self):
        return hash(self.tuple())
    def __eq__(self, other):
        if not isinstance(other, State): return False
        return self.tuple() == other.tuple()

#function decorator which memorizes previously calculated values.
def memoized(fn):
    answers = {}
    def mem_fn(*args):
        if args not in answers:
            answers[args] = fn(*args)
        return answers[args]
    return mem_fn

#finds the best possible move for the current player.
#returns a (move, value) tuple.
@memoized
def get_best_move(state):
    cur_player = state.whose_turn
    next_player = 1 - state.whose_turn
    if state.is_game_over():
        return (None, state.scores[cur_player] - state.scores[next_player])
    best_move = None
    best_score = float("inf")
    #choose the move that gives our opponent the lowest score
    for move in state.possible_moves():
        future = state.copy()
        future.make_move(move)
        _, score = get_best_move(future)
        if score < best_score:
            best_move = move
            best_score = score
    return [best_move, -best_score]

n = 2
m = 2
s = State(n,m)
best_move, relative_value = get_best_move(s)
if relative_value > 0:
    print("win")
elif relative_value == 0:
    print("draw")
else:
    print("lose")
Kevin
quelle
Kann mit pypy bis zu 18 Sekunden beschleunigt werden.
2

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.

//first row is a horizontal edges and second is vertical
var gameEdges = [
    [false, false],
    [false, false, false],
    [false, false]
]

//track all possible moves and score outcome
var moves = []

function minimax(edges, isPlayersTurn, prevScore, depth) {

    if (depth <= 0) {
        return [prevScore, 0, 0];
    }
    else {

        var pointValue = 1;
        if (!isPlayersTurn)
            pointValue = -1;

        var moves = [];

        //get all possible moves and scores
        for (var i in edges) {
            for (var j in edges[i]) {
                //if edge is available then its a possible move
                if (!edges[i][j]) {

                    //if it would result in game over, add it to the scores array, otherwise, try the next move
                    //clone the array
                    var newEdges = [];
                    for (var k in edges)
                        newEdges.push(edges[k].slice(0));
                    //update state
                    newEdges[i][j] = true;
                    //if closing this edge would result in a complete square, get another move and get a point
                    //square could be formed above, below, right or left and could get two squares at the same time

                    var currentScore = prevScore;
                    //vertical edge
                    if (i % 2 !== 0) {//i === 1
                        if (newEdges[i] && newEdges[i][j - 1] && newEdges[i - 1] && newEdges[i - 1][j - 1] && newEdges[parseInt(i) + 1] && newEdges[parseInt(i) + 1][j - 1])
                            currentScore += pointValue;
                        if (newEdges[i] && newEdges[i][parseInt(j) + 1] && newEdges[i - 1] && newEdges[i - 1][j] && newEdges[parseInt(i) + 1] && newEdges[parseInt(i) + 1][j])
                            currentScore += pointValue;
                    } else {//horizontal
                        if (newEdges[i - 2] && newEdges[i - 2][j] && newEdges[i - 1][j] && newEdges[i - 1][parseInt(j) + 1])
                            currentScore += pointValue;
                        if (newEdges[parseInt(i) + 2] && newEdges[parseInt(i) + 2][j] && newEdges[parseInt(i) + 1][j] && newEdges[parseInt(i) + 1][parseInt(j) + 1])
                            currentScore += pointValue;
                    }

                    //leaf case - if all edges are taken then there are no more moves to evaluate
                    if (newEdges.every(function (arr) { return arr.every(Boolean) })) {
                        moves.push([currentScore, i, j]);
                        console.log("reached end case with possible score of " + currentScore);
                    }
                    else {
                        if ((isPlayersTurn && currentScore > prevScore) || (!isPlayersTurn && currentScore < prevScore)) {
                            //gained a point so get another turn
                            var newMove = minimax(newEdges, isPlayersTurn, currentScore, depth - 1);

                            moves.push([newMove[0], i, j]);
                        } else {
                            //didnt gain a point - opponents turn
                            var newMove = minimax(newEdges, !isPlayersTurn, currentScore, depth - 1);

                            moves.push([newMove[0], i, j]);
                        }
                    }



                }


            }

        }//end for each move

        var bestMove = moves[0];
        if (isPlayersTurn) {
            for (var i in moves) {
                if (moves[i][0] > bestMove[0])
                    bestMove = moves[i];
            }
        }
        else {
            for (var i in moves) {
                if (moves[i][0] < bestMove[0])
                    bestMove = moves[i];
            }
        }
        return bestMove;
    }
}

var player1Turn = true;
var squares = [[0,0],[0,0]]//change to "A" or "B" if square won by any of the players
var lastMove = null;

function output(text) {
    document.getElementById("content").innerHTML += text;
}

function clear() {
    document.getElementById("content").innerHTML = "";
}

function render() {
    var width = 3;
    if (document.getElementById('txtWidth').value)
        width = parseInt(document.getElementById('txtWidth').value);
    if (width < 2)
        width = 2;

    clear();
    //need to highlight the last move taken and show who has won each square
    for (var i in gameEdges) {
        for (var j in gameEdges[i]) {
            if (i % 2 === 0) {
                if(j === "0")
                    output("*");
                if (gameEdges[i][j] && lastMove[1] == i && lastMove[2] == j)
                    output(" <b>-</b> ");
                else if (gameEdges[i][j])
                    output(" - ");
                else
                    output("&nbsp;&nbsp;&nbsp;");
                output("*");
            }
            else {
                if (gameEdges[i][j] && lastMove[1] == i && lastMove[2] == j)
                    output("<b>|</b>");
                else if (gameEdges[i][j])
                    output("|");
                else
                    output("&nbsp;");

                if (j <= width - 2) {
                    if (squares[Math.floor(i / 2)][j] === 0)
                        output("&nbsp;&nbsp;&nbsp;&nbsp;");
                    else
                        output("&nbsp;" + squares[Math.floor(i / 2)][j] + "&nbsp;");
                }
            }
        }
        output("<br />");

    }
}

function nextMove(playFullGame) {
    var startTime = new Date().getTime();
    if (!gameEdges.every(function (arr) { return arr.every(Boolean) })) {

        var depth = 100;
        if (document.getElementById('txtDepth').value)
            depth = parseInt(document.getElementById('txtDepth').value);

        if (depth < 1)
            depth = 1;

        var move = minimax(gameEdges, true, 0, depth);
        gameEdges[move[1]][move[2]] = true;
        lastMove = move;

        //if a square was taken, need to update squares and whose turn it is

        var i = move[1];
        var j = move[2];
        var wonSquare = false;
        if (i % 2 !== 0) {//i === 1
            if (gameEdges[i] && gameEdges[i][j - 1] && gameEdges[i - 1] && gameEdges[i - 1][j - 1] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j - 1]) {
                squares[Math.floor(i / 2)][j - 1] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
            if (gameEdges[i] && gameEdges[i][parseInt(j) + 1] && gameEdges[i - 1] && gameEdges[i - 1][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j]) {
                squares[Math.floor(i / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
        } else {//horizontal
            if (gameEdges[i - 2] && gameEdges[i - 2][j] && gameEdges[i - 1] && gameEdges[i - 1][j] && gameEdges[i - 1] && gameEdges[i - 1][parseInt(j) + 1]) {
                squares[Math.floor((i - 1) / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
            if (gameEdges[i + 2] && gameEdges[parseInt(i) + 2][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][parseInt(j) + 1]) {
                squares[Math.floor(i / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
        }

        //didnt win a square so its the next players turn
        if (!wonSquare)
            player1Turn = !player1Turn;

        render();

        if (playFullGame) {
            nextMove(playFullGame);
        }
    }

    var endTime = new Date().getTime();
    var executionTime = endTime - startTime;
    document.getElementById("executionTime").innerHTML = 'Execution time: ' + executionTime;
}

function initGame() {

    var width = 3;
    var height = 2;

    if (document.getElementById('txtWidth').value)
        width = document.getElementById('txtWidth').value;
    if (document.getElementById('txtHeight').value)
        height = document.getElementById('txtHeight').value;

    if (width < 2)
        width = 2;
    if (height < 2)
        height = 2;

    var depth = 100;
    if (document.getElementById('txtDepth').value)
        depth = parseInt(document.getElementById('txtDepth').value);

    if (depth < 1)
        depth = 1;

    if (width > 2 && height > 2 && !document.getElementById('txtDepth').value)
        alert("Warning. Your system may become unresponsive. A smaller grid or search depth is highly recommended.");

    gameEdges = [];
    for (var i = 0; i < height; i++) {
        if (i == 0) {
            gameEdges.push([]);
            for (var j = 0; j < (width - 1) ; j++) {
                gameEdges[i].push(false);
            }
        }
        else {
            gameEdges.push([]);
            for (var j = 0; j < width; j++) {
                gameEdges[(i * 2) - 1].push(false);
            }
            gameEdges.push([]);
            for (var j = 0; j < (width - 1) ; j++) {
                gameEdges[i*2].push(false);
            }
        }
    }

    player1Turn = true;

    squares = [];
    for (var i = 0; i < (height - 1) ; i++) {
        squares.push([]);
        for (var j = 0; j < (width - 1); j++) {
            squares[i].push(0);
        }
    }

    lastMove = null;

    render();
}

document.addEventListener('DOMContentLoaded', initGame, false);
rdans
quelle
Die Demo ist wirklich toll! Das 3 x 3 ist wirklich interessant, da sich der Gewinner hin und her ändert, wenn Sie die Suchtiefe erhöhen. Kann ich überprüfen, ob Ihr Minimax jemals nach einer halben Kurve stehen bleibt? Ich meine, wenn jemand ein Quadrat bekommt, erstreckt es sich immer bis zum Ende seines Zuges?
2x2 ist 3 mal 3. Sind Sie sicher, dass Ihr Code das genau in 20 ms lösen kann?
"Wenn jemand ein Feld bekommt, reicht es immer bis zum Ende seines Zuges?" - Wenn der Spieler ein Feld erhält, bewegt er sich immer noch zum nächsten Zug, aber dieser nächste Zug gilt für denselben Spieler, dh er erhält einen zusätzlichen Zug, um ein Feld fertigzustellen. "2x2 ist 3 mal 3 Punkte" - Whoops. In diesem Fall ist meine Punktzahl 1x1.
rdans