import numpy as np from typing import Tuple, List, Optional class Board: """ 翻转棋 (Reversi/Othello) 为深度学习神经网络提供多通道输入。 核心算法基于从落子点向8个方向扫描,寻找并翻转被“夹住”的对方棋子。 """ def __init__(self, h: int, w: int): """ 初始化棋盘。 参数: h, w: 棋盘的高度和宽度,建议为大于4的偶数。 """ if h < 4 or w < 4 or h % 2 != 0 or w % 2 != 0: raise ValueError("高度和宽度必须是大于等于4的偶数。") self.h = h self.w = w # 定义8个方向的偏移量 (dr, dc) self._DIRECTIONS = np.array([[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]], dtype=np.int8) # 存储棋盘状态的变量 self.board: np.ndarray = None # 主棋盘: 1=黑, -1=白, 0=空 self.player: int = None # 当前玩家: 1=黑, -1=白 # 为神经网络准备的通道 self.board_b: np.ndarray = None # 通道2: 黑棋位置 (0/1) self.board_w: np.ndarray = None # 通道3: 白棋位置 (0/1) self.board_move: np.ndarray = None # 通道4: 当前玩家的合法走法 (0/1) self.player_channel: np.ndarray = None # 通道5: 当前玩家指示 (全1或全-1) self.reset() def reset(self): """ 重置棋盘到初始状态,开始新游戏。 """ board = np.zeros((self.h, self.w), dtype=np.int8) # 初始中心棋子 mid_h, mid_w = self.h // 2, self.w // 2 board[mid_h - 1, mid_w - 1] = -1 # 白 board[mid_h - 1, mid_w] = 1 # 黑 board[mid_h, mid_w - 1] = 1 # 黑 board[mid_h, mid_w] = -1 # 白 # 黑棋先手 self.load(board, 1) def load(self, board: np.ndarray, player: int): """ 加载指定的棋盘状态和当前玩家。 参数: board: 一个 (h, w) 的 numpy 数组,1=黑, -1=白, 0=空。 player: 当前玩家, 1=黑, -1=白。 """ if board.shape != (self.h, self.w): raise ValueError("加载的棋盘尺寸与初始化尺寸不符。") self.board = board.copy().astype(np.int8) self.player = player self._update_channels() def _update_channels(self): """ 【核心解析器】 根据 self.board 和 self.player,更新所有输入通道。 这个函数是所有状态变更后必须调用的,以确保数据一致性。 """ # 通道2 & 3: 使用布尔索引高效生成黑棋和白棋位置通道 self.board_b = (self.board == 1).astype(np.float32) self.board_w = (self.board == -1).astype(np.float32) # 通道4: 生成合法移动位置通道 self.board_move = np.zeros_like(self.board, dtype=np.float32) # 遍历所有空位 empty_cells = np.argwhere(self.board == 0) for r, c in empty_cells: if len(self._get_flips_for_move(r, c)) > 0: self.board_move[r, c] = 1.0 # 通道5: 生成玩家指示通道 self.player_channel = np.full((self.h, self.w), float(self.player), dtype=np.float32) def _get_flips_for_move(self, r: int, c: int) -> List[Tuple[int, int]]: """ 【核心算法】 计算在 (r, c) 位置落子后,能够翻转的所有对方棋子的坐标列表。 这也是判断 (r, c) 是否为合法走法的基础。 返回: 一个包含所有可被翻转棋子坐标 `(row, col)` 的列表。如果列表为空,则该走法不合法。 """ opponent = -self.player pieces_to_flip = [] # 扫描8个方向 for dr, dc in self._DIRECTIONS: line_flips = [] curr_r, curr_c = r + dr, c + dc # 持续沿该方向探索 while 0 <= curr_r < self.h and 0 <= curr_c < self.w: if self.board[curr_r, curr_c] == opponent: line_flips.append((curr_r, curr_c)) elif self.board[curr_r, curr_c] == self.player: # 找到了己方棋子,形成"夹击",该方向上的翻转有效 pieces_to_flip.extend(line_flips) break else: # 遇到空位或边界,中断该方向的扫描 break curr_r, curr_c = curr_r + dr, curr_c + dc return pieces_to_flip def play(self, r: int, c: int): """ 在 (r, c) 位置执行走子操作。 参数: r, c: 落子位置的行和列。 """ if not (0 <= r < self.h and 0 <= c < self.w and self.board_move[r, c] == 1): raise ValueError(f"位置 ({r}, {c}) 不是一个合法的走法。") # 1. 获取要翻转的棋子 flips = self._get_flips_for_move(r, c) # 2. 在棋盘上执行落子和翻转 self.board[r, c] = self.player for fr, fc in flips: self.board[fr, fc] = self.player # 3. 交换玩家 self.player *= -1 # 4. 更新所有通道以反映新状态 self._update_channels() # 5. 如果新玩家无棋可走,则跳过其回合 if np.sum(self.board_move) == 0 and not self.is_game_over(): self.player *= -1 self._update_channels() def get_state(self) -> np.ndarray: """ 获取为神经网络准备的5通道输入状态。 返回: 一个 (5, h, w) 的 numpy 数组。 """ return np.stack([ self.board.astype(np.float32), # 通道1: 主棋盘 (1, -1, 0) self.board_b, self.board_w, self.board_move, self.player_channel ]) def is_game_over(self) -> bool: """检查游戏是否结束。""" # 如果棋盘已满 if np.all(self.board != 0): return True # 如果双方都无棋可走 if np.sum(self.board_move) == 0: # 临时切换到对手,检查对手是否也无棋可走 original_player = self.player self.player *= -1 opponent_has_move = False empty_cells = np.argwhere(self.board == 0) for r, c in empty_cells: if len(self._get_flips_for_move(r, c)) > 0: opponent_has_move = True break self.player = original_player # 恢复玩家 return not opponent_has_move return False def get_winner(self) -> Optional[int]: """ 获取赢家。 返回: 1 (黑棋赢), -1 (白棋赢), 0 (平局), None (游戏未结束)。 """ if not self.is_game_over(): return None score = np.sum(self.board) if score > 0: return 1 elif score < 0: return -1 else: return 0 def __str__(self): """方便打印和调试棋盘。""" symbols = {1: 'X', -1: 'O', 0: '.'} header = ' ' + ' '.join(f'{i:X}' for i in range(self.w)) + '\n' board_str = header for r in range(self.h): board_str += f'{r:X} ' + ' '.join(symbols[self.board[r, c]] for c in range(self.w)) + '\n' current_player = 'Black (X)' if self.player == 1 else 'White (O)' board_str += f"\nCurrent Player: {current_player}\n" winner = self.get_winner() if winner is not None: winner_str = {1: "Black (X) Wins", -1: "White (O) Wins", 0: "Draw"}[winner] board_str += f"Game Over! Winner: {winner_str}\n" return board_str