2025-10-28 17:22:49 +08:00
|
|
|
|
"""五子棋游戏逻辑模块"""
|
|
|
|
|
|
from typing import Optional, Tuple, List, Dict, Any
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_empty_board() -> List[List[int]]:
|
|
|
|
|
|
"""创建空棋盘
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
15x15的二维列表,0表示空位
|
|
|
|
|
|
"""
|
|
|
|
|
|
return [[0] * 15 for _ in range(15)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_coord(coord_str: str) -> Optional[Tuple[int, int]]:
|
|
|
|
|
|
"""解析坐标字符串
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
coord_str: 如 "A1", "O15", "h8"
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
(row, col) 或 None
|
|
|
|
|
|
"""
|
|
|
|
|
|
coord_str = coord_str.strip().upper()
|
|
|
|
|
|
|
|
|
|
|
|
if len(coord_str) < 2:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 解析列(A-O)
|
|
|
|
|
|
col_char = coord_str[0]
|
|
|
|
|
|
if not ('A' <= col_char <= 'O'):
|
|
|
|
|
|
return None
|
|
|
|
|
|
col = ord(col_char) - ord('A')
|
|
|
|
|
|
|
|
|
|
|
|
# 解析行(1-15)
|
|
|
|
|
|
try:
|
|
|
|
|
|
row = int(coord_str[1:]) - 1
|
|
|
|
|
|
if not (0 <= row <= 14):
|
|
|
|
|
|
return None
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
return (row, col)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_coord(row: int, col: int) -> str:
|
|
|
|
|
|
"""格式化坐标
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
row: 0-14
|
|
|
|
|
|
col: 0-14
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
如 "A1", "O15"
|
|
|
|
|
|
"""
|
|
|
|
|
|
col_char = chr(ord('A') + col)
|
|
|
|
|
|
row_num = row + 1
|
|
|
|
|
|
return f"{col_char}{row_num}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid_position(row: int, col: int) -> bool:
|
|
|
|
|
|
"""检查坐标是否在棋盘范围内
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
row: 行号
|
|
|
|
|
|
col: 列号
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
是否有效
|
|
|
|
|
|
"""
|
|
|
|
|
|
return 0 <= row <= 14 and 0 <= col <= 14
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def count_consecutive(board: List[List[int]], row: int, col: int,
|
|
|
|
|
|
direction: Tuple[int, int], player: int) -> int:
|
|
|
|
|
|
"""统计某方向连续同色棋子数(包括当前位置)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
board: 棋盘状态
|
|
|
|
|
|
row, col: 起始位置
|
|
|
|
|
|
direction: 方向向量 (dr, dc)
|
|
|
|
|
|
player: 玩家 (1:黑, 2:白)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
连续棋子数
|
|
|
|
|
|
"""
|
|
|
|
|
|
dr, dc = direction
|
|
|
|
|
|
count = 1 # 包括当前位置
|
|
|
|
|
|
|
|
|
|
|
|
# 正方向
|
|
|
|
|
|
r, c = row + dr, col + dc
|
|
|
|
|
|
while is_valid_position(r, c) and board[r][c] == player:
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
r += dr
|
|
|
|
|
|
c += dc
|
|
|
|
|
|
|
|
|
|
|
|
# 反方向
|
|
|
|
|
|
r, c = row - dr, col - dc
|
|
|
|
|
|
while is_valid_position(r, c) and board[r][c] == player:
|
|
|
|
|
|
count += 1
|
|
|
|
|
|
r -= dr
|
|
|
|
|
|
c -= dc
|
|
|
|
|
|
|
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_win(board: List[List[int]], row: int, col: int, player: int) -> bool:
|
|
|
|
|
|
"""检查是否获胜(恰好五连珠)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
board: 棋盘状态
|
|
|
|
|
|
row, col: 最后落子位置
|
|
|
|
|
|
player: 玩家 (1:黑, 2:白)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
是否五连珠获胜
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 四个方向:横、竖、左斜、右斜
|
|
|
|
|
|
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
|
|
|
|
|
|
|
|
|
|
|
|
for direction in directions:
|
|
|
|
|
|
count = count_consecutive(board, row, col, direction, player)
|
|
|
|
|
|
if count == 5:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_line(board: List[List[int]], row: int, col: int,
|
|
|
|
|
|
direction: Tuple[int, int], player: int) -> Dict[str, Any]:
|
|
|
|
|
|
"""分析某方向的棋型
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
board: 棋盘状态
|
|
|
|
|
|
row, col: 待分析位置(假设已落子)
|
|
|
|
|
|
direction: 方向向量
|
|
|
|
|
|
player: 玩家
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
{
|
|
|
|
|
|
"consecutive": int, # 连续数
|
|
|
|
|
|
"left_open": bool, # 左侧是否开放
|
|
|
|
|
|
"right_open": bool, # 右侧是否开放
|
|
|
|
|
|
"pattern": str # 棋型类型
|
|
|
|
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
|
|
dr, dc = direction
|
|
|
|
|
|
|
|
|
|
|
|
# 统计正方向连续数
|
|
|
|
|
|
right_count = 0
|
|
|
|
|
|
r, c = row + dr, col + dc
|
|
|
|
|
|
while is_valid_position(r, c) and board[r][c] == player:
|
|
|
|
|
|
right_count += 1
|
|
|
|
|
|
r += dr
|
|
|
|
|
|
c += dc
|
|
|
|
|
|
right_open = is_valid_position(r, c) and board[r][c] == 0
|
|
|
|
|
|
|
|
|
|
|
|
# 统计反方向连续数
|
|
|
|
|
|
left_count = 0
|
|
|
|
|
|
r, c = row - dr, col - dc
|
|
|
|
|
|
while is_valid_position(r, c) and board[r][c] == player:
|
|
|
|
|
|
left_count += 1
|
|
|
|
|
|
r -= dr
|
|
|
|
|
|
c -= dc
|
|
|
|
|
|
left_open = is_valid_position(r, c) and board[r][c] == 0
|
|
|
|
|
|
|
|
|
|
|
|
# 总连续数(包括当前位置)
|
|
|
|
|
|
consecutive = left_count + 1 + right_count
|
|
|
|
|
|
|
|
|
|
|
|
# 判定棋型
|
|
|
|
|
|
pattern = "none"
|
|
|
|
|
|
|
|
|
|
|
|
if consecutive >= 6:
|
|
|
|
|
|
pattern = "overline"
|
|
|
|
|
|
elif consecutive == 5:
|
|
|
|
|
|
pattern = "five"
|
|
|
|
|
|
elif consecutive == 4:
|
|
|
|
|
|
if left_open and right_open:
|
|
|
|
|
|
pattern = "live_four"
|
|
|
|
|
|
elif left_open or right_open:
|
|
|
|
|
|
pattern = "rush_four"
|
|
|
|
|
|
elif consecutive == 3:
|
|
|
|
|
|
if left_open and right_open:
|
|
|
|
|
|
pattern = "live_three"
|
|
|
|
|
|
elif left_open or right_open:
|
|
|
|
|
|
pattern = "sleep_three"
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"consecutive": consecutive,
|
|
|
|
|
|
"left_open": left_open,
|
|
|
|
|
|
"right_open": right_open,
|
|
|
|
|
|
"pattern": pattern
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_forbidden(board: List[List[int]], row: int, col: int) -> Tuple[bool, str]:
|
|
|
|
|
|
"""检查黑方禁手
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
board: 棋盘状态(不包含待落子)
|
|
|
|
|
|
row, col: 待落子位置
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
(是否禁手, 禁手类型)
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 只有黑方(玩家1)有禁手
|
|
|
|
|
|
player = 1
|
|
|
|
|
|
|
|
|
|
|
|
# 临时落子
|
|
|
|
|
|
original_value = board[row][col]
|
|
|
|
|
|
board[row][col] = player
|
|
|
|
|
|
|
|
|
|
|
|
# 四个方向
|
|
|
|
|
|
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
|
|
|
|
|
|
|
|
|
|
|
|
live_threes = 0
|
|
|
|
|
|
fours = 0
|
|
|
|
|
|
has_overline = False
|
|
|
|
|
|
|
|
|
|
|
|
for direction in directions:
|
|
|
|
|
|
analysis = analyze_line(board, row, col, direction, player)
|
|
|
|
|
|
|
|
|
|
|
|
if analysis["pattern"] == "overline":
|
|
|
|
|
|
has_overline = True
|
|
|
|
|
|
elif analysis["pattern"] == "live_three":
|
|
|
|
|
|
live_threes += 1
|
|
|
|
|
|
elif analysis["pattern"] in ["live_four", "rush_four"]:
|
|
|
|
|
|
fours += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 恢复棋盘
|
|
|
|
|
|
board[row][col] = original_value
|
|
|
|
|
|
|
|
|
|
|
|
# 判定禁手
|
|
|
|
|
|
if has_overline:
|
|
|
|
|
|
return True, "长连禁手"
|
|
|
|
|
|
if live_threes >= 2:
|
|
|
|
|
|
return True, "三三禁手"
|
|
|
|
|
|
if fours >= 2:
|
|
|
|
|
|
return True, "四四禁手"
|
|
|
|
|
|
|
|
|
|
|
|
return False, ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render_board(board: List[List[int]], last_move: Optional[Tuple[int, int]] = None) -> str:
|
|
|
|
|
|
"""渲染棋盘为字符串
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
board: 棋盘状态
|
|
|
|
|
|
last_move: 最后落子位置(可选,用于标记)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
棋盘的字符串表示
|
|
|
|
|
|
"""
|
|
|
|
|
|
lines = []
|
|
|
|
|
|
|
2025-10-28 17:57:02 +08:00
|
|
|
|
# 列标题 - 使用全角空格确保对齐
|
2025-10-29 09:29:42 +08:00
|
|
|
|
col_labels = "\t " + " ".join([chr(ord('A') + i) + "" for i in range(15)])
|
2025-10-28 17:57:02 +08:00
|
|
|
|
lines.append(col_labels.rstrip())
|
2025-10-28 17:22:49 +08:00
|
|
|
|
|
|
|
|
|
|
# 绘制棋盘
|
|
|
|
|
|
for row in range(15):
|
|
|
|
|
|
row_num = f"{row + 1:2d}" # 右对齐行号
|
|
|
|
|
|
row_cells = []
|
|
|
|
|
|
|
|
|
|
|
|
for col in range(15):
|
|
|
|
|
|
cell = board[row][col]
|
|
|
|
|
|
|
|
|
|
|
|
# 标记最后落子
|
|
|
|
|
|
if last_move and last_move == (row, col):
|
|
|
|
|
|
if cell == 1:
|
|
|
|
|
|
row_cells.append("⚫")
|
|
|
|
|
|
elif cell == 2:
|
|
|
|
|
|
row_cells.append("⚪")
|
|
|
|
|
|
else:
|
|
|
|
|
|
row_cells.append("➕")
|
|
|
|
|
|
else:
|
|
|
|
|
|
if cell == 0:
|
|
|
|
|
|
row_cells.append("➕")
|
|
|
|
|
|
elif cell == 1:
|
|
|
|
|
|
row_cells.append("⚫")
|
|
|
|
|
|
elif cell == 2:
|
|
|
|
|
|
row_cells.append("⚪")
|
|
|
|
|
|
|
2025-10-28 17:57:02 +08:00
|
|
|
|
# 每个emoji后面加一个空格
|
|
|
|
|
|
lines.append(f"{row_num} " + "".join([cell + " " for cell in row_cells]).rstrip())
|
2025-10-28 17:22:49 +08:00
|
|
|
|
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|