Python Pygame 实现五子棋游戏完整教程
本文详细介绍如何使用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才能看到绘图显示
以上就是本文的全部内容,希望对大家的学习有所帮助,也欢迎大家继续支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













