欢乐斗地主看起来是一个小游戏,但真正把它做到“可维护、可扩展、不卡顿”,需要解决的问题并不比一个中型桌面应用少。规则引擎必须独立于界面才能被单元测试,牌型识别的递归回溯要经得起飞机带翅膀这类复合牌型的考验,AI 搜索不能阻塞 UI 线程,网络对局还要处理 TCP 粘包。这些工程约束,恰恰是 C++/Qt 桌面棋牌项目最有价值的部分。
分层架构:让规则层不依赖任何 QWidget
项目的底线分层是数据模型层、逻辑控制层与 UI 视图层彻底解耦。Card 封装花色、点数与权值,CardGroup 负责手牌排序与频次统计,Player 派生出 HumanPlayer 与 AIPlayer。这些类不包含任何 UI 头文件,保证规则可以脱离窗口独立测试。GameController 继承 QObject,承载洗牌、叫分、出牌、结算的完整状态机,通过信号通知视图刷新,视图层只发操作请求、不修改核心规则。
牌型识别:递归回溯解决“飞机带翅膀”
牌型识别是棋牌项目的算法核心。简单牌型(单张、对子、三张、炸弹、王炸)靠频次统计即可判定,但飞机带翅膀需要同时满足两个条件:至少两组连续三张作为主体,剩余牌恰好充当“翼”。贪心法容易误判,必须用回溯:枚举每一个可能的三张点数,模拟移除,检查剩余能否配对,失败则恢复状态、尝试下一个候选。
enum BombType { NO_BOMB, NORMAL_BOMB, ROYAL_BOMB };
BombType detectBomb(const std::vector<Card>& hand) {
std::map<int, int> freq;
for (const auto& c : hand) freq[c.rank()]++;
if (freq.count(16) && freq.count(17)) return ROYAL_BOMB; // 大小王
for (const auto& [rank, cnt] : freq)
if (cnt == 4) return NORMAL_BOMB; // 普通炸弹
return NO_BOMB;
}
bool findAirplaneWithWings(std::map<int, int>& freq, int minLength = 2) {
std::vector<int> tripleRanks;
for (const auto& [rank, cnt] : freq)
if (cnt >= 3 && rank >= 3 && rank <= 14) tripleRanks.push_back(rank);
std::sort(tripleRanks.begin(), tripleRanks.end());
for (size_t i = 0; i <= tripleRanks.size() - minLength; ++i) {
int start = tripleRanks[i], len = 0;
for (int r = start; r <= 14; ++r) {
if (std::find(tripleRanks.begin(), tripleRanks.end(), r) != tripleRanks.end())
++len;
else break;
}
if (len < minLength) continue;
auto remaining = freq;
for (int r = start; r < start + len; ++r) remaining[r] -= 3;
int wingsNeeded = len, singles = 0;
for (const auto& [rank, cnt] : remaining)
if (cnt > 0 && rank <= 15) singles += cnt;
if (singles >= wingsNeeded) return true;
}
return false;
}detectBomb 用 rank=16/17 区分王炸与普通炸弹;findAirplaneWithWings 的关键在于复制频次表后做回溯,而不是在原表上直接修改,避免后续分支被污染。
AI 与线程:跨线程连接必须显式声明
AI 决策无论用权重贪心还是 Alpha-Beta 搜索,都不能跑在 UI 线程上。正确做法是把 AIPlayer 移入独立 QThread,通过信号槽把手牌状态传过去、把决策结果传回来。跨线程连接必须显式写 Qt::QueuedConnection,让代码审查者一眼看出这是一次异步投递,而不是普通函数调用。AI 侧最实用的基线是权重法:按“剩余手数最少、大牌保留最多”给候选拆牌方案打分,几十毫秒内即可完成 20 张手牌的估值,足够支撑流畅的对局节奏。
网络与渲染:协议先行,视图只做展示
联机版本采用“4 字节长度头 + JSON”的应用层协议,长度头解决 TCP 粘包,JSON 中的 title 字段负责将消息路由到登录、用户、游戏等模块。渲染侧,牌桌与手牌动画用 QGraphicsView/QGraphicsScene 承载比纯 QWidget 更合适,QGraphicsItemAnimation 配合 QTimeLine 可以实现出牌飞行动画。
这个项目真正的工程含量不在“能否发牌出牌”,而在规则层能否独立测试、复合牌型能否正确回溯、AI 线程与 UI 线程是否严格隔离。三者做到位,单机版才有资格向网络对局平滑扩展。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。