《头脑王者》是一款在线排位制答题小应用,我们希望在学习完 QT 后,能做一个类似的应用,那么在真正开始着手之前,将功能细化、拆分,待整体思路清晰后,再开始写代码。
抛开 排位制 和 在线 两个修饰语,答题这个功能,我们还是比较熟悉的。
如何答题?
在学完 C++ 的容器后,对数据的存储有了更加方便的选择,在 QT 中 进行“单机版”模拟时,我们先使用链表这个容器保存题目、选项、正确答案、实际选择答案
QList <string> Quest;// 题干QList <string> Answer1; // 选项 1QList <string> Answer2; // 选项 2QList <string> Answer3; // 选项 3QList <string> CorAnsw; // 正确选项QList <string> Choice; // 实际选项
此时,想要完成答题还需要一个定时器,通过这个定时器去触发一个事件,这个事件去完成实际选项的获取,题目和选项的切换(切换的方法有很多,可以利用 textEdit 的属性,也可以使用 stacked Widget 进行页面切换),得分的计算。在答题完毕时,通过消息对话框打印最终得分情况,那么这样“单机版”的答题也就完成了。
下一步,就是构建一个题库,每次从题库中抽取不同的题目,在构建这个题库时,我个人的想法是:表 包含 题干、选项、正确答案。
那么,实际答案的正确性如何判断因人而异,我想的是,在数据库中,就预先保存了正确答案是第几个选项(比如,有 A、B、C 三个选项,我将正确答案选项 2 保存在数据库中,这样 我通过控件的编号,就可以知道 你这题是否答对了)。
在创建题库时,可以使用 avicat Premium 进行数据库 和 表的建立,插入数据也很方便,但在QT中读出中文时,会出现一些乱码,暂时没能想到好的解决方法,只是使用了 .toLocal8bit() 应急,在随机抽题时,方法比较多就不多介绍了。
数据库建立好,大致就是下面这个样子;
单机版 + 数据库 实现后,我们来细化 服务器 和 客户端,将功能抽离,首先在 Client端,我们先建立好主界面、登录界面、注册界面,以及一些小细节的处理。。。
实现一个简单的主界面,有注册与登录两个功能按钮,两个按钮的槽函数分别实现注册与登录信息的获取及传递,这里顺便说一下,界面的跳转,根据个人喜好,可以在需要的时候, new 一个新的,并且把不需要的界面给 close 掉,当需要返回上一级时在 new 一个;这个方法我没使用,我使用的是一种比较 low 的方法,首先建立好所需的窗口对象,界面跳转时使用 hide 和 show;
我做出的样子是这样子的。。。。(美工我就不管咯)
主界面
登录
注册
这样,简单的注册与登录界面就完成了,下面,我们要尝试搭建服务器,并且完成用户的登录及注册;
搭建服务器的几个步骤,这里也说一下:
// 创建服务器_tcpServer = new QTcpServer(this);// 监听ip 地址和端口_tcpServer->listen(QHostAddress::Any, 9999);// 等待客户端连接connect (_tcpServer, SIGNAL(newConnection()), this, SLOT(acceptClient()));
与之对应的客户端也需要进行相关配置的设置:
// 服务器的 IP 地址_IP = "192.168.1.127";// 服务器的 端口号_Port = 9999;_tcpSocket = new QTcpSocket(this);connect(_tcpSocket, SIGNAL(connected()), this, SLOT(connectServer()));
当客户端点击了注册或者登陆按钮时,将注册的信息都发给服务器,服务器接收到后,插入数据库中;发送时,使用了 QDataStream 进行发送,将流的前两个字节保存其有用数据的长度,读的时候,也是这样,先读取前两个字节的长度,在读后续内容;
发送// infomation 为保存信息的结构体infomation.cmd = REGISTER;infomation.name = name;infomation.passwd= passwd;infomation.con_passwd = con_passwd;infomation.tele = tele;// block 为QByteArray 的对象,out 为 QDataStream 的对象,通过输出流对象 out 保存数据并发送QByteArray block;QDataStream out(&block, QIODevice::WriteOnly);out<<(qint16)0;out<<infomation;out.device()->seek(0);out<<(qint16)(block.size() - sizeof(qint16));_tcpSocket->write(block, block.length());
接受QDataStream in(client);blocksize = 0;if(blocksize == 0){if(client->bytesAvailable() < (int)sizeof(qint16)){return;}in >> blocksize;}if(client->bytesAvailable() < blocksize){return;}in >> Rec_msg;
在搭建好服务器和客户端后,所需要完成的,就是注册与登录,这两个功能实现起来比较简单,无非在一些细节上需要考虑,例如说,注册填写的密码是否正确、昵称是否可用,这些在实现起来的时候,可以自行选择,这两部分如何实现,我在这就不多费口舌了,和之前写的聊天室基本相同,值得注意的是线程在什么时刻开启,以及相关的信号与槽如何设计,留给读者自行思考,我会在下一篇博客公布源码,还是希望你能自己思考。着重来讲一下,登录成功之后的事情吧;
首先,我做了一个这样的界面
,在登陆成功之后,我希望向用户展示 Ta 的昵称、段位和积分,这个实现起来也不难,在登录成功后,服务器将你所需要的信息发送给你就 OK 了。
当你点击开始匹配的时候,会向服务器发送一个请求,请求服务器将你放入匹配列表中,当匹配列表中出现符合与你对战的情况时,匹配成功,服务器会发送匹配成功的标志告诉你,这样你就可以做答题前的准备了(界面的切换,相关控件的初始化等等);
那么,服务器是如何做到两个客户端之前同步答题的呢?
(这个系统,我没有对匹配进行段位限制,通俗的说,所有用户是随机组队的,比如说,你是黄金的,你也有可能遇上星耀。。。)
其实,当服务器一开始的时候,就会存在一个匹配列表(我使用的是一个 Vector 容器)用来存放申请匹配的用户的信息,存入的顺序完全就是客户端申请的顺序。服务器会有一个定时器,每秒去监测一些容器当前保存的用户数量,一旦 >= 2 的情况出现时,将最前面的两个进行组队,并且将这两个客户端从容器里踢出去。那么,对这两个匹配成功的客户端发送匹配成功的信息后,会立即从题库中抽取题目,分别在各自的线程中,将题目一题题的发送过去,这样就是一个简单的发题过程。
既然有发题,就会有接收题目的过程,并且在接收题目的时候,会有一些细节需要处理。什么细节?在第一次发送题目的时候,客户端需要做的事情有如下两件:第一、将接收到的题目先暂存在自己的容器中(读者会问,为什么不直接打印在答题界面呢?稍后为您揭晓);第二、触发答题定时器、用来切换题目、选项和计时所用。那么,之后的接收题目,只需要将题目暂存容器里就好了,切换题目的工作完全交给客户端自己去完成。这样的话,即便你服务器和客户端之间信息传递存在一定的时间差,也不会因为这段时间差而影响用户的正常答题、更直白的说,客户端的计时器是真正控制用户答题的、而服务器的计时器只是控制向客户端发送题目的时间间隔,因此我没有采用服务器的定时器去控制客户端的答题。
先看一下,我做的答题界面,
在这个界面,需要展示答题剩余时间、双方的得分情况、题干、选项;其中,倒计时、“你的得分”、题干、三个选项都是客户端依赖于自己的定时器去解决的、唯独“Ta的得分”,是要靠服务器发送过来的;
这里便涉及到了答题过程中会触发到的信号与槽了,在每个客户端点击某一选项时,都会去判断正确与否,一旦正确了,会改变自己分数,还会将这个分数发送给服务器,再让服务器发给“对手”,当“对手”收到后,刷新一下就好啦!当所以题目答题完毕后,系统会根据你的累计积分,对数据库中的信息进行更新,同样对这个显示界面也会刷新,那么这样,整个答题系统差不多就算完成了。
如果觉得《《头脑王者》--排位答题系统构思》对你有帮助,请点赞、收藏,并留下你的观点哦!