Pemain tercepat untuk Dots and Boxes

16

Tantangannya adalah menulis solver untuk pensil dan permainan kertas klasik Dots and Boxes . Kode Anda harus mengambil dua bilangan bulat mdan nsebagai input yang menentukan ukuran papan.

Dimulai dengan kisi-kisi titik yang kosong, pemain bergiliran, menambahkan garis horizontal atau vertikal tunggal antara dua titik yang berdekatan yang tidak terhubung. Seorang pemain yang menyelesaikan sisi keempat kotak 1 × 1 menghasilkan satu poin dan mengambil giliran lain. (Poin biasanya direkam dengan menempatkan di kotak tanda pengidentifikasi pemain, seperti inisial). Permainan berakhir ketika tidak ada lagi garis yang dapat ditempatkan. Pemenang permainan adalah pemain dengan poin terbanyak.

masukkan deskripsi gambar di sini

Anda dapat mengasumsikan bahwa baik n = matau n = m - 1dan msetidaknya 2.

Tantangannya adalah menuju solvepermainan Dots and Boxes sebesar mungkin dalam waktu kurang dari satu menit. Ukuran gim ini sederhana n*m. Output dari kode Anda seharusnya win, drawatau loseyang seharusnya menjadi hasil untuk pemain pertama dengan asumsi kedua pemain bermain secara optimal.

Kode Anda harus dapat dikompilasi / dijalankan di ubuntu menggunakan alat yang mudah diinstal dan gratis. Silakan laporkan skor Anda sebagai area terbesar yang dapat Anda selesaikan di komputer Anda dalam 1 menit bersama waktu. Saya kemudian akan menguji kode di komputer saya dan membuat daftar entri urutan peringkat.

Dalam kasus tie-break, pemenang akan menjadi kode tercepat di papan ukuran terbesar yang bisa diselesaikan dalam waktu kurang dari satu menit.


Akan lebih baik jika kode yang dihasilkan tidak hanya menang atau kalah tetapi juga skor aktual. Ini membuat pemeriksaan kewarasan kebenaran.


sumber
2
Apakah kita harus menggunakan minimax?
qwr
@ qwr Bisakah Anda memberi tahu saya opsi lain yang ada dalam pikiran Anda?
Tunggu, ada pemenang yang bisa ditebak dalam game ini hanya berdasarkan ukuran grid?
Bukan karena Charles
@ Charles Ya jika kedua pemain bermain secara optimal.
1
@ PeterTaylor Saya pikir Anda mendapatkan dua poin tetapi hanya satu putaran tambahan.

Jawaban:

15

Papan C99 - 3x3 dalam ukuran 0,084

Sunting: Saya refactored kode saya dan melakukan beberapa analisis yang lebih dalam pada hasilnya.

Pengeditan Lebih Lanjut: Menambahkan pemangkasan oleh simetri. Ini membuat 4 konfigurasi algoritma: dengan atau tanpa simetri X dengan atau tanpa pemangkasan alpha-beta

Edit Terjauh: Menambahkan memoisasi menggunakan tabel hash, akhirnya mencapai hal yang mustahil: menyelesaikan papan 3x3!

Fitur utama:

  • implementasi minimax langsung dengan pemangkasan alpha-beta
  • sangat sedikit manajemen memori (mempertahankan dll dari langkah yang valid; O (1) pembaruan per cabang dalam pencarian pohon)
  • file kedua dengan pemangkasan berdasarkan simetri. Masih mencapai pembaruan O (1) per cabang (secara teknis O (S) dengan S adalah jumlah simetri. Ini adalah 7 untuk papan persegi dan 3 untuk papan non-persegi)
  • file ketiga dan keempat menambahkan memoisasi. Anda memiliki kontrol atas ukuran hashtable ( #define HASHTABLE_BITWIDTH). Ketika ukuran ini lebih besar dari atau sama dengan jumlah dinding, itu tidak menjamin tabrakan dan pembaruan O (1). Hashtable yang lebih kecil akan memiliki lebih banyak tabrakan dan sedikit lebih lambat.
  • kompilasi dengan -DDEBUGuntuk cetakan

Peningkatan potensial:

  • memperbaiki kebocoran memori kecil yang diperbaiki pada edit pertama
  • pemangkasan alpha / beta ditambahkan di edit ke-2
  • simetri prune ditambahkan pada edit ke-3 (perhatikan bahwa simetri tidak ditangani oleh memoisasi, sehingga tetap merupakan optimasi yang terpisah.)
  • memoisasi ditambahkan di edit ke-4
  • Saat ini memoisasi menggunakan bit indikator untuk setiap dinding. Papan 3x4 memiliki 31 dinding, sehingga metode ini tidak dapat menangani papan 4x4 terlepas dari kendala waktu. perbaikannya adalah meniru bilangan bulat X-bit, di mana X setidaknya sama besar dengan jumlah dinding.

Kode

Karena kurangnya pengorganisasian, jumlah file telah bertambah. Semua kode telah dipindahkan ke Gudang Github ini . Dalam edit memoisasi, saya menambahkan skrip makefile dan pengujian.

Hasil

Catat alur waktu eksekusi

Catatan tentang Kompleksitas

Pendekatan brutal terhadap titik dan kotak meledak dengan sangat cepat .

Pertimbangkan papan dengan Rbaris dan Ckolom. Ada R*Ckotak, R*(C+1)dinding vertikal, dan C*(R+1)dinding horizontal. Itu total W = 2*R*C + R + C.

Karena Lembik meminta kami untuk menyelesaikan permainan dengan minimax, kita perlu melintasi ke daun pohon permainan. Mari kita abaikan pemangkasan untuk saat ini, karena yang penting adalah urutan besarnya.

Ada Wopsi untuk langkah pertama. Untuk masing-masing, pemain berikutnya dapat memainkan salah satu dari W-1dinding yang tersisa, dll. Itu memberi kita ruang pencarian SS = W * (W-1) * (W-2) * ... * 1, atau SS = W!. Faktorial sangat besar, tetapi itu baru permulaan. SSadalah jumlah node daun di ruang pencarian. Lebih relevan dengan analisis kami adalah jumlah total keputusan yang harus dibuat (yaitu jumlah cabang B di pohon). Lapisan pertama cabang memiliki Wopsi. Untuk masing-masing, level selanjutnya W-1, dll.

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

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

Mari kita lihat beberapa ukuran meja kecil:

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

Angka-angka ini semakin konyol. Setidaknya mereka menjelaskan mengapa kode brute-force tampaknya menggantung selamanya di papan 2x3. Ruang pencarian papan 2x3 adalah 742560 kali lebih besar dari 2x2 . Jika 2x2 membutuhkan 20 detik untuk menyelesaikan, ekstrapolasi konservatif memperkirakan lebih dari 100 hari waktu eksekusi untuk 2x3. Jelas kita perlu memangkas.

Analisis Pemangkasan

Saya mulai dengan menambahkan pemangkasan sangat sederhana menggunakan algoritma alpha-beta. Pada dasarnya, ia berhenti mencari jika lawan yang ideal tidak akan pernah memberikannya kesempatan saat ini. "Hei, lihat - saya menang banyak jika lawan saya membiarkan saya mendapatkan setiap kotak!", Pikir tidak AI, pernah.

sunting Saya juga menambahkan pemangkasan berdasarkan papan simetris. Saya tidak menggunakan pendekatan memoisasi, kalau-kalau suatu hari saya menambahkan memoisasi dan ingin memisahkan analisis itu. Sebagai gantinya, ia bekerja seperti ini: sebagian besar garis memiliki "pasangan simetris" di tempat lain di grid. Ada hingga 7 simetri (horizontal, vertikal, 180 rotasi, 90 rotasi, 270 rotasi, diagonal, dan diagonal lainnya). Semua 7 berlaku untuk papan persegi, tetapi 4 terakhir tidak berlaku untuk papan non-persegi. Setiap dinding memiliki pointer ke "pasangan" untuk masing-masing simetri ini. Jika, setelah berbelok, papan itu simetris secara horizontal, maka hanya satu dari setiap pasangan horisontal yang perlu dimainkan.

sunting sunting Memoisasi! Setiap dinding mendapatkan id unik, yang saya setel dengan mudah sebagai indikator; dinding ke-n memiliki id 1 << n. Hash papan, maka, hanya OR dari semua dinding yang dimainkan. Ini diperbarui di setiap cabang dalam waktu O (1). Ukuran hashtable diatur dalam a #define. Semua tes dijalankan dengan ukuran 2 ^ 12, karena mengapa tidak? Ketika ada lebih banyak dinding daripada pengindeksan bit hashtable (12 bit dalam kasus ini), 12 paling signifikan ditutup dan digunakan sebagai indeks. Tabrakan ditangani dengan daftar tertaut di setiap indeks hashtable. Bagan berikut ini adalah analisis cepat dan kotor saya tentang bagaimana ukuran hashtable mempengaruhi kinerja. Pada komputer dengan RAM tak terbatas, kami akan selalu mengatur ukuran tabel dengan jumlah dinding. Papan 3x4 akan memiliki hashtable 2 ^ 31 panjang. Sayangnya kami tidak memiliki kemewahan itu.

Efek Ukuran Hashtable

Ok, kembali ke pemangkasan .. Dengan menghentikan pencarian tinggi di pohon, kita dapat menghemat banyak waktu dengan tidak turun untuk pergi. 'Faktor Pemangkasan' adalah fraksi dari semua cabang yang memungkinkan yang harus kami kunjungi. Brute-force memiliki faktor pemangkasan 1. Semakin kecil, semakin baik.

Log plot cabang diambil

Log plot faktor pemangkasan

salah
sumber
23s tampaknya sangat lambat untuk bahasa cepat seperti C. Apakah Anda dengan kasar memaksa?
qwr
Brute force dengan sedikit pemangkasan dari alpha beta. Saya setuju bahwa 23-an itu mencurigakan, tetapi saya tidak melihat alasan dalam kode saya bahwa itu akan menjadi tidak konsisten .. Dengan kata lain, ini adalah sebuah misteri
salah
1
input diformat seperti yang ditentukan oleh pertanyaan. dua bilangan bulat yang dipisahkan oleh ruang rows columnsmenentukan ukuran papan
salah
1
@Lembik Saya tidak berpikir ada yang bisa dilakukan. Saya sudah selesai dengan proyek gila ini!
wrongu
1
Saya pikir jawaban Anda pantas mendapat tempat khusus. Saya mencarinya dan 3 oleh 3 adalah ukuran masalah terbesar yang pernah dipecahkan sebelumnya dan kode Anda hampir instan untuk itu. Jika Anda dapat menyelesaikan 3 dengan 4 atau 4 dengan 4 Anda dapat menambahkan hasilnya ke halaman wiki dan menjadi terkenal :)
4

Python - 2x2 dalam 29-an

Posting silang dari teka-teki . Tidak dioptimalkan secara khusus, tetapi dapat menjadi titik awal yang berguna untuk pendatang lain.

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
sumber
Dapat dipercepat hingga 18 detik menggunakan pypy.
2

Javascript - papan 1x2 dalam 20 ms

Demo online tersedia di sini (peringatan - sangat lambat jika lebih besar dari 1x2 dengan kedalaman pencarian penuh ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html

Dikembangkan untuk kriteria kemenangan asli (kode golf) dan bukan untuk kecepatan.

Diuji di google chrome v35 pada 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);
rans
sumber
Demo ini sangat bagus! 3 x 3 sangat menarik karena pemenang berubah bolak-balik saat Anda meningkatkan kedalaman pencarian. Bisakah saya memeriksa, apakah minimax Anda pernah berhenti setengah jalan melalui belokan? Apa yang saya maksud adalah jika seseorang mendapatkan kotak, apakah itu selalu meluas sampai akhir giliran mereka?
2x2 adalah 3 titik oleh 3. Apakah Anda yakin kode Anda dapat menyelesaikannya tepat dalam 20ms?
"Jika seseorang mendapatkan kotak, apakah itu selalu meluas sampai akhir giliran mereka?" - Jika pemain mendapat kuadrat, ia masih bergerak ke belokan berikutnya tetapi belokan berikutnya adalah untuk pemain yang sama yaitu mereka mendapat giliran tambahan untuk menyelesaikan kuadrat. "2x2 adalah 3 titik dengan 3" - Aduh. Dalam hal ini, skor saya adalah 1x1.
rans