新闻详情

新闻详情

首页 / 资讯中心 / 详情

用Visual Studio实现五子棋人机对战:从Win32界面到AI评分表

发布时间:2026/9/29 20:02:53来源:尧图网络
用Visual Studio实现五子棋人机对战:从Win32界面到AI评分表
简介面向Visual Studio与C游戏开发初学者这份五子棋项目完整实现了人机对战与人人对战两种模式可帮助读者掌握Windows程序开发、棋盘状态管理、AI落子逻辑及胜负判定等核心环节。资源包共49个文件、约107MB主要包含C源码与头文件.cpp/.h、VS工程配置.sln/.vcxproj/.filters、界面与棋盘素材.jpg/.png、游戏音效.wav以及调试文件.pdb/.tlog/.log等解压后可直接用Visual Studio打开工程查看完整实现。AI部分覆盖从初级穷举搜索到进阶Minimax与Alpha-Beta剪枝的思路并附带悔棋、重新开始、保存与加载等附加功能可作为课程设计、毕业设计或自学练手的完整样例。已有2655人学习下载对理解事件驱动编程、EasyX图形库集成和游戏逻辑分层设计均有参考价值。1. 用 Visual Studio 写五子棋人机对战难的不是界面是电脑怎么走棋用 Visual Studio 写五子棋人机对战被问得最多的不是“棋盘怎么画”而是“电脑到底怎么走棋”。控制台版本很多人一天就能写完可一旦换成带界面、带鼠标的完整对战程序就卡在 AI 选点上。这份资源是一套完整的 VS 2022 可编译 Win32 五子棋工程自绘棋盘、鼠标落子、胜负判定、评分表 AI 开箱即用编译环境只要 Visual Studio 社区版加一个“使用 C 的桌面开发”工作负载。适合三类人刚学完 C 拿它做课设的在校生、想熟悉 Win32 消息循环的开发者、想低成本搞懂一个简单博弈 AI 的从业者。整条链路理顺之后你手里就不只是一份能交差的代码而是一个能继续往深处改的底子。2. 工程骨架新建 Win32 项目、消息循环与文件怎么划先说工具。这里用的是 Visual Studio 2022 社区版不是 VS Code。VS Code 是编辑器要在 Windows 上跑 Win32 窗口工程得自己写 tasks.json、launch.json 去调 cl.exe 和 link.exe人机对战还没开始人先被配置打倒了。VS 里新建项目点一下就能编译这套资源就是以它为标准写的。2.1 为什么选原生 Win32 API而不是 MFC 或 Qt依赖最轻是最大理由。装 VS 的时候只勾选“使用 C 的桌面开发”不装 MFC 也能把整套工程编译跑起来。MFC 的问题在于你本来只是想写个五子棋却要先学它那套 DDX、消息映射宏等于额外背一个框架。Qt 更麻烦要在 VS 里配 Qt VS Tools、配库路径、配环境变量卡环境的概率比卡代码更大。从学习价值看Win32 把消息驱动这套东西完全裸露给你窗口类注册、消息循环、窗口过程回调、GDI 绘制每个概念都能在五子棋里找到落点。等你会用消息循环处理鼠标事件了再去看任何 GUI 框架脑子里都有一张清晰的底图。2.2 新建项目的正确姿势安装阶段一个常见误区在 VS Installer 里只勾了“.NET 桌面开发”结果新建项目时找不到任何 C 模板。先确认勾的是“使用 C 的桌面开发”里面有 MSVC 编译器和 Windows SDK。新建项目时选“Windows 桌面应用程序C”项目名建议纯英文比如 Gobang别用中文路径老版本工具链在中文路径下偶尔会抽风。控件台和控制台项目不要选控制台应用的入口是 mainWindows 桌面应用的入口是 WinMain链接器按入口找函数找错了直接报 unresolved external symbol。如果你手头只有“空项目”模板手动改两处也能跑项目属性 - 链接器 - 系统 - 子系统从“未设置”改成“Windows”然后在代码里写 WinMain否则链接器默认会找 main。2.3 入口与消息循环窗口是怎么活起来的整套工程的主循环骨架长这样看懂它基本就懂 Win32 的事件驱动模型#include windows.h #include board.h #include ai.h LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_PAINT: DrawBoard(hwnd); return 0; case WM_LBUTTONUP: OnLButtonUp(hwnd, lp); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmd, int show) { WNDCLASS wc {0}; wc.lpfnWndProc WndProc; wc.hInstance hInst; wc.lpszClassName LGobangWin; wc.hbrBackground (HBRUSH)GetStockObject(WHITE_BRUSH); RegisterClass(wc); HWND hwnd CreateWindow(LGobangWin, L五子棋 - 人机对战, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 700, 700, NULL, NULL, hInst, NULL); ShowWindow(hwnd, show); UpdateWindow(hwnd); MSG msg; while (GetMessage(msg, NULL, 0, 0) 0) { TranslateMessage(msg); DispatchMessage(msg); } return 0; }这段代码的逻辑顺序是注册窗口类 - 创建窗口 - 显示窗口 - 进消息循环。消息循环里的三个函数各管一件事GetMessage 从系统取一条消息TranslateMessage 把键盘按键翻译成字符消息DispatchMessage 把消息分发回 WndProc 去处理。程序的所有行为包括重绘棋盘、响应鼠标、判断落子都挂在 WndProc 的 switch 分支上。窗口类的 hbrBackground 设了白色画刷避免窗口刚出来时底色发灰。lpszClassName 的值必须和 CreateWindow 第一个参数完全一致拼错一个字母窗口就创建不出来GetLastError 会返回 1400 系列错误码。2.4 资源包的文件划分与改动入口拿到资源包后先别急着点编译把文件职责摸一遍后续改代码才知道往哪下手。文件职责什么时候需要动gobang.cppWinMain、WndProc、消息循环基本不动board.h / board.cpp棋盘绘制、坐标换算、落子、胜负判定改棋盘尺寸、颜色、规则时ai.h / ai.cpp棋型评分、AI 选点逻辑调难度、调棋风时replay.txt固定测试棋谱只读用于回归验证README.txt编译步骤与环境说明只读一个容易忽略的约束N、SIZE、MARGIN 三个常量共同决定棋盘尺寸而 CreateWindow 传的 700×700 是窗口外框尺寸不是客户区尺寸。资源包里棋盘画在 660×660 的客户区范围内视觉上居中新手不用去调 AdjustWindowRect知道这回事就行。3. 棋盘绘制与鼠标落子双缓冲防闪、坐标换算与四方向判胜窗口能弹出来只是开始真正让五子棋“活”起来的是三件事把棋盘画对、把鼠标点准、把胜负判对。这一章逐个落地。3.1 常量约定与棋盘数据结构先统一棋盘的行列坐标约定后面所有代码都按这个来#define N 15 // 15x15 棋盘 #define SIZE 40 // 格子边长单位像素 #define MARGIN 30 // 棋盘左侧与上侧留白 #define RADIUS 16 // 棋子半径 #define W (N * SIZE 2 * MARGIN) // 客户区宽度 660 #define H (N * SIZE 2 * MARGIN) // 客户区高度 660 int board[N][N]; // 0 空1 黑棋2 白棋SIZE 取 40 是为了鼠标点击时容错率高40 像素的格子正常人拿鼠标点偏差能控制在 10 像素内换成 20 就很容易点错行。RADIUS 取 16大约是格宽的 0.4 倍两个相邻棋子之间还有 8 像素缝隙视觉上不拥挤也避免误画到其他格子里。棋盘数据就用 int 二维数组不需要什么哈希表。五子棋的状态就是“哪个格子放了什么颜色的棋”数组下标即坐标N15 时整个棋盘最多 225 个格子任何数据结构都没有直接下标访问快。3.2 双缓冲绘制先画到内存再一次贴上去不双缓冲的后果是每落一子窗口整个闪一下拖拽窗口时棋盘线条全是残影。原因是你在 WM_PAINT 里一边画一边往屏幕 DC 上输出系统会在你画到一半时就把半成品内容显示出去。void DrawBoard(HWND hwnd) { PAINTSTRUCT ps; HDC hdc BeginPaint(hwnd, ps); HDC mem CreateCompatibleDC(hdc); HBITMAP bmp CreateCompatibleBitmap(hdc, W, H); SelectObject(mem, bmp); // 1. 白色背景 PatBlt(mem, 0, 0, W, H, WHITENESS); // 2. 画网格线 HPEN pen CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); SelectObject(mem, pen); for (int i 0; i N; i) { int x MARGIN i * SIZE; MoveToEx(mem, x, MARGIN, NULL); LineTo(mem, x, H - MARGIN); MoveToEx(mem, MARGIN, x, NULL); LineTo(mem, W - MARGIN, x); } // 3. 星位标记天元 四角星位 for (int r 3; r 11; r 4) { for (int c 3; c 11; c 4) { int cx MARGIN c * SIZE, cy MARGIN r * SIZE; Ellipse(mem, cx - 3, cy - 3, cx 3, cy 3); } } // 4. 画已经落下的棋子 for (int r 0; r N; r) { for (int c 0; c N; c) { if (board[r][c] 0) continue; int cx MARGIN c * SIZE, cy MARGIN r * SIZE; HBRUSH br CreateSolidBrush(board[r][c] 1 ? RGB(0, 0, 0) : RGB(255, 255, 255)); SelectObject(mem, br); Ellipse(mem, cx - RADIUS, cy - RADIUS, cx RADIUS, cy RADIUS); DeleteObject(br); } } // 5. 整张棋盘一次性贴回屏幕 BitBlt(hdc, 0, 0, W, H, mem, 0, 0, SRCCOPY); DeleteObject(bmp); DeleteDC(mem); DeleteObject(pen); EndPaint(hwnd, ps); }逻辑说明先在内存 DC 上把背景、网格、星位、棋子全部画完形成一张完整位图最后一次 BitBlt 拷贝到屏幕。屏幕上永远只出现“成品”不会出现“画一半”的中间状态闪烁自然消失。两个参数细节值得留意BeginPaint 和 EndPaint 必须成对使用它和 GetDC 不一样BeginPaint 拿到的 DC 只覆盖无效区域每次自己创建的画笔、画刷、位图都要逐个 DeleteObject 或者 DeleteDC不然 GDI 对象会累积泄漏跑上几十盘之后落子会明显变慢甚至画不出棋子。3.3 鼠标落子坐标换算与回合锁棋盘画出来后核心就是鼠标事件。WM_LBUTTONUP 里要做四件事把屏幕坐标换算成棋盘行列、校验是否合法、落子、判胜、切换回合。#include windowsx.h // GET_X_LPARAM 宏所在头文件 case WM_LBUTTONUP: { int x GET_X_LPARAM(lp); int y GET_Y_LPARAM(lp); int col (x - MARGIN SIZE / 2) / SIZE; int row (y - MARGIN SIZE / 2) / SIZE; if (row 0 || row N || col 0 || col N) break; if (board[row][col] ! 0) break; if (!playerTurn) break; board[row][col] PLAYER_COLOR; if (CheckWin(row, col, PLAYER_COLOR)) { MessageBox(hwnd, L黑棋胜, L对局结束, MB_OK); ResetGame(); } playerTurn false; InvalidateRect(hwnd, NULL, TRUE); PostMessage(hwnd, WM_APP_AI_MOVE, 0, 0); // 让电脑异步走棋 break; }坐标换算里最关键的是 SIZE / 2这一步。如果不加它整除会直接截断小数部分比如点在某格的右边 1/4 处( x - MARGIN ) / SIZE 会算出左边那格的编号结果就是玩家普遍反映的“棋子偏了半格”。GET_X_LPARAM 比 LOWORD 更安全后者在高 DPI 缩放下可能拿到带符号扩展的错误坐标。playerTurn 是回合锁防止人下完电脑还没走玩家又抢一个子。落子后先判胜负再切回合好处是玩家完成五连时对局立刻结束不会出现“明明赢了电脑还要再走一手”的奇怪体验。顺手提一个习惯AI 走棋用 PostMessage 而不是直接调用是因为 AI 在 WM_LBUTTONUP 里同步执行的话UI 线程会阻塞。评分表 AI 计算量小感觉不出来将来换成深度搜索这行改不改直接决定界面卡不卡。3.4 胜负判定四个方向扫一遍落子就要立刻知道有没有人赢判定范围只需要四条线横、竖、两条对角线。每条线以落子点为中心向正反两个方向数连续同色棋子总数大于等于 5 就返回胜利。bool CheckWin(int row, int col, int role) { int dirs[4][2] {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; for (int d 0; d 4; d) { int cnt 1; for (int step 1; ; step) { int nr row dirs[d][0] * step; int nc col dirs[d][1] * step; if (nr 0 || nr N || nc 0 || nc N) break; if (board[nr][nc] ! role) break; cnt; } for (int step 1; ; step) { int nr row - dirs[d][0] * step; int nc col - dirs[d][1] * step; if (nr 0 || nr N || nc 0 || nc N) break; if (board[nr][nc] ! role) break; cnt; } if (cnt 5) return true; } return false; }两个细节方向数组只写四个方向正反扫描合在一起正好覆盖八条射线不用写 8 个分支cnt 用的是 5意味着六连、七连在普通规则下也算赢。五子棋专业规则里黑棋长连是禁手这份资源默认不做禁手判定想拿去打比赛的话需要再改这里但现在这个写法对绝大多数场景是够用的。4. 人机对战 AI评分表、方向扫描与攻守权重AI 是这套资源里价值最高的模块也是最容易改坏的部分。读懂评分表你就能把电脑从一个“只会挡”的工具变成“会赢”的对手。4.1 为什么用评分表而不是暴力搜索15×15 棋盘 225 个空点如果做两层 minimax 搜索每个点展开对手的落子点是 225×224≈5 万个局面每个局面还要重新评估棋型、判胜负计算量直接到百万级别。新手写出来的搜索树调试一次就要很久而且胜负逻辑和 AI 逻辑耦合在一起出了 bug 很难定位。评分表法是单点贪心每个空点独立打分选最高分落子。全部算一遍也就一万多次整数运算在 0.1 毫秒级完成完全实时。它本质上是把“下了这手之后我的棋型好不好、对方的棋型好不好”翻译成分数吃透它之后再回过头看 minimax 里的局面评估函数你会发现评估函数就是评分表的另一个皮。评分表的短板是它只看单点看不出“双冲四”“双活三”这种靠两个点组合才成立的杀招这一点在第 6 章会用两步前瞻补上。4.2 棋型分值表数值比例比绝对值重要资源包默认的分值表如下所有 AI 行为都从这里来棋型分值典型形态成五100000落子后直接五连活四50000四子连珠且两端都空冲四10000四子一端空一端被堵活三4000三子连珠且两端都空眠三800三子一端开放一端被堵活二500二子两端都空眠二100二子单端开放这些数值看着像玄学背后有一条硬逻辑活四的分值必须远大于冲四。对手有一个活四时你堵哪头都没用而有两个冲四时单点评估的 AI 只会看到“有两个冲四需要堵”各堵一个对方下一手还能从另一个点成五。这就是所谓的“双杀”骗局分值表没法解决只能靠第 6 章的前瞻。改动分值表时记住两条比例红线活三要大于 2×眠三冲四要大于 2×活三。比例失调的典型表现是 AI 在“该防守”和“该继续进攻”之间做出错误决策比如明明对方已经冲四了它还在执着地做自己的活二。4.3 方向扫描打分一个空点值多少分先把“某个位置对某方有多重要”这个评估函数写出来它是整个 AI 的引擎int ShapeScore(int cnt, int open1, int open2) { if (cnt 5) return 100000; if (cnt 4) { if (open1 open2) return 50000; if (open1 || open2) return 10000; return 0; } if (cnt 3) { if (open1 open2) return 4000; if (open1 || open2) return 800; return 0; } if (cnt 2) { if (open1 open2) return 500; if (open1 || open2) return 100; return 0; } if (cnt 1) return 10; return 0; } int EvaluatePoint(int board[N][N], int row, int col, int role) { int score 0; int dirs[4][2] {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; for (int d 0; d 4; d) { int cnt 1; // 假设这格已经放了 role 棋子 int open1 0, open2 0; int nr row dirs[d][0], nc col dirs[d][1]; while (nr 0 nr N nc 0 nc N) { if (board[nr][nc] role) { cnt; nr dirs[d][0]; nc dirs[d][1]; } else { if (board[nr][nc] 0) open1 1; break; } } nr row - dirs[d][0]; nc col - dirs[d][1]; while (nr 0 nr N nc 0 nc N) { if (board[nr][nc] role) { cnt; nr - dirs[d][0]; nc - dirs[d][1]; } else { if (board[nr][nc] 0) open2 1; break; } } score ShapeScore(cnt, open1, open2); } return score; }逻辑说明把当前空点假设成已经放了 role 颜色的棋子然后沿四个方向数连续的同色棋子同时记录这条线两端是空的还是被堵死的。open1 和 open2 是 0/1 标记两端都开放就是活棋一端开放就是眠棋。连续计数在遇到对方棋子或边界时停止因为隔了一个异色棋子再往后对当前这手就毫无连续性意义。4.4 AI 选点进攻分 防守权重有了单点评分函数选点就是一次全盘扫描这是 AIThink 的核心POINT AIThink() { if (EmptyCells() N * N) return {7, 7}; // 开局第一手直接抢天元 POINT best {-1, -1}; int bestScore 0; for (int r 0; r N; r) { for (int c 0; c N; c) { if (board[r][c] ! 0) continue; int atk EvaluatePoint(board, r, c, AI_COLOR); // 我下这手能形成什么 int def EvaluatePoint(board, r, c, HUMAN_COLOR); // 对方下这手能形成什么 int total atk def * DEF_WEIGHT; if (total bestScore) { bestScore total; best {c, r}; } } } return best; }空棋盘特判很重要开局时所有格子的攻防分完全一样都只有基础分 1010不特判的话 AI 会落子在左上角而不是天元。DEF_WEIGHT 是资源包里最值得玩的参数AI 执黑先手时建议 0.8~1.0让它更主动执白后手时调到 1.0~1.2让防守优先。提示DEF_WEIGHT 先手和后手要分开存全局改会导致 AI 执黑执白棋风都一样对战体验很平。只算 atk 不管 def 的 AI 表现是“无脑进攻”它永远在做自己的棋对手已经冲四了还在别处落子。把 def 用防守权重缩放后加进总分AI 才第一次同时拥有“会攻”和“会堵”两个能力这才是人机对战里“机”的完整形态。5. 常见问题排查从窗口闪烁到“AI 只堵不攻”的五个坑下面五个坑是实操里被问得最多的也是我实际翻过车的记录。每条按现象、原因、解决写前三条是代码问题后两条是 AI 设计与环境问题。5.1 窗口忽闪忽闪像坏了一样现象每落一子整个棋盘闪一下拖动窗口时棋盘线条留下大量残影。原因没做双缓冲。WM_PAINT 里直接往屏幕 DC 上画线画圆系统在绘制中途就把半成品内容显示出来了视觉上就是闪。解决按 3.2 的 DrawBoard把全部内容先画到内存 DC最后一次 BitBlt 贴回。同时在 WndProc 里加一行case WM_ERASEBKGND: return 1; // 背景我们自己会整块覆盖改完应该完全无闪。如果还有闪检查 CreateCompatibleBitmap 的宽高是不是 W 和 H位图尺寸和客户区不一致时BitBlt 会只贴一部分剩下的区域还是老画面。5.2 棋子总是偏半格现象鼠标点在某格边缘棋子画到相邻格整体看起来向右上偏移。原因坐标换算里写了(x - MARGIN) / SIZE整数除法直接截断小数。点在某格右侧 1/4 处算出来还是左边格的编号。解决换算时先加半格再除int col (x - MARGIN SIZE / 2) / SIZE; int row (y - MARGIN SIZE / 2) / SIZE;原理是把小数部分变成四舍五入而不是截断。改完用四个角的格子做基准测试点每个格的正中心棋子必须落在格内正中心四个角都准全盘基本不会偏。5.3 工具箱空白、找不到 windows.h甚至 VS 启动报错现象一用 VS 新建 C 项目后左边工具箱一个控件都没有怀疑自己装坏了。原因你建的可能是“Windows 窗体应用程序C#/CLR”或者控制台项目。Win32 编程本来就没有可视化设计器UI 全是用 GDI 代码画的工具箱空白是正常现象不是软件故障。解决新建项目时选“Windows 桌面应用程序C”。建对之后该关注的是“本地 Windows 调试器”那个绿色按钮不是工具箱。现象二#include windows.h报“无法打开 windows.h / winuser.h”。原因Windows SDK 版本和平台工具集不匹配比如工具集选了 v143但 SDK 组件没装全。解决项目属性 - 常规 - Windows SDK 版本改成已安装的版本平台工具集改回当前 VS 对应的版本VS 2022 对应 v143VS 2019 对应 v142。现象三环境级故障Visual Studio 启动直接弹“由于出现错误无法启动 Visual Studio”错误号 -2146233082。原因这是 .NET Framework 运行时或 VS Installer 组件损坏和五子棋代码没有关系重装工程文件等于白费功夫。解决控制面板 - 程序和功能 - 选中 Visual Studio 2022 - 更改 - 修复。修复完还不行就重新安装对应版本的 .NET Framework比如 4.8。解决了之后原来写的工程直接打开就能编译。5.4 AI 第一手落左上角而不是天元现象空棋盘开局电脑第一手落在 (0,0) 或 (14,14)而不是棋盘中心。原因空棋盘时每个格子的攻防分完全相同都是 101020 分AIThink 按“第一个最高分”选取遍历顺序是从 (0,0) 开始的所以取了左上角。解决AIThink 开头加空棋盘特判棋盘全空时直接返回 (7,7)if (EmptyCells() N * N) return {7, 7};验证方法点“新游戏”AI 第一手应落在天元位置连开两局结果一致才算修复。5.5 电脑只堵不攻全程被动现象AI 永远在贴着自己的棋子走局面全程被人类主导自己凑不出连续进攻。原因DEF_WEIGHT 调得过高比如设到 2.0 以上def 分量压过 atk导致每次选点都在优先堵对方、放弃做自己的棋型。另一个可能是 ShapeScore 里眠三和活三的比例失调让“堵对方一个眠三”的分值长期高于“自己做一个活三”。解决先把 DEF_WEIGHT 拉回 1.0然后检查分值表冲四 10000、活三 4000、活二 500确保活三大于 2×眠三。验证方法是让 AI 执白后手下一盘观察它是否能在防守之外主动做出活二或活三能主动做棋说明攻守关系恢复了。6. 让 AI 更像人两步前瞻、棋谱回放与改参验证习惯评分表 AI 改到能赢不难难的是让它“下得像个有思路的人”而不是一个只会机械打分的计算器。这章的技巧是在不动大结构的前提下把单点贪心往上再推一层。6.1 两步前瞻打破单点贪心的组合盲区单点评分最大的盲区是双冲四、双活三这类“两个位置组合起来才成立”的棋。常见做法是加一层很轻的模拟对当前评分最高的 3 个候选点分别模拟落子用同一套评分函数评估对手在这个局面下的最佳回应分最后用“我的得分 - 对手的回应分 × 惩罚系数”重排for (int i 0; i 3; i) { POINT p candidate[i]; board[p.y][p.x] AI_COLOR; int oppBest AIThinkScore(board, HUMAN_COLOR); // 对手的最佳回应 board[p.y][p.x] EMPTY; // 悔掉模拟 finalScore[i] myScore[i] - oppBest * 0.6; }参数说明0.6 是资源包默认的惩罚系数值越大 AI 越保守越会避开“下完这手就被对手狠狠回应”的位置候选只取前 3 个两层模拟最多做 9 次评估性能依旧是实时的。6.2 用固定棋谱回放做回归验证改 AI 参数最大的风险是“这次改好了这个局面却悄悄改坏了另一个早就能应对的局面”。别靠手感把固定棋局存成 replay.txt 做回归7,7,1 7,8,2 8,8,1 8,7,2 9,9,1每行三列依次是 col、row、角色角色 1 黑 2 白。验证代码单开一个控制台工程跑不需要挂界面FILE *fp fopen(replay.txt, r); int col, row, role, step 0; while (fscanf(fp, %d,%d,%d, col, row, role) 3) { if (board[row][col] ! EMPTY) { printf(棋谱重复落子: %d,%d\n, col, row); break; } board[row][col] role; step; if (CheckWin(row, col, role)) { printf(第%d手: %s 赢\n, step, role AI_COLOR ? AI : 玩家); break; } } fclose(fp);这套工程连同 replay.txt 测试棋谱都在资源包里下载后按 README 直接编译就能跑。从那以后我每次调完 AI 权值都强制自己先跑一遍这五盘测试棋谱确认该赢的能赢下来、该堵的堵得住再开真人局。AI 棋力这个东西靠手感是玄学靠棋谱回放才有依据。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

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

较早相关资讯

最新相关资讯

无源温感芯片:免供电UHF RFID温度监测从原理到部署 2026/9/29 20:52:09

无源温感芯片:免供电UHF RFID温度监测从原理到部署

1. 一颗不用电池的温度哨兵,到底解决了什么痛点温度监测这件事,听起来简单,做起来全是坑。做过冷链物流、仓储管理、电力设备巡检或者食品医药行业的朋友应该都有体会:想监控一个环境或者一台设备的温度,传统方案无非两…

阅读更多 →
风机叶片缺陷检测数据集 | 风机叶片 缺陷检测 风电运维 无人机巡检 细粒度分类9126期 2026/9/29 20:52:09

风机叶片缺陷检测数据集 | 风机叶片 缺陷检测 风电运维 无人机巡检 细粒度分类9126期

风机叶片缺陷检测数据集 | 风机叶片 缺陷检测 风电运维 无人机巡检 细粒度分类9126期 数据集概述 本数据集专注于风机叶片表面缺陷的视觉检测与分类,服务于风电运维、无人机巡检及能源设施管理。数据涵盖九类典型叶片缺陷,适配缺陷识别、维护决策及自动…

阅读更多 →
ECU故障码不是结论,而是线索:从数据流到根因的实战诊断指南 2026/9/29 20:52:09

ECU故障码不是结论,而是线索:从数据流到根因的实战诊断指南

仪表盘上的黄色故障灯一亮,九成车主第一反应是开去修理店,然后盯着诊断仪屏幕上那些P开头的代码发懵。干这行十来年,我接过的返工车里,至少三成都是因为“读码后瞎换件”造成的——报了P0171就往死里换氧传感器,报了P0…

阅读更多 →
为什么PDF转Excel时表格会错乱(以及如何修复) 2026/9/29 20:52:09

为什么PDF转Excel时表格会错乱(以及如何修复)

你将一份12页的财务报告导出到Excel,打开结果后,原本漂亮的季度表格变成了一列800行的表格。数字还在——只是不知在哪儿——但每个数字现在都变成了文本字符串,列标题每20行重复一次,而且一个总计值还跑到了右边两列的单元格里。…

阅读更多 →
单选框和复选框如何美化 2026/9/29 20:52:09

单选框和复选框如何美化

登录要勾协议、问卷要选一项,浏览器自带的方框圆点难看还难统一。做法是:原生 checkbox / radio 还在,负责取值和提交;看得见的圆点、对勾用旁边的 label 或 span 画,靠 :checked 换样子。 入口是 index.html&#xff…

阅读更多 →
从T+1到分钟级:湖仓一体在大厂的真实落地收益对照(携程/网易云信/腾讯) 2026/9/29 20:52:03

从T+1到分钟级:湖仓一体在大厂的真实落地收益对照(携程/网易云信/腾讯)

从T1到分钟级:湖仓一体在大厂的真实落地收益对照(携程/网易云信/腾讯) 一、先说结论:收益对照比架构图更难 湖仓一体、流式湖仓的架构文章已经不稀缺。公开可见的工程分享里,Flink 与 Paimon/Fluss 组合承担流式入湖&a…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

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

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