Python Pygame 实现五子棋游戏完整教程

Noah
Noah
发布时间:2026-10-13| 6人浏览过
原创

本文详细介绍如何使用Python的Pygame库从零构建一个五子棋游戏。内容涵盖15x15标准棋盘的尺寸计算与绘制、鼠标点击坐标的网格对齐处理、黑白棋子交替落子的逻辑实现,以及基于二维数组的胜负判定算法。代码包含完整的棋盘渲染、事件监听及游戏状态管理,适合Python初学者进行GUI编程练习。

本文分享了使用Pygame库开发五子棋游戏的完整代码示例,供读者参考学习。

棋盘初始化与绘制

1. 棋盘初始化

标准五子棋棋盘为15x15网格。若设定每个格子的尺寸为40x40像素,则棋盘区域的宽度计算为40乘以(15减1),即560像素。为了美观,我们在窗口四周各预留60像素的边距,因此最终窗口的长宽尺寸应设置为40乘以(15减1)再加上60乘以2。


# -*- coding=utf-8 -*-
import random
import pygame
pygame.init()

space = 60 # 四周留下的边距
cell_size = 40 # 每个格子大小
cell_num = 15
grid_size = cell_size * (cell_num - 1) + space * 2 # 棋盘的大小
screencaption = pygame.display.set_caption('FIR')
screen = pygame.display.set_mode((grid_size,grid_size)) #设置窗口长宽

while True:
 for event in pygame.event.get():
  if event.type == pygame.QUIT:
  pygame.quit()
  exit()

 screen.fill((0,0,150)) # 将界面设置为蓝色

 for x in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(x+space,0+space),(x+space,cell_size*(cell_num-1)+space),1)
 for y in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(0+space,y+space),(cell_size*(cell_num-1)+space,y+space),1)
 
 pygame.display.update() # 必须调用update才能看到绘图显示

落子功能与坐标对齐

2. 落子功能实现

首先,定义一个名为chess_arr的列表,用于存储所有已落子的坐标信息。


chess_arr = []

接着,在游戏的主循环中监听鼠标按键释放事件。当捕捉到该事件时,获取当前的鼠标坐标,并将该坐标数据添加到chess_arr列表中。


for event in pygame.event.get():
 ……
 
  if event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起
  x, y = pygame.mouse.get_pos() # 获取鼠标位置
  chess_arr.append((x,y))

最后,在调用pygame.display.update()刷新屏幕之前,遍历chess_arr列表并将棋子绘制到界面上,以观察初步效果。

此时虽然能够显示棋子,但棋子并未准确落在网格线的交叉点上。因此,必须对鼠标获取的原始坐标进行取整处理,不能直接使用原始的x,y坐标。处理x,y坐标对齐的代码如下所示。


for event in pygame.event.get():
 ……
  if event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起
  x, y = pygame.mouse.get_pos() # 获取鼠标位置
  xi = int(round((x - space)*1.0/cell_size)) # 获取到x方向上取整的序号
  yi = int(round((y - space)*1.0/cell_size)) # 获取到y方向上取整的序号
  if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num:
   chess_arr.append((xi*cell_size+space,yi*cell_size+space))

经过上述处理后,落子的位置已经明显更加准确。

为了提高代码的可读性并简化后续的棋盘逻辑计算,我们将存入chess_arr的数据从绝对像素坐标改为网格序号。具体修改如下。


chess_arr.append((xi*cell_size+space,yi*cell_size+space))

将原来的绝对坐标存储方式替换为网格索引存储。


chess_arr.append((xi,yi))

同时,在绘制棋子的代码部分也需进行相应调整,将原来的绘制逻辑修改为基于网格索引的坐标转换。


pygame.draw.circle(screen,(205,205,205), [x, y], 16,16)

将绘制函数中的坐标参数改为通过网格索引计算出的绝对像素位置。


pygame.draw.circle(screen,(205,205,205), [x*cell_size+space, y*cell_size+space], 16,16)

此外,还需要解决重复落子的问题。在将坐标加入chess_arr之前,必须判断该位置是否已有棋子。由于Python支持直接判断元组或列表是否包含特定元素,因此只需增加一个判断条件:(xi,yi) not in chess_arr,即可有效防止重复落子。


for event in pygame.event.get():
  ……
  if event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起
  ……
  if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num and (xi,yi) not in chess_arr:
   chess_arr.append((xi,yi))

为避免代码出现偏差,此处先展示当前阶段的完整代码。


# -*- coding=utf-8 -*-
import random
import pygame
from pygame.locals import MOUSEBUTTONUP
pygame.init()

space = 60 # 四周留下的边距
cell_size = 40 # 每个格子大小
cell_num = 15
grid_size = cell_size * (cell_num - 1) + space * 2 # 棋盘的大小
screencaption = pygame.display.set_caption('FIR')
screen = pygame.display.set_mode((grid_size,grid_size)) #设置窗口长宽

chess_arr = []

while True:
 for event in pygame.event.get():
  if event.type == pygame.QUIT:
  pygame.quit()
  exit()
 
  if event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起
  x, y = pygame.mouse.get_pos() # 获取鼠标位置
  xi = int(round((x - space)*1.0/cell_size)) # 获取到x方向上取整的序号
  yi = int(round((y - space)*1.0/cell_size)) # 获取到y方向上取整的序号
  if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num and (xi,yi) not in chess_arr:
   chess_arr.append((xi,yi))

 screen.fill((0,0,150)) # 将界面设置为蓝色

 for x in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(x+space,0+space),(x+space,cell_size*(cell_num-1)+space),1)
 for y in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(0+space,y+space),(cell_size*(cell_num-1)+space,y+space),1)

 for x, y in chess_arr:
 pygame.draw.circle(screen,(205,205,205), [x*cell_size+space, y*cell_size+space], 16,16)

 pygame.display.update() # 必须调用update才能看到绘图显示

黑白棋子区分逻辑

3. 黑白棋子区分

实现黑白棋子区分主要有两种思路:第一种是假设chess_arr中棋子黑白交替出现,通过索引奇偶性决定颜色(此方法在不考虑五手两打或让子规则的标准对局中可行);第二种是在存入chess_arr时,除了(x,y)坐标外,额外增加一个表示颜色的标记位,改为存储(x,y,flag)。本文采用第二种方案。首先,在全局定义一个flag变量用于标识当前轮到哪方落子。


flag = 1 # 1黑 2白

原有的落子存储代码部分。


if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num and (xi,yi) not in chess_arr:
   chess_arr.append((xi,yi))

修改为在存储坐标时同时记录当前flag的值,并在落子后切换flag(1变2,2变1)。


if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num and (xi,yi,1) not in chess_arr and (xi,yi,2) not in chess_arr:
   chess_arr.append((xi,yi,flag))
   flag = 2 if flag == 1 else 2

接着,修改绘制棋子的代码部分。


for x, y in chess_arr:
 pygame.draw.circle(screen,(205,205,205), [x*cell_size+space, y*cell_size+space], 16,16)

将绘制逻辑改为根据c值(即flag)判断颜色:若c为1则绘制黑色棋子,否则绘制白色棋子。


for x, y, c in chess_arr:
 chess_color = (30,30,30) if c == 1 else (225,225,225)
 pygame.draw.circle(screen, chess_color, [x*cell_size+space, y*cell_size+space], 16,16)

此时界面上的棋子已能正确区分黑白颜色。

胜负判定算法实现

4. 胜负判定逻辑

胜负判定的核心依然依赖于chess_arr数据,但直接遍历列表效率较低。因此,我们将chess_arr转换为一个15x15的二维数组进行计算,转换代码如下。


m = [[0]*15 for i in range(15)] # 先定义一个15*15的全0数组
for x, y, c in chess_arr:
 m[y][x] = 1 # 上面有棋则标1

将上述转换逻辑封装进check_win(chess_arr, flag)函数中,用于判断指定颜色的玩家是否获胜。基本流程是:获取最后一颗落子的坐标,分别检查其横向、纵向及两条对角线方向上是否连续存在5个或以上同色棋子,若满足条件则返回True。函数具体代码如下。


def get_one_dire_num(lx, ly, dx, dy, m):
 tx = lx
 ty = ly
 s = 0
 while True:
 tx += dx
 ty += dy
 if tx < 0 or tx >= cell_num or ty < 0 or ty >= cell_num or m[ty][tx] == 0: return s
 s+=1

def check_win(chess_arr, flag):
 m = [[0]*cell_num for i in range(cell_num)] # 先定义一个15*15的全0的数组,不能用[[0]*cell_num]*cell_num的方式去定义因为一位数组会被重复引用
 for x, y, c in chess_arr:
 if c == flag:
  m[y][x] = 1 # 上面有棋则标1
 lx = chess_arr[-1][0] # 最后一个子的x
 ly = chess_arr[-1][1] # 最后一个子的y
 dire_arr = [[(-1,0),(1,0)],[(0,-1),(0,1)],[(-1,-1),(1,1)],[(-1,1),(1,-1)]] # 4个方向数组,往左+往右、往上+往下、往左上+往右下、往左下+往右上,4组判断方向
 
 for dire1,dire2 in dire_arr:
 dx, dy = dire1
 num1 = get_one_dire_num(lx, ly, dx, dy, m)
 dx, dy = dire2
 num2 = get_one_dire_num(lx, ly, dx, dy, m)
 if num1 + num2 + 1 >= 5: return True

 return False

胜负判断函数编写完成后,我们需要定义一个全局变量来保存当前的游戏状态。

定义game_state = 1,其中1表示游戏正常进行中,2表示黑方获胜,3表示白方获胜。

在鼠标添加棋子的代码逻辑之后,调用上述判断胜利的函数。


if check_win(chess_arr, flag):
  game_state = 2 if flag == 1 else 3
  else:
  flag = 2 if flag == 1 else 1

最后,在pygame.display.update()刷新屏幕之前,增加对game_state的判断逻辑,若游戏结束则绘制并显示获胜文字。


if game_state != 1:
 myfont = pygame.font.Font(None,60)
 white = 210,210,0
 win_text = "%s win"%('black' if game_state == 2 else 'white')
 textImage = myfont.render(win_text, True, white)
 screen.blit(textImage, (260,320))

此外,在鼠标事件监听处也需进行修改,增加对game_state的判断,确保游戏结束后不再响应新的落子操作。


if game_state == 1 and event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起

至此,核心功能代码已全部完成,以下是运行效果展示。

最后附上完整的程序代码。需要注意的是,本版本暂未实现禁手判断和复杂的判输逻辑,后续如有时间将进一步完善。


# -*- coding=utf-8 -*-
import random
import pygame
from pygame.locals import MOUSEBUTTONUP
pygame.init()

space = 60 # 四周留下的边距
cell_size = 40 # 每个格子大小
cell_num = 15
grid_size = cell_size * (cell_num - 1) + space * 2 # 棋盘的大小
screencaption = pygame.display.set_caption('FIR')
screen = pygame.display.set_mode((grid_size,grid_size)) #设置窗口长宽

chess_arr = []
flag = 1 # 1黑 2白
game_state = 1 # 游戏状态1.表示正常进行 2.表示黑胜 3.表示白胜

def get_one_dire_num(lx, ly, dx, dy, m):
 tx = lx
 ty = ly
 s = 0
 while True:
 tx += dx
 ty += dy
 if tx < 0 or tx >= cell_num or ty < 0 or ty >= cell_num or m[ty][tx] == 0: return s
 s+=1

def check_win(chess_arr, flag):
 m = [[0]*cell_num for i in range(cell_num)] # 先定义一个15*15的全0的数组,不能用[[0]*cell_num]*cell_num的方式去定义因为一位数组会被重复引用
 for x, y, c in chess_arr:
 if c == flag:
  m[y][x] = 1 # 上面有棋则标1
 lx = chess_arr[-1][0] # 最后一个子的x
 ly = chess_arr[-1][1] # 最后一个子的y
 dire_arr = [[(-1,0),(1,0)],[(0,-1),(0,1)],[(-1,-1),(1,1)],[(-1,1),(1,-1)]] # 4个方向数组,往左+往右、往上+往下、往左上+往右下、往左下+往右上,4组判断方向
 
 for dire1,dire2 in dire_arr:
 dx, dy = dire1
 num1 = get_one_dire_num(lx, ly, dx, dy, m)
 dx, dy = dire2
 num2 = get_one_dire_num(lx, ly, dx, dy, m)
 if num1 + num2 + 1 >= 5: return True

 return False

while True:
 for event in pygame.event.get():
  if event.type == pygame.QUIT:
  pygame.quit()
  exit()
 
  if game_state == 1 and event.type == pygame.MOUSEBUTTONUP: # 鼠标弹起
  x, y = pygame.mouse.get_pos() # 获取鼠标位置
  xi = int(round((x - space)*1.0/cell_size)) # 获取到x方向上取整的序号
  yi = int(round((y - space)*1.0/cell_size)) # 获取到y方向上取整的序号
  if xi>=0 and xi<cell_num and yi>=0 and yi<cell_num and (xi,yi,1) not in chess_arr and (xi,yi,2) not in chess_arr:
   chess_arr.append((xi,yi,flag))
   if check_win(chess_arr, flag):
   game_state = 2 if flag == 1 else 3
   else:
   flag = 2 if flag == 1 else 1

 screen.fill((0,0,150)) # 将界面设置为蓝色

 for x in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(x+space,0+space),(x+space,cell_size*(cell_num-1)+space),1)
 for y in range(0,cell_size*cell_num,cell_size):
 pygame.draw.line(screen,(200,200,200),(0+space,y+space),(cell_size*(cell_num-1)+space,y+space),1)

 for x, y, c in chess_arr:
 chess_color = (30,30,30) if c == 1 else (225,225,225)
 pygame.draw.circle(screen, chess_color, [x*cell_size+space, y*cell_size+space], 16,16)

 if game_state != 1:
 myfont = pygame.font.Font(None,60)
 white = 210,210,0
 win_text = "%s win"%('black' if game_state == 2 else 'white')
 textImage = myfont.render(win_text, True, white)
 screen.blit(textImage, (260,320))
 
 pygame.display.update() # 必须调用update才能看到绘图显示

以上就是本文的全部内容,希望对大家的学习有所帮助,也欢迎大家继续支持本站。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。