# Socket 网络编程斗地主大作业 设计文档
- ## 程序架构及设计思路
![](https://www.writebug.com/myres/static/uploads/2021/11/10/a8a4a594adeadd89ba61fba4df8adf36.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/11/10/5df12b791fbf6c7b853c20ce7fafef83.writebug)
我写了一个 Card 类,继承自 QWidget,用来存放扑克牌的花色和点数,以及是否被选中。我重写了 paintEvent 函数,画出扑克牌。如果牌被选中,则上移 30 像素,重绘该扑克牌。
![](https://www.writebug.com/myres/static/uploads/2021/11/10/d651b0b6821be06fddeb0771d1b45995.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/11/10/0881735ed62cc0d1d8645825e73ae1d9.writebug)
我还写了一个 CardCombine 类,用来判断玩家所出的牌是什么类型,是否合法,点数是否比上一位玩家大。具体来说,先判断该组牌是空、单牌、顺子、对子、三带(一、二)、四带二、飞机还是炸弹,根据类型判断牌的优先级(王炸 > 炸弹 > 普通牌 > 空 > 非法),优先级高则可出,优先级相同则比较点数。
在准备界面,显示一个“开始连接”按钮,当三个用户都按下这一按钮后(A 知道自己是否和 B、C 连上,B 和 C 连上后向 A 发送消息,A 收到消息后,向 B、C 下达开始指令),页面跳转为游戏界面,update()函数重绘界面背景。我在 A 用户端实现随机发牌和随机确认地主,并将各方的牌和由谁开始叫地主的信息传送给 B 和 C。无论是哪方开始,叫地主顺序和出牌顺序始终是 A-B-C-A。三家都确认是否叫地主后,由 A 用户端确认地主是谁,将信息发送给 B、C,开始游戏。用户出牌后会将出牌信息发给另外两个用户,出牌情况将显示在三个用户的界面上,并更新 last(CardCombine)作为下一次出牌的比较对象。每一次出牌后都会更新三个用户端上各方的余牌数量,并由各方自行判断是否轮到自己出牌和游戏是否结束(一方剩余牌数为 0)。若游戏结束,则显示输赢,并出现“再玩一次”和“退出”两个按钮。若三位玩家都都选择再玩一次,则游戏界面初始化,重新开始随机发牌和随机确认地主。点击退出则程序停止。
## 客户端工作流程
我在 A 用户这里创建了两个 listenSocket,等待 B 和 C 的连入;在 B 用户这里创建了一个 listenSocket,等待 C 的连入。当 B 和 C 连上 A、C 连上 B 之后,利用 connectToHost 和 nextPendingConnection 函数在每个用户端各创建两个 readWriteSocket,实现两两用户之间可以互发消息,通信完成。
**通信协议**
![](https://www.writebug.com/myres/static/uploads/2021/11/10/a7039adda1b372e6d3dcd25e26841c4b.writebug)
- 当一个用户想给另一个用户发送一个 QString 字符串消息时,他需要先获得该字符串的长度,将其转换为一个长度为 2 的 QString,和消息合并起来,再将整个字符串写给另一个用户。
![](https://www.writebug.com/myres/static/uploads/2021/11/10/e93c89879e0d5c3bcdacfa50f4b84197.writebug)
而当用户接收消息时,他需要先读出前两个字符,将其转换为 int 格式,获取该消息的长度信息,再读入完整的消息。这样一来,避免了 QTcpSocket 将一个消息分成两个消息写出,或是将两个消息合并成一个消息读入。
为了避免粘包,我加上了一行代码“while(!readWriteSocketC->atEnd())”,来确保每一条消息都会被读到。否则有可能出现两次 write 只触发一个 readyread 信号的情况,导致第二条消息未被读入。
## 规则设计流程
**连接**
连接时需要确认三个用户两两互联。A 是等待 B 和 C 连接的一方,故 A 用户端可以确认 AB、AC 之间是否连上。而 B 是等待 C 连接的一方,所以 B 用户端可以确认 BC 之间是否连接。当 BC 连上之后,B 给 A 发信息“begin”,A 收到消息后判断三者是否两两互联成功,若成功,则通知 B、C 开始游戏,自己也转入游戏界面。A 在连上 B 和 C 之后也会各做一次判断,直到两两连接成功。
**叫地主**
由 A 用户端随机指定开始叫地主的用户,并发送信息(“jiaodizhu”)让其转入叫地主界面。每一个用户叫完之后将结果通知给另外两个玩家,另外两个玩家需显示并判断是否轮到自己叫地主。当三人都完成叫地主,由 A 用户端判断谁是地主,将结果发给 B 和 C,由地主开始出牌。
**出牌**
每一个用户储存一个 CardCombine 类 last,用来储存上一次有效出牌(不包括不出)。轮到用户出牌时,客户端判断哪些牌被用户选中(Card 类中有一个 bool 成员 isSelected),将这些牌和 last 作比较(compareTo 函数),若优先级更高或点数更大,则可以出牌。用户将出牌信息传给另外两个用户,另外两个用户需将这些牌显示在界面上,更新自己的 last,并判断是否轮到自己出牌。
判断出牌合法性
先判断牌的类型:
- 空:张数为 0;
- 单牌:张数为 1;
- 对子:张数为 2 且两张牌点数相同;
- 顺子:张数大等于 5,每一张牌都比前一张牌点数大一
- 连对:牌数大等于 6 且为双数,重新排序后每一张偶数牌都满足比前一张偶数牌大一
- 三张:牌数为 3 且 3 张点数相同
- 三带一:牌数为 4 且有三张牌点数相同
- 三带二:牌数为 5 且有三张牌点数相同,另外两张牌点数也相同
- 四带二:牌数为 6 且有四张牌点数相同,或牌数为 8 且有四张牌点数相同,另外四张两两点数相同
- 飞机:牌数大于等于 6 且是三的倍数,重新排序后每一张模 3 余数相同的牌都满足比前一张大一
- 飞机带单牌:有飞机存在且飞机数量等于单牌数量
- 飞机带对子:有飞机存在且飞机数量等于对子数量
- 炸弹:牌数为 4 且 4 张点数相同
- 王炸:牌数为 2,点数分别为大王和小王
判断牌的优先级:王炸 > 炸弹 > 普通牌 > 空 > 非法
优先级高则可出,优先级相同则比较点数,点数大则可出。
## 心得
架构要事先规划好,否则最后重开一局的时候初始化很困难。
Ui 界面的设置尽量写在一起,否则东一个按钮显示、西一个 label 位置会让程序看起来很混乱,也不利于最后重新读并梳理自己的代码。
神仙别闹
- 粉丝: 4156
- 资源: 7485
最新资源
- 毕业设计-基于健身房管理系统,健身会所 全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于论文选题系统的设计与实现全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于论文管理系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于农产品溯源系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于人脸识别打卡系统,qt + opencv + mysqlsqlite全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于设备故障预测系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于实验室设备管理系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于书籍推荐系统全部资料+详细文档+高分项目+源码.zip
- 基于STM32单片机的短时应急电源.zip
- 毕业设计-基于网上订餐系统全部资料+详细文档+高分项目+源码.zip
- 基于HTML和CSS的动态3D圣诞树效果实现
- 毕业设计-基于微博用户情感分析系统Django+vue全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于文章推荐系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于校友网管理系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于校园失物招领系统全部资料+详细文档+高分项目+源码.zip
- 毕业设计-基于校园新闻系统,使用SpringBoot、SpringMVC、Mybatis、Bootstrap全部资料+详细文档+高分项目+源码.zip
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈