暑假来啦有野心的宝子请死磕这个游戏(ì _ í)暑假一到很多开发者朋友尤其是学生党都会面临一个经典难题想学点硬核技术又怕枯燥坚持不下去想做个项目又觉得无从下手。结果往往是刷了几天视频收藏夹里堆满了“从入门到放弃”的教程最后什么也没做成。如果你也有这种困扰那么这篇文章就是为你准备的。我们不谈空洞的理论也不推荐那些“看起来很美”的复杂项目。今天要聊的是一个被严重低估的“宝藏级”练手项目——命令行五子棋游戏。别急着划走觉得它太“小儿科”。恰恰相反这个项目是检验和提升你编程基本功、工程思维乃至架构设计能力的绝佳试金石。它门槛不高但上限极高能让你在“玩”的过程中把数据结构、算法、网络通信、AI对战、甚至图形界面开发等核心技能点全部串联起来。本文将带你从零开始用 Python 语言实现一个功能完整的命令行五子棋。我们不止于实现“能下棋”而是要深入探讨如何设计一个可扩展、易维护、具备AI对战能力的游戏引擎。你会学到如何用面向对象思想建模棋盘与棋子如何实现核心的胜负判定算法如何构建一个简单的AI对手以及如何为未来升级如网络对战、图形界面预留接口。读完本文你将获得一个可以直接运行、并且能“自我进化”的五子棋项目源码。对“如何设计一个软件模块”有更深刻的理解而不仅仅是写函数。一套排查程序逻辑Bug的实战方法。清晰的后续学习路径知道如何将这个“玩具”升级为“作品”。无论你是刚学完Python语法的新手还是想找个小项目练手的进阶者这个“有野心”的游戏都值得你“死磕”一下。让我们开始吧。1. 为什么命令行五子棋是绝佳的练手项目在开始写代码之前我们先要搞清楚为什么是五子棋又为什么是命令行版本很多新手会选择“学生管理系统”、“爬虫”作为第一个项目。管理系统容易陷入繁琐的CRUD增删改查而缺乏算法挑战爬虫则要面对复杂多变的网络环境和反爬策略初期容易受挫。五子棋则巧妙地避开了这些坑它目标明确实现游戏规则核心逻辑集中胜负判定并且具备极佳的可扩展性。它的核心价值在于“麻雀虽小五脏俱全”数据结构与算法实践场棋盘本质上是一个二维数组列表的列表落子、判断胜负都需要你熟练操作二维数据。胜负判定算法检查横、竖、斜方向是否有连续五子是对循环和条件判断的完美练习。面向对象编程OOP的入门样板你可以很自然地将棋盘(Board)、玩家(Player)、游戏(Game)抽象成类。通过定义它们的属性和方法你能直观地理解“封装”、“继承”、“多态”这些抽象概念到底有什么用。AI算法的启蒙导师为游戏加入一个简单的AI例如基于规则或极小化极大算法是接触游戏AI和搜索算法最平易近人的方式。你会第一次思考“如何让程序像人一样思考决策”。工程思维的训练你需要考虑如何组织代码文件、如何处理用户输入异常、如何设计清晰的人机交互界面哪怕只是命令行提示。这些都是未来开发大型软件的必备思维。而选择命令行版本作为起点更是明智之举。它让你剥离了图形界面GUI的复杂性专注于最核心的游戏逻辑。先让逻辑跑通再考虑用Pygame、Tkinter或Web前端为其“穿上衣服”这个学习路径是平滑且符合认知规律的。很多项目失败就是因为一开始就想做个“大而全”的漂亮界面结果核心功能一团糟。所以死磕这个项目你磕的不是一个游戏而是一套完整的、可迁移的软件开发方法论。2. 核心概念与项目架构设计在动手编码前我们需要对项目进行顶层设计。一个好的设计能让编码过程事半功倍也便于后续增加新功能。核心概念定义棋盘 (Board)一个15x15的网格标准五子棋棋盘大小。每个交叉点有三种状态空、黑子、白子。棋子 (Piece)包含颜色黑/白和位置行列信息。在我们的简单实现中棋子状态可以直接用字符如‘X’代表黑‘O’代表白存储在棋盘数据结构中不一定需要单独的Piece类。玩家 (Player)进行游戏的一方。可以是人类玩家通过键盘输入也可以是AI玩家自动计算落子。游戏引擎 (Game Engine)控制整个游戏流程的核心。负责初始化棋盘、切换玩家、判定落子是否合法、检查游戏是否结束胜负或平局。项目架构设计我们采用经典的“模型-控制器”简易架构为未来升级留出空间。模型层 (Model)Board类。只负责存储棋盘状态以及提供查询、修改棋盘状态的方法。它不关心谁在下棋也不关心怎么显示。控制器层 (Controller)Game类。它持有Board对象和Player对象负责游戏主循环接收玩家指令调用Board的方法更新状态并调用Board的方法判断胜负。视图层 (View)在命令行版本中视图就是我们的print函数。我们将创建一个display_board函数专门负责将Board对象的状态以文本形式打印到控制台。这种分离的好处是将来如果我们想把命令行界面换成图形界面只需要重写“视图层”而“模型层”和“控制器层”的代码几乎不用改动。3. 环境准备与项目初始化本项目只需要最基础的Python环境无需任何第三方库真正做到开箱即用。环境要求操作系统Windows 10/11, macOS, 或任意Linux发行版均可。Python版本Python 3.6 或以上版本。建议使用Python 3.8以获得更好的体验。开发工具任何你喜欢的文本编辑器或IDE如VS Code、PyCharm、甚至记事本。推荐使用VS Code它轻量且对Python支持良好。项目初始化在你的工作目录下创建一个新的文件夹例如gobang_terminal。然后创建以下Python文件这是我们项目的骨架gobang_terminal/ ├── board.py # 棋盘类 (Board) ├── game.py # 游戏主控类 (Game) ├── player.py # 玩家基类及人类/AI玩家类 ├── ai.py # AI算法实现 (可选初期可放在player.py) └── main.py # 程序入口文件使用命令行进入该目录并确认Python环境cd path/to/your/gobang_terminal python --version如果显示Python 3.x说明环境就绪。4. 第一步实现棋盘核心模型 (Board Class)棋盘是整个游戏的状态核心。我们首先实现board.py。# board.py class Board: 五子棋棋盘类负责维护棋盘状态和基础规则判断。 EMPTY . BLACK X WHITE O def __init__(self, size15): 初始化一个 size x size 的空棋盘。 Args: size: 棋盘大小默认为15。 self.size size # 使用列表推导式初始化一个二维列表所有位置均为空 self.grid [[self.EMPTY for _ in range(size)] for _ in range(size)] # 记录最后一步落子的位置用于优化胜负判定可选 self.last_move None def display(self): 在控制台以文本形式打印当前棋盘状态。 # 打印列号便于用户输入 print( , end) for col in range(self.size): # 格式化列号保持对齐 print(f{col:2}, end ) print() # 打印棋盘行 for row in range(self.size): print(f{row:2}, end ) # 打印行号 for col in range(self.size): print(f {self.grid[row][col]} , end) print() # 换行 def place_piece(self, row, col, piece_color): 在指定位置放置棋子。 Args: row: 行索引 (0-based) col: 列索引 (0-based) piece_color: 棋子颜色应为 Board.BLACK 或 Board.WHITE Returns: bool: 放置成功返回True如果位置无效或已有棋子则返回False。 # 1. 检查坐标是否在棋盘范围内 if not (0 row self.size and 0 col self.size): print(f错误落子位置({row}, {col})超出棋盘范围(0-{self.size-1})。) return False # 2. 检查该位置是否为空 if self.grid[row][col] ! self.EMPTY: print(f错误位置({row}, {col})已有棋子。) return False # 3. 放置棋子 self.grid[row][col] piece_color self.last_move (row, col, piece_color) return True def is_win(self, row, col, piece_color): 判断在(row, col)位置落子后该颜色是否获胜。 核心算法从当前落子点向8个方向检查是否有连续5子。 Args: row: 落子的行 col: 落子的列 piece_color: 棋子颜色 Returns: bool: 如果获胜返回True否则返回False。 # 定义8个方向向量(行增量, 列增量) directions [ (0, 1), # 水平向右 (1, 0), # 垂直向下 (1, 1), # 右下对角线 (1, -1), # 左下对角线 (0, -1), # 水平向左 (-1, 0), # 垂直向上 (-1, -1), # 左上对角线 (-1, 1) # 右上对角线 ] for dr, dc in directions: count 1 # 计数包括当前落子 # 向正方向检查 r, c row dr, col dc while 0 r self.size and 0 c self.size and self.grid[r][c] piece_color: count 1 r dr c dc # 向反方向检查 r, c row - dr, col - dc while 0 r self.size and 0 c self.size and self.grid[r][c] piece_color: count 1 r - dr c - dc # 如果任意方向连续子数大于等于5则获胜 if count 5: return True return False def is_full(self): 检查棋盘是否已满平局。 for row in self.grid: if self.EMPTY in row: return False return True关键代码解读__init__初始化一个size x size的二维列表grid用.表示空位。这是棋盘的内存表示。display将内存中的二维列表可视化打印出来并添加行号和列号极大提升用户体验。place_piece这是核心的状态变更方法。它包含了边界检查和重复落子检查体现了程序的健壮性。is_win这是项目的算法核心。它采用了一种高效且清晰的实现方式以当前落子点为中心向8个方向横、竖、两条斜线分别进行搜索统计连续的同色棋子数量。一旦某个方向计数达到5即判定获胜。这个算法的时间复杂度是O(1)因为最多检查固定步数非常高效。is_full遍历棋盘判断是否还有空位用于处理平局情况。5. 第二步实现玩家与游戏主控 (Player Game Class)接下来我们实现游戏的流程控制。首先在player.py中定义玩家。# player.py class Player: 玩家基类。 def __init__(self, name, piece_color): self.name name self.piece_color piece_color def get_move(self, board): 获取玩家的下一步落子位置。 这是一个抽象方法子类必须实现。 Args: board: 当前棋盘对象用于AI决策或显示。 Returns: tuple: (row, col) raise NotImplementedError(子类必须实现此方法) class HumanPlayer(Player): 人类玩家通过命令行输入落子。 def get_move(self, board): while True: try: # 提示用户输入例如“行,列”或“行 列” input_str input(f玩家{self.name}({self.piece_color})请输入您的落子位置 (行 列如 7 7): ).strip() # 处理多种输入分隔符空格、逗号 if in input_str: row_str, col_str input_str.split() elif , in input_str: row_str, col_str input_str.split(,) else: # 如果输入是两位数如77可以解析为7行7列简单处理 if len(input_str) 2 and input_str.isdigit(): row_str, col_str input_str[0], input_str[1] else: raise ValueError(输入格式不正确。) row, col int(row_str), int(col_str) # 这里不直接调用board.place_piece只返回坐标由Game类处理 return row, col except ValueError as e: print(f输入无效{e}。请重新输入。)然后在game.py中实现游戏主循环。# game.py from board import Board from player import HumanPlayer class Game: 游戏主控类负责管理游戏流程。 def __init__(self, size15, player1_name黑方, player2_name白方): self.board Board(size) # 初始化两名玩家默认为人类玩家 self.player_black HumanPlayer(player1_name, Board.BLACK) self.player_white HumanPlayer(player2_name, Board.WHITE) self.current_player self.player_black # 黑方先行 def switch_player(self): 切换当前行动玩家。 self.current_player self.player_white if self.current_player self.player_black else self.player_black def run(self): 运行游戏主循环。 print( * 30) print(欢迎来到命令行五子棋) print(f棋盘大小{self.board.size} x {self.board.size}) print(黑方: X, 白方: O) print(输入格式行号 列号 (例如7 7 表示第8行第8列)) print( * 30) while True: # 1. 显示棋盘 self.board.display() print() # 2. 获取当前玩家落子 player self.current_player print(f轮到 {player.name}({player.piece_color}) 落子。) while True: try: row, col player.get_move(self.board) # 尝试落子 if self.board.place_piece(row, col, player.piece_color): break # 落子成功跳出内层循环 else: # place_piece方法内已打印错误信息此处只需提示重试 print(请重新选择位置。) except (ValueError, IndexError): print(输入坐标不合法请重新输入。) # 3. 判断胜负 if self.board.is_win(row, col, player.piece_color): self.board.display() print(f恭喜{player.name}({player.piece_color}) 获胜) break # 4. 判断平局 if self.board.is_full(): self.board.display() print(棋盘已满游戏结束平局) break # 5. 切换玩家 self.switch_player() print(- * 20) # 分隔线增加可读性6. 第三步组装与运行 - 你的第一个可玩版本现在我们创建程序入口文件main.py将所有模块组合起来。# main.py from game import Game if __name__ __main__: # 可以在这里进行一些简单配置例如棋盘大小、玩家名字 board_size 15 game Game(sizeboard_size, player1_name玩家A, player2_name玩家B) game.run()运行与验证在项目根目录下打开终端执行python main.py你应该会看到类似下面的输出和交互过程 欢迎来到命令行五子棋 棋盘大小15 x 15 黑方: X, 白方: O 输入格式行号 列号 (例如7 7 表示第8行第8列) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . ... 轮到 玩家A(X) 落子。 请输入您的落子位置 (行 列如 7 7): 7 7此时你可以在命令行中输入坐标与朋友进行对战或者自己左右互搏测试游戏的基本流程和胜负判定是否正确。7. 第四步为游戏注入灵魂 - 实现一个简单的AI对手一个人下棋难免无聊。让我们实现一个最简单的AI让电脑作为你的对手。我们在player.py中增加一个AIPlayer类。# player.py (新增部分) import random class AIPlayer(Player): 一个简单的随机AI玩家。 def get_move(self, board): # 这是一个非常初级的AI随机选择一个空位落子 empty_positions [] for r in range(board.size): for c in range(board.size): if board.grid[r][c] board.EMPTY: empty_positions.append((r, c)) if empty_positions: # 随机选择一个空位 return random.choice(empty_positions) else: # 棋盘已满理论上不会走到这里因为Game主循环会先判断平局 return (-1, -1)然后修改game.py的初始化部分将其中一个玩家替换为AI。# game.py (修改 __init__ 部分) from player import HumanPlayer, AIPlayer # 导入AIPlayer class Game: def __init__(self, size15, player1_name黑方, player2_name白方, modepvp): Args: mode: 游戏模式。pvp为人人对战pve为人机对战。 self.board Board(size) self.mode mode if mode pvp: self.player_black HumanPlayer(player1_name, Board.BLACK) self.player_white HumanPlayer(player2_name, Board.WHITE) elif mode pve: # 默认人类执黑先行AI执白后行 self.player_black HumanPlayer(player1_name, Board.BLACK) self.player_white AIPlayer(电脑, Board.WHITE) else: raise ValueError(模式错误可选 pvp 或 pve) self.current_player self.player_black同时修改main.py让用户可以选择模式。# main.py (升级版) from game import Game if __name__ __main__: print(请选择游戏模式) print(1. 玩家 vs 玩家 (PvP)) print(2. 玩家 vs 电脑 (PvE)) choice input(请输入数字选择 (1 或 2): ).strip() if choice 1: game Game(modepvp, player1_name玩家1(黑), player2_name玩家2(白)) elif choice 2: game Game(modepve, player1_name你, player2_name电脑) else: print(输入错误默认启动 PvP 模式。) game Game(modepvp) game.run()现在重新运行python main.py选择模式2你就可以和电脑对战了。虽然这个AI只会随机落子战斗力几乎为零但它标志着你的程序从“交互程序”迈向了“智能程序”的第一步。8. 项目运行与效果深度验证运行项目后你需要系统性地验证各个功能模块是否正常工作。不要只测试“快乐路径”一切顺利的情况更要测试“异常路径”。验证清单基础流程验证游戏是否能正常启动并显示棋盘两名人类玩家是否能交替输入并正确落子棋盘显示是否正确更新规则逻辑验证边界测试尝试在棋盘外落子如-1, 0或15, 15程序是否提示错误并允许重试重复落子测试在已有棋子的位置再次落子程序是否拒绝胜负判定测试横向连成五子游戏是否立即结束并宣布正确方获胜纵向、左上-右下斜线、右上-左下斜线是否都能正确判定超过五子是否也能正确判定五子棋规则是先连成五子者胜超过五子不算输但我们的算法count 5能正确处理。平局测试可以临时将棋盘改小如3x3下满所有格子看是否会正确宣布平局。AI对战验证选择PvE模式AI是否能在其回合自动落子AI落子位置是否一定是棋盘上的空位用户体验验证输入格式是否灵活支持7 7、7,7甚至77吗错误输入如字母、符号是否被妥善处理程序是否崩溃通过以上测试你不仅能确保项目基本可用更能深刻理解程序健壮性Robustness的重要性。一个优秀的程序必须能优雅地处理用户的“乱来”。9. 常见问题与排查思路 (QA)在开发过程中你几乎一定会遇到下面这些问题。这里提供了排查思路。问题现象可能原因排查方式解决方案运行python main.py报ModuleNotFoundError1. 文件不在同一目录。2. 文件名拼写错误。3. 未在项目根目录运行。1. 检查board.py,game.py,player.py,main.py是否在同一个文件夹下。2. 检查import语句中的模块名是否正确。3. 在终端使用pwd和ls命令确认当前目录。确保所有文件位于同一目录并在该目录下运行命令。输入坐标后程序无反应或报错1.input()获取的值未正确解析。2. 类型转换 (int()) 失败。3. 列表索引越界。1. 在get_move方法中添加print(input_str)调试输入值。2. 使用try...except捕获ValueError。3. 在调用board.place_piece前打印row, col的值。完善输入处理逻辑确保能处理空格、逗号等多种分隔符并进行严格的异常捕获。胜负判定不准赢了不结束或没赢却结束1.is_win函数逻辑错误。2. 传入is_win的坐标或颜色错误。3. 棋盘grid数据异常。1.最有效方法在is_win函数内部打印调试信息查看每个方向的count值。2. 创建一个简单的测试用例例如在(7,7)到(7,11)连放5个黑子单步调试is_win。3. 在place_piece后立即display人工核对棋盘状态。仔细检查directions列表和循环逻辑。确保while循环的条件正确检查边界和棋子颜色。AI落子位置有棋子AIPlayer.get_move中寻找空位的逻辑有误。在AI选择落子后打印其选择的(r, c)和对应board.grid[r][c]的值。检查生成empty_positions列表的双重循环逻辑确保判断条件是board.grid[r][c] board.EMPTY。棋盘打印错位或混乱display函数中格式控制符不对。检查print(f{col:2})和print(f{row:2})中的格式化字符串:2表示占2位宽度右对齐这对于10以上的列号保持对齐很关键。调整格式字符串或使用更简单的固定宽度空格。10. 从“能运行”到“好项目”最佳实践与进阶方向至此你已经拥有了一个可运行的五子棋游戏。但一个“有野心”的开发者不会止步于此。下面这些进阶实践能将你的项目提升到新的高度。1. 代码优化与重构常量集中管理将EMPTY,BLACK,WHITE等字符串常量提取到模块顶部或单独的constants.py文件中。输入验证解耦将人类玩家输入解析和验证的逻辑提炼成独立的函数或方法使get_move更清晰。使用枚举Enum用Python的Enum类来定义棋子状态比直接用字符串更安全、更易维护。from enum import Enum class Piece(Enum): EMPTY . BLACK X WHITE O # 使用时if board.grid[r][c] Piece.BLACK.value:2. AI算法升级让电脑变强随机AI太弱。你可以按以下路径逐步强化它规则型AI让AI优先选择靠近已有棋子的位置、堵住对手的“活三”、“冲四”。评分算法为棋盘上每个空位计算一个分数例如形成自己的连子加分破坏对手的连子加分选择分数最高的位置落子。极小化极大算法Minimax这是棋类游戏AI的经典算法。它通过模拟未来几步棋寻找对自己最有利、对对手最不利的走法。实现它需要理解递归和博弈树。Alpha-Beta 剪枝在Minimax基础上的优化能大幅减少需要搜索的节点数让AI思考得更深。蒙特卡洛树搜索MCTS另一种强大的算法特别适用于像五子棋这样分支因子较大的游戏。3. 功能扩展图形界面GUI使用Pygame或Tkinter库为你的游戏核心引擎绘制一个真正的棋盘和棋子。你只需要将display函数替换为图形绘制逻辑游戏主循环和控制逻辑几乎可以复用。网络对战使用socket编程实现一个客户端-服务器架构。两个玩家可以在不同的电脑上通过网络对战。这能让你学习网络通信、协议设计如何编码落子消息和并发处理。游戏回放与存档记录每一步棋的坐标和玩家保存到文件。之后可以加载存档重现整盘棋局。更复杂的规则实现标准五子棋的“禁手”规则针对黑棋的三三禁手、四四禁手、长连禁手这需要更复杂的判定算法。4. 工程化与部署使用版本控制用Git管理你的代码。为每个大的功能如“基础版本”、“添加AI”、“重构”创建独立的分支。编写单元测试为Board类的is_win,place_piece等核心方法编写测试用例确保代码修改后核心逻辑依然正确。可以使用Python内置的unittest模块。制作安装包使用setuptools将你的项目打包使其可以通过pip install .的方式安装。这个命令行五子棋项目就像一颗种子。你投入的每一分“死磕”无论是调试一个棘手的Bug还是实现一个更优的算法都是在为这颗种子浇水施肥。它最终能长成什么样子——是一个精致的单机游戏一个强大的AI引擎还是一个支持对战的网络平台——完全取决于你的规划和实践。从这个夏天开始动手把它做出来你收获的将远不止几行代码。