
简介这是一份基于C实现的亚马逊棋游戏系统源码面向学习C、MFC网络编程及棋类策略设计的开发者可解决从零搭建人机/人人/网络对战棋类项目的需求。源码工程共84个文件压缩包约522KB核心代码集中在24个头文件与19个cpp源文件中覆盖AI决策、网络通信、游戏控制器、界面对话框等模块其余为Visual Studio工程配置sln/vcxproj、资源文件rc/ico/bmp及可运行exe解压后可直接编译或运行体验。已有81人学习浏览。通过该系统可掌握亚马逊棋规则实现、简单AI搜索思路、基于CAsyncSocket的远程对战流程以及MFC对话框界面与游戏状态管理的完整代码组织方式适合作为课程设计或实战练手项目的参考蓝本。1. 这个 C 亚马逊棋 zip 到底是什么一个能改的博弈框架不是一个成品游戏拿到《基于C的亚马逊棋游戏系统.zip》这种包别急着双击找 exe。它真正值得你花一个下午的不是那个会画棋盘的控制台窗口而是它背后那套“走子、射箭、判定无路可走”的博弈逻辑。亚马逊棋是 10×10 棋盘、每方 4 枚可斜走直走的皇后棋子落一子必须放一支箭变成永久障碍棋盘越下越碎直到一方彻底动不了才分出胜负。对刚学完 C 语法的人来说它比贪吃蛇扫雷更接近真实软件——有数据结构、有规则引擎、还有一点搜索算法。对想碰棋类 AI 的熟手而言它又比五子棋更有嚼头箭会永久改造棋盘剪枝和估值的参数都得动真格。2. 把“游戏系统”当项目读类设计、棋盘表示与一回合两阶段的状态机2.1 工程结构先别猜按“棋盘、规则、AI、入口”四层找文件拿到 zip 并解压后常见的 C 小游戏源码并不会只有孤零零一个 main.cpp而是分成几个文件。你不需要把所有代码都读懂才敢跑先按四层去找表示棋盘状态的 Board 类、管理回合与输入输出的 Game/Controller 层、负责走子和射箭合法性判断的规则函数、以及可选的人工智能 AI 模块。入口永远只有一个——带 main() 的那个文件编译错误信息里第一个出现文件名通常就是入口所在。把这四层映射到文件这类棋类源码的常见结构大致是这样amazon/ ├── main.cpp # 入口菜单、回合循环 ├── board.h # 棋盘数据结构 ├── board.cpp # 棋盘初始化、走子/射箭状态修改 ├── game.h # 回合状态机接口 ├── game.cpp # 玩家输入、胜负判定、流程控制 └── ai.h / ai.cpp # 搜索与估值有些工程把它合进 game.cpp如果你打开压缩包看到的是单文件也不要慌很多教学版本会把上面所有内容写进一个 cpp 里。先跑通再拆是这类源码最稳妥的读法。单文件的好处是编译命令短坏处是函数之间耦合紧改 AI 深度时得在所有调用点之间来回找关系。我建议先记下“哪个函数管合法性、哪个函数管估值、哪个函数是主循环”再动手改代码。2.2 棋盘用二维数组还是位棋盘先求对再求快亚马逊棋的棋盘是 10×10每方 4 枚棋子、加若干支箭棋盘上不超过 100 格。用 std::array 包一层二维字符数组是最容易读懂的方案调试时一眼能看出局面长什么样。用 std::array 而不是原生数组是 C STL 里最值得养成的一个习惯——它能让 Board 整体复制后面做 AI 搜索时经常要拿当前局面生成临时备份这个性质很关键。#include array #include vector #include cstdint enum class Piece : char { Empty ., White W, Black B, Arrow # // 箭永久障碍 }; struct Board { // cell[row][col]row 对应 a-j 行col 对应 1-10 列 std::arraystd::arrayPiece, 10, 10 cell{}; void init() { for (auto row : cell) row.fill(Piece::Empty); // 双方各 4 枚具体初始坐标以本工程 init 函数里的常量表为准 cell[0][0] Piece::Black; // 示意写法实际坐标看源码 // cell[9][9] Piece::White; } bool inBounds(int x, int y) const { return x 0 x 10 y 0 y 10; } };逻辑说明enum class Piece而不是char或宏是为了让编译器帮你拦住“把箭当成空位”这类低级错误std::array而不是原生数组是为了让Board可以整体复制。参数说明行列顺序最好全工程统一我习惯先 row 后 col但有些源码会反过来改的时候只改一处并加注释别两套混用。每方只有 4 子这决定了位棋盘bitboard不是必须的。国际象棋为什么用 64 位位棋盘因为要快速算攻击面、把并行做到极致。亚马逊棋的棋子在 10×10 棋盘上滑行并不是不能用位运算表示但初版工程里为了调试方便二维数组效率已经足够真正的性能瓶颈不在这而是后面的搜索节点数量。2.3 一回合两个动作用状态机管住“走子”和“射箭”读每个棋类源码前先搞清楚它一回合有几步。亚马逊棋一个回合严格分成两步先移动棋子的“走子阶段”再固定射出一支箭的“射箭阶段”。箭一旦落下就是永久格之后的回合都要避着它走。这不是一个可有可无的设计而是亚马逊棋区别于“皇后棋”的本质规则——你不射箭这回合就没结束对手还不能动。enum class Phase { SelectPiece, // 让玩家选哪枚棋子 MovePiece, // 玩家输入目标格做合法移动 ShootArrow // 必须射箭射完才轮到对手 };逻辑说明之所以叫状态机是因为玩家每一步输入都必须严格符合当前阶段——选子阶段不能直接给出射箭目标移动阶段不能不射箭就结束回合。如果源码里只有“移动 轮换”两个状态、没有强制射箭这一步那它对规则的实现是不完整的跑起来会有棋手不射箭也能过回合的漏洞。改动时最容易被忽略的是AI 搜索也要跟着这个状态机走每一个“走子 射箭”才算一个完整的节点。移动合法性的判断是整个规则引擎的核心走子与射箭其实共用同一个“直线滑行”检查bool canSlide(const Board b, int fx, int fy, int tx, int ty) { if (!b.inBounds(fx, fy) || !b.inBounds(tx, ty)) return false; if (b.cell[tx][ty] ! Piece::Empty) return false; int dx tx - fx, dy ty - fy; if (dx 0 dy 0) return false; // 必须沿直线或 45 度斜线走 if (dx ! 0 dy ! 0 std::abs(dx) ! std::abs(dy)) return false; int stepX (dx 0) - (dx 0); // 归一化到 -1/0/1 int stepY (dy 0) - (dy 0); int x fx stepX, y fy stepY; while (x ! tx || y ! ty) { // 路径上每一格都必须空 if (b.cell[x][y] ! Piece::Empty) return false; x stepX; y stepY; } return true; }逻辑说明这段函数同时服务走子和射箭。走子时从亚马逊当前位置出发射箭时从刚落地的新位置出发目标格都必须为空路径上不许穿过任何棋子或箭。(dx 0) - (dx 0)这种写法把一个大数归一化到 -1、0 或 1避免再去分八种方向写八个 if。参数说明如果看到源码里把合法移动找全的逻辑叫generateMoves它内部基本就是遍历八个方向、每个方向一路滑到第一个障碍之前再把所有空格收集起来。写到这里工程的结构主线已经清楚了Board 管“状态长什么样”规则函数管“能不能走”Game 管“轮到谁、处于哪个阶段”。下一步就是把它真正跑起来。3. 把 zip 变成能玩的游戏解压、编译、运行的三种姿势3.1 解压之后先做的三件事拿到 zip 后先解压。Windows 上右键“全部解压缩”就行Linux 下用unzip amazon.zip。解压前建议先看一眼文件列表绝大多数棋类工程里没有可执行文件只有源文件和可能的 README。解压后我一般做三件事确认根目录下有没有 .sln / .vcxprojVisual Studio 工程或 CMakeLists.txt找到 main 函数所在文件读一遍 README 或源码顶部的注释看作者约定的坐标输入格式和编译方式。这三件事做完编译流程基本就清楚了。另外有一个安全习惯压缩包里如果自带 exe别急着双击用源码自己编一个出来再运行。这跟信不信任作者没关系只是源码工程的价值本来就在“自己构建”这一步。自带 exe 还可能因为缺运行库直接报错倒不如老老实实走一遍编译把环境问题一次性解决。3.2 用 Visual Studio 跑工作负载与运行库缺一不可常见做法是直接打开 .sln 或 .vcxproj。如果工程是用 CMake 组织的VS 2019/2022 也支持“打开文件夹”后自动识别 CMakeLists.txt。真正让新手卡住的有两处。第一处是装 VS 时没勾选“使用 C 的桌面开发”工作负载导致连#include iostream都找不到。解决方案不是换编辑器而是到 Visual Studio Installer 里把“使用 C 的桌面开发”勾上并修改安装。装完 VS 版本会有对应的 Microsoft Visual C Redistributable2015-2022随程序目录安装但单独分发 exe 给别人时很容易漏掉它运行就会报缺少 VCRUNTIME140.dll。第二处是平台切换。VS 默认加载的是 x86 配置现代机器建议在工具栏把 Debug 改成 Release、把 x86 改成 x64。Debug 下跑搜索会很慢因为编译器完全不优化Release 下的 -O2 级别优化能让 AI 搜索深一两层体感完全不一样。如果你更喜欢轻量一点的流程VS 里选择“文件 → 打开 → 文件夹”选中解压后的目录VS 会自动检测到源码。按 CtrlShiftB 编译按 F5 运行断点直接打在规则函数里这一步对后面调试 AI 很有用。3.3 用 MinGW / g 一条命令编译参数拆开讲如果源码没有依赖 Windows 专有 API用 MinGW 的 g 也能编。尤其你在 VSCode 里想跑配置 c/c 环境是绕不开的一步。先把编译器路径配进c_cpp_properties.json不然 IntelliSense 满屏红色波浪线看着像报错其实只是它找不到头文件。我自己习惯用 g 而不是 VS因为命令行编译对工程文件的依赖最小任何工具链都能复现。g -stdc17 -O2 -Wall -Wextra \ main.cpp board.cpp game.cpp ai.cpp \ -o amazon参数说明-stdc17指定语言标准工程里如果用了结构化绑定、std::optional这些至少需要 C17-O2是给搜索算法用的很大概率你的 AI 在-O0下慢到像死循环开了-O2之后立刻正常-Wall -Wextra打开全部警告棋类项目最常见的警告是“变量未初始化”和“有符号/无符号比较”这两类会直接导致坐标偏移或死循环。如果源码只有一个 cpp 文件命令更短g -stdc17 -O2 main.cpp -o amazon编译时常见报错是提示缺game.h或board.h原因大多是当前目录不对。在 VSCode 里按 F5 调试前先确认tasks.json里的cwd指向解压目录不要指到任意工作区文件夹。如果工程给出了 CMakeLists.txt那么 C/C 构建就会方便很多用两行命令就够了cmake -B build -G MinGW Makefiles -DCMAKE_BUILD_TYPERelease cmake --build build参数说明-DCMAKE_BUILD_TYPERelease直接决定优化等级忘了加这一行编出来就是默认的 Debug 版本后面跑 AI 会特别慢。CMake 的好处是它会自动把 board.cpp、game.cpp、ai.cpp 都编进去不用你手写一长串文件名。3.4 运行参数与输入方式先弄清楚坐标格式任何棋类源码的输入方式都比编译更值得提前确认。亚马逊棋没有“用鼠标点”的默认版本控制台版最常见的输入是先输入两个坐标第一个坐标表示选哪枚棋子并走到哪第二个坐标表示把箭射到哪。例如输入e2 e4再输入e4 h7就是一个完整回合。小写字母表示行数字表示列这是最常见的约定但有的源码会写成E2 E4或e2e4以 README 为准。main 主循环的逻辑一般长这样int main() { Board board; board.init(); Game game(board); while (!game.isOver()) { game.printBoard(); if (game.getPhase() Phase::MovePiece) { std::cout 输入棋子起点和终点例如 e2 e4: ; } else { std::cout 输入射箭起点和终点例如 e4 h7: ; } std::string line; std::getline(std::cin, line); if (!game.parseAndApply(line)) { std::cout 非法输入请重试\n; } } std::cout 胜方: game.winner() \n; return 0; }逻辑说明先取当前位置再判断阶段每个阶段只接受该阶段的输入parseAndApply内部调用上一章的canSlide做合法性校验。如果源码里没有这段主循环说明作者把循环写进了 Game 类本质也一样。参数说明如果想调试某一特定局面可以在board.init()后面再加几行“摆残局”的代码这比每次都从头下要快得多后面验证 AI 对局时全靠这个能力。编译和运行路径都搭好后真正决定“好不好玩”的部分——AI 的搜索与估值是下一章的主角。4. 亚马逊棋的胜负手可达性检查、搜索深度与估值参数怎么调4.1 判定“无路可走”不是数棋子是滑行的尽头亚马逊棋有个经常被新手写错的地方胜负判定不是看谁吃子多因为规则里根本没有吃子。每个回合结束都会多一支箭棋盘被箭切割得越来越碎直到某一方的所有棋子都无法再移动这一方才判负。因此“当前局面是否终结”这个问题本质上是对每个棋子做一次射线扫描看它还能不能滑到任意空格。std::vectorstd::pairint,int reachableFrom(const Board b, int sx, int sy) { std::vectorstd::pairint,int result; const int dirX[8] {-1, -1, -1, 0, 0, 1, 1, 1}; const int dirY[8] {-1, 0, 1, -1, 1, -1, 0, 1}; for (int d 0; d 8; d) { int nx sx dirX[d], ny sy dirY[d]; while (b.inBounds(nx, ny) b.cell[nx][ny] Piece::Empty) { result.push_back({nx, ny}); nx dirX[d]; ny dirY[d]; } } return result; } bool hasAnyLegalMove(const Board b, Piece side) { for (int x 0; x 10; x) for (int y 0; y 10; y) if (b.cell[x][y] side) if (!reachableFrom(b, x, y).empty()) return true; return false; }逻辑说明reachableFrom返回某一个棋子的所有可达格。因为亚马逊是沿直线滑行所以不需要上队列做 BFS八个方向各走到第一个障碍前就是全部答案。hasAnyLegalMove遍历整个棋盘找到属于当前方的每一枚棋子只要任意一枚能滑到空格就说明还没输。参数说明这个函数会在搜索里被调用几千几万次所以while循环里不要打印、不要用std::function包一层保持最朴素的写法反而最快。如果你的工程把reachableFrom改成用 BFS 队列结果一样但慢了约一个数量级这也是亚马逊棋与围棋、五子棋在实现上的一个明显差异。4.2 搜索骨架用 alpha-beta 把无限分支压到可跑有了合法着法生成器和胜负判定AI 的骨架就是标准的极小化极大搜索。亚马逊棋一回合有两步但搜索时最常见的处理是把它当成“每个节点一次生成完整回合”——即在节点内部先枚举所有走子再对每个走子枚举所有射箭。这样递归的每一层正好切换一次玩家深度计数也比较符合直觉。int alphaBeta(Board b, int depth, int alpha, int beta, int side) { if (!hasAnyLegalMove(b, side)) return -100000 (MAX_DEPTH - depth); // 无路可走输 if (depth 0) return evaluate(b, side); std::vectorMove moves generateFullTurns(b, side); // 按估值降序排列先搜好的着法剪枝更快 std::sort(moves.begin(), moves.end(), [](const Move a, const Move b) { return orderScore(a, side) orderScore(b, side); }); for (const Move m : moves) { b.makeMove(m); int score -alphaBeta(b, depth - 1, -beta, -alpha, other(side)); b.unmakeMove(m); if (score alpha) alpha score; if (alpha beta) break; // 剪枝对手不会给你机会了 } return alpha; }逻辑说明返回负数表示当前方不利越大越好-号切换视角是极小化极大最常见也最容易写错的地方。makeMove一次完成“走子 射箭”unmakeMove负责把棋盘恢复到进入这个节点之前的样子。如果工程里没有unmakeMove而是每个节点复制一份棋盘代码更容易写对只是会更慢我建议第一版先复制棋盘跑通后再改成原位修改。参数说明MAX_DEPTH是搜索深度上限depth为 0 时返回静态估值。alpha初始为-INT_MAXbeta初始为INT_MAX。若你发现 AI 每一步思考时间超过十秒先检查两件事深度是不是设到 6 以上了以及generateFullTurns里是不是把“射箭目标”重复枚举了。箭只能射到空格但同一个空格可能被多枚棋子都会枚举到去重能让节点数量少一半。4.3 估值函数别把“吃子”思维带进来写估值函数前一定要先确认这个棋类有没有吃子规则。亚马逊棋没有吃子双方始终是 4 枚棋子所以“子力价值”是恒定的真正决定胜负的是机动性和对空白格的控制。一个可用的静态估分可以拆成三块可达格总数、中心区域位置奖励、以及己方箭对对手路径的封锁效果。int evaluate(const Board b, int side) { int myMobility 0, opMobility 0; for (int x 0; x 10; x) for (int y 0; y 10; y) { if (b.cell[x][y] side || b.cell[x][y] other(side)) { int reach reachableFrom(b, x, y).size(); if (b.cell[x][y] side) myMobility reach; else opMobility reach; } } int centerBonus 0; // 离中心越近的棋子活动空间通常越大 for (auto p : piecesOf(b, side)) centerBonus (4 - std::max(std::abs(p.x - 4), std::abs(p.y - 4))) * 2; return (myMobility - opMobility) centerBonus; }逻辑说明myMobility - opMobility是机动性差中心奖励是让棋子别缩在角落。箭头封锁很难直接写出一个准确的公式很多实现干脆不写它的权重因为在浅层搜索里机动性已经覆盖了它的大部分效果。参数说明机动性权重的比例是估值函数最敏感的参数。如果 AI 总是下得“很短视”把机动性权重从 1 提到 2 通常立竿见影如果 AI 走出几步后突然崩盘多半是中心奖励权重过大、把棋子全挤到中间互相堵路。没有标准答案只能靠自对弈统计去调。4.4 参数表从“能走”到“会下”的四个参数你可以把 AI 模块里的常量统一收起来方便改参数推荐起始值含义与调整方向搜索深度 depth4深度每加 1耗时约乘分支因子新手先固定 4每步时限 maxTimeMs1000用时钟中断当前层回到上一层的完整结果排序裁剪 topN8每层只对估值最高的前若干着法完整搜索机动性权重 mobilityW2对局表现“短视”时加大下着下着崩盘时减小迭代加深是解决“深度和时限矛盾”的最实用参数AI 从深度 1 开始逐层加深每层都记录完整的搜索结论到时限就返回最后一层结果。这样即使机器慢也不会在浅层死等一个永远算不完的深度。Move iterativeDeepening(Board b, int side, int maxTimeMs) { Move best generateFullTurns(b, side)[0]; // 兜底着法 auto deadline std::chrono::steady_clock::now() std::chrono::milliseconds(maxTimeMs); for (int depth 1; ; depth) { if (std::chrono::steady_clock::now() deadline) break; best alphaBetaRoot(b, depth, side); } return best; }逻辑说明每一层都在上一层的完整结果上覆写best时间不够时至少能返回上一层的着法不会出现“卡住不动”。alphaBetaRoot是 alphaBeta 的根层包装差别只在遍历第一层着法时记录最佳 Move 而不是只返回分数。参数说明maxTimeMs设 500 到 2000 之间普通笔记本在 10×10 棋盘上深度 4 到 5 大约是 1 到 3 秒已经能明显看出 AI 在主动封路。再往深走算法层面就该考虑置换表和杀手着法了那已经不是拿到这个 zip 后第一周该做的事。5. 避坑与排查把“为什么出错”变成“改哪里”5.1 五条高频踩坑记录现象 1编译报错 “cannot open source file iostream” 或 “No such file or directory”。原因不是源码问题是编译器或工具链没配好。最常见的有两类Visual Studio 安装时没勾选“使用 C 的桌面开发”工作负载或者 VSCode 里选了 g 但includePath没指到 MinGW 的 include 目录。IntelliSense 找不到头文件也归这一类它甚至不影响编译只让编辑器满屏红波浪线。解决VS 里打开 Installer 把 C 工作负载装上VSCode 里在c_cpp_properties.json配置compilerPath指到实际 g 路径例如C:\mingw64\bin\g.exe。现象 2编译成功运行时弹窗提示缺少 VCRUNTIME140.dll。原因exe 依赖微软运行库而目标机器上没有对应的 Visual C Redistributable。这个运行库不是一个“可选组件”是很多 C 程序的共同底座x64 程序和 x86 程序需要装对应的 x64/x86 版本混装或漏装都会报错。解决装 Microsoft Visual C Redistributable2015-2022x64 版本个别老工程还依赖 2013 运行库按报错信息补装即可。Debug 模式编出来的程序还经常依赖msvcp140d.dll、vcruntime140d.dll这些带 d 的调试版库正式跑用 Release 编译就没有这个问题。现象 3双击生成的 exe黑框一闪而过根本看不到棋盘。原因程序运行完自动退出控制台被系统关闭。棋类游戏即使没有输入主循环在没有玩家数据时也会直接走到return 0窗口随之关闭。解决不要双击在终端里手动运行./amazonLinux/macOS或amazon.exeWindows。如果只是临时想看输出也可以在 main 的 return 前加一行std::cin.get()等回车但这只对调试有用别留到交付版本。现象 4轮到 AI 走棋时界面卡住几十秒甚至像死机。原因绝大多数不是死循环是搜索深度设太高。亚马逊棋分支因子比五子棋大不少不动手就一层层指数爆炸。10×10 棋盘、无剪枝、深度 6在 Release 下也可能要几十秒到几分钟。Debug 模式下更糟-O0会让同样深度慢十倍。解决先确认编译带-O2再把固定深度降到 4 或更小给 AI 加上章节 4.4 的迭代加深和时限。如果两个都做了还卡请检查unmakeMove是否正确回滚了棋子与箭——回滚不全会让hasAnyLegalMove在错误局面里空转。现象 5明明还有棋能走程序却判我输了。原因胜负判定写得太粗。最常见的实现错误是只判断“当前棋子有没有直接相邻的空格”而亚马逊可以斜着、直线滑行很远相邻空格只是它所有可达格里的一小部分。另一种错误是把“轮到的一方无法行动”和“双方都无法行动”混在一起下结论。解决先单测reachableFrom。找一个棋子旁边被箭围住、但斜上方有大片空地的局面看返回值里是否包含斜上方格子。如果为空说明射线扫描的边界判断或者Piece::Empty比较写错了。5.2 玄学问题怎么定位先打日志再怀疑内存另有一类问题最烦人AI 走几步之后偶尔出错一次重跑又正常。这通常不是随机数而是某个未初始化的变量或数组越界。遇到这种玄学 bug我的习惯是先在makeMove和unmakeMove两头加断言进入函数前后统计棋盘上的棋子数与箭数是否守恒。void checkerboardConsistency(const Board b) { int arrows 0; for (auto row : b.cell) for (auto c : row) if (c Piece::Arrow) arrows; // 每回合结束箭数应该恰好比开局多 1跨界了立刻崩 }逻辑说明如果断言触发先用二分法把 AI 搜索深度减到 1再看是否复现深度 1 不复现那问题多半在递归回溯时没把Arrow正确还原。参数说明越界写入比越界读取难查多了所以数组下标里凡是用row、col的地方一律先经过inBounds再访问不要直接b.cell[tx][ty] ...。这一类问题在很多棋类源码里都能碰到不是你这个 zip 特有。6. 验证与进阶用自对弈测 AI 棋力再给棋盘换张脸6.1 三行脚本跑一百局AI 强不强用数据说话验证是最常用的进阶需求也是最容易被跳过的。人玩很难发现“AI 从深度 3 改到深度 4 到底强了多少”因为一局棋要好几分钟体感全是噪音。给 main 加一个--autoplay参数让两个 AI 对局并输出结果统计才有意义。for seed in $(seq 1 50); do ./amazon --autoplay --seed $seed --depth-a 4 --depth-b 2 done | grep winner | sort | uniq -c参数说明--depth-a 4 --depth-b 2故意让两方搜索深度不同用来验证“深度更高的 AI 胜率是否明显更高”。如果 50 局里深度 4 的一方没有拿到压倒性优势先别急着加深度 5回头检查估值函数——多半是机动性权重没生效。每局用固定--seed可以让同一局面重跑方便复现“哪一步走崩了”。6.2 界面进阶先换控制台的门面再决定要不要上 SDL想从“能跑的命令行”变成“能演示的项目”最短路径是给控制台换一套更清晰的棋盘显示用两个字符的宽度表示一个格子箭显示成██。逻辑层完全不用动只改printBoard。再下一步才考虑 SDL2 画棋盘而那时 Board 的数据结构仍能直接复用因为渲染与规则是解耦的。改参数也没后悔药每次动权重之前先 git commit 一次保证“改回去”是秒级操作。我最后悔的一版正是没做自对弈统计一个人瞎调权重AI 时强时弱完全靠体感。拿到这个 C 亚马逊棋源码第一周这样干跑通、加自动对局、调参数足够你发现它比想象中耐玩。希望帮到你。本文还有配套的精品资源点击获取