JavaScript 进阶——井字棋游戏智能 AI 搭建
发布于: 2021 年 05 月 14 日

目录
井字棋游戏
准备的 HTML
准备的 CSS
第一部分点击出现 O 的 JS 代码:
现阶段效果:
第二部分:判断胜利
现阶段结果:
第三部分:简单的智能 AI
现阶段结果:
第四部分:之前我们很容易就能击败 AI,所以现在要强化 AI 的难度
完整 JavaScript 代码:
学习来源:JavaScript 井字棋游戏开发与 AI 算法
井字棋游戏
当初在高中的时候课间无聊经常和同学会下井字棋游戏,突然想做一下有一个智能陪玩的井字棋游戏。
然后就发现 AI 算法好难啊╭( ̄▽ ̄)╯╧═╧,放弃......
去网上找找,发现了一个讲解比较详细的井字棋游戏搭建\(@^0^@)/
做好之后,结果就没赢过了`(+﹏+)′
ps:算法真的是惨无人道啊
准备的 HTML
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title></title> <link rel="stylesheet" type="text/css" href="css/jingzi.css"/> </head> <body> <table> <tr> <td class="cell" id="0"></td> <td class="cell" id="1"></td> <td class="cell" id="2"></td> </tr> <tr> <td class="cell" id="3"></td> <td class="cell" id="4"></td> <td class="cell" id="5"></td> </tr> <tr> <td class="cell" id="6"></td> <td class="cell" id="7"></td> <td class="cell" id="8"></td> </tr> </table> <div class="endgame"> <div class="text"></div> </div> <button onclick="startGame()">重新开始</button> <script src="js/jingzi.js" type="text/javascript" charset="utf-8"></script> </body></html>复制代码
准备的 CSS
*{ margin: 0; padding: 0;} td { border: 2px solid #333; width: 100px; height: 100px; text-align: center; vertical-align: middle; //垂直 font-family: "微软雅黑"; font-style:italic; font-size: 70px; cursor: pointer; //光标属性} table { /*margin: 30px auto;*/ position: absolute; left: 40%; top: 100px; border-collapse: collapse;}/*去除最外部边框*/ table tr:first-child td{ border-top: 0;} table tr:last-child td{ border-bottom: 0;} table tr td:first-child{ border-left: 0;} table tr td:last-child{ border-right: 0;} .endgame{ display: none; width: 200px; height: 120px; background-color: rgba(205,132,65,0.8); position: absolute; left: 40%; top:180px; margin-left: 50px; text-align: center; border-radius: 5px; color: white; font-size: 2em;}复制代码
第一部分点击出现 O 的 JS 代码:
const winCombos =[ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [6, 4, 2]]/*获取元素*/const cells = document.querySelectorAll(".cell");startGame(); function startGame(){ document.querySelector(".endgame").style.display="none"; //设置阵列点 创建9个数组元素,元素的键0到8 origBoard = Array.from(Array(9).keys());// console.log(origBoard); for (var i = 0; i < cells.length; i++) { //把文本先设置为空 cells[i].innerHTML = ""; //删除属性知道已经有人赢了 cells[i].style.removeProperty('background-color'); //点击方块 cells[i].addEventListener('click',turnClick,false); }} function turnClick(square){ //控制台点击日志// console.log(square.target.id); //人类玩家点击 turn(square.target.id,huPlayer);}//参数是方块ID,播放器function turn(squareId,player){ //这些板阵列数组将属于玩家 origBoard[squareId] = player; document.getElementById(squareId).innerHTML = player; }复制代码
现阶段效果
第二部分:判断胜利
/*1. Basic setup 一些变量并添加能力2. Determine winner 添加逻辑,获胜者并展示3. Basic AI and winner notificatior 创建一个基本AI4. Minimax a lgori thm !*/ var origBoard;const huPlayer = 'O';const aiPlayer = 'X';/*胜利的线组,包括对角线*/const winCombos =[ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [6, 4, 2]]/*获取元素*/const cells = document.querySelectorAll(".cell");startGame(); function startGame(){ document.querySelector(".endgame").style.display="none"; //设置阵列点 创建9个数组元素,元素的键0到8 origBoard = Array.from(Array(9).keys());// console.log(origBoard); for (var i = 0; i < cells.length; i++) { //把文本先设置为空 cells[i].innerHTML = ""; //删除属性知道已经有人赢了 cells[i].style.removeProperty('background-color'); //点击方块 cells[i].addEventListener('click',turnClick,false); }} function turnClick(square){ //控制台点击日志// console.log(square.target.id); //人类玩家点击 turn(square.target.id,huPlayer);}//参数是方块ID,播放器function turn(squareId,player){ //这些板阵列数组将属于玩家 origBoard[squareId] = player; document.getElementById(squareId).innerHTML = player; //让游戏进行检查 var gameWin = checkWin(origBoard,player); if(gameWin){ gameOver(gameWin); }}/*检查是否胜利方法*/function checkWin(board,player){ //使用reduce累加器 let plays = board.reduce((a,e,i)=> (e===player) ? a.concat(i):a ,[]) let gameWin = null; //如果是属于之前winCombos胜利组合 for (let [index,win] of winCombos.entries()) { if (win.every(Element => plays.indexOf(Element) > -1)){ //现在我们知道是哪一个组合胜利了 gameWin = {index:index,player:player}; break; } } return gameWin;}/*游戏结束*/function gameOver(gameWin){ for(let index of winCombos[gameWin.index]){ //人类获胜则为蓝色 document.getElementById(index).style.backgroundColor = gameWin.player == huPlayer? "blue":"red"; } /*事件侦听器删除单击,已经结束了,你不能再点击*/ for (var i = 0; i < cells.length; i++) { cells[i].removeEventListener('click',turnClick,false); }}复制代码
现阶段结果:
第三部分:简单的智能 AI
/*1. Basic setup 一些变量并添加能力2. Determine winner 添加逻辑,获胜者并展示3. Basic AI and winner notificatior 创建一个基本AI4. Minimax a lgori thm !*/ var origBoard;const huPlayer = 'O';const aiPlayer = 'X';/*胜利的线组,包括对角线*/const winCombos =[ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [6, 4, 2]]/*获取元素*/const cells = document.querySelectorAll(".cell");startGame(); function startGame(){ document.querySelector(".endgame").style.display="none"; //设置阵列点 创建9个数组元素,元素的键0到8 origBoard = Array.from(Array(9).keys());// console.log(origBoard); for (var i = 0; i < cells.length; i++) { //把文本先设置为空 cells[i].innerHTML = ""; //删除属性知道已经有人赢了 cells[i].style.removeProperty('background-color'); //点击方块 cells[i].addEventListener('click',turnClick,false); }} function turnClick(square){ //控制台点击日志// console.log(square.target.id);//记住原来走过的方块 if(typeof origBoard[square.target.id] == 'number'){ //人类玩家点击 turn(square.target.id,huPlayer); //由人类转向AI玩家 if(!checkTie()){ //电脑玩家将拐弯,走最合适的地方 turn(bestStep(),aiPlayer); } } }//参数是方块ID,播放器function turn(squareId,player){ //这些板阵列数组将属于玩家 origBoard[squareId] = player; document.getElementById(squareId).innerHTML = player; //让游戏进行检查 var gameWin = checkWin(origBoard,player); if(gameWin){ gameOver(gameWin); }}/*检查是否胜利方法*/function checkWin(board,player){ //使用reduce累加器 let plays = board.reduce((a,e,i)=> (e===player) ? a.concat(i):a ,[]) let gameWin = null; //如果是属于之前winCombos胜利组合 for (let [index,win] of winCombos.entries()) { if (win.every(Element => plays.indexOf(Element) > -1)){ //现在我们知道是哪一个组合胜利了 gameWin = {index:index,player:player}; break; } } return gameWin;}/*游戏结束*/function gameOver(gameWin){ for(let index of winCombos[gameWin.index]){ //人类获胜则为蓝色 document.getElementById(index).style.backgroundColor = gameWin.player == huPlayer? "blue":"red"; } /*事件侦听器删除单击,已经结束了,你不能再点击*/ for (var i = 0; i < cells.length; i++) { cells[i].removeEventListener('click',turnClick,false); } declareWinner(gameWin.player == huPlayer ? "你已经获得了胜利":"对不起,你输了");} function emptySquares(){ //过滤每一个元素,如果元素为number,返回所有方块 return origBoard.filter(s => typeof s=='number');} /*AI最优步骤*/function bestStep(){ return emptySquares()[0];}//眼睛功能,检查是否是平局function checkTie(){ if(emptySquares().length == 0){ for (var i = 0; i < cells.length; i++) { cells[i].style.backgroundColor = "green"; cells[i].removeEventListener('click',turnClick,false); } //谁获胜了 declareWinner("Tie Game"); return true; }else{ //平局 return false; } } function declareWinner(who){ document.querySelector(".endgame").style.display = 'block'; document.querySelector(".endgame .text").innerHTML = who;}复制代码
现阶段结果:
第四部分:之前我们很容易就能击败 AI,所以现在要强化 AI 的难度这里需要比较强的算法知识
完整 JavaScript 代码:
/*1. Basic setup 一些变量并添加能力2. Determine winner 添加逻辑,获胜者并展示3. Basic AI and winner notificatior 创建一个基本AI4. Minimax a lgori thm !*/ var origBoard;const huPlayer = 'O';const aiPlayer = 'X';/*胜利的线组,包括对角线*/const winCombos =[ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [6, 4, 2]]/*获取元素*/const cells = document.querySelectorAll(".cell");startGame(); function startGame(){ document.querySelector(".endgame").style.display="none"; //设置阵列点 创建9个数组元素,元素的键0到8 origBoard = Array.from(Array(9).keys());// console.log(origBoard); for (var i = 0; i < cells.length; i++) { //把文本先设置为空 cells[i].innerHTML = ""; //删除属性知道已经有人赢了 cells[i].style.removeProperty('background-color'); //点击方块 cells[i].addEventListener('click',turnClick,false); }} function turnClick(square){ //控制台点击日志// console.log(square.target.id);//记住原来走过的方块 if(typeof origBoard[square.target.id] == 'number'){ //人类玩家点击 turn(square.target.id,huPlayer); //由人类转向AI玩家 if(!checkTie()){ //电脑玩家将拐弯,走最合适的地方 turn(bestStep(),aiPlayer); } } }//参数是方块ID,播放器function turn(squareId,player){ //这些板阵列数组将属于玩家 origBoard[squareId] = player; document.getElementById(squareId).innerHTML = player; //让游戏进行检查 var gameWin = checkWin(origBoard,player); if(gameWin){ gameOver(gameWin); }}/*检查是否胜利方法*/function checkWin(board,player){ //使用reduce累加器 let plays = board.reduce((a,e,i)=> (e===player) ? a.concat(i):a ,[]) let gameWin = null; //如果是属于之前winCombos胜利组合 for (let [index,win] of winCombos.entries()) { if (win.every(Element => plays.indexOf(Element) > -1)){ //现在我们知道是哪一个组合胜利了 gameWin = {index:index,player:player}; break; } } return gameWin;}/*游戏结束*/function gameOver(gameWin){ for(let index of winCombos[gameWin.index]){ //人类获胜则为蓝色 document.getElementById(index).style.backgroundColor = gameWin.player == huPlayer? "blue":"red"; } /*事件侦听器删除单击,已经结束了,你不能再点击*/ for (var i = 0; i < cells.length; i++) { cells[i].removeEventListener('click',turnClick,false); } declareWinner(gameWin.player == huPlayer ? "你已经获得了胜利":"对不起,你输了");} function emptySquares(){ //过滤每一个元素,如果元素为number,返回所有方块 return origBoard.filter(s => typeof s=='number');} /*AI最优步骤*/function bestStep(){ //简单AI// return emptySquares()[0]; //智能AI return minmax(origBoard,aiPlayer).index;}//眼睛功能,检查是否是平局function checkTie(){ if(emptySquares().length == 0){ for (var i = 0; i < cells.length; i++) { cells[i].style.backgroundColor = "green"; cells[i].removeEventListener('click',turnClick,false); } //谁获胜了 declareWinner("Tie Game"); return true; }else{ //平局 return false; } } function declareWinner(who){ document.querySelector(".endgame").style.display = 'block'; document.querySelector(".endgame .text").innerHTML = who;} function minmax(newBoard,player){ //找到索引,空方块功能设置为a var availSpots = emptySquares(newBoard); if(checkWin(newBoard,player)){ return {score:-10}; }else if(checkWin(newBoard,aiPlayer)){ return {score:20}; }else if(availSpots.length === 0){ return {score:0}; } //之后进行评估 var moves = []; //收集每个动作时的空白点 for (var i = 0; i < availSpots.length; i++) { //然后设置空的索引号 var move = {}; move.index = newBoard[availSpots[i]]; newBoard[availSpots[i]] = player; if( player == aiPlayer){ //存储对象,包括得分属性 var result = minmax(newBoard,huPlayer); move.score = result.score; }else{ //存储对象,包括得分属性 var result = minmax(newBoard,aiPlayer); move.score = result.score; } newBoard[availSpots[i]] = move.index; moves.push(move); } var bestMove; //如果是AI玩家,以非常低的数字和循环通过 if(player === aiPlayer){ var bestScore = -1000; for (var i = 0; i < moves.length; i++) { if(moves[i].score > bestScore){ bestScore = moves[i].score; bestMove = i; } } }else{ var bestScore = 1000; for (var i = 0; i < moves.length; i++) { if(moves[i].score < bestScore){ bestScore = moves[i].score; bestMove = i; } } } return moves[bestMove];}复制代码
结果:不多说了,还没赢过😂
代码链接:https://download.csdn.net/download/qq_36171287/12252143
划线
评论
复制
发布于: 2021 年 05 月 14 日阅读数: 33
版权声明: 本文为 InfoQ 作者【空城机】的原创文章。
原文链接:【http://xie.infoq.cn/article/3c39d545b8a6a505210fe1209】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
空城机
关注
曾经沧海难为水,只是当时已惘然 2021.03.22 加入
业余作者,在线水文 主要干前端的活,业余会学学python 欢迎各位关注,互相学习,互相进步











评论