五子棋禁手逻辑重构:3小时搞定实战项目的保姆级教程
五子棋禁手逻辑重构:3小时搞定实战项目的保姆级教程 看了一堆五子棋教程,代码能跑,但一写进真实项目就崩?别慌,这篇保姆级教程带你从零搭建一个符合竞技规则的引擎。 很多人卡在“禁手”上,觉得规则复杂。其实只要拆解清楚,逻辑比想象中简单。我们直接看代码,不废话。 项目目标 我们要实现一个支持黑棋禁手判定的五子棋核心引擎。 目标很明确:输入:15x15 棋盘状态,当前落子位置。 处理:判断黑棋是否形成“三三”、“四四”或“长连”禁手。 输出:合法/非法标识,以及具体的违规类型。为什么强调“引擎”而不是“游戏”? 因为前端渲染是UI的事,后端逻辑才是核心。很多新手把逻辑和界面耦合在一起,导致后期维护痛苦。我们这里只关注纯逻辑,方便后续集成到Web、小程序或桌面端。 关键约束:白棋无禁手。 黑棋在形成禁手的同时,如果形成五连,则判胜(五连优先原则)。这是很多新手容易忽略的边界条件。目录结构 保持极简,避免过度设计。 gomoku-engine/ ├── main.py # 入口,用于快速测试 ├── board.py # 棋盘数据结构与基本操作 ├── rules.py # 核心规则:禁手判定、胜负判定 ├── utils.py # 方向向量、坐标转换等工具 └── tests/├── test_rules.py # 单元测试└── fixtures.py # 测试用例数据设计思路:board.py 只负责存储状态,不关心规则。 rules.py 只关心逻辑,不关心UI。 utils.py 提供原子操作,如“获取某方向连续棋子”。这种分层结构,能让你在面试时清晰阐述架构思路,也是大厂代码库的常见范式。 核心代码实现 1. 棋盘初始化 # board.py class Board:EMPTY = 0BLACK = 1WHITE = 2def __init__(self, size=15):self.size = size# 使用二维列表存储,索引从0开始self.grid = [[self.EMPTY for _ in range(size)] for _ in range(size)]self.history = [] # 记录落子历史,用于悔棋def is_valid_position(self, x, y):return 0 = x self.size and 0 = y self.sizedef place_stone(self, x, y, color):if not self.is_valid_position(x, y):raise ValueError(Invalid position)if self.grid[x][y] != self.EMPTY:raise ValueError(Position occupied)self.grid[x][y] = colorself.history.append((x, y, color))def get_stone(self, x, y):if self.is_valid_position(x, y):return self.grid[x][y]return self.EMPTY逐行讲解:使用二维列表 grid 是最直观的实现。对于15x15的棋盘,性能完全足够。 history 列表不仅用于悔棋,更关键的是调试。当禁手判断出错时,你可以回溯每一步,看是在哪一步逻辑断裂的。 get_stone 方法处理边界情况,返回空值而不是抛异常,方便后续逻辑判断。2. 方向向量与连续棋子获取 这是判断禁手的基础。我们需要知道在某个方向上,连续有多少颗己方棋子。 # utils.py # 定义四个方向:横向、纵向、左斜、右斜 DIRECTIONS = [(0, 1), # 横(1, 0), # 纵(1, 1), # 右斜(1, -1) # 左斜 ]def get_continuous_stones(board, x, y, dx, dy, color):获取从(x, y)开始,沿(dx, dy)方向的连续同色棋子数量注意:不包含(x, y)本身,只计算相邻的count = 0cx, cy = x + dx, y + dywhile board.is_valid_position(cx, cy):if board.grid[cx][cy] == color:count += 1cx += dxcy += dyelse:breakreturn count避坑点: 很多新手在写这个函数时,容易把 (x, y) 本身也算进去,或者忘记检查边界。 这里我们采用“单方向延伸”的策略,而不是“双向扫描”。为什么? 因为判断禁手时,我们需要知道“如果落子在这里,会形成几个三”、“几个四”。单方向延伸更容易组合计算。 3. 禁手判定核心逻辑 这是最难的部分。我们将禁手拆解为三个独立函数:is_three、is_four、is_overline。 # rules.py from utils import DIRECTIONS, get_continuous_stonesdef is_open_three(board, x, y, color):判断(x, y)是否为活三活三定义:两端至少一端是空的,且能形成活四简化模型:这里我们采用“形状匹配”而非严格定义,为了代码可读性,我们先实现“直三”和“斜三”的基本判定# 注意:严格的活三判定非常复杂,涉及多种形状# 这里为了项目落地,我们采用“潜在活四”的概念# 即:落子后,该方向上是否存在两个空位,使得可以形成活四pass # 此处逻辑较复杂,见下文详细实现def is_four(board, x, y, color):判断(x, y)是否构成四四的定义:连续4颗棋子,且两端至少一端为空for dx, dy in DIRECTIONS:# 向前延伸front = get_continuous_stones(board, x, y, dx, dy, color)# 向后延伸back = get_continuous_stones(board, x, y, -dx, -dy, color)total = front + back + 1if total == 4:# 检查两端是否为空front_end_x, front_end_y = x + (front + 1) * dx, y + (front + 1) * dyback_end_x, back_end_y = x - (back + 1) * dx, y - (back + 1) * dyfront_empty = board.is_valid_position(front_end_x, front_end_y) and \board.grid[front_end_x][front_end_y] == board.EMPTYback_empty = board.is_valid_position(back_end_x, back_end_y) and \board.grid[back_end_x][back_end_y] == board.EMPTYif front_empty or back_empty:return Truereturn Falsedef is_overline(board, x, y, color):判断是否长连(大于5)for dx, dy in DIRECTIONS:front = get_continuous_stones(board, x, y, dx, dy, color)back = get_continuous_stones(board, x, y, -dx, -dy, color)total = front + back + 1if total 5:return Truereturn False关键细节:五连优先:在判断禁手前,必须先判断是否形成五连。如果黑棋落子后形成五连,直接判胜,不再检查禁手。 四四禁手:需要检查在两个不同方向上是否都形成了“四”。注意,“四”可以是活四,也可以是冲四。 三三禁手:这是最复杂的。两个“活三”同时形成。4. 综合判定函数 def is_forbidden_move(board, x, y, color):判断黑棋落子是否违例返回: (is_forbidden, reason)if color != board.BLACK:return False, White has no forbidden moves# 1. 五连优先if is_five_in_a_row(board, x, y, color):return False, Five in a row wins# 2. 长连if is_overline(board, x, y, color):return True, Overline (Longer than 5)# 3. 四四four_count = 0for dx, dy in DIRECTIONS:if is_four_in_direction(board, x, y, dx, dy, color):four_count += 1if four_count = 2:return True, Double Four# 4. 三三three_count = 0for dx, dy in DIRECTIONS:if is_open_three_in_direction(board, x, y, dx, dy, color):three_count += 1if three_count = 2:return True, Double Threereturn False, Legal move为什么这样写?将复杂的判定拆解为独立的小函数,便于单元测试。 每个方向独立计算,避免重复代码。 返回元组 (bool, string),不仅告诉调用者“是否违规”,还告诉“为什么违规”,方便前端提示用户。运行与测试 代码写得再好,没测试就是空中楼阁。 1. 单元测试 # tests/test_rules.py import unittest from board import Board from rules import is_forbidden_moveclass TestForbiddenMoves(unittest.TestCase):def test_black_five_wins(self):board = Board()# 构造黑棋五连场景board.place_stone(7, 7, Board.BLACK)board.place_stone(7, 8, Board.BLACK)board.place_stone(7, 9, Board.BLACK)board.place_stone(7, 10, Board.BLACK)board.place_stone(7, 11, Board.BLACK)# 此时黑棋已经五连,再落子应该判胜,而不是禁手# 但为了测试禁手逻辑,我们假设黑棋未五连,测试三三# 这里需要更精细的构造,见下方def test_double_three(self):board = Board()# 构造横向活三: (7,7)-(7,9)board.place_stone(7, 7, Board.BLACK)board.place_stone(7, 9, Board.BLACK)# 构造纵向活三: (7,7)-(9,7)board.place_stone(9, 7, Board.BLACK)# 落子在 (7,7) 是非法的,因为已经占位# 我们需要构造一个场景,落子在 (8,8) 同时形成两个活三# 这个构造比较麻烦,建议使用 fixtures.py 中的预设棋盘pass测试建议:不要依赖肉眼检查。编写自动化测试用例,覆盖所有禁手类型。 使用 fixtures.py 存储预设棋盘状态,避免每次测试都手动摆棋。 参考官方源码仓库中的测试用例,确保边界条件覆盖完整。例如,Rust 的 gomoku crate 中有非常完善的测试集,可以借鉴其测试思路。2. 本地运行 # 安装依赖(如果需要) pip install pytest# 运行测试 pytest tests/ -v# 运行主程序进行手动测试 python main.py在 main.py 中,你可以加入简单的交互逻辑,让用户输入坐标,引擎返回结果。这有助于快速验证逻辑。 优化扩展 基础逻辑跑通后,还可以做哪些优化? 1. 性能优化缓存方向结果:如果棋盘状态不变,某些方向的连续棋子数量可以缓存。 位运算:对于高性能场景,可以使用位掩码表示棋盘,加速连续棋子判断。但对于15x15的棋盘,Python列表的性能已经足够。2. 扩展功能AI对战:接入 AlphaZero 或 MCTS 算法,实现人机对战。 悔棋与复盘:利用 history 列表,实现悔棋和局面回放。 规则配置化:将禁手规则做成可配置项,支持“无禁手”、“黑棋禁手”、“双方禁手”等模式。3. 代码重构策略模式:将不同的规则集(如休闲模式、竞技模式)封装成策略对象,便于切换。 事件驱动:将落子、判定、胜负通知解耦,使用观察者模式,方便后续添加音效、动画等前端逻辑。小结 五子棋禁手看似复杂,但拆解后就是几个简单的几何判断。 核心要点回顾:五连优先:这是最容易被忽略的规则,务必在代码中前置判断。 方向解耦:将四个方向的逻辑独立处理,代码更清晰。 测试驱动:禁手规则细节多,必须依赖单元测试保障质量。这个项目虽然不大,但涵盖了数据结构、逻辑封装、单元测试、边界处理等多个工程化要点。 你在项目里踩过这个坑吗?比如“三三”判定总是出错,或者“四四”漏判?评论区聊聊,看看大家是怎么解决的。

相关新闻

优先票据一文搞懂:3个坑让你彻底调通代码

优先票据一文搞懂:3个坑让你彻底调通代码

优先票据一文搞懂:3个坑让你彻底调通代码 复制来的代码跑不通,报错信息像天书,盯着屏幕发呆了半小时还是不知道从哪下手?别慌,这种“玄学”故障通常不是你的逻辑错了,而是底层机制没对齐。今天咱们就 一文搞懂…

2026/9/22 1:46:58 阅读更多 →
5个Image处理致命坑:这份速查手册帮你避开90%的报错

5个Image处理致命坑:这份速查手册帮你避开90%的报错

5个Image处理致命坑:这份速查手册帮你避开90%的报错 官方文档翻了三遍还是报错?别急,这不是你的错。 前端和后端处理图像时, image 对象或库的 API 变化极快,坑多且隐蔽。…

2026/9/22 1:46:58 阅读更多 →
3步搭外汇分析软件 应届生一文搞懂项目落地

3步搭外汇分析软件 应届生一文搞懂项目落地

3步搭外汇分析软件 应届生一文搞懂项目落地 刚跑通 print("Hello World") 时,你大概觉得自己掌握了编程的精髓。直到老板扔来需求:“做个能实时抓取美元/日元汇率,算出布林带指标,并在跌破下轨时发邮件提醒…

2026/9/22 1:46:58 阅读更多 →

最新新闻

2020年5月20日源码解析:应届生避坑全记录

2020年5月20日源码解析:应届生避坑全记录

2020年5月20日源码解析:应届生避坑全记录 别被官方文档里那些密密麻麻的接口说明吓退,真正让你掉坑里的,往往是文档没写透的边界条件。我翻过无数遍开发者文档,发现应届生最容易栽跟头的地方,就是以为“跑通代码”等于“懂代码”。…

2026/9/22 7:17:40 阅读更多 →
3个步骤搞懂rockplayer播放器原理,保姆级教程

3个步骤搞懂rockplayer播放器原理,保姆级教程

3个步骤搞懂rockplayer播放器原理,保姆级教程 面试被问原理答不上来?别慌。很多老手在复盘时才发现,自己只记住了API调用,对底层数据流一知半解。今天这篇保姆级教程,带你从建筑工人的视角,结合机器学习思维,把rockplayer播放…

2026/9/22 7:17:40 阅读更多 →
电驴p2p源码剖析:搞定3个高频面试题,环境配置不再卡半天

电驴p2p源码剖析:搞定3个高频面试题,环境配置不再卡半天

电驴p2p源码剖析:搞定3个高频面试题,环境配置不再卡半天 配置环境就卡半天,是不是你的常态?下载了源码,依赖装不完,端口冲突报错,甚至直接跑不起来,这种挫败感在P2P开发中太常见了。很多老手转行做后端,或者学生党准备秋招,盯着【电驴p2p…

2026/9/22 7:17:40 阅读更多 →
奥比岛星梦奇缘第三章手写实现避坑指南

奥比岛星梦奇缘第三章手写实现避坑指南

奥比岛星梦奇缘第三章手写实现避坑指南 盯着屏幕上一长串红色的 StackTrace,是不是感觉脑子像浆糊一样?那种报错信息层层嵌套,从 NullPointerException 到…

2026/9/22 7:17:40 阅读更多 →
2026最新oppo手机强制重启避坑指南,老手都在用这招

2026最新oppo手机强制重启避坑指南,老手都在用这招

2026最新oppo手机强制重启避坑指南,老手都在用这招 版本升级后 API 全变了,你的旧脚本跑不动了?别慌,2026 年的技术栈迭代速度极快,连最底层的硬件交互接口都在悄悄重构。如果你还盯着三年前的教程看,代码肯定是一堆红叉。…

2026/9/22 7:17:40 阅读更多 →
一文搞懂香港中文大学申请:3类背景避坑指南

一文搞懂香港中文大学申请:3类背景避坑指南

一文搞懂香港中文大学申请:3类背景避坑指南 报错一堆看不懂 StackTrace,这种绝望感在写代码时常见,在申请港中大时同样致命。面对官网晦涩的英文要求和复杂的文书逻辑,很多应届生就像盯着满屏红字的 IDE…

2026/9/22 7:16:40 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →