本教程将带领大家使用 Python 的 Pygame 库实现一款九宫棋(井字棋)游戏,支持双人对战,自动判断胜负和平局,最终呈现一个可视化的交互界面。




打开命令提示符(Windows)或终端(Mac/Linux),输入以下命令,若显示版本号则安装成功:
python --version  # Windows
# 或
python3 --version  # Mac/Linux
Python Pygame 是一款专门为开发和设计 2D 电子游戏而生的软件包,封装了图形绘制、声音播放、事件处理等功能,简化游戏开发流程。它支 Windows、Linux、Mac OS 等操作系统,具有良好的跨平台性。
Pygame 在 SDL的基础上开发而成,它提供了诸多操作模块,比如图像模块(image)、声音模块(mixer)、输入/输出(鼠标、键盘、显示屏)模块等。相比于开发 3D 游戏而言,Pygame 更擅长开发 2D 游戏,比如于飞机大战、贪吃蛇、扫雷等游戏。
在命令提示符 / 终端中输入:
pip install pygame  # Windows
# 或
pip3 install pygame  # Mac/Linux
运行以下代码,若弹出一个黑色窗口则安装成功:
import pygame
pygame.init()
pygame.display.set_mode((400, 300))
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
注意:如果Pygame显示红色波浪线,说明系统没有安装Pygame或者当前项目没有检测到Pygame的存在。


Pygame 包含多个模块(如显示、声音、输入等),
pygame.init()用于初始化所有模块,确保后续功能正常运行。
import pygame
import sys  # 用于程序退出
# 初始化pygame
pygame.init()
窗口是游戏的 “画布”,所有图形(棋盘、棋子、提示)都将绘制在窗口上。需指定窗口尺寸(宽 × 高)。
# 创建游戏窗口,设置窗口大小为1200x600(宽×高)
window = pygame.display.set_mode((1200, 600))
# 设置窗口标题为"九宫棋"
pygame.display.set_caption('九宫棋')
set_mode()参数
(1200, 600)是一个元组,表示窗口宽度 1200 像素、高度 600 像素。像素是屏幕显示的基本单位,数值越大窗口越大。
bg.png):游戏背景玩家 1 棋子(
cha.png):如 “X”玩家 2 棋子(
quan.png):如 “O”胜利提示图(
pk1.png、
pk2.png):分别对应玩家 1、2 胜利平局提示图(
pk3.png)
在代码文件同级目录下创建
img文件夹,将所有素材放入该文件夹(路径错误会导致图片加载失败)。
使用
pygame.image.load(路径)加载图片,返回 “表面(Surface)” 对象(可理解为画布上的 “图层”)。
# 加载游戏所需图片资源
bg_image = pygame.image.load('img/bg.png')  # 背景图
player1_shape = pygame.image.load('img/quan.png')  # 玩家1棋子
player2_shape = pygame.image.load('img/cha.png')  # 玩家2棋子
win1_image = pygame.image.load('img/pk1.gif')  # 玩家1胜利图
win2_image = pygame.image.load('img/pk2.gif')  # 玩家2胜利图
draw_image = pygame.image.load('img/pk3.gif')  # 平局图
背景是游戏的基础图层,需先绘制在窗口上,后续元素(棋子、提示)将叠加在背景上。
# 初始化窗口背景(将背景图绘制到窗口左上角(0,0)位置)
window.blit(bg_image, (0, 0))
blit()方法blit(图层, 位置)用于将一个图层绘制到另一个图层上。(0,0)表示窗口左上角坐标(Pygame 中坐标原点在左上角,向右为 x 轴正方向,向下为 y 轴正方向)。
游戏需要持续运行(监听输入、更新画面),因此需要一个 “无限循环”—— 主循环。主循环包含 3 个核心操作:
事件监听(如鼠标点击、窗口关闭)游戏状态更新(如落子、判断胜负)画面刷新(显示最新状态)
# 游戏主循环
while True:
    # 1. 事件监听(后续补充)
    for event in pygame.event.get():
        pass  # 临时占位,后续添加事件处理逻辑
    
    # 2. 游戏状态更新(后续补充:胜负判断等)
    
    # 3. 控制帧率与刷新画面
    pygame.time.Clock().tick(60)  # 限制帧率为60FPS(每秒刷新60次)
    pygame.display.flip()  # 刷新窗口,显示所有绘制的内容
pygame.time.Clock().tick(60)确保循环每秒最多执行 60 次,避免因电脑性能差异导致游戏速度不稳定。
事件(Event)是 Pygame 的重要模块之一,它是构建整个游戏程序的核心,是用户与游戏的交互。比如鼠标点击、键盘敲击、游戏窗口移动、调整窗口大小、触发特定的情节、退出游戏等等,这些都可以看做是“事件”,Pygame 通过
pygame.event.get()获取所有事件,再逐个处理,这些操作随时产生,并且操作量可大可小,那么 Pygame 是如何处理这些事件的呢?
Pygame 定义了一个专门用来处理事件的结构,即事件队列,该结构遵循遵循队列“先到先处理”的基本原则,通过事件队列,我们可以有序的、逐一的处理用户的操作(触发事件)。
当用户点击窗口右上角的 “关闭” 按钮时,程序需退出,否则窗口会卡住。
# 事件监听
for event in pygame.event.get():
    # 处理窗口关闭事件
    if event.type == pygame.QUIT:
        pygame.quit()  # 退出Pygame(释放资源)
        sys.exit()     # 终止程序
if语句
if 条件:用于判断条件是否成立,成立则执行条件成立的代码,不成立则执行条件不成立的代码。例如:
age = 18
if age >= 18:
    print("成年")  # 条件成立,执行此句
else:
    print("未成年") # 条件不成立,执行此句
玩家通过点击棋盘格子落子,需监听
MOUSEBUTTONDOWN事件(鼠标按下时触发),并获取点击位置。
# 处理鼠标点击事件(落子逻辑)
if event.type == pygame.MOUSEBUTTONDOWN:
    # 获取鼠标点击的坐标(x,y),如(500, 300)
    click_x, click_y = event.pos
    print(f"点击位置:({click_x}, {click_y})")  # 调试用,可删除
click_x, click_y = event.pos中,
event.pos是一个包含 x、y 坐标的元组(如
(500, 300)),通过 “元组解包” 直接将两个值分别赋值给变量。
玩家点击棋盘后,需判断点击位置属于哪个格子(3×3 网格中的哪一行哪一列),若为空位则落子(更新棋盘数据并绘制棋子),且玩家交替落子(玩家 1→玩家 2→玩家 1…)。
棋盘的 3×3 格子对应窗口中的坐标范围示例数据如下

if 330 <= click_x <= 508 and 30 <= click_y <= 208:
	print("点击了1号区域")  
if 508 <= click_x <= 686 and 30 <= click_y <= 208:
	print("点击了2号区域")  
if 330 <= click_x <= 508 and 32 <= click_y <= 210:
	window.blit(quan,(330+25,30+25))  
if 508 <= click_x <= 686 and 30 <= click_y <= 208:
	window.blit(quan,(508+25,30+25)) 
棋盘是 3×3 的网格,需要用数据记录每个格子的状态:空位、玩家 1 的棋子、玩家 2 的棋子。
使用二维列表(列表的列表)表示棋盘:
board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]0:空位;1:玩家 1 的棋子;2:玩家 2 的棋子
board[i][j]表示第 i 行第 j 列(i 和 j 均为 0、1、2)
# 九宫棋棋盘数据,3x3矩阵
board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
二维列表是 “列表中包含列表” 的结构,可模拟表格、矩阵等二维数据。例如:
# 3行2列的二维列表
matrix = [[1, 2], [3, 4], [5, 6]]
print(matrix[0][1])  # 输出第0行第1列的元素:2
用
move_count(落子次数)控制玩家:
初始化
move_count:
move_count = 0  # 初始为0,未落子
# 第一行第一列(0,0)位置落子判断
# 条件:点击在坐标范围内,且当前格子为空(board[0][0] == 0)
if 330 <= click_x <= 508 and 32 <= click_y <= 210 and board[0][0] == 0:
    move_count += 1  # 落子次数+1
    if move_count % 2 == 1:  # 奇数:玩家1
        board[0][0] = 1  # 更新棋盘数据
        # 绘制玩家1棋子(位置微调25像素,使棋子居中)
        window.blit(player1_shape, (330 + 25, 32 + 25))
    else:  # 偶数:玩家2
        board[0][0] = 2  # 更新棋盘数据
        window.blit(player2_shape, (330 + 25, 32 + 25))
    print(board, move_count)  # 调试:打印棋盘状态和落子次数
and:逻辑与,所有条件都成立才为真。例如
a <= x <= b等价于
x >= a and x <= b。
%:取模运算符,
move_count % 2 == 1判断是否为奇数(除以 2 余数为 1)。
按上述方法补充其余 8 个格子的判断(代码见原项目,核心逻辑与 (0,0) 一致,仅坐标和
board[i][j]不同)。
满足以下任一条件即为胜利:
同一行 3 个格子相同(非 0)同一列 3 个格子相同(非 0)主对角线(左上→右下)3 个格子相同(非 0)副对角线(右上→左下)3 个格子相同(非 0)
棋盘下满(落子次数 = 9)且无玩家胜利。
# 胜负和平局判断
for i in range(3):
    # 横向获胜(同一行)
    if board[i][0] == board[i][1] == board[i][2] != 0:
        print(f"{i}行赢了!")
        if board[i][0] == 1:
            window.blit(win1_image, (0, 0))  # 玩家1胜利图
        else:
            window.blit(win2_image, (0, 0))  # 玩家2胜利图
    # 竖向获胜(同一列)
    elif board[0][i] == board[1][i] == board[2][i] != 0:
        print(f"{i}列赢了!")
        if board[0][i] == 1:
            window.blit(win1_image, (0, 0))
        else:
            window.blit(win2_image, (0, 0))
# 主对角线获胜(左上→右下)
if board[0][0] == board[1][1] == board[2][2] != 0:
    print("主对角线赢了!")
    if board[0][0] == 1:
        window.blit(win1_image, (0, 0))
    else:
        window.blit(win2_image, (0, 0))
# 副对角线获胜(右上→左下)
if board[2][0] == board[1][1] == board[0][2] != 0:
    print("副对角线赢了!")
    if board[1][1] == 1:
        window.blit(win1_image, (0, 0))
    else:
        window.blit(win2_image, (0, 0))
# 平局判断
if move_count == 9:
    window.blit(draw_image, (0, 0))  # 平局图
for循环
for i in range(3)会让 i 依次取 0、1、2,用于遍历 3 行或 3 列。例如:
for i in range(3):
    print(i)  # 输出:0、1、2
import pygame
import sys
# 初始化pygame
pygame.init()
# 创建游戏窗口,设置窗口大小为1200x600
window = pygame.display.set_mode((1200, 600))
# 设置窗口标题为"九宫棋"
pygame.display.set_caption('九宫棋')
# 加载游戏所需图片资源
# 背景图
bg_image = pygame.image.load('img/bg.png')
# 玩家1的棋子图片(例如:X)
player1_shape = pygame.image.load('img/quan.png')
# 玩家2的棋子图片(例如:O)
player2_shape = pygame.image.load('img/cha.png')
# 玩家1胜利的提示图
win1_image = pygame.image.load('img/pk1.png')
# 玩家2胜利的提示图
win2_image = pygame.image.load('img/pk2.png')
# 平局的提示图
draw_image = pygame.image.load('img/pk3.png')
# 初始化窗口背景
window.blit(bg_image, (0, 0))
# 九宫棋棋盘数据,3x3矩阵,0表示空位,1表示玩家1的棋子,2表示玩家2的棋子
board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# 记录落子次数,用于判断当前落子玩家(奇数为玩家1,偶数为玩家2)
move_count = 0
# 游戏主循环
while True:
    # 事件监听
    for event in pygame.event.get():
        # 处理窗口关闭事件
        if event.type == pygame.QUIT:
            pygame.quit()  # 退出pygame
            sys.exit()     # 终止程序
        # 处理鼠标点击事件(落子逻辑)
        if event.type == pygame.MOUSEBUTTONDOWN:
            # 获取鼠标点击的坐标
            click_x, click_y = event.pos
            # 第一行第一列(0,0)位置落子判断
            # 坐标范围:x[330,508],y[32,210],且当前位置为空
            if 330 <= click_x <= 508 and 32 <= click_y <= 210 and board[0][0] == 0:
                move_count += 1  # 落子次数加1
                if move_count % 2 == 1:  # 奇数次数为玩家1落子
                    board[0][0] = 1
                    window.blit(player1_shape, (330 + 25, 32 + 25))  # 绘制玩家1棋子
                else:  # 偶数次数为玩家2落子
                    board[0][0] = 2
                    window.blit(player2_shape, (330 + 25, 32 + 25))  # 绘制玩家2棋子
                print(board, move_count)  # 调试信息:打印棋盘状态和落子次数
            # 第一行第二列(0,1)位置落子判断
            if 508 <= click_x <= 686 and 32 <= click_y <= 210 and board[0][1] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[0][1] = 1
                    window.blit(player1_shape, (508 + 25, 32 + 25))
                else:
                    board[0][1] = 2
                    window.blit(player2_shape, (508 + 25, 32 + 25))
                print(board, move_count)
            # 第一行第三列(0,2)位置落子判断
            if 686 <= click_x <= 870 and 32 <= click_y <= 210 and board[0][2] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[0][2] = 1
                    window.blit(player1_shape, (686 + 25, 32 + 25))
                else:
                    board[0][2] = 2
                    window.blit(player2_shape, (686 + 25, 32 + 25))
                print(board, move_count)
            # 第二行第一列(1,0)位置落子判断
            if 330 <= click_x <= 508 and 208 <= click_y <= 386 and board[1][0] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[1][0] = 1
                    window.blit(player1_shape, (330 + 25, 208 + 25))
                else:
                    board[1][0] = 2
                    window.blit(player2_shape, (330 + 25, 208 + 25))
                print(board, move_count)
            # 第二行第二列(1,1)位置落子判断
            if 508 <= click_x <= 686 and 208 <= click_y <= 386 and board[1][1] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[1][1] = 1
                    window.blit(player1_shape, (508 + 25, 208 + 25))
                else:
                    board[1][1] = 2
                    window.blit(player2_shape, (508 + 25, 208 + 25))
                print(board, move_count)
            # 第二行第三列(1,2)位置落子判断
            if 686 <= click_x <= 870 and 208 <= click_y <= 386 and board[1][2] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[1][2] = 1
                    window.blit(player1_shape, (686 + 25, 208 + 25))
                else:
                    board[1][2] = 2
                    window.blit(player2_shape, (686 + 25, 208 + 25))
                print(board, move_count)
            # 第三行第一列(2,0)位置落子判断
            if 330 <= click_x <= 508 and 386 <= click_y <= 567 and board[2][0] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[2][0] = 1
                    window.blit(player1_shape, (330 + 25, 386 + 25))
                else:
                    board[2][0] = 2
                    window.blit(player2_shape, (330 + 25, 386 + 25))
                print(board, move_count)
            # 第三行第二列(2,1)位置落子判断
            if 508 <= click_x <= 686 and 386 <= click_y <= 567 and board[2][1] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[2][1] = 1
                    window.blit(player1_shape, (508 + 25, 386 + 25))
                else:
                    board[2][1] = 2
                    window.blit(player2_shape, (508 + 25, 386 + 25))
                print(board, move_count)
            # 第三行第三列(2,2)位置落子判断
            if 686 <= click_x <= 870 and 386 <= click_y <= 567 and board[2][2] == 0:
                move_count += 1
                if move_count % 2 == 1:
                    board[2][2] = 1
                    window.blit(player1_shape, (686 + 25, 386 + 25))
                else:
                    board[2][2] = 2
                    window.blit(player2_shape, (686 + 25, 386 + 25))
                print(board, move_count)
    # 胜负和平局判断
    for i in range(3):
        # 横向获胜判断(同一行三个相同非0值)
        if board[i][0] == board[i][1] == board[i][2] != 0:
            print(f"{i}行赢了!")
            if board[i][0] == 1:  # 玩家1获胜
                window.blit(win1_image, (0, 0))
            else:  # 玩家2获胜
                window.blit(win2_image, (0, 0))
        # 竖向获胜判断(同一列三个相同非0值)
        elif board[0][i] == board[1][i] == board[2][i] != 0:
            print(f"{i}列赢了!")
            if board[0][i] == 1:  # 玩家1获胜
                window.blit(win1_image, (0, 0))
            else:  # 玩家2获胜
                window.blit(win2_image, (0, 0))
        # 主对角线获胜判断(从左上到右下三个相同非0值)
        elif board[0][0] == board[1][1] == board[2][2] != 0:
            print("对角线赢了!")
            if board[i][i] == 1:  # 玩家1获胜
                window.blit(win1_image, (0, 0))
            else:  # 玩家2获胜
                window.blit(win2_image, (0, 0))
        # 副对角线获胜判断(从右上到左下三个相同非0值)
        elif board[2][0] == board[1][1] == board[0][2] != 0:
            print("反对角线赢了!")
            if board[1][1] == 1:  # 玩家1获胜(修正原代码判断条件错误)
                window.blit(win1_image, (0, 0))
            else:  # 玩家2获胜
                window.blit(win2_image, (0, 0))
        # 平局判断:落子次数达到9次(棋盘下满)且无胜负
        elif move_count == 9:
            window.blit(draw_image, (0, 0))
    # 控制游戏帧率为60FPS
    pygame.time.Clock().tick(60)
    # 刷新窗口显示
    pygame.display.flip()
img文件夹与代码文件在同一目录,且素材齐全。运行代码:在 IDE 中点击运行,或在命令行输入
python 文件名.py。
img/文件名是否存在)。窗口卡住:确保主循环中包含
pygame.display.flip()和事件处理。
game_over变量控制)。增加悔棋功能:记录每步落子,允许撤销上一步。实现人机对战:用 AI 逻辑替代玩家 2 的落子(如随机落子、简单策略)。