File size: 12,829 Bytes
079c32c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
/*
这段代码定义了一个名为 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;
|