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;