
简介这份Python五子棋源码面向Python初学者与进阶开发者提供人机对战与人人对战两种模式的完整实现可用于学习GUI编程、AI搜索算法与游戏逻辑设计。压缩包共4个文件以3个py源码文件为主另含1个pyc编译缓存文件整体约9KB体量轻巧便于快速阅读与二次修改。源码围绕棋盘绘制、鼠标事件响应、胜负判定等核心环节展开人机模式中涉及Minimax或Alpha-Beta剪枝等搜索策略人人模式则实现双玩家交替落子与合法性校验代码按功能拆分模块结构清晰。目前已有1890人学习下载适合作为课程设计、练手项目或算法入门的参考实例读者可从中掌握事件驱动编程、递归搜索优化与模块化组织思路并在此基础上扩展难度、界面或AI强度。1. 拆开这个压缩包Python五子棋源码到底能跑出什么很多人第一次搜「Python五子棋源码」心里想的其实不是「我要学 Python」而是「我想马上看到一个能落子的棋盘」。下载完Python五子棋源码人机对战、人人对战.zip解压出来通常是一堆.py文件加一张棋盘图双击运行却可能只闪一下黑框就没了。问题不在源码而在于你没先确认它依赖什么、入口在哪、人机对战的 AI 到底靠什么落子。这份源码的价值是把五子棋最核心的三件事一次性摊开棋盘数据结构、胜负判定、以及一个能陪你下的人机搜索算法。人人对战让你验证规则人机对战让你理解极小化极大Minimax配合评分函数是怎么工作的。适合刚学完 Python 基础语法、想找一个「有界面、有逻辑、能改」的练手项目的人也适合想拿它当课程设计底稿的在校生。接下来我按「先跑通、再读懂、最后改得动」的顺序把这份源码拆到你能自己复现的程度。2. 跑通之前环境、依赖与入口定位2.1 先看清压缩包里的文件结构拿到源码别急着运行先列目录。常见的五子棋源码结构大致是这几类文件主入口main.py或gomoku.py、棋盘与规则逻辑board.py、rules.py、AI 搜索ai.py、minimax.py、界面渲染gui.py或直接用tkinter写在主文件里、资源目录images/、sounds/。用一条命令把结构看清楚# 在解压后的目录里执行只看两层避免输出太长 find . -maxdepth 2 -type f | sort逻辑说明find的-maxdepth 2限制递归深度防止资源文件夹里几百张图刷屏-type f只列文件不列目录sort让输出有序方便你一眼找到main、ai、board这类关键词。参数上如果你在 Windows 的 PowerShell 里用Get-ChildItem -Recurse -Depth 2替代即可。定位入口的判断标准很简单找带if __name__ __main__:的那个文件或者文件名里带main、run、start的。找到后先别改代码直接跑。2.2 依赖安装tkinter、numpy 与 pygame 的分工五子棋源码的依赖通常逃不出三个tkinterPython 自带做界面、numpy棋盘矩阵运算、pygame部分源码用它做图形渲染和音效。先确认 Python 版本再按需装库# 确认版本建议 3.8 及以上 python --version # tkinter 一般随 Python 自带验证一下 python -c import tkinter; print(tkinter.TkVersion) # 如果源码用了 numpy 或 pygame再装 pip install numpy pygame逻辑说明python -c import tkinter是快速验证法能打印出版本号说明自带库没问题如果报ModuleNotFoundError: No module named tkinter那是 Linux 下没装python3-tk系统包用sudo apt install python3-tk补上。pip install numpy pygame里numpy 负责把棋盘当成二维数组做批量评分pygame 负责把落子画成圆并播放音效。参数上如果你用的是虚拟环境先python -m venv venv再激活避免污染全局环境。提示不要一上来就pip install -r requirements.txt很多个人源码的 requirements 里版本号写得很死反而装不上。先手动装 numpy 和 pygame 两个大头缺什么补什么。2.3 第一次运行黑框闪退怎么定位双击闪退是最高频的翻车现场。正确做法是在终端里运行让报错留在屏幕上# 假设入口是 main.py在终端里跑 python main.py逻辑说明终端运行会把Traceback完整打印出来常见的有三类。第一类是FileNotFoundError源码里写死了images/board.png这类相对路径而你没在源码根目录下运行解决方法是cd到入口文件所在目录再跑。第二类是ImportError缺库按 2.2 补装。第三类是AttributeError多半是 Python 版本差异比如老代码用了dict.has_key()改成in即可。参数上如果源码用了argparse可以python main.py --help看它支持哪些启动参数比如棋盘大小、先手方。跑通的标准是出现一个窗口能看到棋盘网格鼠标点击能落子人人对战模式下黑白交替正常。到这一步环境问题就全部清掉了。3. 读懂棋盘与胜负判定数据结构决定一切3.1 用二维列表还是 numpy 数组五子棋棋盘本质是一个 15×15 的矩阵每个格子有三种状态空、黑、白。源码里最常见的两种表示法直接决定后面 AI 写起来顺不顺。第一种是纯 Python 二维列表用 0/1/2 表示# board[i][j]0 空1 黑2 白 BOARD_SIZE 15 board [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] def place(board, row, col, player): if board[row][col] ! 0: return False # 该点已有子落子失败 board[row][col] player return True逻辑说明[[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]这种写法必须用列表推导不能写成[[0]*15]*15后者 15 行指向同一个列表对象改一行全变这是新手最经典的坑。place函数先判空再落子返回布尔值让调用方知道是否成功。参数上player约定 1 为黑、2 为白后续胜负判定和 AI 评分都依赖这个约定。第二种是 numpy 数组好处是可以用切片和向量化做整行整列的胜负扫描import numpy as np board np.zeros((15, 15), dtypenp.int8) def place_np(board, row, col, player): if board[row, col] ! 0: return False board[row, col] player return True逻辑说明dtypenp.int8把每个格子压到 1 字节15×15 才 225 字节内存几乎无感但换来的是board[row, :]直接取整行、np.diagonal直接取对角线。参数上如果你后面要写 AI 的批量评分numpy 版本能省掉大量 for 循环。选型建议纯练手、想看清每一步逻辑用列表想跑得快、后面要接搜索算法用 numpy。3.2 胜负判定的四个方向与边界处理五子棋判定「五连」只需要检查刚落下的那颗子沿四个方向各数一遍横、竖、主对角线左上到右下、副对角线右上到左下。每个方向往正反两侧延伸累计连续同色数达到 5 就赢。def check_win(board, row, col, player): # 四个方向横、竖、主对角、副对角 directions [(0, 1), (1, 0), (1, 1), (1, -1)] for dr, dc in directions: count 1 # 包含当前这颗子 # 正方向延伸 r, c row dr, col dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r dr c dc # 反方向延伸 r, c row - dr, col - dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r - dr c - dc if count 5: return True return False逻辑说明directions里四个(dr, dc)覆盖了所有可能连成五子的方向注意副对角线用(1, -1)行增列减。count初始为 1 是因为当前落子本身算一个。两个while分别向正反方向走边界条件0 r 15 and 0 c 15必须写在前面否则board[r][c]会先越界报错。参数上count 5而不是 5是因为长连六子及以上在多数规则里也算赢如果你要实现「恰好五子」的禁手规则这里改成 5并额外处理。注意判定函数只检查最后落子点不要每次全盘扫描。全盘扫描在 15×15 上虽然不慢但一旦 AI 要模拟成千上万步这个开销会被放大几十倍。3.3 人人对战的事件循环怎么接人人对战是验证规则正确性的最小闭环。用 tkinter 的话核心是把鼠标点击坐标换算成棋盘行列再调用落子和判定import tkinter as tk CELL 40 # 每格像素 MARGIN 20 def on_click(event): col round((event.x - MARGIN) / CELL) row round((event.y - MARGIN) / CELL) if not (0 row 15 and 0 col 15): return global current_player if place(board, row, col, current_player): draw_piece(row, col, current_player) if check_win(board, row, col, current_player): show_winner(current_player) return current_player 3 - current_player # 1 变 22 变 1逻辑说明round((event.x - MARGIN) / CELL)把像素坐标反算成格子索引round比整除更宽容点偏一点也能落准。3 - current_player是黑白切换的巧劲1 和 2 互换不用写 if。place返回 False 时直接 return保证已有子的格子不会被覆盖。参数上CELL和MARGIN要和你的棋盘绘制代码保持一致否则点击位置和视觉位置会对不上这是界面类源码最常见的错位来源。人人对战跑顺之后规则层就算验证完毕接下来才轮到人机对战。4. 人机对战的核心评分函数与极小化极大搜索4.1 棋型评分活四、冲四、活三怎么量化AI 落子好不好全看它怎么给一个局面打分。五子棋的评分函数通常按棋型给分常见的一套分值如下表。这张表是无数五子棋 AI 调出来的经验值你可以直接抄也可以按自己手感微调。棋型说明建议分值连五五子连珠100000活四两端开放的四连10000冲四一端被封的四连1000活三两端开放的三连1000眠三一端被封的三连100活二两端开放的二连100眠二一端被封的二连10评分函数的实现思路是对每个空点假设某方在此落子然后沿四个方向统计形成的棋型累加得分。下面是一个简化版SCORES {five: 100000, live_four: 10000, rush_four: 1000, live_three: 1000, sleep_three: 100, live_two: 100, sleep_two: 10} def evaluate_point(board, row, col, player): 假设 player 在 (row,col) 落子返回该点的进攻分值 total 0 for dr, dc in [(0, 1), (1, 0), (1, 1), (1, -1)]: count 1 blocked 0 # 正方向 r, c row dr, col dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r dr c dc if not (0 r 15 and 0 c 15) or board[r][c] ! 0: blocked 1 # 反方向同理 r, c row - dr, col - dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r - dr c - dc if not (0 r 15 and 0 c 15) or board[r][c] ! 0: blocked 1 total score_of(count, blocked) return total def score_of(count, blocked): if count 5: return SCORES[five] if count 4: return SCORES[live_four] if blocked 0 else SCORES[rush_four] if count 3: return SCORES[live_three] if blocked 0 else SCORES[sleep_three] if count 2: return SCORES[live_two] if blocked 0 else SCORES[sleep_two] return 0逻辑说明evaluate_point对四个方向分别数连续同色子数count和被堵端数blocked再交给score_of查表。blocked为 0 是活棋为 1 是眠棋为 2 基本没价值。参数上SCORES里活三和冲四都给 1000是因为实战中一个活三的威胁往往不亚于一个冲四具体数值你可以按对局体验调。这套评分只算进攻实际 AI 要同时算「我落这里的进攻分」和「对手落这里的进攻分」后者代表防守价值。4.2 Minimax 加 Alpha-Beta 剪枝的最小实现有了评分AI 要做的就是搜索。最朴素的思路是我走一步假设对手也走最优一步我再走一步……往前看若干层选对自己最有利的分支。这就是极小化极大Minimax加上 Alpha-Beta 剪枝能把无效分支砍掉大半。def minimax(board, depth, alpha, beta, is_max): # 到达深度上限或分出胜负就返回评估值 if depth 0: return evaluate_board(board) if is_max: best -float(inf) for row, col in candidate_moves(board): board[row][col] AI_PLAYER val minimax(board, depth - 1, alpha, beta, False) board[row][col] 0 best max(best, val) alpha max(alpha, best) if beta alpha: break # 剪枝 return best else: best float(inf) for row, col in candidate_moves(board): board[row][col] HUMAN_PLAYER val minimax(board, depth - 1, alpha, beta, True) board[row][col] 0 best min(best, val) beta min(beta, best) if beta alpha: break # 剪枝 return best逻辑说明is_max为 True 时是 AI 层取最大值为 False 时是对手层取最小值。alpha是当前已知的最大下界beta是最小上界当beta alpha时说明这条分支不可能影响最终决策直接break剪掉。board[row][col] 0是回溯必须写否则棋盘会被搜索过程污染。参数上depth一般设 2 到 4设太深在纯 Python 里会卡到没法玩后面 5.2 会讲怎么优化。candidate_moves不要遍历全部 225 个空点只取已有棋子周围两格内的空点能把分支数从 200 多降到 20 以内这是搜索能不能跑动的关键。4.3 把 AI 接进界面别让搜索卡死主线程AI 搜索是计算密集型如果直接在点击事件里同步调用界面会假死几秒。常见做法是把搜索放到单独线程算完再回主线程落子import threading def ai_move(): def work(): row, col best_move(board, depth3) # 回到主线程更新界面 root.after(0, lambda: apply_ai_move(row, col)) threading.Thread(targetwork, daemonTrue).start()逻辑说明threading.Thread把搜索丢到后台daemonTrue保证主窗口关闭时线程一起退出不会残留。root.after(0, ...)是 tkinter 的线程安全更新方式把落子动作排回主线程事件队列直接在工作线程里操作界面控件会出各种玄学问题。参数上depth3是响应速度和棋力的折中机器快可以上 4机器慢就降到 2。提示如果源码里 AI 是同步的你第一次点「人机对战」会感觉窗口卡住别以为是死机等几秒就好。想改造成异步按上面这段套进去即可。5. 避坑与排查五子棋源码最常见的五个翻车点5.1 棋盘画出来是歪的或点击错位现象棋盘网格能显示但鼠标点下去落子位置和视觉位置差半格甚至一整格。原因绘制时用的坐标公式和点击反算公式不一致比如绘制用了MARGIN col * CELL反算却写成event.x / CELL忘了减MARGIN。解决把绘制和反算的坐标公式抽成两个函数cell_to_pixel和pixel_to_cell保证它们互为逆运算改一处即可。5.2 AI 第一步想太久或直接卡死现象人机对战开局AI 迟迟不落子甚至界面无响应。原因搜索深度设太深或者candidate_moves遍历了全部空点分支爆炸。解决把候选点限制在已有棋子周围两格内深度先降到 2 验证能跑再逐步加到 3确认 Alpha-Beta 剪枝的break真的生效可以在剪枝处加计数器打印剪掉了多少分支。5.3 胜负判定漏判斜向五连现象横向竖向都能判赢斜着连成五个却没反应。原因directions里漏了(1, 1)或(1, -1)或者副对角线方向写成了(-1, -1)导致只往一个方向数。解决四个方向一个都不能少副对角线用(1, -1)并且正反两个while都要写只写一个方向会漏掉「当前子在一端」的情况。5.4 落子后棋盘数据被 AI 搜索改乱现象AI 思考完之后棋盘上多出一些不该有的子或者原有棋子消失。原因Minimax 递归里落子后忘了回溯board[row][col] 0搜索过程把临时假设的棋子留在了真实棋盘上。解决每一层递归落子后必须紧跟回溯建议用try/finally包住保证异常时也能还原。5.5 打包成 exe 后图片和音效丢失现象源码里跑得好好的用 PyInstaller 打包后一运行就报找不到images/board.png。原因打包后资源路径变了代码里的相对路径失效。解决用sys._MEIPASS判断运行环境写一个resource_path函数统一处理资源路径打包时用--add-data把资源目录带进去。6. 让 AI 从「能下」到「难缠」三个我常调的技巧第一个技巧是给评分函数加「双向威胁」权重。单纯累加单点分值AI 会漏掉「同时形成两个活三」这种杀招。我的做法是在evaluate_board里统计全盘活三和冲四的数量如果一方同时存在两个及以上活三额外加一个大分值。这样 AI 会主动去制造双威胁而不是只顾眼前一步。第二个技巧是开局库。纯搜索的 AI 第一步往往下在天元附近但缺乏变化。我一般会内置一个十几步的开局表前两手直接查表落子既省搜索时间又让开局不那么呆。开局表不用大覆盖常见的第一手、第二手应对即可。第三个技巧是迭代加深。与其固定深度不如从深度 1 开始搜逐步加深每次记录最优走法。这样在时间有限时随时能拿到一个「当前深度下最好」的结果而不是搜到一半被中断什么都没有。配合一个时间上限AI 的响应就稳定了。import time def iterative_deepening(board, max_depth, time_limit2.0): start time.time() best None for depth in range(1, max_depth 1): if time.time() - start time_limit: break best best_move(board, depth) return best逻辑说明iterative_deepening从浅到深逐层搜索每层都更新best一旦超时就返回上一层的结果。time_limit按你的耐心设2 秒对普通玩家已经够用。参数上max_depth给个上限防止无限加深实际生效深度由时间决定。我自己的习惯是先把人人对战跑通确认规则无误再单独拿 AI 模块做对局测试最后才接界面。这样出问题时能快速定位是规则、搜索还是界面的锅。这套源码改到最后你会发现真正难的不是写搜索而是调评分——那才是决定 AI 棋风的地方。希望帮到你。本文还有配套的精品资源点击获取