在 Javascript 中使用极小极大算法解决 TicTacToe

2023-12-12

let _board = [[null, null, null], [null, null, null], [null, null, null]];
   let _flag = true;
   let _AIrowIndex = null;
   let _AIcellIndex = null;
   const _wrapper = document.querySelector(".wrapper");

   const _changeTurn = function () {
       if (_flag == true) {
           _flag = false;
           return playerOne.getSign();
       } else {
           _flag = true;
           return playerTwo.getSign();
       }
   };

   const _displayTurn = function () {
       let turn = document.querySelector(".playerInfo__turn")
       if (_flag == true) {
           turn.innerHTML = `${playerOne.getName()} is your turn`;
       } else {
           turn.innerHTML = `${playerTwo.getName()} is your turn`;
       }
   };

   
   const _evaluation = (winner) => {
           if(winner == "X"){
               return 1;
           }else if(winner == "O"){
               return -1;
           }
           else{
               return null;
           }
   };

   const _evaluationFunction = function (board) {
               /*CHECK 1 DIAG*/
           if (board[0][0] === board[1][1] && board[2][2] === board[0][0]) {
               return _evaluation(board[0][0]);
               /*CHECK 2 DIAG*/
           } 
           if (board[0][2] === board[1][1] && board[2][0] === board[0][2]) {
               return _evaluation(board[0][2]);
               /*CHECK PAIR*/
           } 
           for (let col = 0; col < 3; col++) {
               if (board[0][col] === board[1][col] && board[1][col] === board[2][col]) {
                   return _evaluation(board[0][col]);
               }
           }
           for (let row = 0; row < 3; row++) {
               if (board[row][0] === board[row][1] && board[row][1] === board[row][2]) {
                   return _evaluation(board[row][0]);
               }
           }
           return 0;        
   };

   const minimax = (_board, depth, isMaximizer) => {

       let result = _evaluationFunction(_board);
       console.log(result);
       if (result !== null) {
           return result;
       }

       if (isMaximizer) {
           let bestScore = -Infinity;

           for (let i = 0; i < 3; i++) {
               for (let j = 0; j < 3; j++) {
                   if (_board[i][j] == null) {
                       _board[i][j] = playerOne.getSign();
                       let score = minimax(_board, depth + 1, false);
                       _board[i][j] = null;
                       bestScore = Math.max(score, bestScore);
                   }
               }

           }
           return bestScore;

       } else {
           let bestScore = Infinity;

           for (let i = 0; i < 3; i++) {
               for (let j = 0; j < 3; j++) {
                   if (_board[i][j] == null) {
                       _board[i][j] = playerTwo.getSign();
                       let score = minimax(_board, depth + 1, true);
                       _board[i][j] = null;
                       bestScore = Math.min(score, bestScore);
                   }
               }
           }
           return bestScore;
       }
   };
   
   const _setAIPlay = () => {
       
       let bestScore = Infinity;
       let bestMove;

       for (let i = 0; i < 3; i++) {
           for (let j = 0; j < 3; j++) {
               if (_board[i][j] == null) {
                   _board[i][j] = playerTwo.getSign();
                   let score = minimax(_board, 0, true);       
                   _board[i][j] = null;
                   if(score < bestScore){
                       bestScore = score;
                       console.log(bestScore);
                       bestMove = {i, j}
                   }
               }
           }
       };

       _board[bestMove.i][bestMove.j] = playerTwo.getSign();
       _AIrowIndex = bestMove.i;
       _AIcellIndex = bestMove.j;
       _displayAIPlay(_AIrowIndex, _AIcellIndex);
       _changeTurn();
       _checkWinner();
   };

   


   const _displayAIPlay = (rowIndex, cellIndex) => {
       let AIcell = document.querySelector(`[data-row="${rowIndex}"][data-cell="${cellIndex}"]`);
       AIcell.textContent = playerTwo.getSign();
   }

我试图用极小极大算法解决这个井字游戏问题,但我不明白为什么它继续将“O”放在相邻的单元格中,我试图console.log()结果,以及 minimax 函数内的最佳分数,看起来递归正在工作,但我不明白为什么里面_setAIPlay()

if I console.log(bestScore)在最后if声明它返回我作为最终值或0 or 1并不是-1在这种情况下,我认为这应该是作为最小化器的最佳分数。

如果需要,您可以在这里找到完整的存储库gitHub


以下是 JavaScript 中 Tic Tac Toe 的极小极大算法的简单实现。搜索树不断加深,直到检测到游戏结束状态。如果检测到获胜,则当前玩家无法玩游戏,因此静态值为负数 (-10)。如果检测到平局,则返回值为 0。

在所有其他情况下,搜索都会加深。此实现使用极小极大算法,其中两个玩家的值都最大化。因此,从更深入的评估中返回的价值符号总是会翻转(“对我的对手有利的对我不利,反之亦然”)。

如果发现棋盘有获胜位置,则导致获胜的棋步shortest通往胜利的道路是优先考虑的。这是通过每次回溯将绝对值降低 1 个点来实现的。

如果有多个动作具有相同的值,那么将从这些动作中随机选择一个。

下面是带有一些基本 HTML 的实现:

class TicTacToe {
    constructor() {
        this.board = Array(9).fill(0); // 0 means "empty"
        this.moves = [];
        this.isWin = this.isDraw = false;
    }
    get turn() { // returns 1 or 2
        return 1 + this.moves.length % 2;
    }
    get validMoves() {
        return [...this.board.keys()].filter(i => !this.board[i])
    }
    play(move) { // move is an index in this.board
        if (this.board[move] !== 0 || this.isWin) return false; // invalid move
        this.board[move] = this.turn; // 1 or 2
        this.moves.push(move);
        // Use regular expression to detect any 3-in-a-row
        this.isWin = /^(?:...)*([12])\1\1|^.?.?([12])..\2..\2|^([12])...\3...\3|^..([12]).\4.\4/.test(this.board.join(""));
        this.isDraw = !this.isWin && this.moves.length === this.board.length;
        return true;
    }
    takeBack() {
        if (this.moves.length === 0) return false; // cannot undo
        this.board[this.moves.pop()] = 0;
        this.isWin = this.isDraw = false;
        return true;
    }
    minimax() {
        if (this.isWin) return { value: -10 };
        if (this.isDraw) return { value: 0 };
        let best = { value: -Infinity };
        for (let move of this.validMoves) {
            this.play(move);
            let {value} = this.minimax();
            this.takeBack();
            // Reduce magnitude of value (so shorter paths to wins are prioritised) and negate it
            value = value ? (Math.abs(value) - 1) * Math.sign(-value) : 0;
            if (value >= best.value) {
                if (value > best.value) best = { value, moves: [] };
                best.moves.push(move); // keep track of equally valued moves
            }
        }
        return best;
    }
    goodMove() {
        let {moves} = this.minimax();
        // Pick a random move when there are choices:
        return moves[Math.floor(Math.random() * moves.length)];
    }
}

(function main() {
    const table = document.querySelector("#game");
    const btnNewGame = document.querySelector("#newgame");
    const btnCpuMove = document.querySelector("#cpumove");
    const messageArea = document.querySelector("#message");
    let game, human;

    function display() {
        game.board.forEach((cell, i) => table.rows[Math.floor(i / 3)].cells[i % 3].className = " XO"[cell]);
        messageArea.textContent = game.isWin ? (game.turn == human ? "CPU won" : "You won")
                                : game.isDraw ? "It's a draw"
                                : game.turn == human ? "Your turn" 
                                : "CPU is preparing move...";
        table.className = game.isWin || game.isDraw || game.turn !== human ? "inactive" : "";
    }

    function computerMove() {
        if (game.isWin || game.isDraw) return; 
        human = 3 - game.turn;
        display();
        setTimeout(() => {
            game.play(game.goodMove());
            display();
        }, 500); // Artificial delay before computer move is calculated and played
    }

    function humanMove(i) {
        if (game.turn !== human || !game.play(i)) return; // ignore click when not human turn, or when invalid move
        display();
        computerMove();
    }

    function newGame() {
        game = new TicTacToe();
        human = 1;
        display();
    }

    table.addEventListener("click", e => humanMove(e.target.cellIndex + 3 * e.target.parentNode.rowIndex));
    btnNewGame.addEventListener("click", newGame);
    btnCpuMove.addEventListener("click", computerMove);
    newGame();
})();
#game { border-collapse: collapse }
#game td { border: 1px solid black; width: 30px; height: 30px; text-align: center; cursor: pointer } 
#game td.X, #game td.O { cursor: default }
#game td.X { color: green }
#game td.O { color: red }
#game td.X:after { content: "X" }
#game td.O:after { content: "O" }
#game.inactive { background: silver }
<table id="game">
    <tr><td></td><td></td><td></td></tr>
    <tr><td></td><td></td><td></td></tr>
    <tr><td></td><td></td><td></td></tr>
</table>
<h4 id="message"></h4>
<button id="newgame">New Game</button>
<button id="cpumove">Let CPU play a move</button>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Javascript 中使用极小极大算法解决 TicTacToe 的相关文章

  • 在承诺中运行同步函数

    我是 JS 和异步操作的新手 在使用express的nodeJS路由器中 我使用mongoose从mongo聚合了一些数据 该数据是每隔 15 分钟从不同站点收集的天气数据 我使用猫鼬聚合管道处理数据 以获取每小时数据并按每个站点进行分组
  • “过滤”JSON 以获得唯一键并获取所有相关值

    找到一个组中所有可能的相关值的最佳方法是什么 var table group a stuff new group a stuff old group b stuff newOld group b stuff old group c stuf
  • ExtJS 4 用于选择所选值的组合框事件

    由于某种原因 我需要知道用户何时从组合框中选择了值 即使它已经被选择 仅当用户选择未选择的项目时 选择 事件才起作用 我在组合框或选择器的文档中没有看到任何类似 itemclick 的事件 有任何想法吗 ComboBox uses 绑定列表
  • React useEffect hook 和 Async/await 自己的获取数据函数?

    我尝试创建一个从服务器获取数据的函数 并且它有效 但我不确定这是否正确 我创建了一个函数组件来获取数据 使用useState 使用效果 and 异步 等待 import React useState useEffect from react
  • 将字符串数组转换为对象 Id 数组

    我有一个字符串数组 let stringObjectIdArray fssdlfsd343 43434234242 342424242 我想使用 mongoose 类型将字符串数组更改为对象 Id 数组 但它不起作用 它仅适用于字符串而不是
  • 执行oauth时如何创建弹出窗口?

    我想通过使用弹出窗口来完成 Lifestream 和其他网站使用 oauth 身份验证所做的事情 他们打开一个弹出窗口 不知何故没有被弹出窗口拦截器阻止 并将他们的网站变灰 然后 在允许 oauth 访问时 它会说重定向回原始站点并终止弹出
  • 缩短文本并仅保留重要句子

    德国网站 nandoo net 提供了缩短新闻文章的可能性 如果使用滑块更改百分比值 文本会发生变化并且某些句子会被遗漏 您可以在这里看到它的实际效果 http www nandoo net read article 299925 http
  • 检测 SVGAnimatedString 的类名

    我在构建 SVG 地图时遇到问题 触发的功能 g 上的 onmouseover 不起作用 我当时用的 window onmouseover function e console log e target className 查看类名是否有问
  • 如何将这段 javascript 代码重写为 C++11?

    这是我在 Javascript Definitive Guide 中看到的 javascript 闭包代码 我想把它写成C 11 var uniqueID1 function var id 0 return function return
  • 如何查找给定字符串中仅出现一次的第一个字符[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 无法使用 jQuery 添加两个小数

    我试图将两个小数值相加 但返回的总和是纯整数 怎么了 我找不到它 欢迎任何帮助 jQuery delivery method ship select change function var cost jQuery this val jQue
  • 查找 JavaScript 中函数参数的数量[重复]

    这个问题在这里已经有答案了 可能的重复 获取函数的元数 https stackoverflow com questions 4848149 get a functions arity 假设我有 function a x function b
  • 如何使用 javascript 禁用组合键?

    I would like to disable view source shortcut key for IE using JavaScript To disable Ctrl C I am using the following func
  • AngularJS 中的嵌套模块

    我有 2 个不同的 AngularJs 模块 一个 widgetContainer 和一个 widget 小部件可以显示为独立的应用程序 也可以包含在小部件容器中 一个 widgetContainer 包含 0 N 个 widget 如果我
  • 原型链、构造函数、继承

    我正在玩 javascript 原型 我是新手 所以我有一个小问题 我正在用这个article http mckoss com jscript object htm作为指导 我已经定义了产品和书籍 目的是什么Book prototype c
  • 允许在 Safari 上聊天应用程序使用 audio.play()

    由于苹果禁用了自动播放音频的功能HTMLMedia Element play https developer mozilla org en US docs Web API HTMLMediaElement play在没有用户交互的 java
  • 更改哈希值而不触发 hashchange 事件

    我使用哈希来动态加载内容 为了使后退按钮正常工作 我正在捕获哈希更改 然而 有时我需要更改哈希值而不触发哈希更改函数 例如 当页面重定向到服务器端时 我需要在内容返回后更新哈希值 我想出的最佳解决方案是取消绑定 hashchange 事件
  • 编程 Pearls - 随机选择算法

    Programming Pearls 第一版第 120 页介绍了从 N 个整数总体中选择 M 个等概率随机元素的算法 InitToEmpty Size 0 While Size lt M do T RandInt 1 N if not Me
  • Nodejs 解码 base64 并使用流将它们保存到文件中

    在我的node js应用程序中 我使用以下代码行解码base64编码的图像 const fileDataDecoded Buffer from base64EncodedfileData base64 到目前为止 我可以使用以下代码编写一个
  • jQuery 倒计时插件 - 只显示非零周期

    我正在使用 jQuery 倒计时插件编写倒计时 我只希望它显示活动 非零 周期 例如代替 剩余时间 0 天 0 小时 13 分 20 秒 它应该只显示 13 分 20 秒 我的代码是 countdown countdown expiryUr

随机推荐