在CS课程项目设计专栏的上一篇文章,我们介绍了支持AI人机对战的井字棋游戏:
CS课程项目设计3:支持AI人机对战的井字棋游戏-CSDN博客https://blog.csdn.net/weixin_36431280/article/details/149432343?spm=1001.2014.3001.5501理由在于,随着人工智能的火热,AI人机对战的引入显得很有必要,本质上就是引入强化学习的概念了,这也是现在大模型领域比较关心的方向了。
因此,和上述第三个CS课程项目一样,我们继续向现有五子棋代码基础上,增加一个简单的 AI 对手,实现人机对战功能:
今天,我们分享第四个CS课程项目:支持AI人机对战的井字棋游戏。欢迎交流(关注公粽子号:AI课程项目笔记本)。

1. 研究背景
五子棋作为一种传统的棋类游戏,规则简单但策略性强,深受大众喜爱。随着计算机技术的发展,开发具备人机对战功能的五子棋游戏成为了一个有趣的研究方向。实现人机对战的五子棋游戏不仅能为玩家提供娱乐,还能作为人工智能算法实践的一个良好载体,例如使用极小极大算法和 Alpha - Beta 剪枝等算法来实现 AI 的决策过程。
2. 研究目的
本代码的主要研究目的是开发一个支持人机对战的五子棋游戏,用户可以与 AI 进行对弈。具体目标包括:
- 提供友好的图形用户界面(GUI),方便玩家操作。
- 允许玩家设置游戏模式(双人对战或人机对战)和 AI 难度(简单、中等、困难)。
- 实现保存和加载游戏进度的功能,方便玩家随时继续游戏。
- 为游戏添加音效和动画效果,提升游戏体验。
- 运用人工智能算法使 AI 能够做出合理的落子决策。
3. 技术方案
1. 图形用户界面(GUI)
使用 Python 的tkinter
库创建游戏的图形用户界面,包括棋盘按钮、玩家信息标签、状态标签和操作按钮等。
本游戏主要设置了双人对战和人机对战两种对战模式,在双人对战模式中,你需要分别设置你和对手的用户昵称,可视化界面如下图所示:

在人机对战模式中,你只需要设置你的用户昵称,可视化界面如下图所示:

2. 游戏逻辑实现
通过二维列表self.board
来表示棋盘状态,使用self.current_player
来记录当前玩家。在玩家或 AI 落子后,调用check_winner
方法检查是否有玩家获胜,调用is_board_full
方法检查棋盘是否已满。
3. AI 算法
采用极小极大算法(Minimax Algorithm)并结合 Alpha - Beta 剪枝技术来实现 AI 的落子决策。根据不同的 AI 难度设置不同的搜索深度,以平衡 AI 的计算时间和决策质量。同时,为了避免 AI 计算超时,设置了self.ai_timeout
参数,当计算时间超过该值时,AI 会随机选择一个空位落子。
4. 游戏状态保存和加载
使用 JSON 格式将游戏状态(如棋盘状态、当前玩家、落子历史等)保存到文件中,方便后续加载游戏进度。
5. 音效和动画
使用playsound
库在玩家落子、获胜、平局和悔棋等情况下播放相应的音效。通过tkinter
的update
方法和time.sleep
函数实现棋子放置和获胜动画效果。
4. 实现流程
1. 初始化
在Gomoku
类的__init__
方法中,初始化游戏的各种属性,包括棋盘大小、当前玩家、玩家名称、AI 难度等。同时,创建游戏界面组件,如棋盘按钮、状态标签和操作按钮等,并允许用户设置玩家名称。
我们实现了三种AI难度级别(简单、中等、困难),满足不同技能水平玩家的需求,可视化界面如下图所示:

2. 玩家落子
当玩家点击棋盘上的按钮时,调用make_move
方法处理玩家的落子操作。该方法会更新棋盘状态、检查游戏是否结束,并切换玩家。如果当前游戏模式为人机对战且轮到 AI 玩家,则调用ai_make_move
方法让 AI 落子。
其中,处理玩家点击事件,更新棋盘状态的make_move
代码如下所示:
def make_move(self, row, col):
"""处理玩家移动"""
if self.board[row][col] == ' ' and self.game_active:
# 记录当前移动到历史
self.move_history.append((row, col, self.current_player))
self.undo_button.config(state=tk.NORMAL) # 启用悔棋按钮
# 播放放置音效
self.play_sound('place')
# 添加放置动画
self.animate_cell(row, col)
# 更新棋盘数据
self.board[row][col] = self.current_player
# 更新按钮显示(使用●表示黑棋,○表示白棋)
symbol = '●' if self.current_player == 'X' else '○'
self.buttons[row][col].config(text=symbol)
# 记录上一步
self.last_move = (row, col)
self.last_move_label.config(
text=f"上一步: {self.player_names[self.current_player]} 在位置 {row + 1},{col + 1}"
)
# 检查游戏状态
if self.check_winner(self.current_player):
self.status_label.config(text=f"{self.player_names[self.current_player]} 获胜!")
self.game_active = False
self.undo_button.config(state=tk.DISABLED) # 禁用悔棋按钮
# 播放胜利音效和动画
self.play_sound('win')
self.animate_winning_cells()
messagebox.showinfo("游戏结束", f"{self.player_names[self.current_player]} 获胜!")
elif self.is_board_full():
self.status_label.config(text="游戏平局!")
self.game_active = False
self.undo_button.config(state=tk.DISABLED) # 禁用悔棋按钮
# 播放平局音效
self.play_sound('draw')
messagebox.showinfo("游戏结束", "游戏平局!")
else:
# 切换玩家
self.current_player = 'O' if self.current_player == 'X' else 'X'
self.status_label.config(text=f"当前玩家: {self.player_names[self.current_player]}")
# 如果是AI玩家且游戏模式为人机对战,则AI落子
if self.game_mode == 'human_vs_ai' and self.current_player == self.ai_player:
self.status_label.config(text=f"{self.player_names[self.current_player]} 正在思考...")
self.root.after(500, self.ai_make_move) # 延迟500ms开始AI思考,避免界面卡顿
调用ai_make_move
方法让 AI 落子的代码如下所示:
def ai_make_move(self):
"""AI落子"""
if not self.game_active or self.game_mode != 'human_vs_ai' or self.current_player != self.ai_player:
return
self.ai_start_time = time.time()
self.best_move = None
# 根据难度选择不同的搜索深度
depth = 1 if self.ai_difficulty == 0 else 3 if self.ai_difficulty == 1 else 5
self.initial_depth = depth # 保存初始深度用于记录最佳落子
# 使用极小极大算法寻找最佳落子
self.minimax(depth, -float('inf'), float('inf'), True)
# 检查是否超时,如果超时则随机选择一个空位
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout or self.best_move is None:
# 随机选择一个空位
empty_cells = [(r, c) for r in range(self.board_size) for c in range(self.board_size) if
self.board[r][c] == ' ']
if empty_cells:
self.best_move = random.choice(empty_cells)
# 执行落子
if self.best_move:
row, col = self.best_move
self.make_move(row, col)
可视化界面如下所示:

3. AI 落子
在ai_make_move
方法中,根据 AI 难度设置搜索深度,调用minimax
方法使用极小极大算法和 Alpha - Beta 剪枝技术寻找最佳落子位置。如果计算超时或未找到最佳位置,则随机选择一个空位落子。
4. 极小极大算法
minimax
方法实现了极小极大算法和 Alpha - Beta 剪枝技术。在递归过程中,根据当前玩家的角色(最大化或最小化)选择最优的落子位置。同时,在每次递归调用时检查是否超时,如果超时则提前返回结果。该算法的代码如下所示:
def minimax(self, depth, alpha, beta, is_maximizing):
"""极小极大算法,带alpha-beta剪枝和超时检查"""
# 检查超时
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout:
return -1000 if is_maximizing else 1000
# 检查游戏状态
if self.check_winner(self.ai_player):
return 10000 + depth # AI获胜
if self.check_winner('X' if self.ai_player == 'O' else 'O'):
return -10000 - depth # 玩家获胜
if self.is_board_full():
return 0 # 平局
if depth == 0:
return self.evaluate_board() # 评估当前局面
if is_maximizing:
max_score = -float('inf')
# 获取所有空位并按分数排序(启发式排序)
empty_cells = [(r, c) for r in range(self.board_size) for c in range(self.board_size) if
self.board[r][c] == ' ']
# 按中心距离排序,优先考虑中心区域
empty_cells.sort(key=lambda pos: abs(pos[0] - 7) + abs(pos[1] - 7))
for r, c in empty_cells:
self.board[r][c] = self.ai_player
score = self.minimax(depth - 1, alpha, beta, False)
self.board[r][c] = ' '
if score > max_score:
max_score = score
if depth == self.initial_depth: # 记录最佳落子位置
self.best_move = (r, c)
alpha = max(alpha, score)
if beta <= alpha:
break # Beta剪枝
# 检查超时
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout:
return max_score
return max_score
else:
min_score = float('inf')
# 获取所有空位并按分数排序(启发式排序)
empty_cells = [(r, c) for r in range(self.board_size) for c in range(self.board_size) if
self.board[r][c] == ' ']
# 按中心距离排序,优先考虑中心区域
empty_cells.sort(key=lambda pos: abs(pos[0] - 7) + abs(pos[1] - 7))
for r, c in empty_cells:
opponent = 'X' if self.ai_player == 'O' else 'O'
self.board[r][c] = opponent
score = self.minimax(depth - 1, alpha, beta, True)
self.board[r][c] = ' '
if score < min_score:
min_score = score
beta = min(beta, score)
if beta <= alpha:
break # Alpha剪枝
# 检查超时
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout:
return min_score
return min_score
5. 评估函数
evaluate_board
方法用于评估当前棋盘状态的分数,根据 AI 和玩家的连续棋子数给予不同的分数奖励或惩罚,同时对中心位置的棋子给予额外的奖励或惩罚。该算法的代码如下所示:
def evaluate_board(self):
"""评估棋盘状态,返回分数"""
score = 0
ai_player = self.ai_player
human_player = 'X' if ai_player == 'O' else 'O'
# 评估行、列、对角线
directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
for row in range(self.board_size):
for col in range(self.board_size):
if self.board[row][col] == ' ':
continue
# 检查当前位置在各个方向上的连续棋子
for dx, dy in directions:
# 计算当前方向上AI和玩家的连续棋子数
ai_count, human_count = self.count_consecutive(row, col, dx, dy)
# 根据连续棋子数给予分数
if ai_count > 0:
if ai_count >= 5: # AI五连
score += 100000
elif ai_count == 4: # AI四连
score += 10000
elif ai_count == 3: # AI三连
score += 1000
elif ai_count == 2: # AI二连
score += 100
else: # AI一连
score += 10
if human_count > 0:
if human_count >= 5: # 玩家五连
score -= 100000
elif human_count == 4: # 玩家四连
score -= 10000
elif human_count == 3: # 玩家三连
score -= 1000
elif human_count == 2: # 玩家二连
score -= 100
else: # 玩家一连
score -= 10
# 中心位置奖励
center = self.board_size // 2
for r in range(center - 1, center + 2):
for c in range(center - 1, center + 2):
if 0 <= r < self.board_size and 0 <= c < self.board_size:
if self.board[r][c] == ai_player:
score += 5
elif self.board[r][c] == human_player:
score -= 5
return score
6. 游戏状态保存和加载
save_game
方法将游戏状态保存为 JSON 文件,load_game
方法从 JSON 文件中加载游戏状态并恢复
7. 悔棋功能
undo_move
方法实现了悔棋功能,将上一步的落子撤销,恢复棋盘状态和玩家信息。悔棋也是一个关键功能,实现代码如下所示:
def undo_move(self):
"""悔棋功能"""
if not self.move_history:
return # 没有历史记录
# 播放悔棋音效
self.play_sound('undo')
# 恢复上一步
row, col, player = self.move_history.pop()
self.board[row][col] = ' '
self.buttons[row][col].config(text='', bg='SystemButtonFace') # 恢复默认背景
# 清除获胜高亮
if self.winning_cells:
for r, c in self.winning_cells:
self.buttons[r][c].config(bg='SystemButtonFace')
self.winning_cells = []
# 更新上一步信息
if self.move_history:
last_row, last_col, last_player = self.move_history[-1]
self.last_move = (last_row, last_col)
self.last_move_label.config(
text=f"上一步: {self.player_names[last_player]} 在位置 {last_row + 1},{last_col + 1}"
)
else:
self.last_move = None
self.last_move_label.config(text="上一步: 无")
# 切换回上一个玩家
self.current_player = player
self.status_label.config(text=f"当前玩家: {self.player_names[self.current_player]}")
# 重新激活游戏(如果之前结束了)
self.game_active = True
# 如果没有历史记录了,禁用悔棋按钮
if not self.move_history:
self.undo_button.config(state=tk.DISABLED)
可视化界面如下所示:

8. 重置游戏
reset_game
方法将游戏状态重置为初始状态,清空棋盘和落子历史。
5. 总结
本代码成功实现了一个支持人机对战的五子棋游戏,具备友好的图形用户界面、多种游戏模式和 AI 难度选择、游戏状态保存和加载、悔棋功能以及音效和动画效果。通过使用极小极大算法和 Alpha - Beta 剪枝技术,AI 能够做出合理的落子决策,为玩家提供了一定的挑战。同时,代码的结构清晰,各个功能模块分工明确,便于后续的扩展和维护。然而,代码也存在一些可以改进的地方,例如可以进一步优化 AI 的评估函数,提高 AI 的决策质量;可以添加更多的游戏规则和玩法,增加游戏的趣味性。
6. 项目展示
前面说太多了,最后还是上传个该项目的简要演示视频,供大家了解。
支持AI人机对战的五子棋游戏