Python五子棋人机对战:从零实现AI博弈算法与Pygame图形界面

📅 2026/7/11 5:11:34
Python五子棋人机对战:从零实现AI博弈算法与Pygame图形界面
1. 项目概述与核心价值五子棋这个规则简单却蕴含无限变化的棋类游戏一直是检验算法和编程能力的绝佳试金石。你可能在网上见过不少用Python实现五子棋的教程但很多要么是纯人人对战要么是电脑随机落子离真正的“智能”还差得远。今天我们就来啃一块硬骨头用Python、Pygame和NumPy从零开始搭建一个具备基础博弈能力的五子棋人机对战程序。这不仅仅是把棋子画到屏幕上那么简单它涉及到游戏状态的高效管理、胜负判断算法的优化以及最核心的——如何让电脑“思考”并走出一步好棋。这个项目的价值在于它是一个绝佳的综合性练手项目。对于Python初学者它能让你系统地练习面向对象编程、事件处理和基础算法对于想深入游戏开发或AI入门的朋友它则是理解状态空间搜索和评估函数设计的敲门砖。更重要的是过程中你会频繁遇到各种“坑”从Pygame的安装报错到NumPy数组操作的细节再到算法效率的瓶颈。本文将不仅提供“怎么做”的代码更会重点分享“为什么这么做”以及“如何避开那些常见的坑”相当于一份来自实战的“避坑指南”。无论你是想做一个课程设计、丰富个人作品集还是单纯对游戏AI感兴趣跟着这篇教程一步步走你收获的将是一个可运行、可对战、代码结构清晰的项目以及一整套解决问题的思路。2. 环境搭建与避坑实战工欲善其事必先利其器。环境配置是项目的第一步也是最容易让新手受挫的地方。网上教程往往一句“pip install pygame numpy”带过但实际操作中版本冲突、依赖缺失、权限问题层出不穷。我们将详细拆解每一步确保你的环境一次配好。2.1 Python解释器与包管理器的选择首先确保你有一个干净的Python环境。强烈建议使用Python 3.7到3.10之间的版本这是目前主流库兼容性最好的范围。避免使用系统自带的Python尤其是macOS和Linux也尽量不要用太新的版本如3.11因为一些库的预编译轮子可能尚未跟进。安装Python后pip是默认的包管理器。但为了环境隔离我强烈推荐使用venv创建虚拟环境。这能保证项目依赖独立不会污染全局环境也便于后期管理和打包。# 在项目目录下创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate激活后命令行提示符前会出现(venv)字样表示你已进入虚拟环境。2.2 Pygame与NumPy安装的深度解析接下来安装核心库Pygame和NumPy。命令很简单但背后的坑不少。pip install pygame numpy避坑指南1Pygame安装失败 “error: failed to build ‘pygame’ when getting requirements to build wheel”这是最常见的错误尤其在Windows系统上。其根本原因是Pygame安装过程中需要编译C扩展而你的系统缺少C/C编译环境主要是Microsoft Visual C Build Tools。解决方案首选方案安装预编译的轮子wheel。访问 Python Extension Packages for Windows 或 Unofficial Windows Binaries for Python Extension Packages 找到与你Python版本和系统架构32位或64位匹配的Pygame的.whl文件下载。然后在命令行中pip install 你下载的.whl文件的完整路径次选方案安装编译工具。对于Windows用户安装 Microsoft C Build Tools 。运行安装程序勾选“使用C的桌面开发”工作负载确保安装了Windows 10/11 SDK和MSVC编译器。替代方案使用conda。如果你安装了Anaconda或Miniconda可以尝试通过conda安装conda通常会提供预编译的二进制包。conda install -c conda-forge pygame避坑指南2NumPy版本与函数兼容性 “AttributeError: module ‘numpy’ has no attribute ‘trapz’”这个错误提示numpy模块没有trapz属性通常是因为代码中使用了np.trapz梯形法数值积分函数但你安装的NumPy版本可能较旧或者更常见的是你错误地命名了一个自己的脚本文件为numpy.py。当Python导入时会优先在当前目录下寻找numpy.py而不是真正的NumPy库。解决方案检查你的项目目录下是否有文件命名为numpy.py或numpy.pyc。如果有立即重命名例如改为my_numpy.py。确保安装的是较新版本的NumPy。可以升级一下pip install --upgrade numpy在代码中使用import numpy as np后通过print(np.__version__)确认导入的确实是官方库。2.3 集成开发环境IDE配置建议一个好的IDE能极大提升效率。VSCode和PyCharm都是绝佳选择。VSCode配置安装Python扩展后在项目根目录下创建.vscode/settings.json文件可以指定解释器路径避免每次切换的麻烦。{ python.defaultInterpreterPath: ${workspaceFolder}/venv/Scripts/python.exe }PyCharm配置新建项目时直接选择“Previously configured interpreter”然后定位到你创建的venv文件夹下的python.exe即可。环境配好我们才算真正站在了起跑线上。接下来进入核心的游戏逻辑设计。3. 游戏核心框架设计与棋盘表示一个清晰的架构是项目成功的一半。我们将采用面向对象的思想把游戏拆分为几个核心类各司其职。3.1 项目整体架构设计我们的五子棋程序主要包含以下几个部分数据层Model负责棋盘状态的存储、更新和查询。这是游戏的大脑所有逻辑判断的基础。视图层View负责将棋盘和棋子渲染到屏幕上处理用户界面。这是游戏的脸面由Pygame负责。控制层Controller负责处理鼠标事件连接用户操作与数据更新。这是游戏的神经。AI层负责模拟电脑玩家的思考过程根据当前棋盘状态生成落子位置。这是本项目的灵魂。我们将创建以下几个核心文件main.py: 程序入口主循环。game.py: 定义Game类整合控制流和游戏状态。board.py: 定义Board类核心数据模型用NumPy数组表示棋盘。ai.py: 定义AI类实现电脑的落子算法。constants.py: 定义常量如颜色、棋盘大小、格子尺寸等。3.2 棋盘数据模型为什么选择NumPy棋盘的本质是一个二维矩阵每个位置有三种状态空、黑子、白子。用Python原生的列表的列表list of lists可以吗可以但效率是瓶颈。五子棋AI需要进行大量的位置评估和模拟对棋盘状态的读取和修改操作极其频繁。NumPy数组在存储效率和数值计算速度上具有压倒性优势。在board.py中我们这样定义棋盘import numpy as np class Board: def __init__(self, size15): 初始化一个size*size的棋盘。 使用NumPy数组0表示空1表示黑子2表示白子。 self.size size # 使用int8类型节省内存 self.grid np.zeros((size, size), dtypenp.int8) self.current_player 1 # 1为黑玩家先手2为白AI self.game_over False self.winner None避坑指南3数组索引与行列顺序Pygame绘制时我们通常用(x, y)坐标表示像素位置。而NumPy数组索引是[row, column]即[行, 列]。在棋盘上行对应y轴列对应x轴。这非常容易混淆。我建议在代码中统一约定所有内部计算如判断胜负、AI搜索使用(row, col)格式仅在需要绘制或从鼠标坐标转换时进行(x, y)与(row, col)的转换。在Board类中提供一个清晰的转换方法def get_row_col(self, pos_x, pos_y, grid_width, grid_height): 将像素坐标(pos_x, pos_y)转换为棋盘行列索引(row, col)。 # 假设棋盘左上角为(0,0)每个格子宽grid_width高grid_height col int((pos_x) // grid_width) row int((pos_y) // grid_height) # 确保索引在棋盘范围内 if 0 row self.size and 0 col self.size: return row, col return None, None3.3 胜负判断算法的高效实现这是游戏逻辑的核心。最直观的方法是每次落子后从该位置向四个方向水平、垂直、两个对角线检查是否有连续五个同色棋子。但直接写四个大循环代码冗余且不易维护。更优雅高效的方式是使用方向向量。定义一个包含四个方向右下右下左下的向量列表。对于新落子的位置遍历每个方向分别向正向和反向延伸计数。class Board: # ... 其他代码 ... def check_win(self, row, col): 检查在(row, col)处落子后是否获胜。 player self.grid[row, col] if player 0: return False # 四个方向向量: (d_row, d_col) directions [(0, 1), (1, 0), (1, 1), (1, -1)] for d_row, d_col in directions: count 1 # 当前位置已经有一颗棋子 # 正向延伸 r, c row d_row, col d_col while 0 r self.size and 0 c self.size and self.grid[r, c] player: count 1 r d_row c d_col # 反向延伸 r, c row - d_row, col - d_col while 0 r self.size and 0 c self.size and self.grid[r, c] player: count 1 r - d_row c - d_col # 判断是否五连 if count 5: self.winner player self.game_over True return True return False避坑指南4边界条件与“长连”注意循环中的边界判断0 r self.size。此外标准的五子棋规则中超过五子的连线长连通常不算赢具体规则可能不同。上面的count 5是常见的无禁手规则。如果你想实现有禁手规则黑棋不能双活三、双四、长连等判断逻辑会复杂得多需要额外维护更复杂的棋盘状态信息。4. Pygame图形界面与交互实现有了强大的数据模型我们需要一个友好的界面来展示和交互。Pygame虽然底层但足够灵活。4.1 窗口初始化与棋盘绘制在main.py或game.py中我们初始化Pygame并设置窗口和颜色常量。import pygame from constants import * # 初始化pygame pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(五子棋人机对战) clock pygame.time.Clock()constants.py中定义了所有魔法数字# constants.py SCREEN_WIDTH 800 SCREEN_HEIGHT 600 GRID_SIZE 15 # 15*15棋盘 GRID_WIDTH SCREEN_WIDTH // (GRID_SIZE 2) # 计算每个格子的像素宽度留出边距 GRID_HEIGHT SCREEN_HEIGHT // (GRID_SIZE 2) BLACK (0, 0, 0) WHITE (255, 255, 255) BOARD_COLOR (220, 179, 92) # 棋盘颜色仿木色 LINE_COLOR (0, 0, 0)绘制棋盘就是在屏幕上画线和点def draw_board(screen, board): 绘制棋盘网格和星位点。 screen.fill(BOARD_COLOR) # 绘制网格线 for i in range(board.size): # 竖线 start_pos (GRID_WIDTH * (i 1), GRID_HEIGHT) end_pos (GRID_WIDTH * (i 1), GRID_HEIGHT * board.size) pygame.draw.line(screen, LINE_COLOR, start_pos, end_pos, 2) # 横线 start_pos (GRID_WIDTH, GRID_HEIGHT * (i 1)) end_pos (GRID_WIDTH * board.size, GRID_HEIGHT * (i 1)) pygame.draw.line(screen, LINE_COLOR, start_pos, end_pos, 2) # 绘制星位点天元和四个边星 star_points [(3, 3), (3, 11), (7, 7), (11, 3), (11, 11)] for point in star_points: center_x GRID_WIDTH * (point[0] 1) center_y GRID_HEIGHT * (point[1] 1) pygame.draw.circle(screen, LINE_COLOR, (center_x, center_y), 5)4.2 棋子绘制与鼠标事件处理绘制棋子就是根据board.grid中的数据在对应位置画圆。处理鼠标事件则是将点击的像素坐标转换为棋盘坐标并调用Board类的落子方法。def draw_pieces(screen, board): 绘制所有棋子。 for row in range(board.size): for col in range(board.size): player board.grid[row, col] if player ! 0: center_x GRID_WIDTH * (col 1) center_y GRID_HEIGHT * (row 1) color BLACK if player 1 else WHITE pygame.draw.circle(screen, color, (center_x, center_y), GRID_WIDTH // 2 - 2) # 为白棋添加一个细黑边使其在浅色棋盘上更清晰 if player 2: pygame.draw.circle(screen, BLACK, (center_x, center_y), GRID_WIDTH // 2 - 2, 1)事件处理循环是游戏运行的核心def handle_events(game): 处理Pygame事件。 for event in pygame.event.get(): if event.type pygame.QUIT: return False # 退出游戏 if event.type pygame.MOUSEBUTTONDOWN and event.button 1: # 左键点击 if not game.board.game_over and game.board.current_player 1: # 玩家回合 x, y event.pos row, col game.board.get_row_col(x, y, GRID_WIDTH, GRID_HEIGHT) if row is not None and game.board.place_piece(row, col): # 玩家落子成功 if game.board.check_win(row, col): print(f玩家{game.board.winner}获胜) else: # 切换为AI回合 game.board.current_player 2 # 可以添加键盘事件例如按R键重新开始 if event.type pygame.KEYDOWN: if event.key pygame.K_r: game.restart() return True避坑指南5落子有效性判断与状态更新在Board.place_piece方法中必须检查目标位置是否为空(grid[row, col] 0)。落子后除了更新数组还要记得切换当前玩家(current_player)并调用check_win。这些步骤必须原子化放在一个方法里避免状态不一致。class Board: # ... def place_piece(self, row, col): 在(row, col)放置当前玩家的棋子。成功返回True否则返回False。 if self.grid[row, col] ! 0 or self.game_over: return False self.grid[row, col] self.current_player self.check_win(row, col) # 只有没赢才切换玩家 if not self.game_over: self.current_player 3 - self.current_player # 巧妙切换1和2 return True5. 人机对战AI算法实现这是本项目的精华所在。我们将实现一个基于极大极小搜索Minimax和Alpha-Beta剪枝的简单AI。为了让AI有基本的“棋感”我们还需要一个评估函数来量化棋盘上某个位置对某一方的价值。5.1 评估函数设计量化棋盘优劣评估函数是AI的“价值观”。它需要扫描棋盘为当前玩家假设是AI计算一个分数。分数越高局面越有利。我们采用一种常见模式识别并评估各种“棋型”如连五、活四、冲四、活三、眠三等并赋予不同的权重。为了高效我们同样使用方向向量在棋盘上滑动一个“窗口”进行检查。这里给出一个简化版的评估思路专注于对单个位置的评估用于后续的搜索排序。# 在ai.py中 import numpy as np class AI: def __init__(self, player2, depth3): # depth是搜索深度 self.player player # AI是白棋(2)还是黑棋(1)通常AI是白棋 self.opponent 3 - player self.depth depth # 定义一些棋型模式及其分数简化版实际更复杂 self.patterns { FIVE: 100000, # 连五 LIVE_FOUR: 10000, # 活四 BLOCKED_FOUR: 1000, # 冲四单边被堵 LIVE_THREE: 1000, # 活三 BLOCKED_THREE: 100, # 眠三 LIVE_TWO: 100, # 活二 BLOCKED_TWO: 10, # 眠二 } def evaluate_point(self, board, row, col, player): 评估在(row, col)位置对player的价值简化版。 if board.grid[row, col] ! 0: return 0 # 已有棋子价值为0 # 这里应实现复杂的棋型识别遍历四个方向统计能形成的潜在棋型。 # 作为示例我们返回一个基于空位周围棋子数量的简单启发值。 directions [(0,1),(1,0),(1,1),(1,-1),(-1,1),(-1,0),(-1,-1),(0,-1)] score 0 for dr, dc in directions: r, c row dr, col dc if 0 r board.size and 0 c board.size: if board.grid[r, c] player: score 2 elif board.grid[r, c] self.opponent: score - 1 return score5.2 极大极小搜索与Alpha-Beta剪枝原理AI的思考过程可以看作一棵博弈树。根节点是当前棋盘状态AI要选择一步对自己最有利的棋。它假设对手玩家也会选择对AI最不利的棋即对玩家最有利。这就是“极大极小”AI在己方回合最大化分数在对手回合最小化分数。朴素极大极小搜索会递归地展开所有可能的走法直到一定深度或终局然后通过评估函数给叶子节点打分再回溯选择最优解。其计算量随深度指数级增长对于15*15的棋盘即使深度为3也难以承受。Alpha-Beta剪枝是对极大极小搜索的优化。它在搜索过程中传递两个值alpha和beta。alpha表示当前路径上AI方至少能保证的分数下限beta表示对手方至少能保证的分数上限即AI的分数上限。在搜索过程中如果发现某个分支的估值已经不可能比已知的最佳选择更好就直接“剪掉”这个分支不再继续搜索从而大幅减少计算量。5.3 AI落子算法的代码实现结合评估函数和Alpha-Beta剪枝AI的决策流程如下获取当前棋盘所有空位。根据一个简单的启发式评估如evaluate_point对空位进行排序优先搜索价值高的位置这能提高剪枝效率。对排序后的位置进行深度为depth的Alpha-Beta搜索。返回搜索得到的分数最高的位置。class AI: # ... __init__ 和 evaluate_point ... def get_available_moves(self, board): 获取棋盘上所有空位。可以优化为只搜索有棋子相邻的空位星位扩展。 moves [] for r in range(board.size): for c in range(board.size): if board.grid[r, c] 0: # 简单启发只考虑周围有棋子的空位大幅减少搜索空间 if self.has_neighbor(board, r, c, distance2): moves.append((r, c)) # 如果没有这样的位置开局返回中心点附近 if not moves: moves.append((board.size//2, board.size//2)) return moves def has_neighbor(self, board, row, col, distance2): 判断(row, col)周围distance范围内是否有棋子。 for dr in range(-distance, distance1): for dc in range(-distance, distance1): if dr 0 and dc 0: continue r, c row dr, col dc if 0 r board.size and 0 c board.size: if board.grid[r, c] ! 0: return True return False def alpha_beta(self, board, depth, alpha, beta, maximizing_player): Alpha-Beta剪枝搜索。返回当前节点的估值。 if depth 0 or board.game_over: # 到达叶子节点返回评估值。这里需要一个全局评估函数而非单点评估。 return self.evaluate_board(board) if maximizing_player: # AI的回合最大化分数 max_eval -float(inf) moves self.get_available_moves(board) # 按启发值排序提高剪枝效率 moves.sort(keylambda move: self.evaluate_point(board, move[0], move[1], self.player), reverseTrue) for move in moves: row, col move # 模拟落子 board.grid[row, col] self.player # 注意这里简化了没有处理落子后胜负状态对评估的影响。严谨的做法需要更新状态。 eval self.alpha_beta(board, depth-1, alpha, beta, False) # 撤销落子 board.grid[row, col] 0 max_eval max(max_eval, eval) alpha max(alpha, eval) if beta alpha: break # Beta剪枝 return max_eval else: # 对手的回合最小化分数 min_eval float(inf) moves self.get_available_moves(board) moves.sort(keylambda move: self.evaluate_point(board, move[0], move[1], self.opponent), reverseTrue) for move in moves: row, col move board.grid[row, col] self.opponent eval self.alpha_beta(board, depth-1, alpha, beta, True) board.grid[row, col] 0 min_eval min(min_eval, eval) beta min(beta, eval) if beta alpha: break # Alpha剪枝 return min_eval def evaluate_board(self, board): 评估整个棋盘对AI的分数。这是一个非常简化的版本。 # 这里应该实现一个全面的评估函数遍历整个棋盘识别所有棋型。 # 作为演示我们返回一个随机数实际中这需要复杂的逻辑。 # 更简单的方法如果游戏结束返回极大/极小值否则返回0。 if board.game_over: return 100000 if board.winner self.player else -100000 # 简化计算AI和对手的简单棋子数量差 ai_count np.sum(board.grid self.player) opp_count np.sum(board.grid self.opponent) return ai_count - opp_count def get_best_move(self, board): AI主接口返回最佳落子位置(row, col)。 best_move None best_value -float(inf) moves self.get_available_moves(board) moves.sort(keylambda move: self.evaluate_point(board, move[0], move[1], self.player), reverseTrue) # 搜索深度不宜过深否则速度很慢。深度3是一个平衡点。 for move in moves: row, col move board.grid[row, col] self.player move_value self.alpha_beta(board, self.depth-1, -float(inf), float(inf), False) board.grid[row, col] 0 if move_value best_value: best_value move_value best_move move # 如果没有找到理论上不会返回第一个可走位置 return best_move if best_move else moves[0]避坑指南6搜索效率与启发式排序深度为3的完全搜索即使有剪枝在15*15棋盘上也可能很慢。关键在于get_available_moves和排序。我们通过has_neighbor只搜索有棋子相邻的空位这符合五子棋的常识能极大减少分支因子。同时在搜索前用简单的evaluate_point对走法排序让好的走法先被搜索能极大提高Alpha-Beta剪枝的效率。这是性能优化的关键。避坑指南7评估函数的准确性上面的evaluate_board函数是极度简化的实际效果很差。一个强大的AI其评估函数非常复杂需要识别数十种棋型并精细打分。对于入门项目你可以参考开源的五子棋项目如Gobang借鉴其评估函数。这是AI“智力”的上限。6. 项目整合、优化与扩展将各个模块整合在一起形成一个完整的游戏循环。在Game类中协调Board、AI和Pygame的渲染。6.1 主游戏循环与状态管理# game.py import pygame from board import Board from ai import AI from constants import * class Game: def __init__(self): self.board Board() self.ai AI(player2, depth3) # AI执白搜索深度3 self.running True self.screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(五子棋人机对战) def run(self): clock pygame.time.Clock() while self.running: # 处理事件 self.running self.handle_events() # AI回合 if not self.board.game_over and self.board.current_player self.ai.player: # 可以加一个延迟让AI思考看起来更自然 # pygame.time.delay(500) row, col self.ai.get_best_move(self.board) if self.board.place_piece(row, col): self.board.check_win(row, col) # AI落子后当前玩家已自动切换为玩家(1) # 绘制 self.draw() pygame.display.flip() clock.tick(60) # 控制帧率 pygame.quit() def handle_events(self): # ... 同前面的handle_events函数但现在是Game的方法 ... for event in pygame.event.get(): if event.type pygame.QUIT: return False if event.type pygame.MOUSEBUTTONDOWN and event.button 1: if not self.board.game_over and self.board.current_player 1: x, y event.pos row, col self.board.get_row_col(x, y, GRID_WIDTH, GRID_HEIGHT) if row is not None and self.board.place_piece(row, col): self.board.check_win(row, col) if event.type pygame.KEYDOWN: if event.key pygame.K_r: self.restart() return True def draw(self): # 绘制棋盘和棋子 self.screen.fill(BOARD_COLOR) # 调用之前定义的draw_board和draw_pieces函数或将其实现为Game的方法 self._draw_grid() self._draw_pieces() # 绘制游戏状态信息如当前玩家、胜负信息 self._draw_status() def _draw_status(self): font pygame.font.SysFont(None, 36) if self.board.game_over: text f游戏结束{黑棋 if self.board.winner 1 else 白棋}获胜 color (255, 0, 0) else: text f当前回合{黑棋 if self.board.current_player 1 else 白棋} color (0, 0, 255) text_surface font.render(text, True, color) self.screen.blit(text_surface, (10, SCREEN_HEIGHT - 40)) hint font.render(按 R 键重新开始, True, (100, 100, 100)) self.screen.blit(hint, (SCREEN_WIDTH - 200, SCREEN_HEIGHT - 40)) def restart(self): self.board Board() # AI对象可以重用 if __name__ __main__: game Game() game.run()6.2 性能优化与体验提升AI思考速度深度为3的搜索在开局时可能已经感觉迟缓。除了优化评估和排序可以引入迭代加深Iterative Deepening先深度1搜索再深度2再深度3在每次加深时复用之前搜索的结果来排序并在时间限制内返回最深度的结果。棋盘绘制优化不需要每帧重绘整个棋盘。可以只绘制发生变化的区域脏矩形更新但对于这个规模的项目全屏重绘在60FPS下完全没问题。音效与动画使用pygame.mixer添加落子音效使用简单的帧动画让棋子落下更有质感。悔棋功能在Board类中维护一个落子历史栈move_history悔棋时弹出栈顶的位置并将棋盘对应位置置零切换玩家。6.3 常见问题排查与调试技巧AI下子慢或卡顿降低搜索深度depth。检查get_available_moves是否过滤了太多或太少的空位。调整has_neighbor中的distance参数通常1或2。在alpha_beta函数中添加print语句输出搜索的节点数观察剪枝效果。使用Python的cProfile模块进行性能分析找到瓶颈函数。AI水平太“傻”首要原因是评估函数太弱。投入精力设计一个更好的评估函数其提升远大于增加搜索深度。增加搜索深度。实现更复杂的开局库和定式。画面闪烁或更新不正常确保所有绘制操作都在screen.fill()之后并且在pygame.display.flip()或pygame.display.update()之前完成。检查游戏循环的结构确保事件处理、逻辑更新、画面绘制顺序正确。游戏逻辑错误如无法判断赢棋在check_win函数中打印row, col, player, count等变量检查循环逻辑。使用一个简单的测试用例例如在棋盘上手动设置一个五连珠然后调用check_win看返回值。这个项目从环境搭建到AI对战涵盖了软件开发的多个环节。完成它你不仅会得到一个能玩的五子棋游戏更重要的是掌握了解决复杂问题、调试代码和优化算法的系统性方法。代码中留有很多可以优化和扩展的接口比如更智能的评估函数、更高效的搜索算法如启发式搜索、蒙特卡洛树搜索MCTS甚至是引入机器学习这都可以作为你下一步探索的方向。