微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

TicTacToe - minimax-function 总是返回 -10 (Javascript)

如何解决TicTacToe - minimax-function 总是返回 -10 (Javascript)

我正在开发一个 tictactoe Person vs Person 游戏。作为奖励,我们可以尝试实现一个极小极大算法来对抗计算机。经过无数次试验和错误,我认为到目前为止我已经有了这个功能,它可以通过计算而没有错误。然而,最佳得分的返回值始终为 -10,假设获胜者是玩家而不是计算机。

我在代码中有两个问题:

  • minimax 函数总是返回值 -10,假设此人会获胜。
  • 我不能玩最后一回合。在调试器中,我可以看到跟踪可见字段的 gameData 数组正常工作,如下所示:[0,"X","O",7,"X"]。但是数组 player.personplayer.computer 已经存储了 0 和 7。这不应该发生。

此时我被卡住了。我会告诉你到目前为止我一直在尝试什么,但过去几个小时几乎只是在黑暗中摸索。因此,我至少可以写下代码直到最后的步骤:

重要变量等:

  • 计算机用“O”表示,人用“X”表示
  • 数组 gameData:以 [0,1,2,...,8] 开头并用“X”或“O”替换数字。它试图防止可以多次选择 tictactoe 字段。
  • 带有 player-Array 和 .person-Array 的对象 .computer 都以 empty 开头。我用这些来检查他们是否赢得了比赛。
  • isWinner(PLAYER) 遍历获胜条件并检查人或计算机是否匹配。如果是,则该函数返回对象 ({win:true})
  • isTie() 检查是否有平局。如果满足条件 ({tie:true])isWinner(player.computer) = falseisWinner(player.person) = false,它返回对象 emptySpaces(gameData).length = 0
  • 函数bestMove() 搜索计算机的bestMove。这是执行 minimax 的地方。

整体代码逻辑:

  • 代码从第 140 行开始,带有 function vsComputerGame()
  • 下一步是第 301 行中的 function personMove()
  • 玩家的选择“X”存储在 gameData 数组中。 EG gameData = [0,8]. And the position of "X" (in the exp 1`) 被推入 player.person。
  • 接下来它会检查此人是否赢得了比赛或比赛是否平局。
  • 接下来执行函数 bestMove()。它为计算机选择一个 tictactoe 字段。
  • 一个 player.personplayer.computer 得到更新。如果没有这一步,minimax 会不断推送这些数组中的数字。

代码逻辑bestMove()minimax()

  • bestMove() 从第 190 行开始
  • 保存初始 player.personplayer.computer 以在 minimax() 函数返回值后立即恢复它们。与上述相同的问题:否则,isWinner() 在玩家第二次点击时返回 true。
  • for 循环被执行。它选择 gameData 中的第一个可用位置并将其替换为“O”。 player.computer 通过计算机上的这一可能移动得到更新。
  • 后执行 minimax,它对于 player.computerplayer.person 基本上具有相同的代码,如本 for 循环中所述。
  • minimax() 返回一个值时,它被存储在变量 score 中。
  • 现在 gameData 数组、player.personplayer.computer 已重置,因此 for 循环的下一次迭代不会淹没它们。
  • 最后 if (score.eval > bestscore) 检查最高分。如果最高,则当前迭代的索引存储在 move 变量中,然后用于将“O”放在可见字段和 gameData 内部。

"use strict"

// this function stores the chosen players (condition: player1,player2,computer). The start-game is in another module
let menupage = (function() {
    let playerSelection = document.querySelectorAll(".player-selection");
    let modalContainer = document.querySelector(".modal-bg");
    let submitName = document.querySelector("#submit");
    let btnColorPlayerTwo = document.querySelector("#player-two");
    let btnColorComputer = document.querySelector("#computer");
    let btnColorplayerOne = document.querySelector("#player-one");
    let modalClose = document.querySelector(".modal-close");
    let inputField = document.querySelector("#name");
    let isplayerOne;

    let gameModeData = {
        playerOne : "",playerTwo : "",computer : false,}


    function closeModal() {
        inputField.value = "";
        modalContainer.classList.remove("bg-active");
    }
    function submitPlayer() {
        if (isplayerOne === true) {
            if (inputField.value === "") {
                alert("Please enter your battle-tag");
            } else if (inputField.value !== "") {
            btnColorplayerOne.style.backgroundColor = "#4CAF50";
            gameModeData.playerOne = inputField.value;
            inputField.value = "";
            modalContainer.classList.remove("bg-active");
            }
        } 
        if (isplayerOne === false) {
            if (inputField.value === "") {
                alert("Please enter your battle-tag");
            } else if (inputField.value !== "") {
                gameModeData.playerTwo = inputField.value;
                btnColorPlayerTwo.style.backgroundColor = "#f44336";
                gameModeData.computer = false;
                btnColorComputer.style.backgroundColor = "#e7e7e7";
                inputField.value = "";
                modalContainer.classList.remove("bg-active");
            }
        }
        
    }
    
    function definePlayer(id,color) {
        modalClose.addEventListener("click",closeModal);


        if (id === "player-one") {
            if (color.backgroundColor === "" || color.backgroundColor === "rgb(231,231,231)") {
                isplayerOne = true;
                modalContainer.classList.add("bg-active");
                submitName.addEventListener("click",submitPlayer);
            } else if (color.backgroundColor === "rgb(76,175,80)") {
                color.backgroundColor = "#e7e7e7";
                gameModeData.playerOne = "";
            }
        } 
        
        if (id === "player-two") {
            if (color.backgroundColor === "" || color.backgroundColor === "rgb(231,231)") {
                isplayerOne = false;
                modalContainer.classList.add("bg-active");
                submitName.addEventListener("click",submitPlayer);
            } 
        }
        
    }

    function defineOponent(target) {
        if (target.backgroundColor === "rgb(0,140,186)") {
            return;
        } else if (target.backgroundColor === "rgb(231,231)" || target.backgroundColor === "") {
            target.backgroundColor = "#008CBA";
            btnColorPlayerTwo.style.backgroundColor = "#e7e7e7";
            gameModeData.playerTwo = "";
            gameModeData.computer = true;
        }
    }

    let setupPlayers = function setupPlayers() {
        if (this.id === "player-one" || this.id === "player-two") {
            definePlayer(this.id,this.style);
        } else if (this.id === "computer") {
            defineOponent(this.style);
        }
    }

    playerSelection.forEach(button => button.addEventListener("click",setupPlayers))

    return gameModeData;

}())

let startRound = (function startRound() {
    let startGameBtn = document.querySelector("#start-game");
    let startScreen = document.querySelector(".start-screen");
    let gameboard = document.querySelector(".gameboard");
    let selectionMenu = document.querySelector(".window-container");
    let frame = document.querySelector(".frame");
    let scoreboardplayer = document.querySelector(".scoreboard-left");
    let scoreboardOponent = document.querySelector(".scoreboard-right");
    let scorePlayer = document.querySelector(".scoreboard-player");
    let scoreOponent = document.querySelector(".scoreboard-oponent");

    function displayscore() {
        scorePlayer.innerText = menupage.playerOne;

        menupage.computer === false ? scoreOponent.innerText = menupage.playerTwo : scoreOponent.innerText = "Computer";
    }

    function startGame() {
        if (menupage.playerOne === "") {
            alert("Please choose your profile.");
        } else if (menupage.playerTwo === "" && menupage.computer === false) {
            alert("Please choose an opponent.")
        } else {
            startScreen.style.display = "none";
            gameboard.style.display = "grid";
            scoreboardplayer.style.display = "grid";
            scoreboardOponent.style.display = "grid";
            frame.style.display = "none";         
            selectionMenu.style.gridTemplateAreas = '"header header header" "scoreboard-left gameboard scoreboard-right" "frame frame frame"';
            displayscore();
            game();
        }
    }

    startGameBtn.addEventListener("click",startGame);

}())
/* ***************************** GAME VS COmpuTER FUNCTION STARTS HERE ************************* */
let vsComputerGame = (function vsComputerGame() {

    let player = {
        person : [],computer : [],}

    let gameData = [0,3,4,5,6,8]

    let isWinner = function isWinner(PLAYER) {
        let check = PLAYER.join();

        let condition = {
            1 : ["0","1","2"],2 : ["3","4","5"],3 : ["6","7","8"],4 : ["0",5 : ["2","6"],6 : ["0","3",7 : ["1","7"],9 : ["2","5","8"]
        }
        for (const property in condition) {
            if (condition[property].every(v => check.includes(v)) === true) {
                return ({win : true});
            }
        }
        return ({win : false });
    };

    let isTie = function isTie() {
        if (emptySpaces(gameData).length === 0 && isWinner(player.computer).win === false && isWinner(player.person).win === false) {
            return ({tie: true});
        } else {
            return ({tie : false});
        }
    }

    function emptySpaces(gameData) {
        let updatedBoard = [];
        for (let i = 0; i < gameData.length; i++) {
            if (gameData[i] !== "X") {
                if (gameData[i] !== "O") {
                    updatedBoard.push(gameData[i]);
                }
            }
        }
        return updatedBoard;
    }

    function bestMove() {
        let bestscore = -Infinity;
        let move;
        
        // the object player with the values {player:[],computer:[]} is used in isWinner to check who won,// storedComputer and storedplayer is needed,to reset both arrays after they go through minimax,// without,the two object-arrays get fludded
        let storedComputer = player.computer.map(x => x);
        let storedplayer = player.person.map(x => x);

        // first round of the for loop sets a field for the computer,// first execution of minimax jumps to player.person
        for (let i = 0; i < 9; i++) {
            // gameData is the Array,that stores the players' moves. Example: [0,8]
            if (gameData[i] !== "X") {
                if (gameData[i] !== "O") {
                    
                    gameData[i] = "O";
                    player.computer.push(i);
                    
                    let score = minimax(gameData,player.person);

                    gameData[i] = i;
                    player.person = storedplayer;
                    player.computer = storedComputer;

                    if (score.eval > bestscore) {
                        bestscore = score.eval;
                        move = i;
                        console.log(bestscore);

                    }

                }
            }
        }
        // after a move is found for the computer,O gets logged in the gameData Array and on the visible gameboard
        let positionO = document.getElementsByName(move);
        gameData[move] = "O";
        positionO[0].innerText = "O";
    }


    function minimax(gameData,PLAYER) {
        // the BASE of minimax.
        // ************ console.log shows,that it always returns -10 ***************
        if (isWinner(player.person).win === true) { return ({eval:-10});}
        if (isWinner(player.computer).win === true) { return ({eval:10});}
        if (isTie().tie === true) {return ({eval:0});};

        /*
            PLAYER.push pushes the index-number into either player.computer or player.person
            This is needed to check the isWinner function
            After that,these Arrays get stored in storedComputer and storedplayer
        */
        if (PLAYER === player.computer) {
            let bestscore = -Infinity;
            for (let i = 0; i < 9; i++) {
                if (gameData[i] !== "X") {
                    if (gameData[i] !== "O") {
                        PLAYER.push(i);
                        //let storedComputer = player.computer.map(x => x);
                        //let storedplayer = player.person.map(x => x);
                        gameData[i] = "O";

                        let score = minimax(gameData,player.person);

                        //player.person,player.computer and gameData are resetted,after minimax returns a value
                        gameData[i] = i;
                        //player.person = storedplayer;
                        //player.computer = storedComputer;

                        if (score.eval > bestscore) {
                            bestscore = score.eval;

                        }
                    }
                }
            }
            return bestscore;
        } else {
            let bestscore = Infinity;
            for (let i = 0; i < 9; i++) {
                if (gameData[i] !== "X") {
                    if (gameData[i] !== "O") {
                        PLAYER.push(i);
                        //let storedComputer = player.computer.map(x => x);
                        //let storedplayer = player.person.map(x => x);
                        gameData[i] = "X";

                        let score = minimax(gameData,player.computer);

                        //player.person = storedplayer;
                        //player.computer = storedComputer;
                        gameData[i] = i;

                        if (score.eval < bestscore) {
                            bestscore = score.eval;

                        }
                    }
                }
            }
            return bestscore;
        }
    }


    let cells = document.querySelectorAll(".cell");
    cells.forEach(cell => cell.addEventListener("click",personMove));

    function personMove() {

        if (this.innerText === "X" || this.innerText === "O") {
            return;
        } else {
        let playersChoice = this.getAttribute("data-parent");
        player.person.push(Number(this.getAttribute("data-parent")));
        this.innerText = "X";
        gameData[playersChoice] = "X";

        if (isWinner(player.person).win === true) {
            console.log("Win");
        };
        isTie();
        bestMove();
       player.computer = [];
        player.person = [];
        for (let i = 0; i < 9; i++) {
            if (gameData[i] === "X") {
                player.person.push(i);
            } else if (gameData[i] === "O") {
                player.computer.push(i);
            }
        }
        
    }

    }

})

/* ***************************** GAME VS COmpuTER FUNCTION ENDS HERE ************************* */


let vsPersonGame = (function vsPersonGame() {
    let i = 1;
    let player = [];
    let oponent = [];
    let buttons = document.querySelectorAll(".cell");
    buttons.forEach(button => button.addEventListener("click",selection));

    function selection() {
        
        let newLocal = this
        storeSelection(newLocal);
        checkWinner();
    }
    function storeSelection(input) {

        if (i >= 10 || input.innerText !== "") {
            return;
        } else if (i % 2 === 0) {
            return input.innerText = "O",oponent.push(input.dataset.parent),i++;
        } else if (i % 2 !== 0) {
            return input.innerText = "X",player.push(input.dataset.parent),i++;
        }
    
    }

    function checkWinner() {
        let condition = {
            1 : ["0","8"]
        }
    
        for (const property in condition) {
            let toStringplayer = player.join();
            let toStringoponent = oponent.join();
            if (condition[property].every(v => toStringplayer.includes(v)) === true) {
                return alert(menupage.playerOne + " won");
            } else if (condition[property].every(v => toStringoponent.includes(v)) === true) {
                return alert(menupage.playerTwo + " won");
            } else if (i === 10) {
                if (condition[property].every(v => toStringplayer.includes(v)) === true) {
                    return alert(menupage.playerOne + " won");
                } else if (condition[property].every(v => toStringoponent.includes(v)) === true) {
                    return alert(menupage.playerTwo + " won");
                } else {
                return alert("You tied");
                }
            }
        }
    }
})

let game = (function() {
    if (menupage.computer === true) {
        vsComputerGame();
    } else {
        vsPersonGame();
        }
});
html,body {
    display: grid;
    height: 100%;
    margin: 0;
    padding: 0;
}
.window-container {
    display: grid;
    height: 100%;
    grid-template-rows: 10% 80% 10%;
    grid-template-areas: 
    "header header header"
    ". start-screen ."
    "frame frame frame";

}

.header {
    grid-area: header;
    margin-top: 50px;
    font-size: 30px;
    text-align: center;
    color: red;
    font-weight: bolder;
}

.start-screen {
    grid-area: start-screen;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-bottom: 30%;
}

.selection-menu {
    flex: 1;
    display: grid;
    grid-template-rows: 40% 20% 40%;
    grid-template-areas:
    ". . player-two"
    "player-one vs ."
    ". . computer"
}

#player-one {
    grid-area: player-one;
    background-color: #e7e7e7;
}

#player-two {
    grid-area: player-two;
    background-color: #e7e7e7;
}

#computer {
    grid-area: computer;
    background-color: #e7e7e7;
}

#start-game {
    display: flex;
    cursor: pointer;
    border: none;
    color: white;
    background-color: rgb(37,36,36);
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;

}


#vs {
    grid-area: vs;
    text-align: center;
    font-size: 30px;
    font-weight: bold;
    color: #4CAF50;
}

.player-selection {
    cursor: pointer;
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
}

.gameboard {
    grid-area: gameboard;
    margin-top: 10%;
    display: none;
    justify-content: center;
    grid-template-rows: 150px 150px 150px;
    grid-template-columns: 150px 150px 150px;
    grid-template-areas: 
    "tL tM tR"
    "mL mM mR"
    "bL bM bR";
}

.frame {
    grid-area: frame;
    display: flex;
    position: fixed;
    height: 250px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    margin-bottom: 0px;
    justify-content: center;
    align-items: center;

}

.cell {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 40px;
    cursor: pointer;
}

.unselectable {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

#tL {
    grid-area: tL;
    border-bottom: 2px solid black;
    border-right: 2px solid black;
}

#tM {
    grid-area: tM;
    border-bottom: 2px solid black;
    border-right: 2px solid black;  
}

#tR {
    grid-area: tR;  
    border-bottom: 2px solid black;
}

#mL {
    grid-area: mL; 
    border-bottom: 2px solid black;
    border-right: 2px solid black;
}

#mM {
    grid-area: mM; 
    border-bottom: 2px solid black;
    border-right: 2px solid black;
}

#mR {
    grid-area: mR; 
    border-bottom: 2px solid black;
}

#bL {
    grid-area: bL;
    border-right: 2px solid black;
}

#bM {
    grid-area: bM;
    border-right: 2px solid black;
}

#bR {
    grid-area: bR;
}

.modal-bg {
    position: fixed;
    width: 100%;
    height: 100vh;
    top: 0;
    left: 0;
    background-color: rgba(0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    visibility: hidden; 
    opacity: 0;
    transition: visibility 0s,opacity 0.5s;
}

.bg-active {
    visibility: visible;
    opacity: 1;
}

.modal {
    position: relative;
    background-color: white;
    border-radius: 5px 5px 5px 5px;
    width: 30%;
    height: 20%;
    display: flex;
    justify-content: space-around;
    align-items: center;
    flex-direction: column;
}

.modal button {
    padding: 10px 50px;
    background-color: #2980b9;
    color: white;
    border: none;
    cursor: pointer;
}

.modal-close {
    position: absolute;
    top: 10px;
    right: 10px;
    font-weight: bold;
    cursor: pointer;
}

#modal-headline {
    font-size: 20px;
}

#submit {
    margin-top: 5px;
}

.scoreboard-left {
    display: none;
}

.scoreboard-player {
    grid-area: scoreboard-player;
    display: flex;
}

.scoreboard-right {
    display: none;
}

.scoreboard-oponent {
    grid-area: scoreboard-oponent;
    display: flex;
}
<!DOCTYPE html>
<head>
    <Meta name="viewport" content="width=device-width,initial-scale=1.0">
    <Meta charset="UTF-8">
    <link href="styles.css" rel="stylesheet" type="text/css" />
    <script src="script.js" defer></script>
    <title>Tic Tac Toe</title>
</head>
<body>
    <div class="window-container">
    <div class="header">Tic Tac Toe</div>
    <div class="start-screen">
        <div class="selection-menu">
        <button class="player-selection" id="player-one">Player One</button>
        <button class="player-selection" id="player-two">Player Two</button>
        <div id="vs">vs</div>
        <button class="player-selection" id="computer">Computer</button>
    </div>
    </div>

    <div class="gameboard">
        <div class="cell unselectable" id="tL" data-parent="0" name="0"></div>
        <div class="cell unselectable" id="tM" data-parent="1" name="1"></div>
        <div class="cell unselectable" id="tR" data-parent="2" name="2"></div>
        <div class="cell unselectable" id="mL" data-parent="3" name="3"></div>
        <div class="cell unselectable" id="mM" data-parent="4" name="4"></div>
        <div class="cell unselectable" id="mR" data-parent="5" name="5"></div>
        <div class="cell unselectable" id="bL" data-parent="6" name="6"></div>
        <div class="cell unselectable" id="bM" data-parent="7" name="7"></div>
        <div class="cell unselectable" id="bR" data-parent="8" name="8"></div>
    </div>

    <div class="modal-bg">
        <div class="modal">
            <h2 id="modal-headline">Choose a Name:</h2>
            <input type="text" id="name">
            <button id="submit" class="submit">Submit</button>
            <span class="modal-close">X</span>
        </div>
    </div>
    <div class="frame">
        <button id="start-game">Start Game</button>
    </div>
    <div class="scoreboard-left">
            <div class="display-player">
            <div class="scoreboard-player"></div>
            <div class="p-win">Wins: </div><div class="player-win">0</div>
            <div class="p-loss">Losses: </div><div class="player-loss">0</div>
            <div class="p-tie">Ties: </div><div class="player-tie">0</div>
        </div>
    </div>
    <div class="scoreboard-right">
        <div class="display-oponent">
            <div class="scoreboard-oponent"></div>
            <div class="o-win">Wins: </div><div class="oponent-win">0</div>
            <div class="o-loss">Losses: </div><div class="oponent-loss">0</div>
            <div class="o-tie">Ties: </div><div class="oponent-tie">0</div>
        </div>
    </div>
</div>
</body>
</html>

感谢任何解决问题的反馈和提示

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。