news 2026/9/1 4:35:16

Python实战:打造象棋打谱与AI分析桌面小软件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python实战:打造象棋打谱与AI分析桌面小软件

初学 Python 想做点带“AI 味”的小项目,象棋打谱加分析是一个性价比很高的方向:既有图形界面,又有数据交互,还能把搜索算法、局面评估这些 AI 基础概念串起来。市面上的象棋软件虽然很多,但有的带广告,有的交互不顺手,有的功能封闭不方便自定制。自己动手做一个,既能按自己习惯整理棋谱,也能顺便把 AI 搜索原理打通。

这篇文章会从零开始,带着你完成一个“象棋打谱 + AI 分析”的桌面小软件。整体用 Python 开发,界面用 Tkinter,棋盘和走法合法性用 python-chess 库处理,AI 部分先实现一个极简启发式搜索,再预留外部 UCCI 引擎接入位置。文章会尽量使用初学者能看懂的写法,涉及的关键概念、坐标转换、搜索原理、常见报错都会展开说明。

如果你有 Python 基础,只是没写过完整小项目,这篇文章正好适合你。学完以后,你会得到:

  • 一个能正常打开、点击走子、记录棋谱的棋盘程序;
  • 一个能“摆棋、复盘、撤销”的基础打谱流程;
  • 一个能给出推荐着法和评估分的简单 AI;
  • 如何接入更专业象棋引擎做深度分析的思路。

1. 自制象棋软件从哪里开始:先把概念捋清楚

1.1 “打谱”到底是在打什么

“打谱”是中国象棋领域的一个常用词,本质是“按棋谱摆棋、走棋、复盘”。棋谱记录了双方每一步的落子顺序,比如“炮二平五、马八进七”这类中文记谱,或者更底层的坐标移动字符串。

自己做打谱软件,核心要解决的问题有三个:

  1. 如何用程序表示棋盘上的局面;
  2. 如何判断一个走法是否合法;
  3. 如何把一组走法保存下来并支持回放。

第一个问题看起来简单,但如果不做封装,很容易写出大量重复的棋盘数组操作。比如要判断“马走日”“蹩马腿”“炮需要隔子吃”这些规则,手写起来工作量不小。因此,这里推荐借助现成的棋盘库,把更多精力放在界面和 AI 分析上。

1.2 AI 分析在象棋中做了什么事情

象棋 AI 分析,简单说就是让程序从当前局面出发,计算哪一步更有可能取得优势。

传统象棋 AI 的基本流程是:

  1. 生成当前局面下所有合法走法;
  2. 对每个走法后的局面进行评估;
  3. 通过搜索算法往前多看几步,找到综合评分最高的走法。

其中“评估”需要衡量子力价值、位置控制、将军威胁等信息;“搜索”通常使用 minimax 搜索或带 alpha-beta 剪枝的优化版本。初学者可以先从“子力价值 + 一层搜索”入手,后面再慢慢加深。

可以说,象棋 AI 是一个很好的入门项目,因为它把数据结构、递归、评估函数、优化剪枝这些基础点非常自然地结合在一起。

1.3 为什么这个技术组合适合初学者

很多初学者一想到写象棋软件,就打算用 C++ 和 Qt,或者用网页 Canvas 实现。其实用 Python + Tkinter 更合适:

  • Python 语法简单,适合快速验证思路;
  • Tkinter 是 Python 自带的标准库,不需要额外安装 GUI 框架;
  • python-chess 支持多种象棋变体,其中就包括中国象棋,可以帮我们处理大部分规则问题;
  • 代码规模可控,即使只写几百行也能做出一个像模像样的桌面程序。

这篇教程选择的路线是“先会用库,再理解底层”。我们不需要自己写完整的象棋规则引擎,但需要理解棋局数据是怎么表示的,这样才能在界面和 AI 模块之间顺畅对接。

2. 环境准备与项目结构

2.1 Python 与依赖安装

本文示例以 Python 3.9 以上版本为主。Tkinter 通常随 Python 一起提供,如果你在命令行中执行下面的代码没有报错,说明 Tkinter 可用:

python -c "import tkinter"

如果提示找不到 tkinter,说明当前 Python 环境没有安装 GUI 组件。Windows 上安装 Python 时记得勾选“tcl/tk and IDLE”,Ubuntu 或 Debian 可以通过包管理器补装:

sudo apt install python3-tk

接下来安装 python-chess 库:

pip install python-chess

这里没有固定版本号,因为 python-chess 在后续版本中才加入了中国象棋变体支持。如果你的环境版本较旧,导入时发现不支持variant="xiangqi",建议先升级到最新版本:

pip install -U python-chess

如果只想做一个纯本地的打谱工具,到这里依赖就足够了。想连外部引擎做深度分析,还要准备一个支持中国象棋的 UCCI 或 UCI 引擎,具体我们会在第 6 节说明。

2.2 项目目录设计

为了避免所有代码堆在一个文件里,建议按功能拆分成下面几个文件:

xiangqi-studio/ ├── main.py # 程序入口,负责创建 Tkinter 窗口 ├── game.py # 游戏状态管理,保存当前局面和棋谱 ├── board_view.py # 棋盘绘制与点击交互(可并入 main.py) ├── ai.py # 内置简单 AI:评估 + 搜索 ├── engine_client.py # 外部 UCCI 引擎客户端 └── requirements.txt # 依赖说明

对于初学者来说,项目结构不用过于复杂。你可以先把board_view.py的内容写在main.py里,等代码变长后再拆分。不过从第一篇项目代码开始就养成模块化习惯,后面会省很多事。

2.3 版本与兼容性提示

本文涉及的棋盘库、引擎、GUI 组件都在持续更新,文章中的代码表示的是“常见环境下的示例”,不一定能原样跑通所有版本。如果你运行时报错,先检查两个地方:一是 python-chess 版本是否够新,二是引擎路径是否设置正确。

3. 用 python-chess 封装棋盘与走法

3.1 初始化中国象棋棋盘

python-chess 最初主要支持国际象棋,后来加入了变体功能,其中中国象棋可以用variant="xiangqi"创建。

先看一个最简单的例子:

import chess board = chess.Board(variant="xiangqi") print(board)

运行后,控制台会输出一个 10 行 9 列的棋盘,红方在下,黑方在上。这就是 python-chess 对中国象棋变体的内置初始局面。

如果你的 python-chess 版本不支持variant="xiangqi",也可以尝试使用:

import chess.variant board = chess.variant.XiangqiBoard()

两种写法的效果类似,具体使用哪一种,以你本机库版本实际支持的 API 为准。

3.2 合法走法与坐标系统

中国象棋的棋盘是 9 列 10 行。在 python-chess 中,列通常用字母 a 到 i 表示,行用数字 0 到 9 表示。一个走法由“起点方格 + 终点方格”组成,比如h2e2表示红方从 h2 走到 e2,相当于“炮二平五”的中文记谱。

获取当前局面的所有合法走法很简单:

for move in board.legal_moves: print(move.uci())

判断一个走法是否合法,可以直接用成员运算符:

move = chess.Move.from_uci("h2e2") if move in board.legal_moves: board.push(move) print("走法合法,已执行")

这里要注意,board.push()会修改棋盘状态,执行后棋子会真正移动。如果只是试探一个走法而不想真正落子,可以先复制棋盘副本,或者走之后再pop()退回来。

3.3 封装一个 Game 类

为了让 UI 层和 AI 层都使用同一套状态管理,我们可以封装一个简单的Game类,负责保存棋谱、前进和后退。

# 文件路径:game.py import chess class Game: def __init__(self): self.move_list = [] # 保存所有走过的 UCI 字符串 self.current = 0 # 当前显示到第几步 self.board = self._create_board() @staticmethod def _create_board(): return chess.Board(variant="xiangqi") def reset(self): self.move_list.clear() self.current = 0 self.board = self._create_board() def apply_move(self, uci_move): move = chess.Move.from_uci(uci_move) if move not in self.board.legal_moves: return False # 如果当前不是最后一手,说明用户从中途开始改棋,需要截断后面的谱 self.move_list = self.move_list[:self.current] + [uci_move] self.current += 1 self.board.push(move) return True def go_to(self, index): """定位到第 index 步,支持前进和后退。""" if index < 0 or index > len(self.move_list): return self.current = index self.board = self._create_board() for uci in self.move_list[:self.current]: self.board.push(chess.Move.from_uci(uci)) def undo(self): self.go_to(self.current - 1) def redo(self): self.go_to(self.current + 1) def analyze_moves(self): """返回当前回放位置的棋谱列表,方便界面展示。""" return self.move_list[:self.current]

封装之后,Tkinter 界面只需要调用apply_move()下棋,调用undo()/redo()回放,不用关心棋盘底层细节。这对初学者来说,可以明显减少“状态不同步”的 bug。

4. Tkinter 棋盘界面:从网格到可点击棋子

4.1 绘制棋盘底图

Tkinter 自带的 Canvas 组件很适合画棋盘。核心思路是先确定格子大小和边距,再根据行列坐标计算像素坐标。

中国象棋棋盘共 9 列 10 行,如果每个格子宽度是 64 像素,那么棋盘总宽大约是 8 × 64,总高大约是 9 × 64。为了方便,可以在棋盘四周留出 40 像素边距。

下面是一个最简单的棋盘绘制代码:

# 文件路径:board_view.py(核心片段) import tkinter as tk CELL = 64 MARGIN = 40 BOARD_COLS = 9 BOARD_ROWS = 10 def draw_board(canvas): canvas.delete("all") w = (BOARD_COLS - 1) * CELL h = (BOARD_ROWS - 1) * CELL x0, y0 = MARGIN, MARGIN # 画竖线 for col in range(BOARD_COLS): canvas.create_line(x0 + col * CELL, y0, x0 + col * CELL, y0 + h) # 画横线 for row in range(BOARD_ROWS): canvas.create_line(x0, y0 + row * CELL, x0 + w, y0 + row * CELL) # 河界文字 canvas.create_text(x0 + w / 2, y0 + 4.5 * CELL, text="楚河 汉界", font=("微软雅黑", 18, "bold"), fill="#8B4513")

这里没有画炮位、兵位标记和九宫斜线,不影响后续功能。如果你想更还原棋盘,可以继续添加create_linecreate_oval绘制细节。

4.2 绘制棋子

棋盘底图画好后,需要把 python-chess 中的棋盘状态显示到界面上。遍历board.piece_map(),可以拿到每个格子上的棋子:

PIECE_TEXT = { # 红方 'K': '帅', 'A': '仕', 'B': '相', 'N': '马', 'R': '车', 'C': '炮', 'P': '兵', # 黑方 'k': '将', 'a': '士', 'b': '象', 'n': '马', 'r': '车', 'c': '炮', 'p': '卒', } def draw_pieces(canvas, board, selected=None): canvas.delete("piece") for square, piece in board.piece_map().items(): col = chess.square_file(square) row = chess.square_rank(square) x = MARGIN + col * CELL y = MARGIN + row * CELL symbol = piece.symbol() text = PIECE_TEXT.get(symbol, symbol) color = "#B22222" if piece.color == chess.WHITE else "#000000" canvas.create_oval(x - 24, y - 24, x + 24, y + 24, fill="#F5DEB3", outline="#8B4513", width=2, tags="piece") canvas.create_text(x, y, text=text, font=("微软雅黑", 20, "bold"), fill=color, tags="piece") # 高亮当前选中的棋子 if selected is not None: x = MARGIN + chess.square_file(selected) * CELL y = MARGIN + chess.square_rank(selected) * CELL canvas.create_oval(x - 27, y - 27, x + 27, y + 27, outline="#00FF00", width=3, tags="piece")

代码中chess.square_file()chess.square_rank()用于把方格编号转换成行列。注意,PIECE_TEXT中的符号映射基于 python-chess 内部表示,如果你的库版本符号不同,打印piece.symbol()后自行调整即可。

4.3 点击走子与打谱操作

点击处理是整个界面最关键的部分。基本交互逻辑是:

  1. 第一次点击,选中己方棋子;
  2. 第二次点击,如果目标位置是合法落点,则移动;
  3. 如果点击的是己方另一颗棋子,则切换选中。
def on_click(event): if event.x < MARGIN or event.y < MARGIN: return col = round((event.x - MARGIN) / CELL) row = round((event.y - MARGIN) / CELL) if not (0 <= col < 9 and 0 <= row < 10): return square = chess.square(col, row) piece = game.board.piece_at(square) if selected is None: # 必须先选中己方棋子 if piece and piece.color == game.board.turn: selected = square draw_pieces(canvas, game.board, selected) else: move = chess.Move(selected, square) if move in game.board.legal_moves: game.apply_move(move.uci()) selected = None draw_board(canvas) draw_pieces(canvas, game.board) record_text.insert(tk.END, move.uci() + " ") else: # 如果点的是另一颗己方棋子,重新选中 if piece and piece.color == game.board.turn: selected = square draw_pieces(canvas, game.board, selected) else: selected = None draw_pieces(canvas, game.board)

这里有一个值得留意的细节:点击位置不一定落在格子正中心。用round()将像素坐标四舍五入到最近格子,能有效避免“点边缘但选中错格子”的问题。

4.4 棋谱回放:撤销、重做

打谱软件一个重要的功能是“看棋谱回到某一步”。在Game类中,undo()redo()已经封装好了,界面只需要绑定按钮事件:

def on_undo(): game.undo() draw_board(canvas) draw_pieces(canvas, game.board) update_record_text() def on_redo(): game.redo() draw_board(canvas) draw_pieces(canvas, game.board) update_record_text()

update_record_text()可以把当前回放位置的棋谱显示到Text组件中,方便用户看到当前走到第几步。

到这里,一个能“摆棋、走棋、撤销、重做”的基础打谱软件已经形成了。接下来要解决的是“AI 分析”部分。

5. 内置简单 AI:从启发式评估到极小极大搜索

5.1 评估函数:给局面打分

AI 要“分析局面”,第一步是给局面一个数值。最简单的评估方法是计算双方子力价值差。红方分数高,说明红方占优;黑方分数高,说明黑方占优。

# 文件路径:ai.py PIECE_SCORE = { 'K': 10000, 'A': 300, 'B': 300, 'N': 400, 'R': 700, 'C': 500, 'P': 100, 'k': -10000, 'a': -300, 'b': -300, 'n': -400, 'r': -700, 'c': -500, 'p': -100, } def evaluate(board): """返回红方视角的粗略评估分数。""" score = 0 for piece in board.piece_map().values(): score += PIECE_SCORE.get(piece.symbol(), 0) if board.is_checkmate(): # 当前走棋方已经被将死 return -100000 if board.turn else 100000 return score

这里的棋子分值用的是常见参考值:车 700,炮 500,马 400,兵 100,仕相 300。中国象棋中车的价值通常最高,炮和马的配合也很重要,初学者可以先用这套分值体验效果。

piece.symbol()返回的是棋盘内部符号,如果和实际不一致,用print(piece.symbol())排查一下,再调整字典键名即可。

5.2 极小极大搜索:往前多算几步

只评估一步,AI 只能看到“吃了什么子”,看不到后续反击。通过递归搜索,可以让 AI 往前多看几步。

下面是一个极简的极小极大搜索:

def search(board, depth): if depth == 0 or board.is_game_over(): return evaluate(board) best = -float("inf") for move in board.legal_moves: board.push(move) score = -search(board, depth - 1) board.pop() if score > best: best = score return best def best_move(board, depth=2): best = None best_score = -float("inf") for move in board.legal_moves: board.push(move) score = -search(board, depth - 1) board.pop() if score > best_score: best_score = score best = move return best, best_score

这段代码的核心逻辑是:先模拟对手走出一步,然后递归看对手的应对,再用正负号交替表示“红方优势”和“黑方优势”。如果红方行动,我们希望分数高;如果黑方行动,黑方也会选择对自己最有利、也就是对红方最不利的走法,所以用负数取反。

对于初学者来说,深度设置为 2 或 3 才能保证速度。如果你把深度调到 4 以上,会明显感觉到计算变慢。性能优化不在本文讨论范围,简单理解成“搜索深度越深,算得越准,也越慢”即可。

5.3 把这个 AI 接进软件

在 Tkinter 界面里加一个“AI 分析”按钮,点击后调用best_move()并显示推荐走法:

def on_analyze(): move, score = best_move(game.board, depth=2) info_label.config( text=f"AI 推荐走法: {move.uci()},评估分数: {score:.2f}" )

这样,用户每走一步都可以点“AI 分析”,程序会基于当前局面给出一个参考建议。虽然这个 AI 很“初级”,但它已经具备完整流程:合法走法生成、局面评估、递归搜索、推荐展示。理解了这一步,后面接触专业引擎会更容易。

6. 接外部 UCCI 引擎:让分析更可靠

6.1 UCCI 协议基本概念

内置简单 AI 适合学习和演示,但真要对棋局做深度复盘,还需要接入专业象棋引擎。中国象棋引擎常用的协议是 UCCI,可以看成是 UCI 协议的中国象棋变体。

UCCI 交互方式一般如下:

  1. 通过命令行启动引擎程序;
  2. 通过标准输入发送命令;
  3. 引擎通过标准输出返回状态和分析结果。

常见的命令包括ucciisreadypositiongo等。不同引擎对命令细节可能有差异,接入前最好先看一下引擎自带的说明文档。

6.2 最小引擎客户端示例

可以使用 Python 的subprocess模块启动外部引擎进程。下面是一个不完整但思路清晰的示例:

# 文件路径:engine_client.py(思路演示) import subprocess class UCCIEngine: def __init__(self, engine_path): self.process = subprocess.Popen( [engine_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8", ) self.send("ucci") def send(self, command): self.process.stdin.write(command + "\n") self.process.stdin.flush() def analyze(self, board, depth=12): moves = " ".join(m.uci() for m in board.move_stack) self.send("position startpos moves " + moves) self.send(f"go depth {depth}") # 这里需要根据引擎返回格式解析 bestmove # 示例不做完整解析,只演示发送过程

实际项目里,你需要使用一个后台线程读取引擎输出,并设置超时时间,防止引擎卡住导致 GUI 无响应。初学者如果暂时接入失败,可以先用内置 AI 完成主要功能,外部引擎作为后续进阶优化。

6.3 外部引擎接入的注意点

  • 引擎文件要下载到本地,路径中尽量不要包含中文和空格;
  • 不要在主线程里阻塞等待引擎结果,应使用子线程或异步读取;
  • 如果引擎协议不是标准 UCCI,需要先阅读引擎文档;
  • 分析超时后要合理结束引擎进程,避免残留。

接入外部引擎后,软件的能力会从“初学者版的几步搜索”升级到“专业级的深度分析”,复盘体验会好很多。

7. 完整运行流程与预期效果

7.1 启动软件

把所有代码整理好后,在命令行执行:

python main.py

运行后会出现一个 Tkinter 窗口,画面中央是 9 列 10 行的中国象棋棋盘,红黑双方棋子按初始位置摆放。

7.2 打谱流程

在棋盘上点击红方棋子,再点击目标位置,棋步会生效并记录到右侧棋谱区域。点击“撤销”按钮可以回退上一步;“重做”按钮可以回到刚才撤销的位置。如果中途修改棋谱,后续棋步会被自动截断,这是打谱软件的正常行为。

7.3 AI 分析流程

点击“AI 分析”按钮,程序会计算当前局面下的推荐走法。以深度 2 为例,几秒钟内通常会返回结果。棋力虽然一般,但足够演示“AI 如何分析局面”的完整链路。

如果要接入更强的引擎,把引擎路径配置到UCCIEngine类中,再通过类似按钮触发分析即可。

8. 常见问题与排查思路

问题现象常见原因解决思路
导入 chess 后无法使用variant="xiangqi"python-chess 版本较旧,不支持中国象棋变体升级 python-chess 到最新版本,改用chess.variant.XiangqiBoard()
运行后提示 Tkinter 不存在Python 环境缺少 GUI 组件重新安装带 Tk 的 Python,或在系统安装python3-tk
点击棋子没有反应选中棋子颜色不是当前行棋方;坐标转换不对打印点击行列和board.turn调试;检查
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 4:34:35

CNC刀具直径精准测量全攻略:从工具选择到实战流程

在CNC加工车间里&#xff0c;你是否也遇到过这样的场景&#xff1a;程序跑得好好的&#xff0c;突然尺寸就超差了&#xff0c;或者加工出来的表面光洁度总是不理想&#xff1f;排查了半天&#xff0c;最后发现罪魁祸首是刀具的实际直径和程序里设定的“名义直径”对不上。一把标…

作者头像 李华
网站建设 2026/9/1 4:34:01

Windows USB插拔记录清理指南:注册表、日志与一键脚本

简介&#xff1a;一键清理Windows系统USB设备插拔历史记录的工具合集&#xff0c;面向注重系统隐私与设备维护的用户&#xff0c;整合了UsbViewer设备查看器和USBOblivion清理工具&#xff0c;可彻底清除注册表中留存的外接存储设备插拔记录&#xff0c;包括设备ID、序列号、首…

作者头像 李华
网站建设 2026/9/1 4:31:45

UDS 0x28通信控制服务测试用例设计:从协议规范到CANoe自动化验证

在汽车电子网络诊断项目里&#xff0c;很多刚接触 UDS 测试的同学&#xff0c;第一眼看到 0x28 服务时都会觉得它很简单&#xff1a;不就是一个“控制 ECU 通信开关”的服务吗&#xff1f;但当需求文档里写着“在特定条件下临时屏蔽网络管理报文&#xff0c;同时保留诊断链路可…

作者头像 李华
网站建设 2026/9/1 4:30:32

C#基于KEPServerEx的OPC UA客户端开发实战:从配置到排错

简介&#xff1a;本资源是一套基于C#开发的OPC UA客户端完整工程&#xff0c;专为工业自动化领域开发者设计&#xff0c;用于快速连接KEPServerEX&#xff08;Kepware&#xff09;等主流OPC服务器&#xff0c;适用于Visual Studio 2015环境下的工业通信集成与调试。资源共31个文…

作者头像 李华
网站建设 2026/9/1 4:29:29

【C++算法】动态规划背包问题 -> 01背包

01背包的核心是&#xff1a;每个背包只可以用一次 P1048 [NOIP 2005 普及组] 采药 - 洛谷 思路讲解&#xff1a;二维朴素dp f[i][j]是状态表示 i表示我们要遍历的数组&#xff0c;j表示我们遍历的重量 首先我们看题&#xff0c;我们可以得出&#xff1a; 1、所有的用品&am…

作者头像 李华
网站建设 2026/9/1 4:28:28

美团前端移动端笔试复盘:核心考点与手写代码实战思路

2025年秋招的美团前端&移动端第二批笔试&#xff0c;我是在周六上午完成的。整场线上笔试两个小时&#xff0c;平台用的还是常见的牛客网&#xff0c;体感是题量大、覆盖面广、移动端内容占比明显提升。这套卷子给我最直观的感触是&#xff1a;它不再只问“这个API怎么用”…

作者头像 李华