本课程项目专栏的第一篇文章就介绍了交互友好的井字棋游戏,具体内容可以参考以下这篇帖子:
CS课程项目设计1:交互友好的井字棋游戏_井字棋人机交互-CSDN博客https://blog.csdn.net/weixin_36431280/article/details/149309500?spm=1001.2014.3001.5501随着人工智能的火热,后面突然想到可以尝试加入AI人机对战的功能,本质上就是引入强化学习的概念了,这也是现在大模型领域比较关心的方向了。
今天要分享的是第三个CS课程项目:支持AI人机对战的井字棋游戏,向现有的井字棋游戏代码中添加 AI 对战功能,并且让 AI 的思考下棋时间不超过 10 秒钟,我们可以采用 Minimax 算法来实现 AI 的决策逻辑,同时使用threading模块来限制 AI 的思考时间。
1. 研究背景
井字棋(Tic-Tac-Toe)是一种简单的两人对弈棋类游戏,历史悠久且规则易懂。尽管游戏本身复杂度较低(状态空间有限),但它是研究人工智能决策算法的理想实验平台。传统的井字棋游戏仅支持双人对战,而随着人工智能技术的发展,开发具有智能决策能力的 AI 对手成为可能。通过实现不同难度级别的 AI,不仅能提升游戏的趣味性,还能帮助理解基础的博弈论算法和决策模型。
在人机交互领域,开发一个具有不同难度级别的 AI 对手,可以满足不同技能水平玩家的需求,从初学者到高级玩家都能获得适当的挑战。
2. 研究目的
本项目的主要研究目的是开发一个支持人机对战的井字棋游戏,其中 AI 对手具备以下特性:
- 不同难度级别:实现三种难度级别(简单、中等、困难),满足不同技能水平玩家的需求。
- 决策时间限制:确保 AI 的决策时间不超过 10 秒,避免玩家长时间等待。
- 游戏体验优化:通过动画效果、音效和悔棋功能提升用户体验。
- 代码可扩展性:设计模块化的代码结构,便于后续添加新功能或改进 AI 算法。
通过这些目标,项目不仅提供了一个可玩的游戏,还为研究 AI 决策算法在实时交互环境中的应用提供了实践案例。
3. 技术方案
3.1 游戏框架
- 编程语言:Python 3.8
- GUI 库:tkinter(Python 内置库,无需额外安装)
- 多线程处理:使用
threading
模块实现 AI 决策的异步执行,避免 UI 卡顿 - 数据存储:使用 JSON 格式保存和加载游戏状态
3.2 AI 决策算法
- Minimax 算法:用于实现困难难度的 AI,通过递归搜索游戏树找到最优解。
- 启发式搜索:中等和简单难度的 AI 基于启发式规则,如优先占据中心位置、阻止对手连成一线等。
- 随机策略:简单难度的 AI 引入随机性,增加失误概率,使游戏更具挑战性。
3.3 时间控制机制
- 计时器:在 AI 决策过程中实时监控耗时,超过 10 秒时强制终止搜索并选择当前最优解。
- 多线程执行:将 AI 决策放在独立线程中执行,确保 UI 响应性。
4. 实现流程
4.1 基础游戏框架实现
- 界面设计:创建 3×3 网格棋盘、玩家信息显示区和控制按钮。
- 游戏逻辑:实现落子、胜负判断、平局检测等核心功能。
- 状态管理:保存棋盘状态、当前玩家、历史记录等信息。
其中,判断胜负和平局条件的代码如下所示:
def check_winner(self, player):
"""检查玩家是否获胜,并记录获胜的格子"""
# 检查行
for row in range(3):
if all([self.board[row][col] == player for col in range(3)]):
self.winning_cells = [(row, col) for col in range(3)]
return True
# 检查列
for col in range(3):
if all([self.board[row][col] == player for row in range(3)]):
self.winning_cells = [(row, col) for row in range(3)]
return True
# 检查对角线
if all([self.board[i][i] == player for i in range(3)]):
self.winning_cells = [(i, i) for i in range(3)]
return True
if all([self.board[i][2 - i] == player for i in range(3)]):
self.winning_cells = [(i, 2 - i) for i in range(3)]
return True
return False
平局和赢局的可视化界面如下图所示:
保存棋盘状态也是比较关键的一个功能,相当于可以持续保留前面几次游戏的进度,运行代码如下所示:
def save_game(self):
"""保存游戏进度"""
if not self.move_history:
messagebox.showinfo("提示", "游戏尚未开始,无需保存")
return
# 创建游戏状态字典
game_state = {
'board': self.board,
'current_player': self.current_player,
'last_move': self.last_move,
'move_history': self.move_history,
'winning_cells': self.winning_cells,
'player_names': self.player_names,
'game_active': self.game_active
}
# 打开文件对话框
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
title="保存游戏进度"
)
if not file_path:
return # 用户取消了保存
try:
# 将游戏状态保存为JSON文件
with open(file_path, 'w') as f:
json.dump(game_state, f)
messagebox.showinfo("成功", f"游戏进度已保存到 {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("错误", f"保存游戏时出错: {str(e)}")
保持游戏进度的可视化界面如下图所示:
4.2 AI 决策模块实现
- Minimax 算法:实现完整的 Minimax 算法,用于困难难度的 AI 决策。
- 中等难度策略:基于启发式规则,优先选择获胜机会和阻止对手的位置,同时引入 20% 的失误概率。
- 简单难度策略:30% 概率随机选择位置,70% 概率使用中等难度策略。
其中,用于AI决策的Minimax 算法还附带超时检查,避免当AI难度设置为困难时,AI无法在短时间内做出决策,从而影响游戏体验,该算法实现过程如下所示:
def minimax(self, depth, is_maximizing):
"""极小极大算法,带超时检查"""
# 检查超时
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout:
return -1 if is_maximizing else 1
# 检查游戏状态
if self.check_winner('O'):
return 100 + depth # AI获胜,深度越大分数越低,鼓励尽快获胜
if self.check_winner('X'):
return -100 - depth # 玩家获胜,深度越大分数越低,阻止玩家尽快获胜
if self.is_board_full():
return 0 # 平局
if depth == 0:
return self.evaluate_board() # 评估当前局面
if is_maximizing:
max_score = -float('inf')
# 遍历所有空位
for r in range(3):
for c in range(3):
if self.board[r][c] == ' ':
self.board[r][c] = 'O' # AI落子
score = self.minimax(depth - 1, False)
self.board[r][c] = ' ' # 撤销落子
if score > max_score:
max_score = score
if depth == self.initial_depth: # 记录最佳落子位置
self.best_move = (r, c)
# 检查超时
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')
# 遍历所有空位
for r in range(3):
for c in range(3):
if self.board[r][c] == ' ':
self.board[r][c] = 'X' # 玩家落子
score = self.minimax(depth - 1, True)
self.board[r][c] = ' ' # 撤销落子
if score < min_score:
min_score = score
# 检查超时
current_time = time.time()
if current_time - self.ai_start_time > self.ai_timeout:
return min_score
return min_score
AI人机对手的设置界面如下所示:
4.3 时间限制实现
- 计时器集成:在 AI 决策过程中记录时间,超过 10 秒时终止搜索。
- 多线程处理:使用
threading.Thread
将 AI 决策放在后台线程执行,确保 UI 响应性。
4.4 游戏体验优化
- 动画效果:为落子和获胜显示添加闪烁动画。
- 音效系统:添加落子、获胜、平局等事件的音效。
- 游戏功能扩展:实现悔棋、保存 / 加载游戏等功能。
其中,悔棋也是一个关键功能,实现代码如下所示:
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)
悔棋的可视化界面如下所示:
5. 总结
本项目成功实现了一个支持 AI 人机对战的井字棋游戏,具有以下特点:
- 三种难度级别:通过不同的 AI 策略实现了从简单到困难的三个难度级别,满足不同玩家需求。
- 时间控制机制:确保 AI 决策时间不超过 10 秒,通过多线程和计时器实现。
- 良好的用户体验:通过动画、音效和交互功能提升了游戏的趣味性和易用性。
通过这个项目,我们验证了 Minimax 算法在简单博弈游戏中的有效性,同时展示了如何通过启发式策略和随机性创建不同难度的 AI 对手。未来可以进一步扩展该项目,例如添加更复杂的 AI 算法(如 Alpha-Beta 剪枝)、实现联网对战功能或开发移动应用版本。
6. 项目展示
前面说太多了,最后还是上传个该项目的简要演示视频,供大家了解。
支持AI人机对战的井字棋游戏