五子棋AI对战
创建于:2025年2月2日
使用 O3 Mini High 回答 Chat01
创建于:2025年2月2日
使用 O3 Mini High 回答 Chat01
用Python写一个和AI对战的五子棋
下面提供一个简单的 Python 实现,它在终端中运行一个五子棋(又称“连珠”)游戏,玩家(用“X”表示)与 AI(用“O”表示)轮流落子。AI 使用了一个基于局部启发式评分的贪心算法,先检查是否能立即获胜或必须堵住对方的必胜着,再计算每个候选点的得分选择最佳落子位置。
你可以将下面的代码保存为例如 gomoku.py
,然后在终端中运行:
python# -*- coding: utf-8 -*- """ 简单的五子棋(连珠)游戏 玩家(X)与 AI(O)对战 """ import sys # 定义棋盘大小和棋子标识 BOARD_SIZE = 15 PLAYER = 1 # 玩家 AI = 2 # AI def print_board(board): """打印棋盘,行号和列号均从1开始显示""" # 打印列号 print(" ", end="") for x in range(BOARD_SIZE): print(f"{x+1:2}", end=" ") print() for y in range(BOARD_SIZE): print(f"{y+1:2} ", end="") for x in range(BOARD_SIZE): if board[y][x] == 0: print(" .", end=" ") elif board[y][x] == PLAYER: print(" X", end=" ") elif board[y][x] == AI: print(" O", end=" ") print() def check_win(board, x, y, player): """ 检查在 (x, y) 落下 player 的棋子后, 是否形成了连续 5 个棋子(含该点)。 board[y][x] 用来表示棋盘,坐标均从0开始。 """ directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 正方向 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 # 反方向 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 if count >= 5: return True return False def get_line_score(count, open_ends): """ 根据某一方向上连续棋子数 count 以及两端的空口数 open_ends 返回一个局部评分。 """ if count >= 5: return 100000 if count == 4: if open_ends == 2: return 10000 elif open_ends == 1: return 1000 if count == 3: if open_ends == 2: return 1000 elif open_ends == 1: return 100 if count == 2: if open_ends == 2: return 100 elif open_ends == 1: return 10 if count == 1: return 10 return 0 def evaluate_position(board, x, y, player): """ 评估在 (x, y) 处落下 player 棋子后的潜力得分, 考察水平方向、垂直方向和两条对角线上的连续棋子情况。 此函数要求在调用前,已经将 board[y][x] 设为 player 。 """ score = 0 directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 包含候选点本身 open_ends = 0 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 score += get_line_score(count, open_ends) return score def has_neighbor(board, x, y, distance=1): """ 判断 (x, y) 周围 distance 范围内是否存在棋子, 这样可以限制 AI 只在已有棋子附近搜索。 """ for dy in range(-distance, distance + 1): for dx in range(-distance, distance + 1): if dx == 0 and dy == 0: continue nx = x + dx ny = y + dy if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE: if board[ny][nx] != 0: return True return False def ai_move(board): """ AI 搜索所有空位(且附近有棋子),先检查是否有 直接获胜或必须堵住对方必胜着,否则计算启发式得分, 选择得分最高的点。 返回值为 (x, y) 坐标(从 0 开始)。 """ best_score = -1 best_move = None for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if board[y][x] == 0 and has_neighbor(board, x, y): # 检查 AI 是否可以直接获胜 board[y][x] = AI if check_win(board, x, y, AI): board[y][x] = 0 return (x, y) board[y][x] = 0 # 检查玩家是否在此处落子后会获胜,如果是则堵住 board[y][x] = PLAYER if check_win(board, x, y, PLAYER): board[y][x] = 0 return (x, y) board[y][x] = 0 # 计算该点的启发式评分: # 分别计算 AI 落子和玩家落子时的得分, # 后者乘以一定权重用于防守(这里设为0.9)。 board[y][x] = AI score_ai = evaluate_position(board, x, y, AI) board[y][x] = 0 board[y][x] = PLAYER score_player = evaluate_position(board, x, y, PLAYER) board[y][x] = 0 total_score = score_ai + score_player * 0.9 if total_score > best_score: best_score = total_score best_move = (x, y) return best_move def main(): board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)] print("欢迎来到五子棋游戏!") print("玩家使用 X,AI 使用 O。输入格式为:行 列 (例如 8 8 表示落子于第8行第8列)") while True: print_board(board) # 玩家落子 while True: try: move = input("请输入落子位置 (行 列):") if move.lower() in ["quit", "exit"]: print("退出游戏。") sys.exit(0) row, col = map(int, move.split()) row -= 1 # 转换为 0 索引 col -= 1 if 0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE and board[row][col] == 0: break else: print("位置无效或已落子,请重新输入。") except Exception: print("输入格式错误,请输入两个数字,例如:8 8") board[row][col] = PLAYER if check_win(board, col, row, PLAYER): print_board(board) print("恭喜,你赢了!") break # 检查棋盘是否已满 if all(all(cell != 0 for cell in row_data) for row_data in board): print_board(board) print("平局!") break # AI 落子 move_ai = ai_move(board) if move_ai is None: print("平局!") break ai_x, ai_y = move_ai board[ai_y][ai_x] = AI print(f"AI 落子于:第 {ai_y+1} 行,第 {ai_x+1} 列") if check_win(board, ai_x, ai_y, AI): print_board(board) print("很遗憾,你输了!") break # 检查棋盘是否已满 if all(all(cell != 0 for cell in row_data) for row_data in board): print_board(board) print("平局!") break if __name__ == "__main__": main()
棋盘与显示
棋盘用二维列表表示,大小为 15×15。函数 print_board
将棋盘打印出来,空位显示“.”,玩家的棋子显示“X”,AI 的棋子显示“O”。
胜负判断
函数 check_win
从落子点出发,在四个方向上累计连续相同棋子数,若达到 5 则判定为胜利。
启发式评估
函数 evaluate_position
(结合 get_line_score
)在指定位置模拟落子后,考察四个方向上连续棋子数及两端空口数,为该着法打分。
AI 落子策略
AI 在所有空位中(并且要求附近有棋子,以减少搜索范围)首先检查是否有能直接获胜的点,再检查是否必须堵住玩家的必胜着,最后计算启发式得分,选择分数最高的位置。
主循环
在 main()
函数中,首先由玩家输入落子位置(输入格式为“行 列”,例如“8 8”),然后检查胜负;接着 AI 落子,并显示其落子位置。若棋盘已满则为平局。
此代码只是一个较为简单的示例,AI 的算法比较初级,如果需要更强的 AI,可以考虑加入更深入的搜索算法(如 α–β 剪枝的 minimax 搜索或 Monte Carlo Tree Search)。
运行该程序后,即可在终端中与 AI 对战。Enjoy!
要有界面啊
下面提供一个基于 Tkinter 的图形界面版五子棋示例,玩家与 AI 对战。玩家下黑子,AI 下白子。代码中采用了与之前类似的 AI 策略和胜负判断逻辑,但使用 Tkinter 绘制棋盘和棋子,实现鼠标点击落子的交互界面。
你可以将下面代码保存为例如 gomoku_gui.py
,然后用 Python 运行即可:
pythonimport tkinter as tk from tkinter import messagebox # 棋盘和绘图参数 BOARD_SIZE = 15 # 棋盘行列数 CELL_SIZE = 40 # 每个交叉点之间的像素间距 MARGIN = 20 # 棋盘边缘空白 # 棋子标记 PLAYER = 1 # 玩家(黑子) AI = 2 # AI(白子) def check_win(board, x, y, player): """ 检查在 (x, y) 落下 player 棋子后是否获胜(横、竖、斜方向连续 5 个) """ directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 if count >= 5: return True return False def get_line_score(count, open_ends): """ 根据连续棋子数 count 和两端空口数 open_ends 返回评分 """ if count >= 5: return 100000 if count == 4: if open_ends == 2: return 10000 elif open_ends == 1: return 1000 if count == 3: if open_ends == 2: return 1000 elif open_ends == 1: return 100 if count == 2: if open_ends == 2: return 100 elif open_ends == 1: return 10 if count == 1: return 10 return 0 def evaluate_position(board, x, y, player): """ 评估在 (x, y) 处落下 player 棋子的得分,综合横、竖、斜四个方向 """ score = 0 directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 open_ends = 0 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 score += get_line_score(count, open_ends) return score def has_neighbor(board, x, y, distance=1): """ 判断 (x, y) 周围 distance 范围内是否存在棋子, 用以限制 AI 只在已有棋子附近搜索 """ for dy in range(-distance, distance + 1): for dx in range(-distance, distance + 1): if dx == 0 and dy == 0: continue nx = x + dx ny = y + dy if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE: if board[ny][nx] != 0: return True return False def ai_move(board): """ AI 策略: 1. 搜索所有空位(且周围有棋子),首先检查是否能直接获胜, 或者是否需要堵住对方必胜着; 2. 否则计算启发式得分,选择得分最高的点。 返回 (x, y) 坐标(从 0 开始)。 """ best_score = -1 best_move = None for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if board[y][x] == 0 and has_neighbor(board, x, y): # 检查 AI 是否能直接获胜 board[y][x] = AI if check_win(board, x, y, AI): board[y][x] = 0 return (x, y) board[y][x] = 0 # 检查玩家如果落在此处能获胜,需堵住 board[y][x] = PLAYER if check_win(board, x, y, PLAYER): board[y][x] = 0 return (x, y) board[y][x] = 0 # 计算启发式评分 board[y][x] = AI score_ai = evaluate_position(board, x, y, AI) board[y][x] = 0 board[y][x] = PLAYER score_player = evaluate_position(board, x, y, PLAYER) board[y][x] = 0 total_score = score_ai + score_player * 0.9 if total_score > best_score: best_score = total_score best_move = (x, y) return best_move class GomokuGUI: def __init__(self, master): self.master = master self.master.title("五子棋对战 - 人机对战") # 根据棋盘尺寸计算画布大小 self.canvas_size = MARGIN * 2 + CELL_SIZE * (BOARD_SIZE - 1) self.canvas = tk.Canvas(master, width=self.canvas_size, height=self.canvas_size, bg="#F0D9B5") self.canvas.pack() self.canvas.bind("<Button-1>", self.on_click) # 初始化棋盘(二维列表,0为空,1为玩家,2为AI) self.board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)] self.game_over = False self.draw_board() def draw_board(self): """绘制棋盘网格和已落的棋子""" self.canvas.delete("all") # 绘制网格线 for i in range(BOARD_SIZE): x = MARGIN + i * CELL_SIZE self.canvas.create_line(x, MARGIN, x, self.canvas_size - MARGIN) self.canvas.create_line(MARGIN, x, self.canvas_size - MARGIN, x) # 绘制棋子 for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if self.board[y][x] != 0: self.draw_piece(x, y, self.board[y][x]) def draw_piece(self, x, y, player): """在 (x, y) 位置绘制棋子""" cx = MARGIN + x * CELL_SIZE cy = MARGIN + y * CELL_SIZE radius = CELL_SIZE // 2 - 2 if player == PLAYER: color = "black" else: color = "white" self.canvas.create_oval(cx - radius, cy - radius, cx + radius, cy + radius, fill=color) def on_click(self, event): """鼠标点击事件:玩家落子,然后由 AI 落子""" if self.game_over: return # 根据点击位置换算出最近的棋盘交点 x = round((event.x - MARGIN) / CELL_SIZE) y = round((event.y - MARGIN) / CELL_SIZE) if x < 0 or x >= BOARD_SIZE or y < 0 or y >= BOARD_SIZE: return if self.board[y][x] != 0: return # 玩家落子 self.board[y][x] = PLAYER self.draw_piece(x, y, PLAYER) if check_win(self.board, x, y, PLAYER): self.game_over = True messagebox.showinfo("游戏结束", "恭喜,你赢了!") return if all(all(cell != 0 for cell in row) for row in self.board): self.game_over = True messagebox.showinfo("游戏结束", "平局!") return # AI 落子 move = ai_move(self.board) if move: ai_x, ai_y = move self.board[ai_y][ai_x] = AI self.draw_piece(ai_x, ai_y, AI) if check_win(self.board, ai_x, ai_y, AI): self.game_over = True messagebox.showinfo("游戏结束", "很遗憾,你输了!") return else: self.game_over = True messagebox.showinfo("游戏结束", "平局!") return if __name__ == "__main__": root = tk.Tk() game = GomokuGUI(root) root.mainloop()
棋盘与绘图
使用 Tkinter 的 Canvas
组件绘制棋盘网格。棋盘大小由 BOARD_SIZE
、CELL_SIZE
和 MARGIN
定义。每个交叉点绘制一个圆形表示棋子,黑色表示玩家,白色表示 AI。
胜负判断和 AI 策略
函数 check_win
用于检查某个落子后是否获胜。ai_move
函数在所有空位(且周围有棋子)中,先检查是否能直接获胜或需要堵对方,然后依据启发式评分选择最佳位置。
交互逻辑
鼠标点击事件绑定在 on_click
函数上,玩家点击棋盘后,会在最近的交点落子,然后调用 AI 落子,并检查输赢。如果游戏结束,则弹出对话框提示结果。
运行该程序后,即可看到一个带有棋盘的窗口,通过点击棋盘进行对战。你可以根据需要扩展完善界面效果和 AI 算法。
改成AI互相对战
下面给出一个基于 Tkinter 的示例程序,实现两位 AI 互相对战的五子棋。程序中采用与之前类似的胜负判断和启发式评分算法,不再响应鼠标点击,而是由程序自动按照一定延时轮流调用 AI 落子,直至分出胜负或平局。
将下面代码保存为例如 gomoku_ai_vs_ai.py
,运行后即可看到棋盘窗口,AI 会自动对战。
pythonimport tkinter as tk from tkinter import messagebox # 棋盘和绘图参数 BOARD_SIZE = 15 # 棋盘行列数 CELL_SIZE = 40 # 每个交叉点之间的像素间距 MARGIN = 20 # 棋盘边缘空白 # 棋子标记(两位 AI 分别用黑子和白子) BLACK = 1 # 黑子 WHITE = 2 # 白子 def check_win(board, x, y, player): """ 检查在 (x, y) 落下 player 棋子后是否获胜(横、竖、斜方向连续 5 个) """ directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 if count >= 5: return True return False def get_line_score(count, open_ends): """ 根据连续棋子数 count 和两端空口数 open_ends 返回评分 """ if count >= 5: return 100000 if count == 4: if open_ends == 2: return 10000 elif open_ends == 1: return 1000 if count == 3: if open_ends == 2: return 1000 elif open_ends == 1: return 100 if count == 2: if open_ends == 2: return 100 elif open_ends == 1: return 10 if count == 1: return 10 return 0 def evaluate_position(board, x, y, player): """ 评估在 (x, y) 处落下 player 棋子的得分,综合横、竖、斜四个方向 此函数要求在调用前,已将 board[y][x] 设为 player """ score = 0 directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 包含当前棋子 open_ends = 0 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 score += get_line_score(count, open_ends) return score def has_neighbor(board, x, y, distance=1): """ 判断 (x, y) 周围 distance 范围内是否存在棋子, 用以限制 AI 只在已有棋子附近搜索 """ for dy in range(-distance, distance + 1): for dx in range(-distance, distance + 1): if dx == 0 and dy == 0: continue nx = x + dx ny = y + dy if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE: if board[ny][nx] != 0: return True return False def get_ai_move(board, player, opponent): """ 为当前 player 寻找最佳落子位置,考虑: 1. 是否能直接获胜; 2. 是否需要堵住对方必胜; 3. 否则计算启发式评分。 返回 (x, y) 坐标(从 0 开始),如果找不到则返回 None """ best_score = -1 best_move = None for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if board[y][x] == 0 and has_neighbor(board, x, y): # 检查当前 player 是否能直接获胜 board[y][x] = player if check_win(board, x, y, player): board[y][x] = 0 return (x, y) board[y][x] = 0 # 检查对方如果落子能获胜,则必须堵住 board[y][x] = opponent if check_win(board, x, y, opponent): board[y][x] = 0 return (x, y) board[y][x] = 0 # 启发式评分 board[y][x] = player score_player = evaluate_position(board, x, y, player) board[y][x] = 0 board[y][x] = opponent score_opponent = evaluate_position(board, x, y, opponent) board[y][x] = 0 total_score = score_player + score_opponent * 0.9 if total_score > best_score: best_score = total_score best_move = (x, y) return best_move class GomokuAIVsAI: def __init__(self, master): self.master = master self.master.title("五子棋 AI 互相对战") # 根据棋盘尺寸计算画布大小 self.canvas_size = MARGIN * 2 + CELL_SIZE * (BOARD_SIZE - 1) self.canvas = tk.Canvas(master, width=self.canvas_size, height=self.canvas_size, bg="#F0D9B5") self.canvas.pack() # 初始化棋盘(二维列表,0为空,BLACK 为黑子,WHITE 为白子) self.board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)] self.game_over = False # 先手为黑子 self.current_turn = BLACK self.draw_board() # 开始自动对战,每隔一定时间执行下一步 self.master.after(500, self.next_move) # 500 毫秒后开始下一步 def draw_board(self): """绘制棋盘网格和所有已落棋子""" self.canvas.delete("all") # 绘制网格 for i in range(BOARD_SIZE): x = MARGIN + i * CELL_SIZE self.canvas.create_line(x, MARGIN, x, self.canvas_size - MARGIN) self.canvas.create_line(MARGIN, x, self.canvas_size - MARGIN, x) # 绘制已落的棋子 for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if self.board[y][x] != 0: self.draw_piece(x, y, self.board[y][x]) def draw_piece(self, x, y, player): """在棋盘坐标 (x, y) 处绘制棋子""" cx = MARGIN + x * CELL_SIZE cy = MARGIN + y * CELL_SIZE radius = CELL_SIZE // 2 - 2 if player == BLACK: color = "black" else: color = "white" self.canvas.create_oval(cx - radius, cy - radius, cx + radius, cy + radius, fill=color) def next_move(self): """由当前 AI 落子,并判断输赢,然后切换回合""" if self.game_over: return # 根据当前回合决定双方角色 if self.current_turn == BLACK: opponent = WHITE else: opponent = BLACK move = get_ai_move(self.board, self.current_turn, opponent) if move is None: self.game_over = True messagebox.showinfo("游戏结束", "平局!") return x, y = move self.board[y][x] = self.current_turn self.draw_piece(x, y, self.current_turn) if check_win(self.board, x, y, self.current_turn): self.game_over = True winner = "黑子" if self.current_turn == BLACK else "白子" messagebox.showinfo("游戏结束", f"{winner}获胜!") return # 检查棋盘是否已满 if all(all(cell != 0 for cell in row) for row in self.board): self.game_over = True messagebox.showinfo("游戏结束", "平局!") return # 切换回合:利用 3 - current_turn (1变2,2变1) self.current_turn = 3 - self.current_turn # 每隔一定延时后调用下一步 self.master.after(500, self.next_move) if __name__ == "__main__": root = tk.Tk() game = GomokuAIVsAI(root) root.mainloop()
公共函数
check_win
:判断在某个位置落子后是否形成连续五子。evaluate_position
及 get_line_score
:根据当前落子后各个方向的棋型,给出启发式评分。has_neighbor
:用于限制 AI 只在已有棋子附近搜索。get_ai_move
:为给定的当前玩家(player
)计算最佳落子点,同时考虑直接获胜和阻止对方必胜。界面与对战逻辑
GomokuAIVsAI
负责绘制棋盘、显示棋子,并在 next_move
方法中自动由当前 AI 落子。after
方法延时调用下一步,使得对战过程自动进行。运行该程序后,会弹出一个窗口显示棋盘,黑白两方 AI 会自动落子并互相对战,直到分出胜负或平局。你可以根据需要调整延时或进一步完善 AI 算法。
要有过程啊,我要看,不要之间计算出结果
如果你希望在 AI 对战过程中能够看到每一步的执行过程,而不是直接计算出最终结果,我们可以通过添加逐步显示的功能来实现。具体来说,我们会让程序在每一步落子后显示当前棋盘状态,并且添加一个按钮来让游戏继续进行。这样你可以在界面上看到每一步的AI决策过程。
下面是修改后的代码:
pythonimport tkinter as tk from tkinter import messagebox # 棋盘和绘图参数 BOARD_SIZE = 15 # 棋盘行列数 CELL_SIZE = 40 # 每个交叉点之间的像素间距 MARGIN = 20 # 棋盘边缘空白 # 棋子标记(两位 AI 分别用黑子和白子) BLACK = 1 # 黑子 WHITE = 2 # 白子 def check_win(board, x, y, player): """ 检查在 (x, y) 落下 player 棋子后是否获胜(横、竖、斜方向连续 5 个) """ directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE or board[ny][nx] != player: break count += 1 i += 1 if count >= 5: return True return False def get_line_score(count, open_ends): """ 根据连续棋子数 count 和两端空口数 open_ends 返回评分 """ if count >= 5: return 100000 if count == 4: if open_ends == 2: return 10000 elif open_ends == 1: return 1000 if count == 3: if open_ends == 2: return 1000 elif open_ends == 1: return 100 if count == 2: if open_ends == 2: return 100 elif open_ends == 1: return 10 if count == 1: return 10 return 0 def evaluate_position(board, x, y, player): """ 评估在 (x, y) 处落下 player 棋子的得分,综合横、竖、斜四个方向 此函数要求在调用前,已将 board[y][x] 设为 player """ score = 0 directions = [(1, 0), (0, 1), (1, 1), (1, -1)] for dx, dy in directions: count = 1 # 包含当前棋子 open_ends = 0 # 正方向扫描 i = 1 while True: nx = x + i * dx ny = y + i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 # 反方向扫描 i = 1 while True: nx = x - i * dx ny = y - i * dy if nx < 0 or nx >= BOARD_SIZE or ny < 0 or ny >= BOARD_SIZE: break if board[ny][nx] == player: count += 1 elif board[ny][nx] == 0: open_ends += 1 break else: break i += 1 score += get_line_score(count, open_ends) return score def has_neighbor(board, x, y, distance=1): """ 判断 (x, y) 周围 distance 范围内是否存在棋子, 用以限制 AI 只在已有棋子附近搜索 """ for dy in range(-distance, distance + 1): for dx in range(-distance, distance + 1): if dx == 0 and dy == 0: continue nx = x + dx ny = y + dy if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE: if board[ny][nx] != 0: return True return False def get_ai_move(board, player, opponent): """ 为当前 player 寻找最佳落子位置,考虑: 1. 是否能直接获胜; 2. 是否需要堵住对方必胜; 3. 否则计算启发式评分。 返回 (x, y) 坐标(从 0 开始),如果找不到则返回 None """ best_score = -1 best_move = None for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if board[y][x] == 0 and has_neighbor(board, x, y): # 检查当前 player 是否能直接获胜 board[y][x] = player if check_win(board, x, y, player): board[y][x] = 0 return (x, y) board[y][x] = 0 # 检查对方如果落子能获胜,则必须堵住 board[y][x] = opponent if check_win(board, x, y, opponent): board[y][x] = 0 return (x, y) board[y][x] = 0 # 启发式评分 board[y][x] = player score_player = evaluate_position(board, x, y, player) board[y][x] = 0 board[y][x] = opponent score_opponent = evaluate_position(board, x, y, opponent) board[y][x] = 0 total_score = score_player + score_opponent * 0.9 if total_score > best_score: best_score = total_score best_move = (x, y) return best_move class GomokuAIVsAI: def __init__(self, master): self.master = master self.master.title("五子棋 AI 互相对战") # 根据棋盘尺寸计算画布大小 self.canvas_size = MARGIN * 2 + CELL_SIZE * (BOARD_SIZE - 1) self.canvas = tk.Canvas(master, width=self.canvas_size, height=self.canvas_size, bg="#F0D9B5") self.canvas.pack() # 初始化棋盘(二维列表,0为空,BLACK 为黑子,WHITE 为白子) self.board = [[0 for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)] self.game_over = False self.current_turn = BLACK # 先手为黑子 self.draw_board() # 创建控制按钮 self.step_button = tk.Button(master, text="下一步", command=self.next_move) self.step_button.pack() def draw_board(self): """绘制棋盘网格和所有已落棋子""" self.canvas.delete("all") # 绘制网格 for i in range(BOARD_SIZE): x = MARGIN + i * CELL_SIZE self.canvas.create_line(x, MARGIN, x, self.canvas_size - MARGIN) self.canvas.create_line(MARGIN, x, self.canvas_size - MARGIN, x) # 绘制已落的棋子 for y in range(BOARD_SIZE): for x in range(BOARD_SIZE): if self.board[y][x] != 0: self.draw_piece(x, y, self.board[y][x]) def draw_piece(self, x, y, player): """在棋盘坐标 (x, y) 处绘制棋子""" cx = MARGIN + x * CELL_SIZE cy = MARGIN + y * CELL_SIZE radius = CELL_SIZE // 2 - 2 if player == BLACK: color = "black" else: color = "white" self.canvas.create_oval(cx - radius, cy - radius, cx + radius, cy + radius, fill=color) def next_move(self): """由当前 AI 落子,并判断输赢,然后切换回合""" if self.game_over: return # 根据当前回合决定双方角色 if self.current_turn == BLACK: opponent = WHITE else: opponent = BLACK move = get_ai_move(self.board, self.current_turn, opponent) if move is None: self.game_over = True messagebox.showinfo("游戏结束", "平局!") return x, y = move self.board[y][x] = self.current_turn self.draw_piece(x, y, self.current_turn) if check_win(self.board, x, y, self.current_turn): self.game_over = True winner = "黑子" if self.current_turn == BLACK else "白子" messagebox.showinfo("游戏结束", f"{winner}获胜!") return # 检查棋盘是否已满 if all(all(cell != 0 for cell in row) for row in self.board): self.game_over = True messagebox.showinfo("游戏结束", "平局!") return # 切换回合:利用 3 - current_turn (1变2,2变1) self.current_turn = 3 - self.current_turn if __name__ == "__main__": root = tk.Tk() game = GomokuAIVsAI(root) root.mainloop()
步骤控制
通过 self.step_button
按钮手动触发每一步,用户可以点击按钮,看到每一步 AI 的决策过程,而不是计算出所有结果。
逐步展示每一步
每点击一次按钮,AI 会根据当前局面计算出最佳落子位置并更新棋盘,直到游戏结束。
游戏结束提示
游戏结束时会弹出一个消息框,提示胜者或平局。