Người chơi nhanh nhất cho Dots và Hộp


16

Thử thách là viết một bộ giải cho bút chì và trò chơi giấy cổ điển Dots and Box . Mã của bạn sẽ lấy hai số nguyên mnlàm đầu vào chỉ định kích thước của bảng.

Bắt đầu với một lưới các chấm trống, người chơi thay phiên nhau, thêm một đường ngang hoặc dọc duy nhất giữa hai dấu chấm liền kề không liền kề. Người chơi hoàn thành phần thứ tư của hộp 1 × 1 sẽ kiếm được một điểm và thực hiện một lượt khác. (Các điểm thường được ghi lại bằng cách đặt vào hộp một dấu hiệu nhận dạng của người chơi, chẳng hạn như chữ cái đầu tiên). Trò chơi kết thúc khi không thể đặt thêm dòng nào. Người chiến thắng trong trò chơi là người chơi có nhiều điểm nhất.

nhập mô tả hình ảnh ở đây

Bạn có thể giả sử rằng một n = mhoặc n = m - 1mít nhất là 2.

Thách thức là solvetrò chơi Dots and Box lớn nhất có thể trong chưa đầy một phút. Kích thước của một trò chơi chỉ đơn giản là n*m. Đầu ra của mã của bạn nên có win, drawhoặc losecó phải là kết quả cho cầu thủ đầu tiên giả định cả hai cầu thủ chơi một cách tối ưu.

Mã của bạn phải có thể biên dịch / chạy được trên Ubuntu bằng các công cụ miễn phí và có thể cài đặt dễ dàng. Vui lòng báo cáo điểm số của bạn là khu vực lớn nhất bạn có thể giải quyết trên máy tính của mình sau 1 phút cùng với thời gian. Sau đó tôi sẽ kiểm tra mã trên máy tính của mình và tạo một bảng xếp hạng các mục nhập.

Trong trường hợp hòa vốn, người chiến thắng sẽ là mã nhanh nhất trên bảng kích thước lớn nhất mà nó có thể giải quyết trong vòng một phút.


Sẽ tốt hơn nếu mã xuất ra không chỉ thắng hay thua mà cả điểm thực tế. Điều này làm cho một kiểm tra vệ sinh chính xác.


2
Chúng ta có phải sử dụng minimax không?
qwr

@qwr Bạn có thể cho tôi biết bạn có lựa chọn nào khác trong đầu không?

Đợi đã, có một người chiến thắng dự đoán trong trò chơi này chỉ dựa trên kích thước lưới?
Không phải Charles

@Charles Có nếu cả hai người chơi chơi tối ưu.

1
@PeterTaylor Tôi nghĩ bạn được hai điểm nhưng chỉ có thêm một lượt.

Câu trả lời:


15

Bảng C99 - 3x3 trong 0,084s

Chỉnh sửa: Tôi đã cấu trúc lại mã của mình và phân tích sâu hơn về kết quả.

Chỉnh sửa thêm : Thêm cắt tỉa bằng đối xứng. Điều này tạo ra 4 cấu hình thuật toán: có hoặc không có đối xứng X có hoặc không có cắt tỉa alpha-beta

Chỉnh sửa xa nhất: Đã thêm ghi nhớ bằng cách sử dụng bảng băm, cuối cùng đạt được điều không thể: giải quyết bảng 3x3!

Các tính năng chính:

  • thực hiện đơn giản minimax với cắt tỉa alpha-beta
  • quản lý bộ nhớ rất ít (duy trì dll các di chuyển hợp lệ; O (1) cập nhật cho mỗi nhánh trong tìm kiếm cây)
  • tập tin thứ hai với việc cắt tỉa bằng đối xứng. Vẫn đạt được cập nhật O (1) trên mỗi nhánh (về mặt kỹ thuật O (S) trong đó S là số lượng đối xứng. Đây là 7 cho bảng vuông và 3 cho bảng không vuông)
  • tập tin thứ ba và thứ tư thêm ghi nhớ. Bạn có quyền kiểm soát kích thước của hashtable ( #define HASHTABLE_BITWIDTH). Khi kích thước này lớn hơn hoặc bằng số lượng tường, nó đảm bảo không có va chạm và cập nhật O (1). Các hashtag nhỏ hơn sẽ có nhiều va chạm hơn và chậm hơn một chút.
  • biên dịch với -DDEBUGbản in

Cải tiến tiềm năng:

  • sửa lỗi rò rỉ bộ nhớ nhỏ trong lần chỉnh sửa đầu tiên
  • cắt tỉa alpha / beta được thêm vào trong lần chỉnh sửa thứ 2
  • đối xứng prune được thêm vào trong lần chỉnh sửa thứ 3 (lưu ý rằng các đối xứng không được xử lý bằng cách ghi nhớ, do đó vẫn là một tối ưu hóa riêng biệt.)
  • ghi nhớ được thêm vào trong lần chỉnh sửa thứ 4
  • hiện tại ghi nhớ sử dụng một bit chỉ thị cho mỗi bức tường. Một bảng 3x4 có 31 bức tường, vì vậy phương pháp này không thể xử lý các bảng 4 x 4 bất kể hạn chế về thời gian. cải tiến sẽ là mô phỏng các số nguyên X-bit, trong đó X ít nhất bằng số lượng tường.

Do thiếu tổ chức, số lượng tệp đã vượt quá tầm tay. Tất cả mã đã được chuyển đến Kho lưu trữ Github này . Trong phần chỉnh sửa ghi nhớ, tôi đã thêm một tập lệnh makefile và thử nghiệm.

Các kết quả

Nhật ký lô thời gian thực hiện

Ghi chú về độ phức tạp

Brute-force tiếp cận với các chấm và hộp nổ tung rất phức tạp .

Hãy xem xét một bảng với Rcác hàng và Ccột. Có R*Chình vuông, R*(C+1)tường dọc và C*(R+1)tường ngang. Đó là tổng cộng W = 2*R*C + R + C.

Bởi vì Lembik yêu cầu chúng tôi giải quyết trò chơi bằng minimax, chúng tôi cần đi qua những chiếc lá của cây trò chơi. Bây giờ chúng ta hãy bỏ qua việc cắt tỉa, bởi vì điều quan trọng là thứ tự cường độ.

Có những Wlựa chọn cho bước đầu tiên. Đối với mỗi người, người chơi tiếp theo có thể chơi bất kỳ W-1bức tường nào còn lại, v.v. Điều đó cho chúng ta không gian tìm kiếm SS = W * (W-1) * (W-2) * ... * 1, hoặc SS = W!. Yếu tố rất lớn, nhưng đó mới chỉ là khởi đầu. SSlà số nút lá trong không gian tìm kiếm. Liên quan nhiều hơn đến phân tích của chúng tôi là tổng số quyết định phải đưa ra (tức là số nhánh B trong cây). Lớp đầu tiên của các nhánh có Wcác tùy chọn. Đối với mỗi người, cấp độ tiếp theo có W-1, v.v.

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

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

Hãy xem xét một số kích thước bảng nhỏ:

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

Những con số này đang trở nên vô lý. Ít nhất họ giải thích lý do tại sao mã lực lượng vũ phu dường như bị treo mãi mãi trên bảng 2x3. Không gian tìm kiếm của bảng 2x3 lớn hơn 742560 lần so với 2x2 . Nếu 2x2 mất 20 giây để hoàn thành, phép ngoại suy bảo thủ dự đoán hơn 100 ngày thời gian thực hiện cho 2x3. Rõ ràng chúng ta cần cắt tỉa.

Phân tích cắt tỉa

Tôi bắt đầu bằng cách thêm việc cắt tỉa rất đơn giản bằng thuật toán alpha-beta. Về cơ bản, nó ngừng tìm kiếm nếu một đối thủ lý tưởng sẽ không bao giờ cho nó cơ hội hiện tại. "Này này - tôi sẽ thắng rất nhiều nếu đối thủ của tôi cho phép tôi có được mọi ô vuông!", Không bao giờ nghĩ đến AI.

chỉnh sửa Tôi cũng đã thêm cắt tỉa dựa trên bảng đối xứng. Tôi không sử dụng phương pháp ghi nhớ, chỉ trong trường hợp một ngày nào đó tôi thêm ghi nhớ và muốn tách riêng phân tích đó. Thay vào đó, nó hoạt động như thế này: hầu hết các dòng có "cặp đối xứng" ở một nơi khác trên lưới. Có tới 7 đối xứng (ngang, dọc, xoay 180, xoay 90, xoay 270, chéo và các đường chéo khác). Tất cả 7 áp dụng cho bảng vuông, nhưng 4 cuối cùng không áp dụng cho bảng không vuông. Mỗi bức tường có một con trỏ là "cặp" cho mỗi đối xứng này. Nếu, đi vào một ngã rẽ, bảng là đối xứng theo chiều ngang, thì chỉ cần chơi một trong mỗi cặp ngang .

chỉnh sửa chỉnh sửa Ghi nhớ! Mỗi bức tường có một id duy nhất, mà tôi thuận tiện đặt là một bit chỉ báo; bức tường thứ n có id 1 << n. Băm của một bảng, sau đó, chỉ là OR của tất cả các bức tường được chơi. Điều này được cập nhật tại mỗi chi nhánh trong thời gian O (1). Kích thước của hashtable được đặt trong a #define. Tất cả các thử nghiệm đã được chạy với kích thước 2 ^ 12, vì tại sao không? Khi có nhiều bức tường hơn các bit lập chỉ mục hashtable (12 bit trong trường hợp này), 12 ít quan trọng nhất được che dấu và được sử dụng làm chỉ mục. Va chạm được xử lý với một danh sách được liên kết tại mỗi chỉ số hashtable. Biểu đồ sau đây là phân tích nhanh và bẩn của tôi về cách kích thước có thể băm ảnh hưởng đến hiệu suất. Trên máy tính có RAM vô hạn, chúng tôi sẽ luôn đặt kích thước của bảng thành số lượng tường. Một bảng 3x4 sẽ có hashtable dài 2 ^ 31. Than ôi chúng ta không có sự xa xỉ đó.

Ảnh hưởng của kích thước Hashtable

Ok, quay lại cắt tỉa .. Bằng cách dừng tìm kiếm ở trên cây, chúng ta có thể tiết kiệm rất nhiều thời gian bằng cách không đi xuống lá. "Yếu tố cắt tỉa" là một phần của tất cả các nhánh có thể mà chúng tôi phải ghé thăm. Brute-force có hệ số cắt tỉa là 1. Càng nhỏ thì càng tốt.

Nhật ký lô ngành lấy

Nhật ký lô của các yếu tố cắt tỉa


Những năm 23 dường như chậm chạp một cách rõ rệt đối với một ngôn ngữ nhanh như C. Bạn có bị ép buộc không?
qwr

Lực lượng vũ phu với một lượng nhỏ cắt tỉa từ alpha beta. Tôi đồng ý rằng số 23 là đáng ngờ, nhưng tôi không thấy bất kỳ lý do nào trong mã của mình rằng nó sẽ không nhất quán .. Nói cách khác, đó là một bí ẩn
sai

1
đầu vào được định dạng theo quy định của câu hỏi. hai số nguyên được phân tách bằng dấu cách rows columnschỉ định kích thước của bảng
sai

1
@Lembik Tôi không nghĩ còn gì để làm. Tôi đã hoàn thành dự án điên rồ này!
sai

1
Tôi nghĩ rằng câu trả lời của bạn xứng đáng một vị trí đặc biệt. Tôi đã xem xét nó và 3 trên 3 là kích thước vấn đề lớn nhất từng được giải quyết trước đây và mã của bạn gần như ngay lập tức cho nó. Nếu bạn có thể giải quyết 3 x 4 hoặc 4 bằng 4, bạn có thể thêm kết quả vào trang wiki và nổi tiếng :)

4

Python - 2x2 sau 29 giây

Đăng chéo từ câu đố . Không được tối ưu hóa đặc biệt, nhưng có thể tạo điểm khởi đầu hữu ích cho những người tham gia khác.

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")

Có thể tăng tốc tối đa 18 giây bằng pypy.

2

Javascript - bảng 1x2 trong 20ms

Bản demo trực tuyến có sẵn tại đây (cảnh báo - rất chậm nếu lớn hơn 1x2 với độ sâu tìm kiếm đầy đủ ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html

Được phát triển cho các tiêu chí chiến thắng ban đầu (mã golf) và không phải cho tốc độ.

Đã thử nghiệm trong google chrome v35 trên 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);

Bản demo thực sự tuyệt vời! 3 x 3 thực sự thú vị khi người chiến thắng thay đổi qua lại khi bạn tăng độ sâu tìm kiếm. Tôi có thể kiểm tra xem, minimax của bạn có dừng được nửa đường không? Ý tôi là nếu ai đó có được một hình vuông, nó có luôn kéo dài đến cuối lượt của họ không?

2x2 là 3 chấm bằng 3. Bạn có chắc chắn mã của bạn có thể giải quyết chính xác trong 20ms không?

"nếu ai đó có được một hình vuông, nó có luôn kéo dài đến cuối lượt của họ không?" - Nếu người chơi nhận được một hình vuông, nó vẫn chuyển sang lượt tiếp theo nhưng lượt tiếp theo đó dành cho cùng một người chơi, tức là họ có thêm một lượt chơi để hoàn thành một hình vuông. "2x2 là 3 chấm 3" - Rất tiếc. Trong trường hợp đó, điểm của tôi là 1x1.
Ngày
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.