Spaces:
Running
Running
| /* | |
| 这段代码定义了一个名为 Board 的类,用于表示和管理围棋或象棋等棋盘游戏的状态。以下是其功能和结构概述: | |
| 初始化棋盘大小、当前玩家角色以及各种缓存。 | |
| 提供检测游戏是否结束的方法。 | |
| 提供获取当前获胜者的方法。 | |
| 提供获取合法走法的方法。 | |
| 提供执行走子及撤销走子的方法。 | |
| 提供坐标与位置之间转换的方法。 | |
| 提供获取有价值走法的方法。 | |
| 提供显示棋盘的方法。 | |
| 提供棋盘状态的哈希方法。 | |
| 提供评估棋盘状态的方法。 | |
| 提供复制当前棋盘并反转角色的方法。 | |
| 提供将棋盘状态转换为字符串的方法。 | |
| */ | |
| // 导入相关模块 | |
| import Zobrist from './zobrist'; | |
| import Cache from './cache'; | |
| // import { evaluate } from './evaluate'; // 这一行已经被注释掉,因为下面使用了新的评估方法 | |
| import Evaluate, { FIVE } from './eval'; | |
| // 定义棋盘类 | |
| class Board { | |
| constructor(size = 15, firstRole = 1) { | |
| this.size = size; // 棋盘大小,默认为15x15 | |
| this.board = Array(this.size).fill().map(() => Array(this.size).fill(0)); // 初始化棋盘,所有位置为空(用0表示) | |
| this.firstRole = firstRole; // 第一个玩家的角色,默认为1(通常代表黑棋) | |
| this.role = firstRole; // 当前玩家的角色 | |
| this.history = []; // 历史记录,用于记录每次走子的位置和角色 | |
| this.zobrist = new Zobrist(this.size); // 初始化Zobrist哈希 | |
| this.winnerCache = new Cache(); // 获胜者缓存 | |
| this.gameoverCache = new Cache(); // 游戏结束缓存 | |
| this.evaluateCache = new Cache(); // 评估分数缓存 | |
| this.valuableMovesCache = new Cache(); // 有价值走法缓存 | |
| this.evaluateTime = 0; // 评估时间 | |
| this.evaluator = new Evaluate(this.size); // 初始化评估器 | |
| } | |
| // 检查游戏是否结束 | |
| isGameOver() { | |
| const hash = this.hash(); // 获取当前棋盘的哈希值 | |
| // 如果游戏结束缓存中有记录,直接返回结果 | |
| if (this.gameoverCache.get(hash)) { | |
| return this.gameoverCache.get(hash); | |
| } | |
| // 如果已经有获胜者,游戏结束 | |
| if (this.getWinner() !== 0) { | |
| this.gameoverCache.put(hash, true); // 缓存结果 | |
| return true; | |
| } | |
| // 如果棋盘上没有空位,则游戏结束,否则游戏继续 | |
| for (let i = 0; i < this.size; i++) { | |
| for (let j = 0; j < this.size; j++) { | |
| if (this.board[i][j] === 0) { | |
| this.gameoverCache.put(hash, false); | |
| return false; | |
| } | |
| } | |
| } | |
| this.gameoverCache.put(hash, true); | |
| return true; | |
| } | |
| // 定义getWinner函数,用于判断当前棋盘上是否有获胜者 | |
| getWinner() { | |
| // 计算当前棋盘状态的哈希值 | |
| const hash = this.hash(); | |
| // 如果在缓存中已经存在当前哈希值对应的获胜者信息,则直接返回该信息 | |
| if (this.winnerCache.get(hash)) { | |
| return this.winnerCache.get(hash); | |
| } | |
| // 定义四个检查方向:水平、垂直、正对角线、反对角线 | |
| let directions = [[1, 0], [0, 1], [1, 1], [1, -1]]; | |
| // 遍历棋盘上的所有格子 | |
| for (let i = 0; i < this.size; i++) { | |
| for (let j = 0; j < this.size; j++) { | |
| // 如果当前格子为空,则跳过 | |
| if (this.board[i][j] === 0) continue; | |
| // 遍历四个方向 | |
| for (let direction of directions) { | |
| let count = 0; | |
| // 在当前方向上连续检查相同棋子的数量 | |
| while ( | |
| i + direction[0] * count >= 0 && | |
| i + direction[0] * count < this.size && | |
| j + direction[1] * count >= 0 && | |
| j + direction[1] * count < this.size && | |
| this.board[i + direction[0] * count][j + direction[1] * count] === this.board[i][j] | |
| ) { | |
| count++; | |
| } | |
| // 如果连续相同的棋子数量达到5个或以上,则该玩家获胜 | |
| if (count >= 5) { | |
| // 将获胜者信息存入缓存 | |
| this.winnerCache.put(hash, this.board[i][j]); | |
| // 返回获胜者信息 | |
| return this.board[i][j]; | |
| } | |
| } | |
| } | |
| } | |
| // 如果没有获胜者,则在缓存中记录当前哈希值对应的获胜者信息为0(无获胜者) | |
| this.winnerCache.put(hash, 0); | |
| // 返回0表示当前没有获胜者 | |
| return 0; | |
| } | |
| // 定义getValidMoves函数,用于获取当前棋盘上所有合法的落子位置 | |
| getValidMoves() { | |
| let moves = []; | |
| // 遍历棋盘的每一个格子 | |
| for (let i = 0; i < this.size; i++) { | |
| for (let j = 0; j < this.size; j++) { | |
| // 如果当前格子为空,则可以落子 | |
| if (this.board[i][j] === 0) { | |
| // 将该位置加入到合法落子位置列表中 | |
| moves.push([i, j]); | |
| } | |
| } | |
| } | |
| // 返回所有合法的落子位置列表 | |
| return moves; | |
| } | |
| // 定义put函数,用于在棋盘上放置一个棋子 | |
| put(i, j, role) { | |
| // 如果没有指定角色,则使用当前角色 | |
| if (role === undefined) { | |
| role = this.role; | |
| } | |
| // 如果输入的坐标不是数字,则打印错误信息并返回false | |
| if (isNaN(i) || isNaN(j)) { | |
| console.log("Invalid move:input position is not numbers!", i, j); | |
| return false; | |
| } | |
| // 如果当前坐标已经有棋子,则打印错误信息并返回false | |
| if (this.board[i][j] !== 0) { | |
| console.log("Invalid move: current position is not empty!", i, j); | |
| return false; | |
| } | |
| // 在指定位置放置棋子 | |
| this.board[i][j] = role; | |
| // 将此次移动记录到历史记录中 | |
| this.history.push({ i, j, role }); | |
| // 使用Zobrist散列更新当前棋盘的哈希值 | |
| this.zobrist.togglePiece(i, j, role); | |
| // 更新评估器中的棋盘状态 | |
| this.evaluator.move(i, j, role); | |
| // 切换角色,如果当前是1,切换为-1;如果当前是-1,切换为1 | |
| this.role *= -1; // Switch role | |
| return true; | |
| } | |
| // 实现撤销操作的函数 | |
| undo() { | |
| // 如果历史记录为空,说明没有可撤销的步骤 | |
| if (this.history.length === 0) { | |
| console.log("No moves to undo!"); // 打印提示信息 | |
| return false; // 返回false,表示撤销失败 | |
| } | |
| // 从历史记录中取出最后一步棋的信息 | |
| let lastMove = this.history.pop(); | |
| // 将棋盘上对应的位置重置为0(假设0代表该位置没有棋子) | |
| this.board[lastMove.i][lastMove.j] = 0; | |
| // 将当前的玩家角色恢复到前一步的玩家 | |
| this.role = lastMove.role; | |
| // 切换Zobrist哈希中的棋子,用于快速哈希棋盘状态 | |
| this.zobrist.togglePiece(lastMove.i, lastMove.j, lastMove.role); | |
| // 调用评估器的undo函数,撤销上一步的评估效果 | |
| this.evaluator.undo(lastMove.i, lastMove.j); | |
| // 返回true,表示撤销成功 | |
| return true; | |
| } | |
| // 将一维位置索引转换为二维坐标 | |
| position2coordinate(position) { | |
| // 计算行索引 | |
| const row = Math.floor(position / this.size) | |
| // 计算列索引 | |
| const col = position % this.size | |
| // 返回二维坐标数组 | |
| return [row, col] | |
| } | |
| // 将二维坐标转换为一维位置索引 | |
| coordinate2position(coordinate) { | |
| // 根据行、列索引和棋盘大小计算一维位置索引 | |
| return coordinate[0] * this.size + coordinate[1] | |
| } | |
| // 获取价值较高的可落子点 | |
| getValuableMoves(role, depth = 0, onlyThree = false, onlyFour = false) { | |
| // 获取当前棋盘的哈希值 | |
| const hash = this.hash(); | |
| // 尝试从缓存中获取此哈希值对应的价值较高的落子点 | |
| const prev = this.valuableMovesCache.get(hash); | |
| if (prev) { | |
| // 如果缓存中存在,并且各项参数都相同,则直接返回缓存中的落子点 | |
| if (prev.role === role && prev.depth === depth && prev.onlyThree === onlyThree && prev.onlyFour === onlyFour) { | |
| return prev.moves; | |
| } | |
| } | |
| // 否则,调用评估器获取价值较高的落子点 | |
| const moves = this.evaluator.getMoves(role, depth, onlyThree, onlyFour); | |
| // 如果不是仅考虑三连或四连的情况,则默认在中心点落子(如果中心点为空) | |
| if (!onlyThree && !onlyFour) { | |
| const center = Math.floor(this.size / 2); | |
| if (this.board[center][center] == 0) moves.push([center, center]); | |
| } | |
| // 将计算出的落子点存入缓存 | |
| this.valuableMovesCache.put(hash, { | |
| role, | |
| moves, | |
| depth, | |
| onlyThree, | |
| onlyFour | |
| }); | |
| // 返回价值较高的落子点数组 | |
| return moves; | |
| } | |
| // 用于显示棋盘的函数,可以传入额外的位置列表以显示问号,辅助调试 | |
| display(extraPoints = []) { | |
| // 将额外的点转换为一维位置索引 | |
| const extraPosition = extraPoints.map((point) => this.coordinate2position(point)); | |
| let result = ''; // 初始化结果字符串 | |
| for (let i = 0; i < this.size; i++) { | |
| for (let j = 0; j < this.size; j++) { | |
| // 获取当前遍历的点的一维位置索引 | |
| const position = this.coordinate2position([i, j]); | |
| // 如果当前点在额外的位置列表中,将其显示为问号 | |
| if (extraPosition.includes(position)) { | |
| result += '? '; | |
| continue; | |
| } | |
| // 根据棋盘上的值,显示不同的字符 | |
| switch (this.board[i][j]) { | |
| case 1: | |
| result += 'O '; // 玩家1的棋子用'O'表示 | |
| break; | |
| case -1: | |
| result += 'X '; // 玩家-1的棋子用'X'表示 | |
| break; | |
| default: | |
| result += '- '; // 空位用'-'表示 | |
| break; | |
| } | |
| } | |
| result += '\n'; // 每行结束后添加换行符 | |
| } | |
| // 返回棋盘的字符串表示 | |
| return result; | |
| } | |
| // 生成当前棋盘状态的哈希值的函数 | |
| hash() { | |
| // 调用zobrist实例的getHash方法来获取当前棋盘的哈希值 | |
| return this.zobrist.getHash(); | |
| } | |
| // 注释掉的旧的评估函数,可能是用于调试或替换的函数 | |
| //evaluate(role) { | |
| // const start = + new Date(); | |
| // const hash = this.hash(); | |
| // const prev = this.evaluateCache.get(hash); | |
| // if (prev) { | |
| // if (prev.role === role) { | |
| // return prev.value; | |
| // } | |
| // } | |
| // const value = evaluate(this.board, role); | |
| // this.evaluateTime += +new Date - start; | |
| // this.evaluateCache.put(hash, { role, value }); | |
| // return value; | |
| //} | |
| // 新的评估函数,用于评估当前棋盘对指定玩家的得分 | |
| evaluate(role) { | |
| // 获取当前棋盘的哈希值 | |
| const hash = this.hash(); | |
| // 从评估缓存中获取之前的评估结果 | |
| const prev = this.evaluateCache.get(hash); | |
| if (prev) { | |
| // 如果缓存中有对应角色的评估结果,直接返回该结果 | |
| if (prev.role === role) { | |
| return prev.score; | |
| } | |
| } | |
| // 获取当前棋盘的胜者 | |
| const winner = this.getWinner(); | |
| let score = 0; | |
| // 如果已经有胜者,根据胜者和当前角色计算分数 | |
| if (winner !== 0) { | |
| score = FIVE * winner * role; | |
| } else { | |
| // 否则通过评估器计算当前角色的得分 | |
| score = this.evaluator.evaluate(role); | |
| } | |
| // 将评估结果存入缓存 | |
| this.evaluateCache.put(hash, { role, score }); | |
| // 返回评估得分 | |
| return score; | |
| } | |
| // 反转棋盘的函数,反转棋盘上所有棋子的角色 | |
| reverse() { | |
| // 创建新的Board实例,大小与当前棋盘相同,但是首个落子角色相反 | |
| const newBoard = new Board(this.size, -this.firstRole); | |
| // 遍历历史记录中的所有落子 | |
| for (let i = 0; i < this.history.length; i++) { | |
| // 获取落子的坐标和角色 | |
| const { i: x, j: y, role } = this.history[i]; | |
| // 在新棋盘上落子,但是角色取反 | |
| newBoard.put(x, y, -role); | |
| } | |
| // 返回反转后的棋盘 | |
| return newBoard; | |
| } | |
| // 将棋盘转换为字符串形式的函数 | |
| toString() { | |
| // 遍历棋盘的每一行,将每个位置的值转换为字符串,并将每行连接起来 | |
| return this.board.map(row => row.join('')).join(''); | |
| } | |
| } | |
| // 导出Board类 | |
| export default Board; | |