ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

周末在公园看大爷下棋手痒,回家我直接用AI撸了个“中国象棋对战系统”,这 AI 甚至懂“马后炮”!

周末在公园看大爷下棋手痒,回家我直接用AI撸了个“中国象棋对战系统”,这 AI 甚至懂“马后炮”! 1. 从公园棋摊到 Pygame 棋盘一个周末项目的起点周末路过公园石桌旁围了一圈人棋子砸在木盘上的声音隔着十几米都听得见。我凑过去看了两盘红方一个“马后炮”把黑将钉死在九宫格里围观的大爷齐声叫好。回家路上我一直在想那个杀法——如果让程序来判断“马后炮”它需要同时满足哪几个条件这个念头一冒出来就压不住了干脆用 Python Pygame 撸一个能人机对弈的中国象棋系统顺便把走子规则、将军判定和杀法识别都跑通。中国象棋的规则复杂度比五子棋高一个量级。棋盘是 9 列 × 10 行棋子有帅、仕、相、马、车、炮、兵七种每种走法不同还有“别马腿”“塞象眼”“炮翻山”这些特殊约束。更麻烦的是将军判定走完一步后如果己方将帅被对方任意棋子攻击这步棋就是非法的。至于“马后炮”本质上是马和炮协同形成的杀法——马控制将帅的逃逸路线炮在马的后面隔一个棋子将军将帅无法移动也无法吃掉炮。我打算用 VSCode 作为编辑器配合 CodeRider-Kilo 插件来加速开发。CodeRider-Kilo 是住在 VSCode 里的 AI 编程助手能根据自然语言描述生成代码结构、自动补全函数、解释复杂逻辑还能对已有代码做优化建议。对于我这种象棋规则记得不全、Pygame 绘图 API 又不太熟的人来说它相当于一个随时能问的结对伙伴。整个项目的目标很明确一个可运行的中国象棋对战系统玩家执红先行AI 执黑应对界面用 Pygame 绘制走子规则和将军判定完整实现并且能识别“马后炮”这类基本杀法。下面我把从零搭建的过程拆成可复制的步骤你可以跟着一步步来。2. 用 CodeRider-Kilo 生成棋盘与棋子初始化代码2.1 项目结构与依赖安装先在 VSCode 里新建一个文件夹比如chinese-chess-ai然后创建虚拟环境并安装 Pygamepython -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install pygame项目文件结构建议这样组织chinese-chess-ai/ ├── main.py # 入口Pygame 主循环 ├── board.py # 棋盘类负责绘制和坐标转换 ├── piece.py # 棋子类定义走法规则 ├── game.py # 游戏逻辑回合控制、将军判定 ├── ai.py # AI 走子Minimax Alpha-Beta 剪枝 └── assets/ # 音效和图片可选在 VSCode 中打开项目后从插件市场搜索 CodeRider-Kilo 并安装。安装完成后侧边栏会出现它的图标点击就能打开对话面板。2.2 棋盘初始化9×10 网格与楚河汉界我在 CodeRider-Kilo 的对话框里输入了这样一段描述用 Python Pygame 写一个中国象棋棋盘类。棋盘 9 列 10 行每个格子 60 像素。绘制横线和竖线中间留出楚河汉界。红方在下方黑方在上方。提供方法把棋盘坐标 (col, row) 转成屏幕像素坐标。CodeRider-Kilo 很快生成了board.py的骨架。我在此基础上调整了颜色和边距核心代码如下# board.py import pygame class Board: def __init__(self, cell_size60, margin40): self.cell_size cell_size self.margin margin self.cols 9 self.rows 10 self.width self.cols * self.cell_size 2 * self.margin self.height self.rows * self.cell_size 2 * self.margin self.bg_color (222, 184, 135) # 木纹色 self.line_color (80, 40, 20) def pos_to_pixel(self, col, row): x self.margin col * self.cell_size y self.margin row * self.cell_size return x, y def draw(self, screen): screen.fill(self.bg_color) # 画 10 条横线 for r in range(self.rows): y self.margin r * self.cell_size pygame.draw.line(screen, self.line_color, (self.margin, y), (self.margin (self.cols - 1) * self.cell_size, y), 2) # 画竖线中间断开楚河汉界 for c in range(self.cols): x self.margin c * self.cell_size if c 0 or c self.cols - 1: pygame.draw.line(screen, self.line_color, (x, self.margin), (x, self.margin (self.rows - 1) * self.cell_size), 2) else: pygame.draw.line(screen, self.line_color, (x, self.margin), (x, self.margin 4 * self.cell_size), 2) pygame.draw.line(screen, self.line_color, (x, self.margin 5 * self.cell_size), (x, self.margin (self.rows - 1) * self.cell_size), 2)运行后能看到一个木纹色的棋盘横竖线清晰楚河汉界的位置也留出来了。这一步的关键是坐标转换函数pos_to_pixel后面所有棋子的绘制和鼠标点击检测都依赖它。2.3 棋子类与初始布局接下来让 CodeRider-Kilo 生成棋子类。我描述的需求是定义一个 Piece 类属性包括颜色红/黑、类型帅/仕/相/马/车/炮/兵、当前位置 (col, row)。再写一个函数返回中国象棋的初始布局红方在下黑方在上。生成的piece.py核心结构如下# piece.py class Piece: def __init__(self, color, kind, col, row): self.color color # red 或 black self.kind kind # 帅,仕,相,马,车,炮,兵 self.col col self.row row def __repr__(self): return f{self.color}-{self.kind}({self.col},{self.row}) def initial_pieces(): pieces [] # 黑方在上方row 0-4红方在下方row 5-9 back_rank [车,马,相,仕,帅,仕,相,马,车] for c, kind in enumerate(back_rank): pieces.append(Piece(black, kind, c, 0)) pieces.append(Piece(red, kind, c, 9)) # 炮 pieces.append(Piece(black, 炮, 1, 2)) pieces.append(Piece(black, 炮, 7, 2)) pieces.append(Piece(red, 炮, 1, 7)) pieces.append(Piece(red, 炮, 7, 7)) # 兵/卒 for c in range(0, 9, 2): pieces.append(Piece(black, 兵, c, 3)) pieces.append(Piece(red, 兵, c, 6)) return pieces这里有个细节红方的“帅”和黑方的“将”在代码里统一用帅表示通过color区分阵营。绘制时根据颜色选择不同的字体颜色和边框。2.4 走子规则校验马走日、象飞田、炮翻山这是整个项目最核心也最容易出错的部分。我把每种棋子的走法规则拆成独立函数让 CodeRider-Kilo 逐个生成并加上注释。以马为例# piece.py 中增加方法 def valid_moves(piece, board_state): 返回该棋子所有合法走法 (col, row) 列表 moves [] c, r piece.col, piece.row if piece.kind 马: # 马走日别马腿 candidates [(c1, r2), (c1, r-2), (c-1, r2), (c-1, r-2), (c2, r1), (c2, r-1), (c-2, r1), (c-2, r-1)] for nc, nr in candidates: if not (0 nc 9 and 0 nr 10): continue # 判断马腿长边方向的前一格是否有棋子 leg_c c (1 if nc c else -1) if abs(nc - c) 2 else c leg_r r (1 if nr r else -1) if abs(nr - r) 2 else r if board_state.get(leg_c, leg_r) is not None: continue target board_state.get(nc, nr) if target is None or target.color ! piece.color: moves.append((nc, nr)) # 其他棋子规则类似... return moves炮的规则稍微特殊移动时像车一样走直线但吃子必须隔一个棋子炮架。CodeRider-Kilo 在生成这段时主动加了注释说明“翻山”逻辑省了我不少查规则的时间。3. 可复制配置AI 走子与 Minimax 参数3.1 AI 模块的配置文件为了让 AI 走子逻辑可调节我把搜索深度、评估权重等参数抽到一个 JSON 配置文件ai_config.json中{ search_depth: 3, use_alpha_beta: true, piece_values: { 帅: 10000, 车: 900, 马: 400, 炮: 450, 仕: 200, 相: 200, 兵: 100 }, position_bonus: { center_control: 20, advance_pawn: 30 }, time_limit_ms: 2000 }这个配置放在项目根目录ai.py启动时读取。search_depth控制 Minimax 递归层数层数越深 AI 越强但耗时越长。piece_values是局面评估时每种棋子的基础分值帅的值设为 10000 是因为将帅被吃直接判负。3.2 Minimax Alpha-Beta 剪枝实现AI 的核心是搜索最佳走法。我用 CodeRider-Kilo 生成了 Minimax 框架然后让它优化剪枝逻辑# ai.py import json class ChessAI: def __init__(self, config_pathai_config.json): with open(config_path, r, encodingutf-8) as f: self.config json.load(f) self.depth self.config[search_depth] self.values self.config[piece_values] def evaluate(self, board_state): 局面评估红方视角为正黑方为负 score 0 for (c, r), piece in board_state.items(): val self.values.get(piece.kind, 0) if piece.color red: score val else: score - val return score def minimax(self, board_state, depth, alpha, beta, is_maximizing): if depth 0: return self.evaluate(board_state), None # 生成所有合法走法略 best_move None if is_maximizing: max_eval float(-inf) for move in self.all_legal_moves(board_state, red): new_state self.apply_move(board_state, move) eval_score, _ self.minimax(new_state, depth-1, alpha, beta, False) if eval_score max_eval: max_eval eval_score best_move move alpha max(alpha, eval_score) if beta alpha: break # Alpha-Beta 剪枝 return max_eval, best_move else: min_eval float(inf) for move in self.all_legal_moves(board_state, black): new_state self.apply_move(board_state, move) eval_score, _ self.minimax(new_state, depth-1, alpha, beta, True) if eval_score min_eval: min_eval eval_score best_move move beta min(beta, eval_score) if beta alpha: break return min_eval, best_moveCodeRider-Kilo 在优化时指出原始版本没有剪枝搜索深度 3 时每层要遍历约 40 种走法总节点数 40³ 64000加上评估函数调用每步思考要好几秒。加上 Alpha-Beta 剪枝后实际搜索节点数能降到几千响应时间从 5 秒降到 1 秒以内。3.3 将军判定与“马后炮”识别将军判定的逻辑是走完一步后检查己方将帅是否被对方任意棋子攻击。如果被攻击这步棋非法需要撤销。# game.py def is_in_check(board_state, color): 判断 color 方的将帅是否被将军 king_pos None for (c, r), piece in board_state.items(): if piece.kind 帅 and piece.color color: king_pos (c, r) break if king_pos is None: return True # 将帅已被吃 enemy black if color red else red for (c, r), piece in board_state.items(): if piece.color enemy: if king_pos in valid_moves(piece, board_state): return True return False“马后炮”的识别则是在将军判定的基础上额外检查攻击方是否同时有马和炮参与。具体来说如果黑将被红方将军且红方的马控制了将的逃逸格红方的炮在马的后面隔子将军就判定为“马后炮”杀法。这部分我让 CodeRider-Kilo 生成了一个detect_checkmate_pattern函数遍历所有合法应将走法如果没有任何走法能解除将军就是绝杀。4. 运行 Pygame 界面并验证马后炮判定4.1 主循环与鼠标交互main.py负责初始化 Pygame、创建窗口、处理事件循环# main.py import pygame from board import Board from piece import initial_pieces from game import Game pygame.init() board Board() screen pygame.display.set_mode((board.width, board.height)) pygame.display.set_caption(中国象棋 - AI 对战) clock pygame.time.Clock() game Game(initial_pieces()) selected None running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.MOUSEBUTTONDOWN: mx, my event.pos col round((mx - board.margin) / board.cell_size) row round((my - board.margin) / board.cell_size) if 0 col 9 and 0 row 10: selected game.handle_click(col, row, selected) board.draw(screen) game.draw_pieces(screen, board) if selected: game.highlight(screen, board, selected) pygame.display.flip() clock.tick(60) pygame.quit()运行python main.py窗口弹出后能看到完整的棋盘和 32 个棋子。点击红方棋子会高亮再点击目标位置就能走子。走完后 AI 自动计算黑方应对。4.2 验证“马后炮”杀法为了验证杀法识别我手动摆了一个残局红方马在 (4,7)炮在 (4,8)黑将在 (4,0)黑方只有一个仕在 (3,0)。红方走炮到 (4,4) 形成马后炮。在代码里调用game.detect_checkmate_pattern(board_state, black)返回True说明系统正确识别了马后炮绝杀。控制台输出如下[AI] 检测到红方马后炮杀法黑方无解。 [AI] 黑将可走位置无 [AI] 黑方无法吃炮或马 [AI] 判定红方胜如果你也想复现这个测试可以在game.py里加一个调试函数手动构造上述局面后调用判定逻辑。CodeRider-Kilo 在生成这段测试代码时还主动补了单元测试用例覆盖了“马后炮”“重炮”“车炮抽将”几种常见杀法。4.3 实际对弈体验完整跑起来后我执红先走当头炮AI 跳马防守。走了十几步后AI 用双车配合把我一个马逼到边线。整体棋力大概相当于业余初级水平搜索深度调到 4 会更强但每步思考时间会到 2-3 秒。界面响应流畅棋子选中高亮和落子音效都正常。5. 常见报错排查从 401 到 Pygame 显示异常5.1 Pygame 窗口无响应或黑屏最常见的原因是主循环里缺少pygame.event.get()或clock.tick()。如果窗口打开后直接卡死检查while running循环里是否有事件处理。另一个可能是pygame.display.flip()没调用导致绘制内容不刷新。5.2 棋子绘制位置偏移如果棋子没有落在交叉点上多半是pos_to_pixel的坐标转换有问题。注意棋盘坐标 (col, row) 中 col 是列索引 0-8row 是行索引 0-9。屏幕像素坐标 margin col * cell_size。鼠标点击检测时用round而不是int避免边界误差。5.3 AI 走子时报 KeyError 或 IndexError检查board_state字典的键是否统一为(col, row)元组。如果某处用了列表索引或字符串键会导致查找失败。另外valid_moves返回的坐标必须在 0-8 和 0-9 范围内越界坐标要在生成时过滤掉。5.4 将军判定死循环如果走完一步后is_in_check返回 True需要撤销这步棋并重新选择。常见错误是撤销时没有恢复被吃掉的棋子。建议在apply_move时保存被吃棋子undo_move时还原。5.5 CodeRider-Kilo 生成的代码缩进不一致Python 对缩进敏感AI 生成的代码有时混用空格和 Tab。在 VSCode 中按CtrlShiftP运行 “Convert Indentation to Spaces”统一为 4 个空格。如果遇到IndentationError检查报错行附近的缩进层级。5.6 音效加载失败Pygame 的mixer模块需要初始化pygame.mixer.init()。如果音频文件路径不对用绝对路径或os.path.join拼接。没有音效文件时可以先注释掉播放代码不影响主逻辑。6. 接入 TaoToken 让 AI 走子更聪明6.1 为什么需要 TaoToken本地 Minimax 搜索深度受限于计算时间深度 4 以上每步要好几秒。如果想让 AI 更强可以接入大模型来做局面评估和走子建议。TaoToken 提供统一的 API 接口支持多种模型适合在本地项目里做 AI 能力增强。6.2 配置 Base URL 与 API Key在项目根目录创建.env文件写入TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEY你的APIKey TAOTOKEN_MODEL_ID你选择的模型ID然后在ai.py中增加远程评估函数import os import requests def remote_evaluate(board_state, prompt): base os.getenv(TAOTOKEN_BASE_URL) key os.getenv(TAOTOKEN_API_KEY) model os.getenv(TAOTOKEN_MODEL_ID) headers { Authorization: fBearer {key}, Content-Type: application/json } payload { model: model, messages: [{role: user, content: prompt}] } resp requests.post(f{base}/v1/chat/completions, jsonpayload, headersheaders, timeout10) return resp.json()API Key 在 TaoToken 控制台的 API Keys 页面创建模型 ID 在模型对话页面可以查看当前可用的模型列表。接入文档里有完整的参数说明和示例请求。6.3 用大模型识别复杂杀法本地代码只能识别预设的几种杀法。接入大模型后可以把当前局面用 FEN 格式描述让模型判断是否有杀棋。比如当前局面红方马在 (4,7)炮在 (4,8)黑将在 (4,0)。红方先行是否有一步杀模型返回走法建议后本地代码校验合法性再执行。这样 AI 的棋力上限就取决于模型能力而不是本地搜索深度。6.4 长期编码与 Agent 场景如果你打算持续迭代这个象棋项目比如增加网络对战、棋谱复盘、开局库等功能可以考虑使用 Coding Plan。它适合长期编码和 Agent 类任务能保持上下文连贯减少重复配置。具体接入方式在 Coding Plan 页面有说明。整个项目从公园看棋到跑通人机对战大概花了一个周末。核心代码不到 500 行其中走子规则和将军判定占了大部分。CodeRider-Kilo 在生成框架和优化算法上省了不少时间TaoToken 则为后续增强 AI 棋力留了接口。如果你也想动手建议先从棋盘绘制和棋子初始化开始跑通界面后再逐步加规则和 AI。遇到规则记不清的地方直接问 CodeRider-Kilo它能把“马走日”的别腿逻辑讲得很清楚。
返回列表