【问题标题】:Is it possible to brute force this 5*5 Tic-Tac-Toe game?是否可以暴力破解这款 5*5 井字游戏?
【发布时间】:2021-08-13 08:06:41
【问题描述】:

所以我编造了这个游戏,但我无法找到始终获胜的好策略。

它与original 3×3 Tic-Tac-Toe 非常相似;但有变化。

所以你画了一个 5×5 的棋盘。然后每个玩家轮流放一个十字架和一个圆圈。然后我们计算“分数”。

This is a completed game 我画的。得分是通过计算每 3 次连续罢工来计算的。因此,例如在第一行,跨玩家获得 1 分。

然后,您可以水平、垂直和对角两种方式进行计数;对于每一行、每一列和对角线。

对于 4-in-a-row,您将获得 2 分,因为您可以将其视为 2 个不同的 3-in-a-rows。类似地,一个 5-in-a-row 将获得 3 分。

在示例游戏中,cross 获得 9,而 circle 仅获得 7。

我经常玩这个;但总是很难说出下一步该往哪里走。我注意到,首先开始会给您带来显着的优势。为了弥补这一点,我将第一个开始的玩家的分数降低了一个。

是否有可能强制计算机学习每场比赛的最佳动作?

提前致谢!

旁注:如果有人可以将其编程为一个带有随机计算机动作的简单游戏,那就太好了。我刚刚开始编程,我很难弄清楚如何去做。

【问题讨论】:

    标签: tic-tac-toe 2d-games


    【解决方案1】:

    暴力破解

    下面的 sn-p 将暴力破解玩家 X 和玩家 O 的所有结束游戏

    有 33,542,145 种排列可供测试,其中约 5,000,000 种是有效的最终游戏。为了防止页面锁定,它将任务分成 250,000 个排列的组

    X 的最高分是 16,O 的最高分是 14。运行 sn-p 以查看示例结束游戏布局和其他详细信息。

    我没有包括任何障碍。

    这只能做最终游戏,因为它使用位字段来保持高性能。然而位域没有空棋盘位置的空间。

    可以通过反转每个游戏排列的位来优化同时玩两个玩家

    const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
    const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
    const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) :  el.textContent = data.toString();
       
          
    
    const scores = {
        [0b00111] : 1, 
        [0b01110] : 1, 
        [0b11100] : 1,
        [0b11101] : 1, 
        [0b10111] : 1,
        [0b01111] : 2,
        [0b11110] : 2,
        [0b11111] : 3,
    };
    const rotateMat = [
        0, 5, 10, 15, 20,
        1, 6, 11, 16, 21,
        2, 7, 12, 17, 22,
        3, 8, 13, 18, 23,
        4, 9, 14, 19, 24
    ];
    const diagonMat = [
        2,   8, 14, -1, -1,
        1,   7, 13, 19, -1, 
        0,   6, 12, 18, 24,
        5,  11, 17, 23, -1,
        10, 16, 22, -1, -1,
    ];
    const diagonMatUp = [
        10, 6,  2, -1, -1,
        15, 11, 7,  3, -1,
        20, 16, 12, 8,  4,
        21, 17, 13, 9, -1,
        22, 18, 14,-1, -1,
    ];
    
    function transform(board, matrix) {
        var i = 25, b = 0;
        while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
        return b;
    }
    function scoreLines(board) {
        var i = 5, score = 0, l = 0;
        while (i--) {  score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }        
        return score;
    }
    function score(board) {
        return scoreLines(board) + 
            scoreLines(transform(board, rotateMat)) +
            scoreLines(transform(board, diagonMat)) +
            scoreLines(transform(board, diagonMatUp));
    }
    function isValidGame(board, side) {
        var c = 0, i = 25, bits = side + 1;
        while (i-- && c < bits) { (board & (1 << i)) && c++ }
        return c === side;
    }
    function showBoard(board, score, side) {
        var i = 5;
        log(games, "------------------------");
        log(games, "End game score: " + score + " player: " + (side===13 ? "X" : "O"));
        log(games, "Example end game");
        while (i--) { 
            const line = ((board >> (i * 5)) & 0b11111).toString(2).padStart(5, "0");
            const lined = side === 13 ? 
                line.replace(/1/g,"X").replace(/0/g,"O") :
                line.replace(/1/g,"O").replace(/0/g,"X");
             log(games,  lined); 
        }    
    }
    function brute(side = 13) {
        function doSet(i) {
            var ii = 251357;
            while (i >= min && ii--) {
                if (isValidGame(i, side)) {
                    gameCount ++;
                    const s = score(i);
                    if (s >= maxScore) {
                        if (s > maxScore) { 
                            bestEndGames.length = 0 
                            game = i;
                        }
                        maxScore = s;
                        bestEndGames.push(i);
                    }
                }
                i--;
            }        
            if (i >= min) {
                setTimeout(doSet, 8, i);
                log(progress, status + " is: "  + maxScore + " tested: " + ((max - min) - (i - min)) + " of " + (max - min) + " permutations", false);
            } else {
                log(games, status + " is: " + maxScore + " of " + gameCount + " end games");
                log(games, "Number of end games with best score: " + bestEndGames.length);           
                showBoard(game, maxScore, side);
                if (side === 13) { setTimeout(brute, 1000, 12) }
                else { log(progress, "Done", false) } 
            }
        }
        var game, gameCount = 0;
        var maxScore = 0;
        const bestEndGames = [];
        const status = "Best score player: " + (side===13 ? "X" : "O");
        const [min, max] = side === 13 ? [0b1111111111111, 0b1111111111111000000000000] : [0b111111111111, 0b1111111111110000000000000];
        doSet(max)
    }
    brute(13);
    <div id="progress"></div>
    <div id="games"></div>

    为了得分,下一个 sn-p 将这样做。有点hack,在输入字段中输入游戏并会吐出分数。

    const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
    const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
    const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) :  el.textContent = data.toString();
       
          
    
    const scores = {
        [0b00111] : 1, 
        [0b01110] : 1, 
        [0b11100] : 1,
        [0b11101] : 1, 
        [0b10111] : 1,
        [0b01111] : 2,
        [0b11110] : 2,
        [0b11111] : 3,
    };
    const rotateMat = [
        0, 5, 10, 15, 20,
        1, 6, 11, 16, 21,
        2, 7, 12, 17, 22,
        3, 8, 13, 18, 23,
        4, 9, 14, 19, 24
    ];
    const diagonMat = [
        2,   8, 14, -1, -1,
        1,   7, 13, 19, -1, 
        0,   6, 12, 18, 24,
        5,  11, 17, 23, -1,
        10, 16, 22, -1, -1,
    ];
    const diagonMatUp = [
        10, 6,  2, -1, -1,
        15, 11, 7,  3, -1,
        20, 16, 12, 8,  4,
        21, 17, 13, 9, -1,
        22, 18, 14,-1, -1,
    ];
    
    function transform(board, matrix) {
        var i = 25, b = 0;
        while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
        return b;
    }
    function scoreLines(board) {
        var i = 5, score = 0, l = 0;
        while (i--) {  score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }        
        return score;
    }
    function score(board) {
        return scoreLines(board) + 
            scoreLines(transform(board, rotateMat)) +
            scoreLines(transform(board, diagonMat)) +
            scoreLines(transform(board, diagonMatUp));
    }
    function isValidGame(board, side, asStr) {
        var c = 0, i = 25, bits = side + 1;
        while (i-- && c < bits) { (
            (asStr[24-i] !== "-" &&  asStr[24-i] !== " ") && board & (1 << i)) && c++ 
        }
        return [c <= side, c];
    }
    function showBoard(board, asStr) {
        var i = 0;
        var j = 0;
        while (i < 5) { 
            const line = ((board >> ((4-i) * 5)) & 0b11111).toString(2).padStart(5, "0");
            const lined = line.replace(/1/g,"X").replace(/0/g,"O");
            var str = "";
            j = 0;
            while (j < 5) {
                str += (asStr[i * 5 + j] !== "-" && asStr[i * 5 + j] !== " ") ? lined[j] : ".";
                j++;
            }
            log(games,  str); 
            i++;
        }    
    }
    gameVal1.addEventListener("input", testGame);
    gameVal2.addEventListener("input", testGame);
    gameVal3.addEventListener("input", testGame);
    gameVal4.addEventListener("input", testGame);
    gameVal5.addEventListener("input", testGame);
    
    function testGame() {
        var board = gameVal1.value.slice(0,5).padEnd(5, "-");
        board += gameVal2.value.slice(0,5).padEnd(5, "-");
        board += gameVal3.value.slice(0,5).padEnd(5, "-");
        board += gameVal4.value.slice(0,5).padEnd(5, "-");
        board += gameVal5.value.slice(0,5).padEnd(5, "-");
        board = board.replace(/[^OX\- ]/gi,"-");
        const X = eval("0B" + board.replace(/X/gi,"1").replace(/O|-| /gi,"0"));
        const O = eval("0B" + board.replace(/X|-| /gi,"0").replace(/O/gi,"1"));
        const [vx, movesX] = isValidGame(X, 13, board);
        const [vo, movesY] = isValidGame(O, 12, board);
        vx && log(resultX, "Player X score: " + score(X) + " moves: " + movesX, false);
        vo && log(resultO, "Player O score: " + score(O) + " moves: " + movesY, false);
        games.innerHTML = "";
        showBoard(X, board);
    }
    testGame()
    input  {
       font-family: monospace;
       font-size: large;
    }
    .fixFont {
       font-family: monospace;
       font-size: large;
    }
    #games {
       position: absolute;
       top: 28px;
       left: 80px;
       border: 1px solid black;
       padding: 0px 3px;
       letter-spacing: 3px;
    }
    #resultX {
       position: absolute;
       top: 40px;
       left: 160px;
    }
    #resultO {
       position: absolute;
       top: 60px;
       left: 160px;
    }
    <div class="fixFont">
    Enter "x" "x" "O" or "o". Empty slots "-" or space<br>
    <input type="text" id="gameVal1" value="XXXXX" size="5"><br>
    <input type="text" id="gameVal2" value="XXXXO" size="5"><br>
    <input type="text" id="gameVal3" value="XXXXO" size="5"><br>
    <input type="text" id="gameVal4" value="OOOOO" size="5"><br>
    <input type="text" id="gameVal5" value="OOOOO" size="5"><br>
    <div id="resultX"></div>
    <div id="resultO"></div>
    <div id="games"></div>
    </div>

    【讨论】:

    • “这只能做最终游戏,因为它使用位域来保持高性能。” - 您可以使用两个位域,一个用于 X,一个用于 O,然后你可以代表所有的董事会。
    • @Vepir 这需要 50 位,而 JavaScript 仅支持 32 位运算符。第二个 sn-p 通过使用 twp 位字段和 on 位分别表示 X 和 O 来支持空单元格。然后您可以使用 !(plyX &amp; plyO &amp; (1 &lt;&lt; cellIdx)) 检查单元格是否为空。我使用位字段,因为它比使用数组或字符串来保存和查询游戏状态要快几十倍。
    • @Vepir 评论 sn-p 中的代码应该是 !(plyX |&amp; plyO &amp; (1 &lt;&lt; cellIdx))
    猜你喜欢
    • 2013-03-22
    • 1970-01-01
    • 2014-01-20
    • 1970-01-01
    • 1970-01-01
    • 2015-06-12
    • 1970-01-01
    • 2015-01-08
    相关资源
    最近更新 更多