如何解决TicTacToe - minimax-function 总是返回 -10 (Javascript)
我正在开发一个 tictactoe Person vs Person 游戏。作为奖励,我们可以尝试实现一个极小极大算法来对抗计算机。经过无数次试验和错误,我认为到目前为止我已经有了这个功能,它可以通过计算而没有错误。然而,最佳得分的返回值始终为 -10,假设获胜者是玩家而不是计算机。
我在代码中有两个问题:
- minimax 函数总是返回值 -10,假设此人会获胜。
- 我不能玩最后一回合。在调试器中,我可以看到跟踪可见字段的 gameData 数组正常工作,如下所示:
[0,"X","O",7,"X"]
。但是数组player.person
和player.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) = false
和isWinner(player.person) = false
,它返回对象emptySpaces(gameData).length = 0
。 - 函数
bestMove()
搜索计算机的bestMove。这是执行 minimax 的地方。
整体代码逻辑:
- 代码从第 140 行开始,带有
function vsComputerGame()
- 下一步是第 301 行中的
function personMove()
- 玩家的选择“X”存储在
gameData
数组中。 EGgameData = [0,8]. And the position of "X" (in the exp
1`) 被推入 player.person。 - 接下来它会检查此人是否赢得了比赛或比赛是否平局。
- 接下来执行函数
bestMove()
。它为计算机选择一个 tictactoe 字段。 - 下一个
player.person
和player.computer
得到更新。如果没有这一步,minimax 会不断推送这些数组中的数字。
代码逻辑bestMove()
和minimax()
:
-
bestMove()
从第 190 行开始 - 保存初始
player.person
和player.computer
以在minimax()
函数返回值后立即恢复它们。与上述相同的问题:否则,isWinner()
在玩家第二次点击时返回 true。 -
for
循环被执行。它选择gameData
中的第一个可用位置并将其替换为“O”。player.computer
通过计算机上的这一可能移动得到更新。 - 然后执行 minimax,它对于
player.computer
和player.person
基本上具有相同的代码,如本 for 循环中所述。 - 当
minimax()
返回一个值时,它被存储在变量score
中。 - 现在
gameData
数组、player.person
和player.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 举报,一经查实,本站将立刻删除。