CS课程项目设计5:基于Canvas交互友好的井字棋游戏

本专栏的第一篇和第三篇文章分别介绍了交互友好的井字棋游戏、支持AI人机对战的井字棋游戏,具体内容可以参考以下两篇帖子:

CS课程项目设计1:交互友好的井字棋游戏_井字棋人机交互-CSDN博客https://blog.csdn.net/weixin_36431280/article/details/149309500?spm=1001.2014.3001.5501CS课程项目设计3:支持AI人机对战的井字棋游戏-CSDN博客https://blog.csdn.net/weixin_36431280/article/details/149432343?spm=1001.2014.3001.5501但是这两个项目实现的井字棋棋盘都太简陋了,不太美观,因此,最近想到可以进一步改进这两个项目,提高页面布局的美观度,原始交互友好的井字棋游戏棋盘和现有基于Canvas交互友好的井字棋游戏棋盘的对比图如下所示:

咱们这个专栏主打就是持续变强!


1. 研究目的

这个项目重新设计了棋盘、按键、设置玩家昵称页面、赢局和平局显示页面,并且对照本专栏第一篇文章来介绍这个基于Canvas交互友好的井字棋游戏。

具体而言,为了让井字棋棋盘更加美观,我们可以使用 tkinter 的 Canvas 组件来绘制棋盘,并用 Canvas 上的图形表示棋子,同时可以调整颜色和线条样式来提升视觉效果。

主要修改点

  1. 使用 Canvas 绘制棋盘:使用 Canvas 组件绘制棋盘网格,并通过 create_line 方法绘制横线和竖线。
  2. 绘制棋子:使用 create_line 和 create_oval 方法在 Canvas 上绘制 X 和 O 棋子。
  3. 动画效果:通过 create_rectangle 方法在 Canvas 上实现闪烁动画效果。
  4. 鼠标点击事件:使用 bind 方法绑定鼠标点击事件,处理玩家的落子操作。

同时,对按钮和文字窗口进行了美化,使用了更现代的设计风格和交互效果。主要改进包括:

  • 为按钮添加了圆角、阴影和悬停效果
  • 使用更现代的字体和颜色方案
  • 为文字窗口添加了边框和背景色
  • 优化了整体布局,增加了间距和对齐
  • 为玩家状态添加了颜色标记
  • 添加了棋盘边框和分隔线样式

最后,对设置用户昵称和显示玩家胜利的布局进行了美化,使用了更现代的设计风格和交互效果。主要改进包括:

  • 为设置用户昵称创建了自定义对话框
  • 使用更现代的字体和颜色方案
  • 为胜利消息添加了动画效果和视觉反馈
  • 优化了整体布局,增加了间距和对齐
  • 为玩家状态添加了颜色标记
  • 添加了平滑过渡效果

2. 技术方案

本项目采用 Python 语言结合 tkinter 库实现,具体技术方案如下:

  • 开发语言:Python 3.8
  • GUI 库:tkinter(Python 内置库,无需额外安装)
  • 数据存储:JSON 格式文件用于保存和加载游戏进度
  • 多线程处理:使用 threading 模块处理音效播放,避免阻塞 UI 线程
  • 动画效果:通过 tkinter 的 update () 方法和延时函数实现简单动画

系统架构采用面向对象设计,将游戏逻辑和界面交互分离,提高代码的可维护性和可扩展性。主要类包括:

  • TicTacToe:游戏主类,负责管理游戏状态、处理用户输入和更新界面
  • 界面组件:包括棋盘按钮、状态标签、控制按钮等,通过 tkinter 实现

3. 实现流程

明确游戏的基本功能和交互逻辑,设计数据结构和类的关系。

确定需要实现的核心功能包括:棋盘显示、玩家轮流落子、胜负判定、悔棋、保存 / 加载游戏等。

首先实现游戏的核心逻辑,包括:

  1. 初始化棋盘和游戏状态
  2. 处理玩家点击事件,更新棋盘状态
  3. 判断胜负和平局条件
  4. 实现玩家轮流机制

其中,初始化棋盘和设置玩家名称的代码如下所示:

def set_player_names(self):
    """使用自定义对话框设置玩家名称"""
    dialog = tk.Toplevel(self.root)
    dialog.title("设置玩家名称")
    dialog.geometry("400x200")
    dialog.configure(bg=self.colors['background'])
    dialog.transient(self.root)
    dialog.grab_set()

    # 创建对话框内容
    title_label = tk.Label(
        dialog,
        text="请设置玩家名称",
        font=('Arial', 16, 'bold'),
        bg=self.colors['background'],
        fg=self.colors['text']
    )
    title_label.pack(pady=10)

    # 玩家X名称输入
    x_frame = tk.Frame(dialog, bg=self.colors['background'])
    x_frame.pack(pady=5, padx=20, fill=tk.X)

    x_label = tk.Label(
        x_frame,
        text="玩家X:",
        font=('Arial', 12),
        bg=self.colors['background'],
        fg=self.colors['text']
    )
    x_label.pack(side=tk.LEFT, padx=5)

    self.x_entry = tk.Entry(
        x_frame,
        font=('Arial', 12),
        bg='white',
        fg=self.colors['text'],
        relief=tk.FLAT,
        bd=2
    )
    self.x_entry.insert(0, self.player_names['X'])
    self.x_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)

    # 玩家O名称输入
    o_frame = tk.Frame(dialog, bg=self.colors['background'])
    o_frame.pack(pady=5, padx=20, fill=tk.X)

    o_label = tk.Label(
        o_frame,
        text="玩家O:",
        font=('Arial', 12),
        bg=self.colors['background'],
        fg=self.colors['text']
    )
    o_label.pack(side=tk.LEFT, padx=5)

    self.o_entry = tk.Entry(
        o_frame,
        font=('Arial', 12),
        bg='white',
        fg=self.colors['text'],
        relief=tk.FLAT,
        bd=2
    )
    self.o_entry.insert(0, self.player_names['O'])
    self.o_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)

    # 按钮框架
    button_frame = tk.Frame(dialog, bg=self.colors['background'])
    button_frame.pack(pady=10, padx=20, fill=tk.X)

    ok_button = tk.Button(
        button_frame,
        text="确定",
        font=('Arial', 12, 'bold'),
        bg=self.colors['button'],
        fg='white',
        relief=tk.FLAT,
        padx=20,
        pady=8,
        command=lambda: self.on_name_dialog_ok(dialog)
    )
    ok_button.pack(side=tk.RIGHT, padx=5)
    ok_button.bind("<Enter>", lambda e: ok_button.config(bg=self.colors['button_hover']))
    ok_button.bind("<Leave>", lambda e: ok_button.config(bg=self.colors['button']))

    # 将 wait_window 移到对话框内容构建完成后
    self.root.wait_window(dialog)

初始化棋盘和设置玩家名称的可视化界面如下图所示:

其中,处理玩家点击事件,更新棋盘状态的代码如下所示:

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
        # 更新按钮显示
        self.buttons[row][col].config(text=self.current_player)
        # 记录上一步
        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]}")

当前玩家可以看到上一位玩家的下子坐标位置,可视化界面如下所示:

判断胜负和平局条件的代码如下所示:

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

其中,平局和赢局的可视化界面如下图所示:

此外,该项目使用 tkinter 创建用户界面,包括:

  • 设计棋盘布局和样式

  • 添加状态显示区域,显示当前玩家和游戏状态

  • 实现控制按钮(悔棋、重新开始等)

  • 支持玩家自定义名称

我们逐步添加附加功能:

  • 悔棋功能:记录历史操作,支持撤销上一步

  • 保存 / 加载功能:使用 JSON 格式保存游戏状态到文件

  • 音效系统:使用 playsound 库播放操作音效

  • 动画效果:为棋子放置和获胜状态添加视觉动画

其中,悔棋功能的代码如下所示:

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)

悔棋功能的可视化界面如下所示:

因此,该游戏的主要流程如下所示:

  1. 初始化 3x3 空棋盘
  2. 玩家 X 先开始游戏
  3. 轮流输入位置(行和列,范围 1-3)
  4. 程序会验证输入有效性并更新棋盘
  5. 每次移动后检查是否有玩家获胜或平局
  6. 游戏结束时显示结果

4. 总结

本代码通过 Python 和 Tkinter 库成功实现了一个交互友好的井字棋游戏。游戏具有直观的图形用户界面,支持玩家名称设置、游戏保存与加载、悔棋和重新开始等功能。同时,通过添加音效和动画效果,提升了游戏的趣味性和吸引力。代码结构清晰,逻辑严谨,适合作为学习 GUI 编程和游戏开发的参考示例。然而,代码也存在一些可以改进的地方,例如可以进一步优化动画效果、增加更多的游戏模式或难度级别等。

5. 项目展示

最后上传个该项目的简要演示视频,供大家了解。

基于Canvas交互友好的井字棋游戏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习的学习者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值