简介:面向Python编程与数据分析课程的结课报告场景,五子棋对弈的算法设计资料包将算法思路与编码实现完整串联,适合正在准备课程设计或结课报告的学生参考。报告部分按程序思路介绍、设计方案、源程序代码、程序运行及结语五章逐层展开,图文表并茂,逻辑清晰,可帮助快速梳理从问题分析到结果呈现的完整过程;代码部分包含4个Python文件,分别负责棋盘绘制、人人对战与人机对战等核心模块,结构独立、可直接运行,也便于替换或扩展策略后二次练习。资料包共5个文件,以py脚本和docx文档为主,压缩包大小仅1.08MB,轻量易用;内部按“报告”与“代码”分区存放,查找使用十分方便。目前已有1831人学习下载,适合作为Python编程与数据分析相关课程的结课报告范例,也可作为五子棋算法入门、人机策略设计和小型游戏项目实践的参考材料。
1. 五子棋人机对战:一个把语法、算法和数据分析串起来的结课项目
很多Python基础课结束时的作业都停在增删改查或爬虫,但这个五子棋对弈项目不太一样:它要同时处理棋盘的状态表示、人机交互的事件循环、AI决策的评分逻辑,还要把整个过程写进一份像样的结课报告。整个代码量不算大,四个Python文件加起来也就几百行,但你把它跑起来后会发现自己其实已经在用“数据分析”的思维了——把一局对弈拆成特征、评估、决策,最后还能用图表说明为什么AI会在某一手选择那个位置。这个资源里既有完整源码也有Word报告,适合那些想把期末作业做得有点工程感的人。
2. 棋盘建模与程序模块拆分:先定义状态,再谈算法
五子棋的对弈逻辑在纸面上很简单,但落到代码里第一个关键问题是:棋盘怎么表示,才算既容易判断胜负,又能快速计算AI的落子点。最常见的做法是用一个15×15的二维数组,0表示空位,1表示黑子,2表示白子。这个方案没有花哨的对象封装,但胜在直白,访问任意位置都是O(1)开销。
2.1 用二维数组保存棋盘状态
# checkerboard.py SIZE = 15 # 棋盘15x15 def init_board(): """初始化空棋盘, 返回15x15的二维列表""" return [[0 for _ in range(SIZE)] for _ in range(SIZE)] def is_valid_move(board, x, y): """判断(x, y)是否可落子: 坐标合法且为空""" if not (0 <= x < SIZE and 0 <= y < SIZE): return False return board[x][y] == 0 def place_piece(board, x, y, player): """落子并返回新棋盘, 不直接修改原对象""" new_board = [row[:] for row in board] new_board[x][y] = player return new_board这里我刻意让place_piece返回新棋盘而不是原地修改,这是为了后面做AI搜索时方便回溯。如果只是人人对战,直接board[x][y] = player就够了,但人机对战的多步计算会需要临时状态,提前用复制方式能省去恢复现场的麻烦。player取值1或2,对应黑子和白子。
2.2 四个Python文件怎么分工
资源包里的四个文件不是随手扔在一起,它们刚好对应了程序的三层职责。checkerboard.py只负责棋盘数据的初始化和基础判断,相当于数据层。ManAndMan.py把数据层和pygame的绘制、鼠标事件撮合在一起,完成人人对战。ManAndMachine.py在ManAndMan.py的基础上引入AI落子函数,相当于把决策层塞进事件循环。另外一个文件名带“五子棋【人机对战】”的脚本通常保存了AI的具体算法实现,有时会和ManAndMachine.py合并,取决于当初写报告时的拆分习惯。
模块边界清晰有个很实际的好处:结课报告里写“程序结构设计”章节时,直接画一张表把四个文件对应到功能就非常清楚。
| 文件 | 职责 | 依赖 |
|---|---|---|
| checkerboard.py | 棋盘初始化、合法落子判断、胜负检测的数据支撑 | 无 |
| ManAndMan.py | 人人对战主循环、pygame画面、鼠标落子 | checkerboard |
| ManAndMachine.py | 人机对战主循环、调用AI接口、显示AI落子 | checkerboard, AI函数 |
| 五子棋【人机对战】.py | AI评估与选点算法 | checkerboard |
这里多说一句:把AI算法单独放一个文件,能避免以后你想给AI加“难度等级”时去主循环里翻逻辑。我一般会在项目里再建一个ai.py,但这个资源里既然已经拆出单独文件,就保持原样,报告里也好解释。
3. pygame棋盘绘制与人人对战事件循环
棋盘画在屏幕上不是简单画线,要处理坐标换算和窗口尺寸的对应关系。常见做法是把窗口设为640×640,棋盘每条线间隔40像素,留20像素边距,这样15条线刚好铺满。每一个交叉点就是落子位置,鼠标点击时需要把像素坐标反向换算成棋盘坐标。
3.1 坐标换算与棋子绘制
import pygame from checkerboard import SIZE CELL = 40 MARGIN = 20 WIDTH = HEIGHT = CELL * (SIZE - 1) + MARGIN * 2 def screen_to_board(pos): x, y = pos col = round((x - MARGIN) / CELL) row = round((y - MARGIN) / CELL) return row, col def draw_board(screen, board): screen.fill((210, 180, 140)) # 木色背景 for i in range(SIZE): start = MARGIN + i * CELL pygame.draw.line(screen, (0, 0, 0), (MARGIN, start), (MARGIN + CELL * (SIZE - 1), start), 1) pygame.draw.line(screen, (0, 0, 0), (start, MARGIN), (start, MARGIN + CELL * (SIZE - 1)), 1) for r in range(SIZE): for c in range(SIZE): if board[r][c] == 1: pos = (MARGIN + c * CELL, MARGIN + r * CELL) pygame.draw.circle(screen, (0, 0, 0), pos, 17, 0) elif board[r][c] == 2: pos = (MARGIN + c * CELL, MARGIN + r * CELL) pygame.draw.circle(screen, (255, 255, 255), pos, 17, 0)screen_to_board里用了round而不是int,这样才能保证点击交叉点附近的像素都能落到最近的格子上。如果你用int做强制转换,点击位置在棋盘左侧时会因为浮点误差出现坐标偏一格的问题。棋子半径设为17像素,比半个格子略小,黑子和白子之间留一点视觉空隙。
3.2 事件循环里的回合切换
人人对战的主循环并不复杂,但容易在“落子后切换玩家”这里出错。下面的代码片段展示了最小可用逻辑:
# ManAndMan.py import pygame from checkerboard import init_board, is_valid_move, place_piece def main(): pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("五子棋 - 人人对战") board = init_board() current_player = 1 # 1=黑, 2=白 clock = pygame.time.Clock() running = True while running: clock.tick(30) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: r, c = screen_to_board(event.pos) if is_valid_move(board, r, c): board = place_piece(board, r, c, current_player) current_player = 3 - current_player # 1变2, 2变1 draw_board(screen, board) pygame.display.flip() pygame.quit()current_player = 3 - current_player是回合切换的小技巧,比current_player = 2 if current_player == 1 else 1简洁。注意place_piece返回新棋盘,所以主循环里要重新赋值给board,否则棋盘数组不会更新。这个坑我见过很多同事踩,因为place_piece内部用列表推导复制,如果不接返回值,棋盘永远停在初始状态。
3.3 胜负检测:单次落子后只需查四条线
胜负判断最鲁棒的办法是每下一次就全盘扫描,但那样做了很多无用功。高效做法是只查最后落子位置所在的行、列、主对角线、副对角线,四条线各向两端延伸,数连续同色棋子数。
DIRS = [(1, 0), (0, 1), (1, 1), (1, -1)] def check_win(board, r, c): """在(r,c)落子后检查是否形成五连, 返回玩家号或0""" player = board[r][c] for dr, dc in DIRS: count = 1 # 正方向 nr, nc = r + dr, c + dc while 0 <= nr < SIZE and 0 <= nc < SIZE and board[nr][nc] == player: count += 1 nr += dr nc += dc # 负方向 nr, nc = r - dr, c - dc while 0 <= nr < SIZE and 0 <= nc < SIZE and board[nr][nc] == player: count += 1 nr -= dr nc -= dc if count >= 5: return player return 0这段代码里的DIRS定义了四个方向向量:水平、垂直、两条对角线。每个方向先往正方向数,再往反方向数,加起来等于连续同色棋子总数。注意对角线使用(1, 1)和(1, -1),配合循环里的边界判断,数组下边不会越界。这个函数在人人对战里放在落子切换玩家之前调用,一旦返回非0值就可以弹窗或打印胜者。
4. 人机对战核心:权值表与评估函数设计
人机对战的AI并不需要真的像AlphaGo那样做蒙特卡洛树搜索。对于15×15标准棋盘和结课报告的演示需求,一个基于手工权值表的评分算法就能下出像模像样的棋。它的核心思想是:对棋盘上每个空位,分别从四个方向统计黑白子的各种连子模式,给每个模式打一个经验分数,最后所有方向得分之和就是该空位的总评分,AI选择得分最高的空位落子。
4.1 为什么不直接跑极大极小搜索
五子棋的分支因子在开局阶段超过30,深度到4层时节点数就超过80万,用Python做纯递归搜索非常吃力。权值法的复杂度是O(棋盘空位数×方向数×方向长度),只有几千次操作,一秒钟能算很多遍。它不保证走出理论上最优解,但在结课设计中有两个直接好处:第一,计算快,界面不会卡顿;第二,权值表可以对外开放,你改一个数字就能改变AI的攻防风格,写报告时能顺带做一点“参数对比实验”。
4.2 模式与权值对应表
AI判断的核心是把一段方向上的连续子序列映射成得分。先列出常用的模式作为基准,权值需要根据实际对局微调:
| 模式 | 含义 | 权值建议 |
|---|---|---|
| 五连 | 已有5个己方棋子 | 100000 |
| 活四 | 两端均开放 | 50000 |
| 冲四 | 一端被堵 | 10000 |
| 活三 | 可成活四的形势 | 5000 |
| 眠三 | 只能成冲四 | 1000 |
| 活二 | 两端开放 | 500 |
| 眠二 | 一端受堵 | 100 |
这个表的直观含义是:AI优先成五连和堵对手的五连,其次是创造活四,再到活三。权值差距拉开得越大,AI越“贪”,会不惜一切代价追求连子。
4.3 方向扫描与评分数值化
实现时我习惯写一个evaluate_position函数,它对每个空位调用score_point,然后让AI选择得分最高的点。下面是关键逻辑:
# 五子棋【人机对战】.py from checkerboard import SIZE # 不同连子模式对应分数字典 SCORES = { (1, True): 100000, # 5连 (4, True): 50000, # 活四 (4, False): 10000, # 冲四 (3, True): 5000, (3, False): 1000, (2, True): 500, (2, False): 100, } def count_pattern(seq): """seq是一维列表, 统计当前玩家的连子数以及两端是否开放 返回 (连续数, 开放端数量) """ pass # 实现略, 核心是连续扫描 def score_point(board, row, col, ai_player): """计算空位(row,col)放ai_player棋子后的总分""" if board[row][col] != 0: return -1 total = 0 for dr, dc in [(1,0),(0,1),(1,1),(1,-1)]: line = [] for step in range(1, 6): nr, nc = row + dr * step, col + dc * step if 0 <= nr < SIZE and 0 <= nc < SIZE: line.append(board[nr][nc]) else: break for step in range(1, 6): nr, nc = row - dr * step, col - dc * step if 0 <= nr < SIZE and 0 <= nc < SIZE: line.insert(0, board[nr][nc]) else: break # 在line末尾加一个0表示当前空位, 再统计模式 line.append(0) cnt, open_ends = analyze_line(line, ai_player) key = (cnt, open_ends >= 2) total += SCORES.get(key, 0) return totalscore_point里用step控制扫描半径到5,因为超过5的连续对判断五连没有意义,只需要看落子点周围10格距离内的信息。analyze_line会从当前空位出发,把左右两边的同色棋子数连在一起,再检查两端是否被己方棋子或边界阻挡。如果两端都开放,open_ends就是2,对应活模式;如果只有一端开放,则是眠模式。
AI选点的完整过程是:遍历棋盘全部空位,调用score_point,把结果按从大到小排序。但若每次都全盘扫描,虽然只有225个点,再加上方向分析,性能也够,可视觉上会有一点顿挫。我一般会做一个小优化:只扫描已有棋子周围半径2以内的空位。这既保留候选质量,又把计算量降到原来的十分之一。
def ai_move(board, ai_player): best_score = -1 best_pos = (SIZE // 2, SIZE // 2) for r in range(SIZE): for c in range(SIZE): if board[r][c] != 0: continue score = score_point(board, r, c, ai_player) if score > best_score: best_score = score best_pos = (r, c) return best_pos这个实现是一个纯进攻型AI,它只会找自己得分最高的点,不会主动防御。实际对战你会发现AI经常只顾自己连子,对手快赢了也不管。所以标准的做法是在评分时同时计算对手在当前空位落子的得分,并把它加到AI自己的评分里。总得分 = AI得分 + 对手得分 * 防御系数,防御系数取0.9~1.0时就变成攻守兼备,取0.8时偏攻击,取1.2时偏防守。
在报告里展示算法时,把防御系数当作变量,对比不同系数下的胜负结果,就是一个很好的数据分析案例,比单纯贴代码更有说服力。
5. 结课报告的图表呈现与运行排错技巧
报告一般分五章:程序思路、设计方案、源程序代码、程序运行、结语。前几章靠文字和代码就能撑起来,关键是“程序运行”这一章不能让老师只看控制台截图,最好在pygame窗口里截取不同的对局阶段,抠出黑白子交替的画面。我建议每张截图下面加一句话说明当前局面是AI处于进攻还是防守,这样评审老师能一眼看出算法的工作状态。
关于代码和文档的对应,有个很简单的技巧:在Word里把关键函数名用等宽字体标出,比如score_point、check_win,然后在源程序代码章节里同样用等宽字体,这样读起来非常舒服。如果还想加一点数据分析味道,可以用matplotlib把AI不同防御系数下的50局模拟结果画成柱状图,横轴是系数,纵轴是胜率,虽然这超出了原代码的范围,但属于对资源的合理扩展。
运行环境上最常见的坑是pygame没装上。给结课报告写“环境依赖”时,直接写pip install pygame即可。如果你用的是新版Python,偶发出现No module named 'pygame',通常是没有在当前的虚拟环境里安装。注意别把虚拟环境目录一起压缩进提交包,老师解压后看到一大堆venv反而扣分。另一个容易出错的地方是中文文件名,比如“五子棋【人机对战】.py”,在Linux或Mac下如果编码不对会抛SyntaxError,稳妥做法是把代码文件都改成英文名,报告里再写清对应关系。
如果程序启动后窗口能打开但点棋子没反应,八成是pygame.event.get()的事件类型检查写错。很多人会写成if event.type == pygame.MOUSEBUTTONDOWN:而不检查event.button,结果把鼠标中键也当成落子,在Windows上有时会误触。这里建议统一用event.button == 1限定左键。
最后提一个写结语时能拉开层次的做法:不要简单说“我掌握了Python编程”,可以写“通过五子棋项目,我完成了从二维数组状态管理到启发式评估函数的闭环实践”,并把棋盘建模比作数据清洗,AI评分比作特征工程,落子决策比作模型预测。这套类比放在数据分析类课程的答辩里,高分和面试印象都会更好。
本文还有配套的精品资源,点击获取