Files
WPSBot/games/gomoku_logic.py
2025-10-29 09:29:42 +08:00

288 lines
7.2 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""五子棋游戏逻辑模块"""
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 = []
# 列标题 - 使用全角空格确保对齐
col_labels = "\t " + " ".join([chr(ord('A') + i) + "" for i in range(15)])
lines.append(col_labels.rstrip())
# 绘制棋盘
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("")
# 每个emoji后面加一个空格
lines.append(f"{row_num} " + "".join([cell + " " for cell in row_cells]).rstrip())
return "\n".join(lines)