经过一个星期的 Qt 图形组件的学习,为了加深对于图形界面动态显示原理的理解。本人基于 github 与 CSDN 上现有的五子棋程序思想编写此“在线五子棋”图形界面项目,逻辑与代码均由本人设计编写存在缺陷见谅。同样项目完整源码下载请前往 👉 项目源码下获取“在线五子棋游戏源码”

项目介绍

开发环境

  • 代码编写:QT v5.2.1 应用程序 QtWidget Application
  • 绘图工具:processon 在线绘图
  • 图标素材:iconfont 阿里巴巴矢量图标库、百度图片

功能介绍

项目主要实现基本的五子棋游戏需求:

  1. 重新开始游戏
  2. 鼠标选定位置下棋
  3. 裁判输赢(检测五连珠)
  4. 悔棋回到上一步
  5. 显示\隐藏下棋步数
  6. 开局前显示 UTC +8 时间,开局中记录比赛时长
  7. 提供两种下棋背景主题

演示说明

项目演示

受限于图床图片单张大小限制,更多演示请下载源码后运行自行查看

项目说明

  • 主菜单有游戏设置与关于更多,游戏设置放置关于游戏的一些操作设置,不能选的游戏设置图标会变暗并且不可选

  • 棋盘为标准五子棋棋盘,中心有五个定位点,行数与列数分别用 1~15 和 A~O 标明

  • 游戏进行时鼠标光标标识可下棋的区域,棋盘下方提示轮到哪方回合,游戏结束时不能在棋盘下棋并且计时停止,棋盘下方显示赢方

逻辑结构

游戏界面设计:参考了百度中找到的两张图

棋盘横竖各 15 条线,中心分别有五个定位点 (4,D) (4,L) (8,I) (12,D) (12,L),棋子落子在任意横竖两条线的交叉点。线间隔我们设置为 45,棋子直径设置为 18,棋盘最外层的边界线与主窗口应该有一定的留白间隔,棋盘与棋子宏定义好参数后借助 Qt 的绘图工具直接绘制在主窗口界面上

对棋子和棋盘采用类和对象进行封装,棋子记录自己的位置以及步数信息,棋盘主要提供存储棋子容器、对于棋子的操作以及落棋合理性的判断等相关信息。最终 mianWindow 中有一个棋盘类以及关于设置页面显示的函数

借助 Qt 的 paintEvent 函数每次显示游戏界面(绘制棋盘与棋子),游戏过程中触发函数的调用重新设置游戏界面并刷新(例如借助捕捉鼠标事件确定下的棋子并重新调用 paintEvent 函数刷新游戏界面呈现出动态的效果),计时器的实时显示也是如此只不过我们借助了 Qt 的计时器对象 Ttimer,将它设置为每 1000ms 自动触发 timetoShow 函数

代码分析

参数宏定义以及图片

宏定义帮助棋盘与棋子的绘制,显示棋盘行列数的时候没必要指明结束位置,画线需要是因为线的端点定位需要

qrc 文件是一种 xml 格式的资源配置文件,指定的路径是 .qrc 文件所在目录的相对路径。注意列出的资源文件必须位于 .qrc 文件所在的目录或者其子目录下。具体创建的方法见链接 前往了解

我们这里将项目用到的所以图标全部存储在 image.qrc 文件内部借助相对路径引入

棋子类 Chess

将棋子的属性设置为私有成员防止外界的访问

没什么好说的 😑,就瞅一眼吧

棋盘类 ChessBox

  • playStep 相当于计步器帮助每个棋子确定自己的步数
  • 定义一个 chessBox 用来记录棋盘的相关信息,chessList 存放下过的棋子的信息。我们确定某个位置时是借助 chessBox 确定是否有棋子以及棋子的颜色,在 chessList 中找到坐标对应这个棋子并显示步数
  • 裁判函数都是从当前的坐标沿着指定的方向查找是否有连着四个与当前坐标棋子颜色信息相同
  • 游戏只有两个背景主题所以 choice 设置为 bool 类型,每个取值各代表一个
  • 初始化函数需要将所有数据全部恢复为最初状态,二维数组 chessBox 的清空操作就是再防止一堆存储 0 的一维数组
  • 通过 isWin 函数调用水平、垂直、右斜、左斜四个方向裁判五连珠函数
  • 以水平方向裁判函数为例:设置两个边界判断标志,然后枚举向两个方向走的步数尝试查找周围棋子是否与当前坐标颜色信息相同,不同说明我们走到了这个颜色串的边界,否则就统计 +1,最终找到在这个方向周围 4 个棋子说明已经五连珠

主窗口类 MainWindow

  • 操作对象是棋盘对象

  • 声明一切刷新游戏界面的函数以及显示游戏界面的函数

  • 另外关于主菜单按钮的相应需要的 Action以及建立槽与 Action 信号的函数

  • timer 帮助程序自行每 100 ms 调用一次 timeToShow 函数显示时间信息

  • 关于 timeToShow 的函数比较复杂,需要针对不同的游戏行情况显示不同的时间信息,所以提供 flag 标记游戏是否已经开始进行,lastBeginlastEnd 用来帮助计算计时时间

  • 棋盘大小是固定的,为了保证棋盘和主程序窗口之间的留白固定将主窗口的大小指定大小,竖向多出 100 用来显示比赛信息以及时间
  • 调用 connecSignals 函数简历主菜单的按钮与槽的信号,timer 实例化并单独建立信号(特殊)

  • 鼠标的移动和按下都是只用设置对应不同区域的样式,和鼠标松开的函数设置一样这里不再啰嗦了

  • 为什么选择松开的时候记录落棋的信息呢?因为可能会有人按住拖动选择棋子的位置,如果我们是选择按下的时候记录落棋的信息了那么就事与愿违了

  • 记录颜色的信息应该是0、1切换的,因为黑方先手所以他的步数永远是奇数,而白方全是偶数,所以我们借助取模获取颜色信息

  • 只有在有效的区域内松开左键才会响应存入落子的信息并刷新界面,否则的话应该直接 return

  • 每次落子后都要判断当前这颗棋子是不是结束比赛的棋子,如果是的话就是将 chest.isOver 设置为 1,并且将有效范围缩小为 gapSize 以实现不能在棋盘内下棋

  • 根据下棋的步数确定是显示当前时间还是计时时间
  • 如果 chest.playerStep 是 1 说明已经开始游戏,记录当前节点的时间作为游戏开始的时间并用 flag 标记游戏已经开始,如果没有这个标记那么第一步与第二步棋子之间的思考时间则没记录
  • 如果游戏没有结束就是获取当前节点的时间与游戏开始时间 lastBegin 做差得出计时时间,否则的话就是记录下当前节点作为 endBegin,之后不再继续记录时间而是显示游戏结束时间与游戏开始时间的差实现游戏结束计时时间的定格
  • 重新开始的时候背景选项时不应该回到初始状态的,而是应该保持原样
  • 悔棋就是移除链表的最后一个棋子并将最后一个棋子对应的坐标上的颜色属性置 0
  • 显示\隐藏步数除了修改标志,按钮的显示的文本应该也发生改变

  • 弹出的关于信息用的是 QMassageBox 对象,它的 information 函数参数指明在哪个窗口显示什么标题,显示的文本内容以及关闭按钮的类型

  • 跳转链接目前没找到合适的函数还是借助了 system(),但是这样会中途弹出控制台的小黑窗体验不是很好

下面详细讲解游戏的核心函数 paintEvent,它是 Qt 内部提供的一个虚函数,特点是每次 update 后都会被重新调用一次。借助这个特点配合其他带有 update 的函数就可以实现动态效果,相当于一帧帧的将画面串联起来

  • 先是设置绘制棋盘的画笔画线和涂色信息以及背景图片
  • 截止 QpainterserRenderHint 函数可以保证直线画的顺滑(起到抗锯齿的作用)

  • 画线借助 drawLine,前两个参数指明线的一端,后两个参数指明线的另一端,参数和 Web 放置元素设置一样距离左边、上边多少距离
  • 显示文本借助 drawText,前两个参数指明文本框的位置,中间两个参数指明文本框的大小,最后是位置横竖居中以及文本信息
  • 绘制定位点的时候边框和上色都是黑色,借助 drawEllipse 在指定位置画椭圆(后两个参数设置为一样的就是圆)

  • chessList 中每拿出一个棋子,根据 chessBox 在对应位置存储的颜色信息绘制黑棋子或者白棋子
  • 黑棋子边框与上色都是黑色,如果需要显示步数那么字体颜色为白色
  • 白棋子边框为黑色,上色为白色,如果需要显示步数那么字体颜色为白色

在棋盘指定位置显示比赛信息,如果 chest.isOver 值为 1 说明比赛结束显示输赢信息,否则的话显示回合提示

不足总结

项目缺陷

  • 由于操作会引起界面的刷新,timeToShow 也在不断的刷新界面,运行起来有卡顿。显示事件以及记录时间应该有更好的方法实现
  • 比赛悔棋后有时候会出现计时时间一直显示为 0 的问题,应该是 timeToShow 存在 BUG
  • 鼠标样式不能做到根据位置信息自动切换,总是在单击一次左键后才会更新样式,目前不知道原因

个人总结

  1. 封装分层思想欠缺,验收作业时没有封装棋盘而是将它和主窗口内容放在了一起

还好代码比较简短,功能较少,在现场用 20 分钟就将棋盘类抽离出来了 😎,中途改了 100 多个报错 😵

  1. 学习查询新知识能力较差,Qt 的帮助文档全是英文确实不好读,但是网上也没能精确的查找到解决问题的方法,好多想要的函数都是看了很多篇垃圾文档才找到的
  2. 设计创新不够呀,上交作业是有人做的游戏比较新颖个性,自己还是停留在无数人写过的五子棋还好没翻车 😅(主要是不能粘贴别人的代码,否则临时让改都改不出来)