
简介基于QTC开发的德州扑克游戏项目源码包以答辩评分98分的毕业设计项目形式呈现面向计算机、通信、自动化等相关专业学生与开发者可用作课程设计、大作业或毕业设计参考。项目代码经调试可运行适合入门Qt界面编程和C项目实践也便于在此基础上扩展功能。压缩包共97个文件大小约9.62MB包含cpp/h源文件、ui界面布局、qrc资源文件、doc与README文档以及大量png/jpg素材和图标。源文件覆盖主程序、牌桌交互、AI策略与牌堆管理模块配合规则文档和运行截图能清晰理解发牌、下注、胜负判定与AI决策流程。工程配置中还包含pro、qrc、ts等文件便于梳理Qt项目组织方式与多语言设置结合素材可快速定位界面控件和逻辑关联。目前已有138人学习/下载目录结构清晰提供完整界面素材与工程配置可帮助读者从UI到逻辑完整复盘适合学习、演示或二次开发。1. 基于 QT C 的德州扑克毕业设计能跑通完整对局的源码工程如果你正在找一份能直接跑起来的 C 课程设计又不想做控制台黑窗的图书管理系统这套基于 QT 开发的德州扑克游戏源码值得拆一个晚上。它不是那种只有界面截图、点开代码却跑不通的半成品毕设——洗牌、发牌、加注、弃牌、亮牌、裁定胜负整套对局流程是闭环的AI 玩家会根据牌面强弱选择跟注、加注或弃牌庄家会自动统计彩池并分配筹码。压缩包里带了完整的 Qt 工程文件、牌面与界面图片、一份 docs/rule.doc 规则文档和 README还有两张运行截图 runtime1.png、runtime2.png可以对照结果验证。适合计算机、通信、自动化专业拿来做课程大作业或毕设参考也适合想借一个完整项目把 Qt 信号槽、事件循环、C 枚举与容器用起来的人。我拆完最深的印象是文件不多但界面层和逻辑层分得比较干净。2. 项目结构与核心类从 main.cpp 到 game.cpp 的完整调用链拿到压缩包第一步别急着点开 .pro 就点运行。先把文件按职责分一次类这个项目大致分成四块入口main.cpp、界面层choose、game 的 .ui 与 .cpp、逻辑层poker、pokerheap、banker、player、ai、资源与文档res.qrc、docs/rule.doc、poke-2_zh_CN.ts。2.1 文件清单与职责划分文件职责main.cpp程序入口创建 QApplication 并加载启动界面choose.h/.cpp/.ui开局前的模式选择与参数设置界面game.h/.cpp/.ui主游戏界面牌桌绘制、操作按钮、筹码显示table.h/.cpp牌桌结构座位管理、公共牌位置分布poker.h/.cpp扑克牌定义、牌型评定与比大小pokerheap.h/.cpp牌堆洗牌算法与发牌接口banker.h/.cpp庄家下注轮次、彩池累计、最终结算player.h/.cpp玩家对象手牌、筹码、跟注与加注状态ai.h/.cpp电脑玩家决策choose.ui / game.uiQt Designer 生成的界面布局文件res.qrc资源集合管理 images 目录下的所有 png/jpgpoke-2.proqmake 工程文件指定 QT 模块与源文件列表docs/rule.doc规则说明文档含牌型大小与流程poke-2_zh_CN.tsQt Linguist 翻译文件界面中文本地化入口这个表格基本就是整个项目的骨架。界面层只负责显示和收集鼠标事件业务逻辑全部下沉到 table、banker、player 这些普通 C 类里。这样做的好处是按钮的槽函数里基本看不到复杂算法最多是几个 setVisible、setEnabled 和信号发射真正的规则判断都封装在 game.cpp 的私有方法里。后面如果要改成联机版只需要把 game.cpp 里本地直接调用的逻辑替换成网络消息收发界面层几乎不用动。2.2 main.cpp 入口QApplication 与启动窗口的选择main.cpp 是所有 Qt Widgets 项目的起点顺序基本是固定的构造 QApplication实例化启动界面进入事件循环。这个工程里的入口逻辑可以理解成这个样子#include QApplication #include choose.h int main(int argc, char *argv[]) { QApplication app(argc, argv); // 设置应用名与组织名后续 QSettings 读配置时依赖这两行 app.setApplicationName(PokerGame); app.setOrganizationName(GraduationProject); Choose choose; choose.show(); return app.exec(); }逻辑说明第 5、6 行的 setApplicationName 与 setOrganizationName 在课程设计里经常被忽略但如果以后要保存用户配置——比如联机版的服务器地址、玩家昵称——QSettings 会把配置写到以这两个名字命名的目录里不设置会出现配置乱放的问题。第 9 行先显示 Choose 而不是直接进 Game把“选择模式”和“对局过程”拆成两个界面后续想加联机入口只需要在 Choose 上多放一个按钮。参数说明app.exec() 会阻塞在这里直到最后一个窗口关闭才返回。如果在 choose.show() 之后立刻去构造 Game两个窗口会同时出现正确做法是先让用户选择再由 Choose 内部创建 Game这个就是 2.3 要说的窗口切换。2.3 choose 与 game 的窗口切换构造即初始化choose.ui 里的核心交互是确定玩家人数、起始筹码然后点击开始。确认后choose.cpp 的槽函数里会把参数收集起来再创建游戏窗口void Choose::onBtnStartClicked() { int playerCount ui-spinBoxCount-value(); int initChips ui-spinBoxChips-value(); Game *game new Game(playerCount, initChips); game-show(); this-close(); }逻辑说明把玩家数和起始筹码通过构造函数传给 GameGame 内部在构造函数里完成座位初始化、手牌清空、庄家指针赋值而不是提供一个 setParams() 再手动调用一堆初始化方法。对毕设规模的工程这种“构造即初始化”最简单也不容易漏步骤。参数说明playerCount 决定 player 数组的长度initChips 是所有玩家的起始筹码底数。AI 在决策时会把当前筹码量作为重要输入如果你把 initChips 调得很小比如 200会看到电脑玩家频繁 all-in——因为盲注相对筹码比例变大等待好牌的成本变高了这其实是符合真实扑克策略的不是 bug。2.4 poke-2.pro 与 res.qrc构建和资源的两个隐藏关键点poke-2.pro 决定整个项目在 qmake 体系下如何被构建逐行看一遍能避免很多编译期报错QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET PokerGame TEMPLATE app SOURCES main.cpp \ choose.cpp \ game.cpp \ table.cpp \ poker.cpp \ pokerheap.cpp \ banker.cpp \ player.cpp \ ai.cpp HEADERS choose.h \ game.h \ table.h \ poker.h \ pokerheap.h \ banker.h \ player.h \ ai.h FORMS choose.ui \ game.ui RESOURCES res.qrc逻辑说明FORMS 声明的 .ui 文件会在编译时被 uic 工具转换成 ui_choose.h、ui_game.h这两个头文件是自动生成的不该手改。RESOURCES 指向 res.qrc所有牌面图片会被编进最终可执行文件里所以部署时不需要额外拷贝 images 目录代价是稍微增大 exe 体积。TARGET 是最终生成的可执行文件名。参数说明如果在别的机器上打开 .pro 后出现“无法打开 ui_xxx.h”的报错九成是 FORMS 里漏了对应 .ui 文件或者手改了 ui 头文件后与重新生成的版本冲突。把 .ui 交给 Qt Designer 修改然后重新构建一次即可。res.qrc 的内部结构类似 XML前缀一般是/每张图片一对路径RCC qresource prefix/ fileimages/logo.ico/file fileimages/table.png/file fileimages/back.png/file fileimages/null.png/file fileimages/21.png/file /qresource /RCC逻辑说明代码里引用资源要写冒号加前缀加路径比如QIcon(:/images/back.png)。这个写法经常有人漏掉冒号或前缀导致程序运行时图片全空白具体排查方法放在第 5 章。3. 牌型判定与 AI 决策poker、pokerheap、ai 三个文件里的核心逻辑德州扑克项目的“算法含量”集中在三个点牌的表示与牌型判定、洗牌随机性、AI 的决策模型。这三个点恰好对应 poker、pokerheap、ai 三个文件也是答辩时老师最喜欢问的地方。3.1 poker.h面值与花色的表示方式poker.h 用两个枚举描述一张牌再用 Card 结构体组合起来这是 Qt/C 项目里最自然、也容易被新手写复杂化的地方enum PokerSuit { Spade, Heart, Club, Diamond }; enum PokerValue { V2 2, V3, V4, V5, V6, V7, V8, V9, V10, Jack, Queen, King, Ace }; struct Card { PokerSuit suit; PokerValue value; };逻辑说明点数从 2 开始是因为扑克点数天然是数字让枚举底层值直接等于牌面数字后续排序、判断顺子时就不用再做映射。Ace 放在枚举最后它的值 14 比 King 的 13 大符合常规比大小。但这里有一个著名的坑A-2-3-4-5 是最小的顺子而按纯枚举比较这个组合会被误判成 5432A 的散牌——需要特殊处理见 3.2。参数说明PokerSuit 的底层值在这里没有参与比较运算只用于显示花色图标和避免“红桃 3 与黑桃 3 被当成两张一样的牌”的手牌去重逻辑。3.2 牌型判定从 7 张里选 5 张的组合逻辑一局结束时每个玩家手里有 2 张底牌桌面有 5 张公共牌需要从 7 张里挑出最大的 5 张组合。这是整个项目算法密度最高的地方常见实现是先做排序和分组再按从大到小的牌型顺序试HandRank evaluateHand(const QVectorCard sevenCards, QVectorCard bestFive) { // 第一步按点数降序排序保证后续分组有序 QVectorCard sorted sevenCards; std::sort(sorted.begin(), sorted.end(), [](const Card a, const Card b) { return a.value b.value; }); // 第二步统计每种点数的出现次数 int count[15] {0}; for (const Card c : sorted) { count[c.value] 1; } // 第三步按“四条-葫芦-同花-顺子-三条-两对-一对-散牌”顺序逐一尝试 HandRank rank HighCard; if (hasFourOfAKind(sorted, count, bestFive)) { rank FourOfAKind; } else if (hasFullHouse(sorted, count, bestFive)) { rank FullHouse; } else if (hasFlush(sorted, bestFive)) { rank Flush; } else if (hasStraight(sorted, bestFive)) { rank Straight; } else if (hasThreeOfAKind(sorted, count, bestFive)) { rank ThreeOfAKind; } else if (hasTwoPair(sorted, count, bestFive)) { rank TwoPair; } else if (hasPair(sorted, count, bestFive)) { rank Pair; } else { bestFive sorted.mid(0, 5); rank HighCard; } return rank; }逻辑说明hasFlush、hasStraight 这类辅助函数只做一件事——判断当前牌型是否成立并让 bestFive 带上真正参与比大小的 5 张牌。如果直接返回一个 HandRank 枚举后面的踢脚比较会很难写有了 bestFive比大小就退化为两个 5 张数组逐位比较。参数说明处理 A-2-3-4-5 轮子顺时需要把 A 临时视为 1。我一般会在 hasStraight 里单独判断一次“A、2、3、4、5 五张同时出现”把 A 的点数临时改成 1 参与比较比较完再恢复。这个分支漏掉的话轮子顺会被降级成一对 A比牌结果直接翻车。踢脚比较的代码思路int compareHands(const QVectorCard a, const QVectorCard b) { // 先比最佳五张牌从头逐位比较点数 for (int i 0; i 5; i) { if (a[i].value ! b[i].value) { return a[i].value b[i].value ? 1 : -1; } } return 0; // 平局按池分配 }逻辑说明这个函数依赖 bestFive 是按点数降序排好的所以 evaluateHand 里必须先把排序做对。平局时、即返回 0 的情况在德州扑克里对应“平分底池”banker 结算时要能处理筹码被二分的场景——很多毕设项目在这里直接判先手赢小型演示看不出来但答辩演示时如果有人盯着一手同花顺平分做对比就会被问住。3.3 pokerheap.cppFisher-Yates 洗牌与随机种子pokerheap 负责把 52 张牌打乱并逐张发出。最容易在答辩现场翻车的点不是洗牌算法本身而是随机种子——没有变化种子的洗牌等于没洗。void PokerHeap::shuffle() { // 用当前时间做种子确保两次运行结果不同 unsigned seed QTime::currentTime().msec() QTime::currentTime().second() * 1000; qsrand(seed); for (int i cards.size() - 1; i 0; --i) { int j qrand() % (i 1); qSwap(cards[i], cards[j]); } }逻辑说明循环从最后一张往前遍历每次把当前牌和它之前随机的一张交换。这个算法叫 Fisher-Yates保证每一种排列出现概率相等。第 3 行的种子组合用的是毫秒加秒数基本能保证每次启动游戏牌序不同。参数说明如果写的是qsrand(0)或者忘了调用 qsrand那每次运行都是同一副牌序。有的同学会把 qsrand 放在构造函数里反复调用这也不会比放在 shuffle() 开头更好——构造函数里初始化一次种子shuffle() 里只用 qrand 即可。真正要避免的是每次发一张牌前都 qsrand 一次那会把随机性破坏掉。3.4 ai.cpp电脑玩家的简化决策模型AI 玩家的决策是这个项目的亮点。合理的做法不是硬编码“有 A 就加注”而是把当前手牌强度、公共牌、筹码量、当前注额综合放进一个评分函数int AI::decide(double pot, double currentBet, int myChips, HandRank myRank) { // strength 映射牌型越强评分基数越高 double strength myRank * 10.0; // 公牌风险牌面越“湿”顺子/同花可能越大评分压得越低 double wetBoard boardRisk(); // 0.6 ~ 1.2 区间 strength / wetBoard; // 筹码压力筹码越少越倾向拼运气 double chipRatio myChips / qMax(pot, 1.0); if (chipRatio 0.5) { strength 5; // 短码倾向激进 } // 相对注额跟注成本太高时直接弃牌 double callCost currentBet - myCurrentInPot; if (callCost myChips * 0.4 strength 30) { return FOLD; } if (strength 70) return RAISE; if (strength 40) return CALL; return FOLD; }逻辑说明这个决策模型没有复杂的概率计算本质是给牌力打分、设置两个阈值。胜在可解释性强——答辩时你能说清楚“为什么 AI 在这里弃牌”比一个调参调出来的黑匣子神经网络更能拿分。拿到源码后优先读这个函数改它的阈值就能改变 AI 风格。参数说明strength 是自拟的评分没有物理含义但它内部的相对大小关系决定了 AI 的松紧。把 RAISE 阈值从 70 调到 50AI 会明显变激进把 FOLD 阈值从 40 提到 50AI 会更保守。这些阈值就是你对这个项目做二次开发最直接的切入点。3.5 一轮对局的状态流转整个对局由 game.cpp 管理一局的循环大致是翻前盲注 → 发两张底牌 → 下注轮 → 翻牌3 张公共牌→ 下注轮 → 转牌1 张→ 下注轮 → 河牌1 张→ 下注轮 → 摊牌比大小 → 结算彩池。game.cpp 里对应会有一个轮次枚举和驱动方法enum RoundPhase { PhasePreflop, PhaseFlop, PhaseTurn, PhaseRiver, PhaseShowdown }; void Game::nextRound() { if (phase PhaseShowdown) { banker.settle(pot, players, bestFive); newHand(); } else { int nextPhase static_castint(phase) 1; phase static_castRoundPhase(nextPhase); banker.collectAnte(); dealPublicCards(); emit phaseChanged(phase); // 通知 UI 刷新公共牌区 } }逻辑说明这个状态机是整个游戏的主干所有玩家操作——跟注、加注、弃牌、all-in——都会先修改当前阶段的注额状态然后推进到下一阶段。UI 层只在 phaseChanged 信号里做界面刷新这也是为什么这个项目的 UI 代码能保持这么短。参数说明phaseChanged 是自定义信号game.ui 里的控件通过 connect 接收从而更新公共牌显示区域。如果你在改造时新增一个“思考动画”阶段只需要在枚举里插入一个值再在 nextRound 里加一个分支。4. 把工程跑起来Qt 环境配置、编译调试与验证清单理论清楚了下一步是复现。这个项目基于 Qt Widgets需要先装一个带 C 编译器的 Qt 开发环境。这里把从下载、安装到编译运行的全过程拆开写每一步都对应一个常见的失败点。4.1 环境准备Qt 版本与编译器选择这套源码用 Qt 5 编写工程文件里也写明了 widgets 模块。我建议直接用 Qt 5.15.2这个版本稳定MinGW 与 MSVC 的预编译包都有兼容性在毕设项目里最好。如果已经装了 Qt 6可以尝试打开 .pro但要注意个别 API 在 Qt 6 里有调整比如 QRegExp 被移除了工程里如果用了它需要做兼容。两种编译器选择编译器适用场景注意点MinGW 64-bit最简单一路下一步生成的 exe 依赖 libgcc 和 Qt5Core.dll部署要带库MSVC 2019需要和 Visual Studio 配合调试必须装 Windows SDK且中文路径问题更敏感我一般选 MinGW原因很现实课程设计场景下MinGW 套件免配置Qt Creator 打开 .pro 直接能跑MSVC 需要额外安装 Windows SDK 和调试器光环境问题就能劝退一半人。工程文件里用到greaterThan(QT_MAJOR_VERSION, 4)说明作者本来就希望兼容 Qt 5 以上的版本。安装完成后确认 Qt Creator 的“工具 选项 Kits”里能看到一个可用的编译器套件并且 Qt 版本检测到了 Qt 5.15.2。这一步没有显示的时候最常见的原因是装了离线安装包但没有勾选 Widgets 模块后面编译会直接报找不到 QApplication 头文件。4.2 导入 .pro 并完成编译拿到解压后的源码目录目录名不要带中文和空格建议直接叫 poker-qt。然后用 Qt Creator 打开 poke-2.pro1. 文件 打开文件或项目选择 poke-2.pro 2. 左侧项目面板确认套件Kit选中 MinGW 64-bit 3. 点击“构建”按钮等待 qmake make 完成 4. 点击“运行”或按 CtrlR 启动程序第一次构建比后续慢因为要生成 ui 头文件和 moc 文件。构建输出区如果出现moc: 未找到、cannot find -lpublic这类链接错误优先看 5.4 的排错条目。构建成功后会生成构建目录在 Qt Creator 的“构建目录”里能看到可执行文件。注意 Qt Creator 的 Shadow Build 默认开在工程目录之外的 build-poke-2-xxx 目录里如果你在资源文件里用了相对路径读取图片这会导致路径失效——但这个项目用的是 res.qrc 内嵌资源不受影响这也是它部署相对省心的原因。4.3 运行验证用三个现象确认程序健康程序能弹窗不等于逻辑正常我会按下面的顺序验证一遍避免答辩现场才发现深层问题。第一确认启动界面choose能正常显示并且点击开始能进入游戏主界面。如果点击开始后窗口消失却没有任何反应检查 choose 的槽函数是否 connect 到了点击信号或者构造函数里 setWindowFlags 把窗口设置成了工具窗。第二进入对局后确认按钮状态按轮次切换。翻前能加注、翻牌后公共牌区显示 3 张牌、转牌显示第 4 张、河牌显示 5 张这个过程如果卡住多半是 game.cpp 的状态机轮次没有推进而不是 UI 问题。第三打完一手牌后确认胜方筹码增加、败方筹码减少彩池归零。这一条能过滤掉 banker 结算逻辑的常见问题——把把都赢、筹码越玩越多基本就是结算里没扣输家的注额。运行截图 runtime1.png 和 runtime2.png 展示了程序跑起来后的界面状态对照着看能快速判断你的运行结果和作者验收时的效果是否一致。差异大的时候优先怀疑字体和分辨率设置而不是代码逻辑因为 Qt 的样式在不同 DPI 下渲染差异确实明显。4.4 调试技巧从 UI 层往业务层定位Qt Creator 的调试器对 Qt 对象树有增强支持这是定位逻辑错误最快的方式。断点位置有讲究想在出问题时停在某个玩家操作上就在 player.cpp 的下注方法入口打断点想查界面刷新问题就在 game.cpp 的 emit phaseChanged 那行打断点。一个实用习惯是开 Qt Creator 的应用输出面板把 qDebug() 输出全部打开。项目里如果见过qDebug() fold这类日志保留它们跑一手牌下来能看到 AI 的决策轨迹。这是我拆这个项目时最推荐的排错入口比打断点直观得多——毕竟一局牌的过程信息是线性的把日志按时间排好逻辑哪里断了立刻能看出来。5. 德州扑克 Qt 项目的常见坑乱码、资源丢失与链接失败这一章是我在拆解和复现同类项目时踩过的真实记录也是答辩前最容易翻车的五个点。每条都按现象、原因、解决三步写方便对着排查。5.1 界面中文变成问号现象按钮文本和标题栏在运行后显示为“???”或乱码。原因源码文件是 UTF-8 编码而 Windows 上 MSVC 编译器默认按本地代码页 GBK 解析源文件中文字符被错误解码。MinGW 一般没事MSVC 必现。解决两个方案选一个。一是把所有源文件统一转成 UTF-8 带 BOM让 MSVC 能识别编码二是在 .pro 文件里加编译选项msvc { QMAKE_CXXFLAGS /utf-8 }逻辑说明/utf-8是 MSVC 2015 之后支持的编译选项告诉编译器源文件按 UTF-8 处理。加了这个就不需要逐个文件转编码改 .pro 后重新 qmake 再编译即可。5.2 运行后所有图片空白现象窗口正常打开但桌面、按钮图标、牌面全部不显示或者某些牌面缺失。原因最常见的有两个——一是在代码里写QIcon(images/back.png)而不是QIcon(:/images/back.png)少了冒号让 Qt 按磁盘路径找了二是 res.qrc 更新了图片但没有重新构建moc 缓存的资源索引还是旧的。解决打开 res.qrc 确认前缀是/代码引用一律写成:/images/xxx.png。改完 qrc 文件后执行一次“清理项目”再重新构建不要只点运行。如果只有个别图片缺失比如 100 到 144 之间的某几张检查 images 目录里文件名是否和 qrc 中的完全一致大小写、后缀名差一个字符都会静默失败。注意资源加载问题在 Qt 里是黑匣子式错误的典型——程序不报错只是界面少东西。排查要快直接去 res.qrc 编辑器里预览每一张图片能预览说明路径没问题问题就在代码引用那一层。5.3 每次开局的牌序完全一样现象重开游戏后第一手牌总是同一副甚至整个洗牌序列不变。原因没调用随机种子初始化或种子固定。很多 C 语言教材遗留习惯会在 main 里写srand(1)导致每次运行随机序列完全一致。解决在 pokerheap.cpp 的洗牌方法里用时间种子初始化推荐 C11 的方式std::mt19937 rng(std::chrono::steady_clock::now() .time_since_epoch().count()); std::shuffle(cards.begin(), cards.end(), rng);逻辑说明mt19937 加 chrono 时间戳作为种子是 Qt 5 项目里比较稳妥的做法不依赖平台相关的 qrand 实现。注意每次只初始化一次种子不要在发每张牌前都构造一个新的 rng那样会得到同一时间片内大量重复排列。5.4 编译报 cannot mix incompatible qt library 或 cannot find -lpublic现象在一台机器上编译好的库拿到另一台机器链接出现 “cannot mix incompatible Qt library (version ex50601) with this library”或者链接阶段报cannot find -lpublic。原因第一种是 Qt 库版本混用比如开发机 Qt 5.15.2 编译的 .obj 文件到只有 5.12 的机器上重新链接。第二种是工程文件里写了LIBS -lpublic但实际没有对应的 lib/public.a 文件属于镜像别人工程时带了多余配置。解决尽量不要在多个 Qt 版本间复用构建目录换机器后先“清理项目”再全量重新构建。对于-lpublic打开 .pro 检查是否存在多余的 LIBS 行没有实际用到的第三方库就删掉如果确实有自定义库要把库路径和库文件一起放到工程目录并写明路径。这两个错误的共同根源是构建环境和发布环境不一致。我的习惯是所有第三方库全部放工程目录的 lib/ 子目录.pro 里用LIBS -L$$PWD/lib写绝不使用全局系统的库路径。5.5 运行启动即崩溃could not find the qt platform plugin现象exe 双击后弹窗报 “qt.qpa.plugin: could not find the qt platform plugin windows in”程序直接退出。原因可执行文件被拷贝到了没有 Qt 运行库的机器或者环境变量 QT_QPA_PLATFORM_PLUGIN_PATH 指向了不存在的目录。毕设演示时最常发生在把 exe 拷到教室电脑运行时。解决最简单的做法是用 Qt 自带的部署工具打开“Qt 5.15.2 (MinGW 64-bit)”命令行终端进入 exe 所在目录执行windeployqt PokerGame.exe逻辑说明这个命令会把 Qt5Core.dll、Qt5Widgets.dll、platforms/qwindows.dll 等运行依赖自动拷到 exe 旁边之后连同 exe 整个目录一起拷贝即可发布。部署完不要只拷单个 exe要把整个目录带走因为 platforms 目录缺一个都起不来。6. 改造方向从单人 AI 对局到可扩展的联机架构先别急着问“能不能改成联机版”先问自己拆到哪一层了。如果你已经把 game.cpp 的状态机、ai.cpp 的评分函数、banker.cpp 的结算读了一遍下一步就不是继续读代码而是动手改一个最小项目。我通常建议从 AI 风格改造开始成本最低、效果最明显把 3.4 里的 RAISE 阈值从 70 改成 50把 CALL 阈值从 40 改成 30跑十手牌看风格变化你就真正掌握了这套系统的可调参数在哪里。这个改动不涉及任何结构性调整但做完你对 ai.cpp 的理解会超过只看代码两遍。再往上是把单机拆成“客户端 服务端”。其实不需要上重型框架Qt 自带 QTcpServer 和 QTcpSocket 就够用。协议层面只需要定义三类消息玩家动作、公共牌状态、结算结果。动作消息可以设计成这样action|playerId|call|raise|fold|allin|amount deal|round|cardCodes settle|winnerId|amount匹配和房间逻辑用一个 Room 类管理每桌最多 6 人人满就开局把 game.cpp 的状态机包在里面。AI 玩家在联机版里可以作为“机器人补位”保留下来——有人中途掉线时把掉线玩家切换成 AI 托管ai.cpp 的函数几乎不用改这是这套架构拆分得比较干净带来的红利。我拆这套源码后给自己定了一个规矩任何 Qt 工程拿到手第一件事不是点运行而是先读 .pro 和三个最小文件——入口、状态机、资源文件确认调用链再动手。这次拆解也一样我从 main.cpp 一路追到 banker 的 settle才敢说“看懂了”。希望这份拆解记录能帮你把一个毕设源码变成真正属于自己的项目也祝你复现顺利。本文还有配套的精品资源点击获取