150行代码写一个Qt井字棋游戏
照例先演示一下:
QT井字棋游戏,可以悔棋。
会在鼠标箭头处跟随一个下棋方的小棋子图标。
棋盘和棋子是自己画的,可以自行在对应的代码处更换自己喜欢的图片,不过要注意尺寸兼容。
以棋会友:
井字棋最关键的就是下棋了,我们需要重写窗口的鼠标点击事件,并且根据坐标进行判断。
如果鼠标点击的范围在棋盘里,那么就接着判断此处能否下棋(是否已经有棋子在这个地方了)。
我这里下棋的逻辑是拿一个3*3的vector来缓存下棋状态 ,0为没棋,1为白棋,2为黑棋。
只需要更新缓存更新绘图事件,让绘图事件按照缓存来重绘即可达到下棋的效果 。
每次下棋之后,轮到对方下棋(白棋下完黑棋下),需要将记录谁下棋的标志更新。
为了悔棋的功能,还需要将下棋的坐标存在一个vector里。
void TTT::mousePressEvent(QMouseEvent* e){ //按下鼠标int x = e->x(), y = e->y();if (x >= 65 && x <= 490 && y>=115 && y<=540 ) { //是否在棋盘范围内x -= 65, y -= 115; //经过测试计算得到的结果,因为棋盘在中间,因此需要减去这些像素值才可以准确判断点击位置对应的缓存位置x /= 140, y /= 140; //获取对应的缓存下标if (cache[x][y] == 0) { //如果该位置没有棋子就下棋if (iswhite) cache[x][y] = 1;else cache[x][y] = 2; iswhite = !iswhite; //更新下棋方last.push_back(vector<int>{x,y}); //添加缓存,用于悔棋check(); //检测是否赢棋以及和棋update(); //手动调用绘图事件}}
}
并且还需要检测落完子之后有没有人赢棋以及是否和棋。
重写一个函数用于检测,因为井字棋是3*3的比较简单,赢棋的情况就8种,所以直接用8条if来判断(试过用for循环来检测,结果还不如8条if来的简洁)。
void TTT::check(){ //检测是否赢棋已经是否和棋bool iswin = false;if (cache[0][0] == cache[0][1] && cache[0][0] == cache[0][2] && cache[0][0] != 0) iswin = true;if (cache[1][0] == cache[1][1] && cache[1][0] == cache[1][2] && cache[1][0] != 0) iswin = true;if (cache[2][0] == cache[2][1] && cache[2][0] == cache[2][2] && cache[2][0] != 0) iswin = true;if (cache[0][0] == cache[1][0] && cache[0][0] == cache[2][0] && cache[0][0] != 0) iswin = true;if (cache[0][1] == cache[1][1] && cache[0][1] == cache[2][1] && cache[0][1] != 0) iswin = true;if (cache[0][2] == cache[1][2] && cache[0][2] == cache[2][2] && cache[0][2] != 0) iswin = true;if (cache[0][0] == cache[1][1] && cache[0][0] == cache[2][2] && cache[0][0] != 0) iswin = true;if (cache[0][2] == cache[1][1] && cache[0][2] == cache[2][0] && cache[0][2] != 0) iswin = true;if (iswin) { //如果有人赢棋QString who;if (cache[(*(last.end() - 1))[0]][(*(last.end() - 1))[1]] == 1) { //根据最后一个落子是谁来判断谁赢棋who = QString::fromLocal8Bit("白棋");}else {who = QString::fromLocal8Bit("黑棋");}//弹出提示框,是否继续游戏int check=QMessageBox::question(this, who+QString::fromLocal8Bit("赢了"), who+QString::fromLocal8Bit("赢了,是否重新开始"));if (check == QMessageBox::Yes) {//如果继续游戏,则清空下棋记录,恢复棋盘清空,更新绘图事件cache = { {0,0,0},{0,0,0},{0,0,0} };last.clear();update();return;}//不继续游戏就退出程序.exit(0);}for (int i = 0; i < 3; i++) { //判断是否和棋,只要有一个地方是0(没下棋)就是没和棋,直接returnfor (int j = 0; j < 3; j++) {if (cache[i][j] == 0) return;}}//弹出提示框,和棋,是否继续游戏,逻辑和上面赢棋的逻辑一样int check = QMessageBox::question(this, QString::fromLocal8Bit("和棋"), QString::fromLocal8Bit("和棋,是否重新开始"));if (check == QMessageBox::Yes) {cache = { {0,0,0},{0,0,0},{0,0,0} };last.clear();update();return;}exit(0);
}
落子无悔:
悔棋这个功能我是后面大致都写完了才想加上去的,然后窗口的大小,棋子的大小,棋盘的大小以及他们的坐标位置我都设计完了,没地方再插一个按钮来悔棋了,所以我直接加在了菜单栏里。
我们在下棋的时候就有把每一步下棋的坐标存起来,想要悔棋的话,我们只需要把最后一个下棋的落子坐标取出,把棋盘缓存中对应的位置改为0(没下棋),然后在更新绘图事件即可。
不要忘了把下棋方再改回去,并且把下棋记录的最后一个删去。
void TTT::initMenubar() { //初始化菜单栏QMenuBar* qb = new QMenuBar(this);QMenu* item = new QMenu(QString::fromLocal8Bit("选项"), this);QAction* restart = new QAction(QString::fromLocal8Bit("重新开始"), this);QAction* revoke = new QAction(QString::fromLocal8Bit("悔棋"), this);connect(restart, &QAction::triggered, [=]() { //重新开始//清空下棋记录,恢复棋盘初始状态,更新绘图事件cache = { {0,0,0},{0,0,0},{0,0,0} };last.clear();update();});connect(revoke, &QAction::triggered, [=]() { //悔棋if (last.size() > 0) { //如果有下棋记录才能悔棋//将最后一个下棋的位置设为0(没下棋),并且需要把下棋方再变回去(取个反),再把最后一个下棋记录删去cache[(*(last.end() - 1))[0]][(*(last.end() - 1))[1]] = 0;iswhite = !iswhite;last.pop_back();update();}});item->addAction(restart);item->addAction(revoke);QMenu* about = new QMenu(QString::fromLocal8Bit("关于"), this);QAction* me = new QAction(QString::fromLocal8Bit("我"), this);QAction* help = new QAction(QString::fromLocal8Bit("帮助"), this);QAction* quit = new QAction(QString::fromLocal8Bit("退出"), this);quit->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));connect(quit, &QAction::triggered, this, &QMainWindow::close);connect(me, &QAction::triggered, [=] {QMessageBox::information(this, QString::fromLocal8Bit("这里是折途"), QString::fromLocal8Bit("bilibili:折途想要敲代码/折途想要长高高\nCSDN:折途\n微信公众号:折途想要敲代码"));});connect(help, &QAction::triggered, [=] {QMessageBox::information(this, QString::fromLocal8Bit("使用帮助"), QString::fromLocal8Bit("井字棋游戏 TicTacToe(TTT)"));});about->addAction(me);about->addAction(help);about->addAction(quit);qb->addMenu(about);qb->addMenu(item);qb->setFixedHeight(50);this->setMenuBar(qb);
}
脑袋跟着屁股走:
脑袋跟着屁股走,棋子跟着鼠标走。
从开头的动图可以看出鼠标剪头所指有下棋方的棋子小图标,要做到这个就需要重写窗口的鼠标移动事件,每次鼠标移动我们都更新鼠标的坐标,然后重写调用绘图事件,在相应的位置画上小棋子。
如果直接重写鼠标移动事件函数的话只有在鼠标点击的时候才会调用,我们需要在加上构造函数的开头加上行代码用于设置跟踪鼠标:
setMouseTracking(true); //设置跟踪获取鼠标坐标,用于更改鼠标指向的棋子
然后重写鼠标移动事件:
void TTT::mouseMoveEvent(QMouseEvent* e){ //实时获取鼠标坐标,用于修改鼠标指向的小图标mouse[0] = e->x();mouse[1] = e->y();update();
}
然后剩下就是绘图事件的工作了。
绘图:
其实要绘的图不多,一个是棋盘,一个是下的棋子,另一个就是跟着鼠标的棋子小图标。
void TTT::paintEvent(QPaintEvent* e){QPainter* p = new QPainter(this);QPixmap board;board.load(":/image/board.png");p->drawPixmap(50,100,board);//绘制已经下过的棋for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (cache[i][j] != 0) {QPixmap piece;if (cache[i][j] == 1) piece.load(":/image/white.png"); //根据缓存的数值决定下什么颜色的棋else piece.load(":/image/black.png");p->drawPixmap(150*i+75,150*j+125,piece); //下在格子里}}} //修改鼠标坐标的指向的图片以及位置QPixmap mou;if (iswhite) {mou.load(":/image/white.png");}else {mou.load(":/image/black.png");}mou = mou.scaled(50, 50);p->drawPixmap(mouse[0] - 30, mouse[1] - 30, mou); //让鼠标在图标的中间p->end();
}
免费领取完整代码:
完整的代码我已经上传到CSDN了,大家可以进入我的主页找到对应资源直接免费下载。
也可以关注我的微信公众号 折途想要敲代码 回复关键词“qt井字棋”免费下载完整代码。
我上传的是VS的完整工程文件,已经自己绘制的图片,如果小伙伴用的是QtCreater,可以直接把cpp和h的文件内容复制过去,再把资源文件配置一下就好啦。
相关文章:

150行代码写一个Qt井字棋游戏
照例先演示一下: QT井字棋游戏,可以悔棋。 会在鼠标箭头处跟随一个下棋方的小棋子图标。 棋盘和棋子是自己画的,可以自行在对应的代码处更换自己喜欢的图片,不过要注意尺寸兼容。 以棋会友: 井字棋最关键的就是下棋了…...
k8s概念-controller
Controller作用和分类 controller用于控制pod 参考: 工作负载资源 | Kubernetes 控制器主要分为: Deployments 部署无状态应用,控制pod升级,回退 ReplicaSet 副本集,控制pod扩容,裁减 ReplicationController(相当于ReplicaSet的老版本,现在建议使用Deployments…...

Gis入门,根据起止点和一个控制点计算二阶贝塞尔曲线(共三个控制点组成的线段转曲线)
前言 本章讲解如何在gis地图中使用起止点和一个控制点(总共三个控制点)生成二阶贝塞尔曲线。 三阶贝塞尔曲线请参考下一章《Gis入门,使用起止点和两个控制点生成三阶贝塞尔曲线(共四个控制点)》 贝塞尔曲线(Bezier curve)介绍 贝塞尔曲线(Bezier curve)是一种数学…...

第1集丨Vue 江湖 —— Hello Vue
目录 一、简介1.1 参考网址1.2 下载 二、Hello Vue2.1 创建页面2.2 安装Live Server插件2.4 安装 vue-devtools2.5 预览效果 一、简介 Vue(读音 /vjuː/, 类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设…...

PCB制版技术
1、在头脑里形成一个原理图----现在就下载AD9盖版,诞生了一个问题,电路板去哪里买,买了怎么焊接电路和芯片,怎样流程化批量制作电子产品 1.1 形成一个PCB板,形成一个结构 1.2 焊接,嫁接,组装等 …...
大数据课程E7——Flume的Interceptor
文章作者邮箱:yugongshiyesina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Interceptor的概念和配置参数; ⚪ 掌握Interceptor的使用方法; ⚪ 掌握Interceptor的Host Interceptor; ⚪ 掌握Interceptor的…...

P2P网络NAT穿透原理(打洞方案)
1.关于NAT NAT技术(Network Address Translation,网络地址转换)是一种把内部网络(简称为内网)私有IP地址转换为外部网络(简称为外网)公共IP地址的技术,它使得一定范围内的多台主机只…...
Gof23设计模式之桥接外观模式
1.概述 又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度࿰…...

微服务性能分析工具 Pyroscope 初体验
Go 自带接口性能分析工具 pprof,较为常用的有以下 4 种分析: CPU Profiling: CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置…...

工作记录------单元测试(持续更新)
工作记录------单元测试 之前的工作中从来没有写过单元测试,新入职公司要求写单元测试, 个人觉得,作为程序员单元测试还是必须会写的 于此记录一下首次编写单元测试的过程。 首先引入单元测试相关的依赖 <dependency><groupId>…...

C#再windowForm窗体中绘画扇形并给其填充颜色
C#再windowForm窗体中绘画扇形并给其填充颜色 Graphics graphics this.CreateGraphics();graphics.SmoothingMode SmoothingMode.AntiAlias;int width this.Width;int height this.Height;h this.Height;w this.Width;Rectangle rct new Rectangle(0 - h / 6, 0 - h / 6…...

MBA拓展有感-见好就收,还是挑战到底?MBA拓展有感-见好就收,还是挑战到底?
今天看到新闻提到某位坚持了14年高考的同学滑档,让人心生感叹:无论在日常工作还是生活中,选择都是非常重要的。不由想起前段时间我参加研究生新生拓展时的一些感悟,和大家分享一下。 事情的起因是拓展活动中的一个分队竞技类的活…...
综合布线系统光缆分类及其特点?
综合布线系统光缆是一种用于数据传输和通信的电缆,常用于建筑物内部网络和通信系统的布线。光缆采用光纤作为传输介质,能够以光的形式传输大量数据,具有高带宽、低延迟、抗干扰等特点,适用于高速数据传输和长距离通信需求。 光缆…...

前端构建(打包)工具发展史
大多同学的前端学习路线:三件套框架慢慢延伸到其他,在这个过程中,有一个词出现的频率很高:webpack 。 作为一个很出名的前端构建工具我们在网上随便一搜,就会有各种教程:loader plugin entry吧啦吧啦。 但…...
【数据可视化】(一)数据可视化概述
目录 0.本章节概述 一、数据可视化 1、什么是数据可视化? 2、数据可视化的好处 3、数据可视化的用途 二、数据探索 1、数据相关工具的使用情景: 2、探索性查询 三、数据挑战 1、什么是数据挑战?...

GoogleLeNet Inception V2 V3
文章目录 卷积核分解第一步分解,对称分解第二步分解,非对称分解在Inception中的改造一般模型的参数节省量可能导致的问题 针对两个辅助分类起的改造特征图尺寸缩减Model Regularization via Label Smoothing——LSR问题描述,也就是LSR解决什么…...

【css】背景图片附着
属性:background-attachment 属性指定背景图像是应该滚动还是固定的(不会随页面的其余部分一起滚动)。 background-attachment: fixed:为固定; background-attachment: scroll为滚动 代码: <!DOCTYPE h…...

解决运行flutter doctor --android-licenses时报错
问题描述: 配置flutter环境时,会使用flutter doctor命令来检查运行flutter的相关依赖是否配好。能看到还差 Android license status unknown.未解决。 C:\Users\ipkiss.wu>flutter doctor Flutter assets will be downloaded from https://storage.…...

在使用Python爬虫时遇到503 Service Unavailable错误解决办法汇总
在进行Python爬虫的过程中,有时会遇到503 Service Unavailable错误,这意味着所请求的服务不可用,无法获取所需的数据。为了解决这个常见的问题,本文将提供一些解决办法,希望能提供实战价值,让爬虫任务顺利完…...
小研究 - 主动式微服务细粒度弹性缩放算法研究(一)
微服务架构已成为云数据中心的基本服务架构。但目前关于微服务系统弹性缩放的研究大多是基于服务或实例级别的水平缩放,忽略了能够充分利用单台服务器资源的细粒度垂直缩放,从而导致资源浪费。为此,本文设计了主动式微服务细粒度弹性缩放算法…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...