尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

五子棋人机对战引擎:从rar解压到alpha-beta剪枝与评估函数调优

五子棋人机对战引擎:从rar解压到alpha-beta剪枝与评估函数调优 简介一份基于VC实现的五子棋人机对战源码工程适合C/C初学者、人工智能算法爱好者以及课程设计学生研读通过实际项目演示极大极小搜索与阿尔法-贝塔剪枝在棋类博弈中的落地方法同时展示微软基础类库图形界面的构建思路。整套资源共29个文件约367KB涵盖4个C源文件、5个头文件以及棋盘位图、程序图标、工程配置与可执行文件还附带一份文档和若干文本说明便于理解代码结构和运行方式。目前已有133人学习下载。包内包含可直接运行的MyChess程序可先运行体验对弈过程再对照源码分析评估函数、搜索策略与胜负判定逻辑同时文件组织清晰适合作为课程设计或人机博弈入门参考帮助读者掌握从界面绘制到智能决策的完整开发流程。1. 五子棋人机这个 rar 包里装的不只是棋盘它是你复刻 AI 对弈的最小工程如果你是循着wuziqi.rar_五子棋人机这个标题点进来的大概率不是想听五子棋规则而是手里有个压缩包、想让它跑起来或者想把它改造成自己能讲明白的课程设计、毕业设计。这个 rar 里通常不会是一堆图片和文档真正值钱的是那个用 Python 写的main.py——一个连深度学习和训练集都不需要的五子棋人机对弈程序。它的核心不是“智能”而是一个可解释的搜索加评分框架棋盘存进二维数组AI 用一层层搜索找出得分最高的落子点GUI 用 tkinter 画出来整个工程不超过十来个文件单机就能跑。这打包了你在人工智能导论课上最常遇到也最想拿下的问题怎么做一个人机对弈相比围棋那种需要蒙特卡洛树搜索的复杂度五子棋用 alpha-beta 剪枝和手工评估函数就能达到“让你不能轻易赢”的强度。如果你正在找能直接复现、能改参、能讲清原理的样例工程这就是一个很好的起点。2. 从 rar 解压到跑通第一局环境准备与最小启动命令2.1 解压 wuziqi.rar用 7-Zip 还是 unrar先看文件头标题带 rar第一步自然是解压。常见做法是 Windows 下右键用 WinRAR 或 7-Zip如果你在 Linux 服务器上拿到这个包命令行里更推荐unrar x而不是tar因为 tar 不认 rar 的压缩头。命令如下先建一个干净目录再解压避免把路径带进中文环境。mkdir -p ~/gobang cd ~/gobang unrar x ~/Downloads/wuziqi.rarmkdir -p创建目录并支持嵌套cd进入目录后解压这样所有文件会落在当前目录下而不是散在下载目录里。unrar x是解压并保留目录结构对比unrar e把文件全平铺到一个目录来说x更适合源码包。如果系统没有unrarDebian/Ubuntu 下先装sudo apt install unrarCentOS 用sudo yum install unrarWindows 下安装 7-Zip 后在资源管理器里直接“解压到 wuziqi”即可。如果解压时提示“文件头损坏”别急着删包——很可能是下载不完整优先重新下载而不是用修复工具去赌。2.2 跑通主程序tkinter 是唯一硬依赖五子棋人机这个项目一般只依赖 Python 标准库tkinter是 Python 自带但某些精简环境里没有的 GUI 库。先验证 Python 环境能否导入 tkinter再启动主程序。python -c import tkinter; print(tkinter.TkVersion) python main.py如果第一行打印出版本号比如8.6说明 tkinter 正常。第二行启动后弹出一个 15×15 的棋盘窗口鼠标点击棋盘AI 会跟着落黑子或白子一局结束弹窗提示胜负这就是标题里的“五子棋人机”跑通了。python -c用于执行单行 Python 表达式这里只做环境探活避免直接运行主程序后才发现缺库。python main.py是大多数这类工程的统一入口文件名可能叫game.py或main.py解压后先看一眼目录里有没有requirements.txt——有的话就pip install -r requirements.txt没有的话通常只有标准库依赖。如果你的机器装了多个 Python 版本python可能指向 Python 2。用python3 main.py显式指定版本。tkinter 在 macOS 上偶尔需要brew install python-tkWindows 官方 Python 安装包默认自带不要额外 pip install因为 tkinter 不是 PyPI 包。2.3 解压后先看清文件分工哪些能改、哪些别动一个典型的 Python 五子棋人机工程会有 4~6 个.py文件我一般按职责分成三块入口与 GUI、棋局逻辑、AI 搜索。给一个常见布局范例你解压后可以对着找wuziqi/ ├─ main.py # 主入口创建窗口和棋盘画布 ├─ board.py # 棋盘类落子、判断胜负、保存棋型 ├─ ai.py # 人机搜索评估函数 alpha-beta 剪枝 ├─ config.py # 参数配置棋盘大小、搜索深度、AI 先手 └─ README.md # 运行说明main.py通常只负责实例化tk.Tk()和绑定鼠标事件board.py把棋盘数据与界面解耦ai.py是核心后面两章讲的评分和搜索都在这里。改参数时优先级是先改config.py再调ai.py里的权重表最后才碰board.py的坐标映射。棋盘大小BOARD_SIZE 15是标准值不要改成 19除非你同时改ai.py里按边界生成候选点的逻辑否则 AI 在靠近边界的位置会漏算棋型。AI_FIRST False表示玩家执黑先行想体验 AI 先手可以改成True这是最快能感受到难度变化的一个开关。如果你在解压后看到根目录还有setup.py或run.sh说明发布者做了封装。我的建议是不要直接用setup.py installWindows 上经常因为权限或路径问题装失败直接跑源码更可控。3. 人机对弈的大脑评估函数与搜索深度怎么调3.1 五子棋 AI 不用神经网络评分表打分才是最稳的方案不少人是冲着“人机”两个字来的预期是不是要装 TensorFlow其实大多数能用的五子棋人机项目走的都是搜索 评分。做法是把每个落子位置放上自己的棋子后沿横、竖、正斜、反斜四个方向扫描统计“连五、活四、冲四、活三、活二”等棋型的数量乘以预设分数加起来就是这一步棋的得分。下面是一个精简的棋型分值表常见做法棋型我方分值对方分值连五10000000无意义活四1000000800000冲四10000090000活三100009000眠三1000900活二10090注意对方分值比同棋型我方略低这是为了让 AI 在“自己能活三”和“破坏对方活三”之间保持平衡。如果完全对等AI 会变成一个纯防守怪永远堵你却不会自己组织进攻对局会拖到 200 手以上。这个表不是数学公式推出来的而是靠对弈经验调的所以当你觉得 AI 太贪攻或太怂时先调这张表不要动搜索算法。SCORE_FIVE 10000000必须远大于其他值确保 AI 看见自己连五就立刻落子同样检测到对方连五时也要给超高阻断分否则 AI 可能先去走自己的活四而漏了堵你。如果你把分值改成同一量级AI 会出现“看到赢棋却不走”的翻车现场。3.2 alpha-beta 剪枝搜索深度不是越大越好有了评分函数AI 每次走棋时会在候选点列表上做递归搜索先假设自己走某一步换对方走再看自己能走什么直到叶子节点才用评估函数打分。这个过程的复杂度是候选点数目的深度次方所以必须用 alpha-beta 剪枝砍掉明显劣于当前最优解的干。常见的 AI 核心函数长这样def best_move(board, depth, ai_color): candidates gen_candidates(board) alpha, beta -float(inf), float(inf) best_score -float(inf) best_point None for x, y in candidates: board[x][y] ai_color score alpha_beta(board, depth - 1, alpha, beta, False, ai_color) board[x][y] 0 if score best_score: best_score, best_point score, (x, y) alpha max(alpha, best_score) return best_pointgen_candidates返回候选落子点通常只取有棋子周围两格以内的空位不然每步要搜 225 个点深度一深就卡死。board[x][y] ai_color是模拟落子递归结束后又board[x][y] 0撤销这是典型的回溯搜索保证每个分支互不污染。alpha_beta内部在轮到对方走时会找最小值并不断缩窄[alpha, beta]区间剪掉无意义的分支。depth是搜索深度团队项目常见默认值是 2 或 3。深度 2 就是“AI 走一步想一步你可能的应对”大约 0.1 秒内出结果深度 3 是“AI 想三步”通常 0.5 到 2 秒之间取决于候选点和棋局阶段。不要一上来就depth 6到了中盘候选点超过 40 个每个点递归 6 层普通 CPU 会卡到让你以为程序假死。3.3 迭代加深与限时用“0.5 秒还在想就走次优解”控制体验alpha-beta 剪枝已经能砍掉大量分支但同样的深度开局和中盘耗时差距很大。更稳的工程方案是迭代加深先算深度 1如果剩余时间还多再算深度 2以此类推。每层结果都缓存下来时间到了直接用最近一层的落子点。代码示意import time def ai_move_with_timeout(board, ai_color, time_limit0.5): deadline time.time() time_limit best None depth 1 while depth 6: move best_move(board, depth, ai_color) if time.time() deadline: break best move depth 1 return best or movedeadline是硬截止时间depth从 1 逐层加每次完整搜索后都刷新best一旦超时立刻返回上一层的落子点。这个方案的洞见是五子棋越到残局可用点位越少深度能相对更深用固定深度还不如用固定时间来得稳。time_limit0.5适合普通笔记本太慢了像在想棋太快了 AI 棋力下降。想加强难度可以调到 1.0但要把max_depth6作为上限防止搜索到某个局面突然指数爆炸。如果你在调参时发现一局棋 AI 步均耗时超过 2 秒先检查gen_candidates是否把整个棋盘 225 个点都返回了——很多翻车都出在这一步而不是剪枝本身。4. 把评估函数改得像人进攻与防守的权重设置4.1 棋子类型打分活四、冲四、活三到底怎么数评估函数里最容易出 bug 的部分就是把一段连续棋子识别成正确的棋型。很多项目用一个方向计数函数从落子点出发向两边数连续同色棋子遇到空位或边界就停然后把两端空位情况加起来判断是活四还是冲四。下面是一个方向统计的常见写法def count_line(board, x, y, dx, dy, color): cnt 1 open_ends 0 for step in (1, -1): nx, ny x dx * step, y dy * step while 0 nx 15 and 0 ny 15 and board[nx][ny] color: cnt 1 nx dx * step ny dy * step if 0 nx 15 and 0 ny 15 and board[nx][ny] 0: open_ends 1 return cnt, open_endsdx, dy取 (1,0)(0,1)(1,1)(1,-1) 四个方向覆盖横、竖、两条斜线。for step in (1,-1)向两个方向延伸cnt数连续子数open_ends数两端是否还有空位。一个子数是 4、两端都通的是活四必胜点子数是 4、只有一端通的是冲四子数是 3、两端都通的是活三。这个函数返回值再组合方向就能得到上面那张表的输入。判断边界时用0 nx 15如果你的棋盘类用的是matrix[x][y]千万别把 x 和 y 当屏幕像素用。open_ends的统计要连空位后立刻停止不要继续数第二个空位否则“眠三”会被误判成“活三”AI 会去走一个根本没威胁的位置。4.2 防守权重不能照抄进攻值先手优势与盲目堵截把对方棋型分值设成和我方一样是最容易犯的错后果是 AI 永远在应对你你活三它堵你冲四它堵它自己永远攒不出一个活三。原因是五子棋里先手优势巨大后手只防守是赢不了的。我一般把对方分设为我方的 0.9并且保留一个独立分支如果检测到对方连五则阻断分设为极大值这时 AI 必须堵。def evaluate_point(board, x, y, ai_color): total 0 for score_dict, own in [(AI_SCORE, True), (HUMAN_SCORE, False)]: color ai_color if own else 3 - ai_color for dx, dy in ((1,0),(0,1),(1,1),(1,-1)): cnt, ends count_line(board, x, y, dx, dy, color) if ends 0: continue key (cnt, ends) total score_dict.get(key, 0) return totalAI_SCORE和HUMAN_SCORE是两个分值字典把 (连续子数, 空端数) 映射到分。例如(4,2)在AI_SCORE里对应 1000000在HUMAN_SCORE里对应 800000。遍历四个方向累加后返回这一步棋给局势带来的总分数。注意这里评估的是“在这个位置放下一颗子后”的棋型变化不是当前已有棋型所以候选点模拟落子后调它才准确。3 - ai_color是对方颜色因为棋盘用 1 和 2 表示黑白3-123-21。如果你看到代码里有人写成ai_color 1 ? 2 : 1也是一样的逻辑。调权重时不要单独调一个键值比如把活三从 10000 改成 15000另一个方向上的冲四也要同步感觉一下否则 AI 会偏爱只在某个方向进攻。4.3 用落子热区验证权重把评估值打印到棋盘上权重调了一轮怎么看效果不要只看胜率先把每个空位评估分输出成热区图这样能直观看到 AI 眼中的“战略要地”。在ai.py里加一段调试函数def print_heatmap(board, ai_color): scores [[0 for _ in range(15)] for _ in range(15)] for i in range(15): for j in range(15): if board[i][j] 0: scores[i][j] evaluate_point(board, i, j, ai_color) for row in scores: print(\t.join(f{v:7} for v in row))两层循环遍历所有空位逐个调用evaluate_point把得分放进一个 15×15 的二维数组然后打印出来。你会在输出里看到活三交叉点附近的分值明显高于边角而如果对方活三在面前那个点必须是全场最高分否则防守权重就有问题。这种打印在中盘 15×15 是 200 个数字控制台会刷屏建议只在下棋前打印一次或者只打印中心 7×7 区域。真正调参时我看热区图的逻辑是让 AI 先手如果它第一手没走在中心说明evaluate_point里的边界处理有问题如果 AI 在自己冲四已形成时去走别的活三说明冲四分值设置偏低。如果热区图上出现了“自己活四分不如对方活三”的情况那就是防守权重压过了必胜权重需要把(4,2)的分值调得比对方任何棋型都高两个数量级。这类问题靠读代码很难发现打印热区图是血泪经验里最有效的诊断手段。5. 解压和运行最常见的五个坑伪加密、中文路径、tkinter 黑窗5.1 rar 伪加密明明没设密码却提示要密码现象下载的wuziqi.rar右键解压WinRAR 弹出输入密码但资源说明里没提密码或者输空密码点确定也进不去。原因rar 格式有“伪加密”标志。有些人打包时把压缩包头部某个标记位改成了加密状态文件本体并没有真正加密数据流可以直接解出但常规解压工具看到加密标志就先索要密码。这类包经常出现在课程资料、代码分享的场景里。解决先用 7-Zip 打开如果 7-Zip 能列出内部文件名却无法提取再用工具直接读文件流。命令行下可以试试7z x wuziqi.rar -p密码参数给空字符串。真正的伪加密包在文件列表可见的前提下用支持“忽略加密标志”的工具可以直接解出来。如果 7-Zip 解压时压根列不出文件那才是真加密只能去找密码来源不要硬猜。伪加密是 rar 的已知特性不是这个项目本身的问题——很多课程资料发布者故意用伪加密防随手转存却忘了告诉使用者。如果你解压后发现额外广告或奇怪脚本先查杀再运行。5.2 解压目录带中文空格importerror 的隐形元凶现象项目解压到D:\下载\课程资料\五子棋人机\后运行python main.py报错ModuleNotFoundError: No module named ai但ai.py明明在同一个目录里。原因当 Python 脚本所在路径包含中文或空格时部分 Windows 环境的模块搜索路径会异常导致脚本无法把当前目录加入sys.path。另外工程内部如果用了相对路径加载资源也会被空格卡住。解决把整个文件夹挪到纯英文路径比如D:\projects\wuziqi。如果不想挪在main.py最前面加import os, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))这段代码把脚本所在的绝对路径加进sys.path保证import board、import ai能找到兄弟模块。os.path.abspath(__file__)获取脚本完整路径dirname去掉文件名insert(0,...)放在模块搜索路径最前面避免被其他同名文件覆盖。5.3 Linux 服务器上跑不了tkinter 缺失和 display 连接失败现象在云服务器上python3 main.py报ModuleNotFoundError: No module named tkinter或者cant open display。原因服务器通常没有桌面环境也没有安装python3-tk包。tkinter 是 Python 标准库的内容但很多发行版把它拆成了独立包默认不装。cant open display则是没有 X Server。解决如果只是想在服务器上跑 AI 测试不要直接开 GUI改写一个命令行对弈入口用同一套board.py和ai.py通过input接收坐标打印棋盘到控制台。这样可以绕过 tkinter 验证人机逻辑。如果就是想弹窗看棋盘可以本地跑或者用 X11 转发ssh -X userserver python3 main.py。装依赖命令sudo apt install python3-tk。ssh -X是 X11 转发在 Windows 上需要配合 X Server 才能显示没装就别指望这一步直接用命令行入口更省事。5.4 AI 落子慢递归深度是表面原因真正的问题是候选点失控现象下到中盘每步 AI 思考超过 10 秒甚至程序无响应。查看 CPU 占用发现单核 100%明显不是卡死而是算不过来。原因gen_candidates返回了所有空位或者返回了包含大量已无价值点的位置。alpha-beta 剪枝只能剪掉“明显不如当前最优解”的分支如果候选点全是四处漏风的位置剪枝率就从 90% 掉到 30%耗时指数级上升。解决限制候选点范围只返回“距离已有棋子 2 格以内”的空位。这是工程里最有效的加速。代码示意def gen_candidates(board): moves set() for i in range(15): for j in range(15): if board[i][j]: for di in range(-2, 3): for dj in range(-2, 3): ni, nj i di, j dj if 0 ni 15 and 0 nj 15 and board[ni][nj] 0: moves.add((ni, nj)) return list(moves)先扫整个棋盘找所有非空点然后以每个非空点为中心把半径 2 格内的空位都加入候选集合。最后用 set 去重转成 list 返回。这样在开局时候选点约 10 个中盘约 40 个搜索深度 3 在 0.5 秒内能完成。range(-2, 3)是半径 2 的意思改成 3 会提高搜索覆盖率但耗时成倍上涨新手建议不要动。如果 AI 还是慢把搜索深度从 3 降到 2先确认逻辑正确再升深度。5.5 棋盘坐标错位GUI 像素坐标和数组下标是两套系统现象鼠标点击棋盘棋子落到与预期对称的位置或者 AI 落子偏一格明明点 (7,7) 却显示在 (7,8)。原因tkinter 画布使用像素坐标棋盘每个交叉点间隔是固定值比如CELL_SIZE 30点击位置换算成格子索引时常见的取整公式是row (event.y - MARGIN) // CELL_SIZE和col (event.x - MARGIN) // CELL_SIZE。如果你把二维列表的行当 y、列当 x就会导致行列互换因为数组的board[i][j]中 i 是行对应屏幕 yj 是列对应屏幕 x。解决统一用row, col命名并写两个转换函数def pixel_to_rc(event): row (event.y - MARGIN) // CELL_SIZE col (event.x - MARGIN) // CELL_SIZE return row, col事件对象里event.x是水平像素event.y是垂直像素。先减MARGIN去掉棋盘边距再整除格子尺寸得到数组下标。注意返回顺序是row, col调用方必须按这个顺序传给board[row][col]。如果你看到 AI 落子后棋盘上对不上优先检查这一步。MARGIN一般是 30CELL_SIZE是 30 或 40两者要配合棋盘 15 线加上左右 margin窗口宽度就是MARGIN * 2 CELL_SIZE * 14不要自己随意改。6. 从能玩到能赢一个轻量威胁空间搜索的落地技巧6.1 先用测试局面验证 AI 是不是“会堵关键棋”调权重最怕的就是改完之后看不出好坏。我的做法是先把必杀局面写成测试用例比如对方已经四连AI 必须堵住一端def test_block_four(): board [[0]*15 for _ in range(15)] for c in range(4, 8): board[7][c] 2 # 对方四连 point ai_move_with_timeout(board, 1, 1.0) assert point in ((7, 3), (7, 8))这个测试会直接反映“防守权重是否有效”。如果断言失败说明 AI 没有意识到对方下一手连五这时候不用看整局棋谱问题范围就缩到HUMAN_SCORE里五连的分值设置。6.2 威胁空间搜索只搜活三、冲四的候选点全盘候选点即使限制到 2 格半径中盘也有 40 个左右。想进一步提速最常见做法是威胁空间搜索先扫描全场把所有“落子后能形成活三、冲四、活四”的位置单独挑出来作为高级候选点优先参与搜索如果高级候选点为空才退回到普通候选点列表。这个改动通常能把深度 3 的耗时从 1 秒压到 0.3 秒而棋力不降反升因为 AI 不再浪费时间思考边角废棋。实现上并不复杂把gen_candidates的返回值排序让评分高于某个阈值的点排在前面alpha-beta 搜索时先展开这些点剪枝效率会明显提高。阈值可以从热区图里观察我一般取“活三分数的 1/2”作为阈值低于这个点直接靠后。6.3 悔棋与对局谱给调参留一剂后悔药改评估函数最怕改完后悔。我在项目里加了最简单的悔棋列表history [] history.append(copy.deepcopy(board))每次落子前压栈悔棋时弹出。有了对局谱和悔棋调参后可以回到关键局面反复测不用重新下一盘。配合测试用例和热区图这套组合拳比单纯看胜率高效得多。我曾经把防守权重调到 1.2AI 变成一只只堵不攻的龟壳连自己的活四都视而不见全靠一个test_block_four把回归找出来——从那以后我所有调参都先写断言再动搜索。这个方向真正值得投入的地方就是让你能亲手控制 AI 的“性格”希望帮到你。本文还有配套的精品资源点击获取
返回列表