使用 Python turtle 库实现数字华容道游戏

一个经典的滑块拼图游戏的完整实现指南

游戏简介

数字华容道是一款经典的滑块拼图游戏,玩家需要通过移动数字方块,将它们按顺序排列。本教程将详细介绍如何使用 Python 的 turtle 图形库实现这个游戏。

游戏预览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

目标状态:将数字按顺序排列,空白块位于右下角

注意: turtle 库是 Python 的标准库,非常适合初学者学习图形编程和游戏开发的基本概念。

turtle 库基础

turtle 库是 Python 中一个简单易用的图形绘制库,特别适合初学者学习编程和实现简单的图形界面游戏。下面我们将介绍一些基本的 turtle 功能:

from turtle import *

# 设置画布大小和位置
setup(400, 400, 300, 300)

# 隐藏海龟图标
hideturtle()

# 移动海龟到指定坐标
goto(100, 100)

# 绘制线条
pendown()  # 放下画笔,开始绘制
forward(100)  # 向前移动100个单位
left(90)  # 左转90度
forward(100)

# 抬起画笔,停止绘制
penup()

# 填充颜色
color('black', 'red')  # 设置边框颜色为黑色,填充颜色为红色
begin_fill()  # 开始填充
# 绘制一个正方形
for _ in range(4):
    forward(100)
    left(90)
end_fill()  # 结束填充

# 更新屏幕显示
update()

# 保持窗口打开
done()

游戏实现

1. 数据结构设计

我们使用字典来存储每个格子的状态,以及定义合法的移动方向:

from freegames import vector

tiles = {}  # 存储格子坐标和对应数字
neighbors = [
    vector(100, 0),   # 右
    vector(-100, 0),  # 左
    vector(0, 100),   # 上
    vector(0, -100),  # 下
]  # 定义相邻格子的向量

2. 初始化游戏板

创建初始游戏板并随机打乱数字:

def load():
    """加载并打乱数字方块"""
    count = 1  # 数字从1开始
    
    # 生成4×4的网格,每个格子间隔100像素
    for y in range(-200, 200, 100):
        for x in range(-200, 200, 100):
            mark = vector(x, y)  # 当前格子坐标
            tiles[mark] = count   # 存储数字
            count += 1
    
    # 将最后一个格子设为空白
    tiles[mark] = None
    
    # 随机移动1000次,打乱数字顺序
    for _ in range(1000):
        neighbor = choice(neighbors)  # 随机选择一个方向
        spot = mark + neighbor        # 计算相邻位置
        
        # 如果相邻位置有效,则交换数字
        if spot in tiles:
            number = tiles[spot]
            tiles[spot] = None
            tiles[mark] = number
            mark = spot
提示: 随机移动1000次确保游戏板被充分打乱,同时保持游戏可解。

3. 绘制游戏界面

使用 turtle 绘制每个数字方块:

def square(mark, number):
    """绘制带数字的白色方块"""
    penup()          # 抬起画笔,移动时不绘制
    goto(mark.x, mark.y)  # 移动到指定坐标
    pendown()        # 放下画笔,开始绘制
    
    # 设置边框和填充颜色
    color('black', 'white')
    begin_fill()     # 开始填充
    
    # 绘制正方形
    for _ in range(4):
        forward(99)  # 边长99像素,留出边框空间
        left(90)
    
    end_fill()       # 结束填充
    
    # 如果不是空白格子,绘制数字
    if number is None:
        return
    
    # 调整数字位置使其居中
    if number < 10:
        forward(20)  # 个位数向右偏移一些
    
    # 绘制数字,设置字体和大小
    write(number, font=('Arial', 60, 'normal'))

4. 处理用户交互

响应玩家的点击并移动方块:

def tap(x, y):
    """处理鼠标点击,交换方块和空白位置"""
    # 将点击坐标对齐到最近的100×100网格
    x = floor(x, 100)
    y = floor(y, 100)
    mark = vector(x, y)
    
    # 检查点击位置的相邻格子
    for neighbor in neighbors:
        spot = mark + neighbor  # 相邻位置坐标
        
        # 如果相邻位置是空白格子,则交换它们
        if spot in tiles and tiles[spot] is None:
            number = tiles[mark]
            tiles[spot] = number
            square(spot, number)  # 绘制新位置的方块
            tiles[mark] = None
            square(mark, None)    # 绘制原位置的空白
            break

5. 主游戏循环

设置游戏窗口并启动主循环:

def draw():
    """绘制所有方块"""
    clear()  # 清除画布
    for mark in tiles:
        square(mark, tiles[mark])  # 绘制每个方块
    update()  # 更新屏幕显示

# 设置游戏窗口
setup(420, 420, 370, 0)  # 窗口大小和位置
hideturtle()  # 隐藏海龟图标
tracer(False)  # 关闭自动刷新,手动控制屏幕更新
load()  # 初始化游戏板
draw()  # 绘制游戏界面
onscreenclick(tap)  # 绑定鼠标点击事件
done()  # 保持窗口打开

游戏规则

  1. 初始状态:游戏开始时,数字方块以随机顺序排列在4×4网格中,右下角为空白位置
  2. 移动规则:只能将与空白位置相邻的数字方块移入空白位置(上下左右方向)
  3. 胜利条件:将所有数字按从左到右、从上到下递增的顺序排列

目标排列示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

完整代码

from random import *
from turtle import *

from freegames import floor, vector

tiles = {}
neighbors = [
    vector(100, 0),
    vector(-100, 0),
    vector(0, 100),
    vector(0, -100),
]


def load():
    """Load tiles and scramble."""
    count = 1

    for y in range(-200, 200, 100):
        for x in range(-200, 200, 100):
            mark = vector(x, y)
            tiles[mark] = count
            count += 1

    tiles[mark] = None

    for count in range(1000):
        neighbor = choice(neighbors)
        spot = mark + neighbor

        if spot in tiles:
            number = tiles[spot]
            tiles[spot] = None
            tiles[mark] = number
            mark = spot


def square(mark, number):
    """Draw white square with black outline and number."""
    up()
    goto(mark.x, mark.y)
    down()

    color('black', 'white')
    begin_fill()
    for count in range(4):
        forward(99)
        left(90)
    end_fill()

    if number is None:
        return
    elif number < 10:
        forward(20)

    write(number, font=('Arial', 60, 'normal'))


def tap(x, y):
    """Swap tile and empty square."""
    x = floor(x, 100)
    y = floor(y, 100)
    mark = vector(x, y)

    for neighbor in neighbors:
        spot = mark + neighbor

        if spot in tiles and tiles[spot] is None:
            number = tiles[mark]
            tiles[spot] = number
            square(spot, number)
            tiles[mark] = None
            square(mark, None)


def draw():
    """Draw all tiles."""
    for mark in tiles:
        square(mark, tiles[mark])
    update()


setup(420, 420, 370, 0)
hideturtle()
tracer(False)
load()
draw()
onscreenclick(tap)
done()

扩展建议

学习建议: 理解基础实现后,尝试逐步添加上述扩展功能,这是提升编程能力的绝佳练习。