新闻详情

新闻详情

首页 / 资讯中心 / 详情

C++五子棋源码详解:从棋盘判定到贪心人机AI

发布时间:2026/9/30 10:14:41来源:尧图网络
C++五子棋源码详解:从棋盘判定到贪心人机AI
简介一款使用C语言编写的五子棋游戏及其完整源码同时支持人机对战与人人对战适合游戏编程初学者、在校学生以及希望积累C项目经验的开发者。程序实现中运用了类与对象机制并通过STL容器管理棋盘数据人机模式采用基于规则或Minimax/Alpha-Beta剪枝的AI决策并提供鼠标版与键盘版两种交互方式。资源共七个文件包含三个可直接运行的可执行程序、三个cpp格式的C源代码文件和一个txt说明文档压缩包大小仅106KB下载与部署非常方便。目前已有180人学习下载。通过研读源码可重点理解棋盘与棋子的类设计、棋盘状态更新与胜负判定、AI落子逻辑、玩家输入校验以及界面渲染流程代码结构清晰、注释完整既适合作为C面向对象与基础算法的实践范本也可作为课程设计或游戏开发入门的参考资料并方便在此基础上继续扩展功能。1. C五子棋与其源码把“人机对战”拆成一局可复现的贪心搜索早点写完这个项目的人应该都经历过那个著名时刻代码编译通过后你信心满满地和 AI 下第一盘结果它第一步就落在棋盘正中间——不管你是不是已经把中腹占了。这种“脑残”行为的根源不是运气差而是 AI 其实是一套“估值 搜索”的机械流程。这个 C 五子棋与其源码项目把人人对战和人机对战揉进同一个状态机里棋盘用二维数组、落子函数返回布尔值、输赢从最新落子点向四个方向延展。对刚学完 C 基础、准备做课程设计的人来说这套实现比直接找一堆神经网络代码更值得复现对熟手来说后面关于候选点和攻守平衡的写法也能直接拿过去改。2. 从棋盘表示到胜负判定人人模式的核心数据流如果你正找 C 小游戏代码来练手五子棋比贪吃蛇多一个胜负判定比俄罗斯方块少一个物理过程是个很好的中间档。但很多人把项目直接砍成“界面 输赢判断”结果写到一半就发现棋盘谁管、落子谁管、悔棋谁管全糊在一起。所以我建议第一步先做人人模式把数据流理顺。2.1 用二维数组还是位棋盘先写最直观的方案再谈优化棋盘表示是所有逻辑的容器也是第一个容易过度设计的点。常见做法是int board[15][15]0 表示空1 表示黑子2 表示白子。位棋盘bitboard更适合需要大量并行计算和剪枝的棋类 AI但五子棋棋盘只有 225 个格子二维数组的访问开销完全可以忽略反而调试时一眼就能看清哪个坐标是什么状态。这个选择还有一个现实理由课程设计和源码复现场景里代码可读性比性能重要得多。等你把 AI 逻辑写出来最需要的是能随时打印棋盘、能随手在某个位置临时放一颗子再看效果这些东西用二维数组做起来最省事。constexpr int BOARD_SIZE 15; int board[BOARD_SIZE][BOARD_SIZE] {0}; // 0: 空, 1: 黑, 2: 白 bool placeStone(int row, int col, int player) { if (row 0 || row BOARD_SIZE || col 0 || col BOARD_SIZE) { return false; // 越界直接拒绝 } if (board[row][col] ! 0) { return false; // 已经有子拒绝覆盖 } board[row][col] player; return true; }这里player只允许传 1 或 2placeStone返回布尔值目的是让上层逻辑能区分“这次落子到底成没成功”。很多人喜欢把非法输入直接cout报错然后继续但那样主循环的流程会很难收场。返回false之后由调用方决定是重新输入还是切换回合状态机会清晰很多。2.2 落子-判定-悔棋三步实现一局完整棋局人人模式的完整一局就是一个循环等待当前玩家输入坐标调用placeStone如果成功就检查胜负然后切换到对手。这里最容易忽视的是胜负判定策略。最常见也最可靠的做法是从最新落子点出发沿水平、垂直、主对角线、副对角线四个方向分别往正负两边数连续同色棋子的数量总数达到 5 就判赢。不需要每次落子都扫描整个棋盘。因为一局棋的胜负只可能由刚落下那颗子带出来旧局面里的五连如果之前没判赢说明它根本不存在。所以“只查最新落子点”既是性能优化也是逻辑简化。bool checkWin(int row, int col) { int player board[row][col]; if (player 0) return false; // 四个方向水平、垂直、主对角线、副对角线 int dx[4] {1, 0, 1, 1}; int dy[4] {0, 1, 1, -1}; for (int i 0; i 4; i) { int count 1; // 当前落子本身 // 正方向延展 for (int step 1; step 5; step) { int nr row dx[i] * step; int nc col dy[i] * step; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) break; if (board[nr][nc] ! player) break; count; } // 反方向延展 for (int step 1; step 5; step) { int nr row - dx[i] * step; int nc col - dy[i] * step; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) break; if (board[nr][nc] ! player) break; count; } if (count 5) return true; } return false; }dx和dy的对应当心写错水平方向是(1,0)垂直方向是(0,1)主对角线是(1,1)副对角线是(1,-1)。方向数组写反是后面避坑章节里最容易出现的翻车点这里先标记一下。step 5不是只能数到 5而是数满 5 就已经能决定胜负再多没有意义。边界检查放在每一步延展前一旦越界或遇到对手棋子就立刻 break不做多余处理。悔棋功能用栈实现最直接vectorpairint,int当历史栈用。每次placeStone成功后把{row, col}push 进去悔棋时 pop 出来把对应格子清零。vectorpairint, int history; bool undoLastMove() { if (history.empty()) return false; auto [r, c] history.back(); history.pop_back(); board[r][c] 0; return true; }这三个函数加在一起就构成人人模式的最小闭环落子、判胜、悔棋。它们都围绕同一个全局二维数组工作没有额外状态需要同步这是我把它们放在一起讲的原因。后续的人机模式也只是在这个闭环里插入一个“AI 代替人类输入坐标”的环节不影响棋盘和判定逻辑本身。3. 人机模式怎么才不“脑残”估值表、候选点与贪心选择人机对战是五子棋源码里最让人又爱又恨的部分。新手以为 AI 需要“思考”实际写出来第一版往往只会堵眼或者干脆随机落子。这一章把 AI 拆成三个可替换的部件候选点生成、打分函数、落子选择。3.1 五子棋AI为什么不用深度学习先解决“可复现”这个刚需如果你搜过一圈会发现很多号称“AI 五子棋”的项目背后其实是神经网络。但对 C 小游戏代码这个体量来说深度学习是彻头彻尾的杀鸡用牛刀要准备数据集、要搭建训练框架、要处理推理时的黑匣子最后你还很难向课程答辩的老师解释清楚“为什么这一步它这么走”。更重要的是神经网络推理结果不稳定同一个局面跑两次可能给出不同落点这在本地调试时非常痛苦。所以这里采用传统方案评估函数 贪心搜索。评估函数是一个纯函数输入局面输出分数AI 对每个候选落子点都算一遍分数取最高分落子。整个过程没有随机性不会出现“昨天还能赢今天突然不会下”的玄学问题。可复现、可解释、可调参这三个特性对入门项目比“棋力更强”值钱得多。3.2 打分逻辑与搜索范围把“AI”写成人类能读懂的代码先解决一个效率问题AI 每次落子总不能把 225 个空位全部打一遍分。虽然 225 对计算机来说不算大但生态上不优雅。常见做法是只考虑“离已有棋子比较近”的空位用候选点生成函数控制搜索范围。vectorpairint,int generateCandidates(int radius 2) { vectorpairint,int cands; for (int r 0; r BOARD_SIZE; r) { for (int c 0; c BOARD_SIZE; c) { if (board[r][c] ! 0) continue; // 只考虑空位 bool nearStone false; for (int dr -radius; dr radius !nearStone; dr) { for (int dc -radius; dc radius; dc) { int nr r dr, nc c dc; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) continue; if (board[nr][nc] ! 0) { nearStone true; break; } } } if (nearStone) { cands.push_back({r, c}); } } } return cands; }radius 2的意思是只有附近 2 格范围内存在棋子的空位才进入候选。这个参数直接决定 AI 的行棋视野太小会漏掉一些关键进攻点太大会把全盘都拉进来计算量膨胀。对普通五子棋对弈开局阶段候选点只有几个中盘阶段通常二三十个性能完全够。注意内层窗口循环每找到一个棋子就通过!nearStone条件跳出避免同一个空位被重复加入候选列表。打分是整个 AI 的核心。先约定棋型分值这些分值不来自玄学而是来自“离赢还有多远”的优先级五连直接赢活四四子且两端都空下一手必胜冲四四子且一端被堵逼迫对方必须应活三要尽早发展成四。数值差距拉得越大AI 越倾向于选择直接能赢的点而不是去布一些长线棋型。棋型含义分数五连当前方向已有 5 子100000活四4 子且两端都空50000冲四4 子且一端被堵10000活三3 子且两端都空5000眠三3 子且一端被堵1000活二2 子且两端都空500弱棋型2 子及以下无威胁100下面这段代码把“假设在某处落子后当前方向形成的棋型”翻译成分数。scoreForLine接收连续棋子数和开口数返回该方向的贡献evaluateMove把四个方向的贡献加起来作为该点的总价值。为了避免破坏真实棋盘函数内部先临时落子打分完毕立刻恢复。int scoreForLine(int count, int openEnds) { if (count 5) return 100000; // 直接赢 if (count 4) { if (openEnds 2) return 50000; // 活四 if (openEnds 1) return 10000; // 冲四 return 100; // 被两边堵死的死四几乎没价值 } if (count 3) { if (openEnds 2) return 5000; // 活三 if (openEnds 1) return 1000; // 眠三 return 50; } if (count 2) { if (openEnds 2) return 500; // 活二 if (openEnds 1) return 100; // 眠二 return 10; } return 5; } int evaluateMove(int row, int col, int player) { int dx[4] {1, 0, 1, 1}; int dy[4] {0, 1, 1, -1}; board[row][col] player; // 临时落子 int score 0; for (int i 0; i 4; i) { int count 1; // 当前这手本身 int openEnds 0; for (int sign -1; sign 1; sign 2) { for (int step 1; step 5; step) { int nr row dx[i] * step * sign; int nc col dy[i] * step * sign; if (nr 0 || nr BOARD_SIZE || nc 0 || nc BOARD_SIZE) break; if (board[nr][nc] player) { count; } else { if (board[nr][nc] 0) openEnds; break; } } } score scoreForLine(count, openEnds); } board[row][col] 0; // 恢复棋盘 return score; }openEnds的统计逻辑是沿着某个方向数遇到同色棋子就继续遇到空位就记一个开口然后停止遇到对手棋子直接停止。棋盘边界视作“无开口”因为边线上的棋型再漂亮发展空间也被砍掉一半。scoreForLine里那句return 100就是处理这种情况。有了单点打分函数AI 的落子策略可以写得很朴素对每个候选点分别从自己视角和对手视角打分然后相加。这个“攻守一体”的设计比单纯让 AI 只顾自己进攻要实用得多。五子棋的胜负手往往就在一念之间你只算自己的活三却看不到对手下一步能连五最后还是输。pairint,int chooseBestMove(int aiPlayer) { auto cands generateCandidates(2); int bestScore -1; pairint,int bestMove {-1, -1}; for (auto [r, c] : cands) { int attack evaluateMove(r, c, aiPlayer); // 自己下这里的价值 int defend evaluateMove(r, c, 3 - aiPlayer); // 对手下这里的威胁 int total attack defend; if (total bestScore) { bestScore total; bestMove {r, c}; } } // 开局棋盘全空时候选列表为空直接下天元 if (bestMove.first -1) { return {BOARD_SIZE / 2, BOARD_SIZE / 2}; } return bestMove; }3 - aiPlayer是切换视角的小技巧如果 AI 是 1黑棋3 - 1 2就是白棋如果 AI 是 23 - 2 1就是黑棋。attack defend意味着这个位置既可能是自己的进攻要点也可能是对手的潜在杀棋。两者相加后AI 会优先选择那种“自己下能赢、对手下也能赢”的兵家必争之地这在大多数局面里都是正确的选择。这套 AI 距离专业棋力还有很大距离它没有前瞻看不到两步之后的变化。但对一个课程设计和入门源码来说它已经能做到不短路、不送杀并且逻辑完全透明。如果你想进一步提升后续可以在这个基础上加一个深度为 2 的 MinMax 搜索那已经是另一个阶段的事了。4. 人机OR人人模式切换的避坑记录编译、回合与胜负误判这个项目源码量不大但坑一点都不少。我自己写第一版时光“窗口秒退”就折腾了一晚上后来帮别人看代码发现大家踩的坑惊人地一致。下面这几条都是实战里最容易撞上的按现象、原因、解决的顺序拆开说。4.1 控制台窗口秒退、中文乱码先让程序稳定跑起来现象在 vscode 配置 c/c 环境后编译运行这个五子棋源码结果控制台窗口一闪而过棋盘内容根本看不清。这不是逻辑错误而是程序跑完 main 后窗口被系统自动关闭了。很多编译器在调试模式下会自动暂停但直接运行 exe 就不会。原因main 函数正常返回控制台生命周期结束。解决在 main 返回前加一个暂停点。Windows 下system(pause)简单粗暴Linux/macOS 下没有这个命令需要用条件编译区分。int main() { // 原本的游戏逻辑... // 所有逻辑跑完后给调试留一个暂停点 #ifdef _WIN32 system(pause); #else std::cout 按 Enter 退出...; std::cin.get(); #endif return 0; }还有一个和窗口闪退一起出现的经典问题中文乱码。棋盘画出来全是“馃挕”之类的符号。根源是源码文件编码和 Windows 控制台代码页不一致。最省事的办法是控制台输出全部用英文Player 1代替“玩家一”棋盘坐标用大写字母加数字彻底绕开编码问题。如果你必须显示中文把源文件另存为 GBK 编码或者在 main 开头调用 Windows API 设置代码页但后者会在跨平台编译时报错我不推荐课程设计里用。4.2 AI 回合不触发状态机里最常见的翻车现场现象选择人机模式后玩家这边能正常落子但电脑那一边始终不动游戏像卡死了一样。你左等右等AI 就是不下棋。原因最常见的写法是把 AI 放进一个死循环里等某个标志位但标志位一直没被置位其次是模式判断写反把“人机模式”判断成了“人人模式”AI 逻辑压根没进到主流程。解决不要用死循环等待 AI而是把“当前是否 AI 回合”作为主循环里的显式状态。玩家落子成功后直接根据模式决定下一步谁来下。if (mode AI_MODE currentPlayer aiPlayer) { auto [r, c] chooseBestMove(aiPlayer); if (placeStone(r, c, aiPlayer)) { history.push_back({r, c}); if (checkWin(r, c)) { printBoard(); std::cout AI 获胜 std::endl; break; } currentPlayer 3 - currentPlayer; } else { std::cerr AI 落子失败候选列表存在问题 std::endl; break; } }注意这里的break一定要放在非法落子的分支里否则 AI 落子失败后游戏会陷入死循环。另外AI 落子后也要调用checkWin不要因为它不是人类就不判胜负否则玩家已经五连了程序还在等 AI 行动。这种“AI 永远不动”的问题调试时先打印mode、currentPlayer、aiPlayer三个变量一眼就能看出状态机卡在哪。我当初就是没打印才反复改了一下午纯属血泪经验。4.3 胜负误判与重复落子两个“看起来正常但结果不对”的坑现象一棋盘上明明已经凑齐了五颗子但程序就是不判赢一直下到棋盘满了也没结果。原因胜负判定函数写得不对常见是方向数组出错比如把某个方向的dy写成了 0导致两条斜线方向实际都在检测同一行还有一种是把所有历史棋盘状态一起判遇到上一局留下的残局开局就被误判。解决严格按照第 2 章的checkWin实现从最新落子点出发只统计包含这颗子的连线。在玩家输入坐标后、落子前先把棋盘点位打印出来确认方向数组的四个组合分别覆盖了横、竖、撇、捺。这个验证过程五分钟就能完成但能省掉后面整个对弈调试的时间。现象二两个玩家可以往同一个格子里落子后落的那颗直接把先落的覆盖掉棋盘上的黑子突然变成白子。原因placeStone里没有做非空校验或者上层输入坐标时没检查board[row][col] 0。解决所有落子入口包括玩家输入和 AI 落子都必须经过同一个placeStone。函数内部已经做了if (board[row][col] ! 0) return false这样非法落子能被统一拦截。不要在main函数里单独写一套“判断棋盘是否为空”的逻辑那等于把同一个校验复制两份改一个忘一个迟早要出事。课程设计答辩现场被评委指出“这个位置你已经下过子却能再下”的尴尬最好在写代码阶段就避免掉。5. 让源码自己证明自己三种不依赖界面的验证技巧游戏界面跑起来还能截图给朋友看但真正要验证逻辑对不对靠鼠标一格格点太慢了。我一般把界面丢到一边直接写几个十几行的测试函数让程序自己跑自己。5.1 构造固定五连验证胜负判定不用从第一手开始下直接在数组里摆一个五连然后调用checkWin。比如// 直接在 (7,7) 到 (7,11) 放五颗黑子 for (int c 7; c 11; c) board[7][c] 1; std::cout checkWin(7, 8) std::endl; // 期望输出 1这句代码能在一秒内揪出方向数组写错的问题比人下半小时棋快得多。你可以把四个方向分别摆一组验证dx、dy没写歪。5.2 固定随机种子让 AI 每个局面都可复现如果你给 AI 加了随机因素比如分数相同时随机选一个点调试时一定要固定随机种子。std::srand(42)之后同样局面 AI 永远走同一个位置方便你反复观察它的行为。不加这行每次调试都是开盲盒遇到问题你根本说不清“上次怎么赢的”。5.3 用 AI vs AI 自动跑满一盘把非法落子全揪出来让两个 AI 自动对弈是最能暴露隐藏问题的做法。chooseBestMove返回非法坐标、placeStone没拦住覆盖、checkWin死活不触发这些 bug 在 AI vs AI 的循环里会一个个现形。int player 1; while (history.size() BOARD_SIZE * BOARD_SIZE) { auto [r, c] chooseBestMove(player); if (!placeStone(r, c, player)) { std::cerr 非法落子: r , c std::endl; break; } history.push_back({r, c}); if (checkWin(r, c)) { std::cout Player player wins std::endl; break; } player 3 - player; }我自己的习惯是写完 AI 后不急着接界面先让这个自动对弈循环跑 50 轮。跑通了再接输入输出能省出大量对着屏幕干瞪眼的时间。后来帮别人看五子棋源码我第一句也是让他们先跑这段自动对弈大部分 bug 在第一次循环里就暴露了算是这个项目留给我的一个后悔药。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

0x0000011b错误修复攻略:Windows共享打印机RPC冲突的注册表解法 2026/9/30 10:51:32

0x0000011b错误修复攻略:Windows共享打印机RPC冲突的注册表解法

先说个场景:你在办公室连共享打印机,前一秒大家都用得好好的,突然你这边弹窗“操作无法完成,错误 0x0000011b”,点多少次重试都没用。旁边的同事一脸无辜,打印机却在自己家里正常出纸。这画面我在处理企业办…

阅读更多 →
impress.js Extras 插件深度解析:自动初始化可选扩展插件的统一入口 2026/9/30 10:51:24

impress.js Extras 插件深度解析:自动初始化可选扩展插件的统一入口

前端 【免费下载链接】impress.js Its a presentation framework based on the power of CSS3 transforms and transitions in modern browsers and inspired by the idea behind prezi.com. 项目地址: https://gitcode.com/gh_mirrors/im/impress.js 点击查看 免费…

阅读更多 →
西安24小时自助健身房解决方案实战指南:从系统架构到运营部署 2026/9/30 10:51:17

西安24小时自助健身房解决方案实战指南:从系统架构到运营部署

西安24小时自助健身房解决方案实战指南:从系统架构到运营部署 一、系统架构设计:构建无人值守的健身闭环 在西安,24小时自助健身房正成为城市健身新趋势。其核心挑战在于:如何在无工作人员在场的情况下,实现会员自助入…

阅读更多 →
CSP初赛计算机网络复习指南:从分层模型到子网掩码全攻略 2026/9/30 10:51:17

CSP初赛计算机网络复习指南:从分层模型到子网掩码全攻略

如果你正在备战CSP初赛,看到“计算机网络”这几个字,先别急着跳过。这一块在初赛里通常能占到四到六分,表面上看是最接近“常识”的送分题,实际上每年都有大量选手在IP地址计算、子网掩码判断、TCP三次握手状态这类细节上翻车。我…

阅读更多 →
Front-end Developer Interview Questions 前端面试题库全解析:基于意大利语版的高频考察点与作答指南 2026/9/30 10:51:17

Front-end Developer Interview Questions 前端面试题库全解析:基于意大利语版的高频考察点与作答指南

教程前端 【免费下载链接】Front-end-Developer-Interview-Questions A list of helpful front-end related questions you can use to interview potential candidates, test yourself or completely ignore. 项目地址: https://gitcode.com/gh_mirrors/fr/Front-e…

阅读更多 →
Python 大麦网自动抢票脚本使用指南:5 个核心参数与运行原理一次讲清 2026/9/30 10:51:17

Python 大麦网自动抢票脚本使用指南:5 个核心参数与运行原理一次讲清

Python 大麦网自动抢票脚本使用指南:5 个核心参数与运行原理一次讲清 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 这个项目的本质是:只在登录环节使…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉