介绍
在这个项目中,我们将使用 Python 和 Pygame 库创建一个数独游戏。该游戏将生成指定难度级别的数独网格,并让玩家通过在空白单元格中填入数字来解决谜题。游戏将提供诸如选择难度、突出显示所选单元格以及检查网格是否完成等功能。
👀 预览

🎯 任务
在这个项目中,你将学习:
- 如何导入所需的库
- 如何初始化 PyGame
- 如何定义颜色
- 如何设置游戏窗口的尺寸和标题
- 如何创建游戏窗口
- 如何加载字体
- 如何生成数独网格
- 如何使用回溯算法解决数独网格
- 如何根据难度从网格中移除数字
- 如何在游戏窗口上绘制数独网格
- 如何检查网格是否已完全填满
- 如何获取鼠标位置下的单元格坐标
- 如何选择难度级别
- 如何实现主游戏循环
🏆 成果
完成这个项目后,你将能够:
- 在 Python 中使用 Pygame 库进行游戏开发
- 生成指定难度级别的数独网格
- 使用回溯算法解决数独网格
- 在 Pygame 中处理鼠标和键盘事件
- 在游戏窗口上绘制形状和文本
- 在 Pygame 中实现主游戏循环
创建项目文件
首先,创建一个名为sudoku_game.py的文件,并在你喜欢的文本编辑器或集成开发环境(IDE)中打开它。
cd ~/project
touch sudoku_game.py
导入所需的库
在文件开头导入所需的库。对于这个游戏,我们需要pygame和random库。
import pygame
import random
使用pip命令安装pygame库。
sudo pip install pygame
初始化 PyGame
初始化 PyGame 库以设置游戏窗口。
pygame.init()
定义颜色
定义游戏中要使用的颜色。我们将使用 RGB 格式来表示颜色。
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (200, 200, 200)
BLUE = (0, 0, 255)
LIGHT_BLUE = (100, 100, 255)
设置游戏窗口尺寸和标题
设置游戏窗口的尺寸,并为游戏设置一个标题。
WINDOW_SIZE = (550, 550)
CELL_SIZE = WINDOW_SIZE[0] // 9
pygame.display.set_caption("Sudoku Game")
创建游戏窗口
使用指定的尺寸创建游戏窗口。
screen = pygame.display.set_mode(WINDOW_SIZE)
加载字体
我们需要加载字体以便在游戏屏幕上显示数字。加载两种字体,一种用于较大的数字,一种用于较小的数字。
font_large = pygame.font.SysFont("calibri", 50)
font_small = pygame.font.SysFont("calibri", 30)
生成数独网格
创建一个函数 generate_sudoku(difficulty),该函数生成指定难度级别的新数独网格。函数应返回生成的网格。
def generate_sudoku(difficulty):
## 函数代码在此处
pass
填充对角子网格
在 generate_sudoku 函数内部,用随机数填充网格的对角子网格。这可确保每个子网格都包含 1 到 9 的数字且无重复。
## 填充对角子网格
for i in range(0, 9, 3):
nums = random.sample(range(1, 10), 3)
for j in range(3):
grid[i + j][i + j] = nums[j]
求解数独网格
创建一个函数 solve_sudoku(grid),该函数使用回溯算法解决数独网格。如果网格可解,函数应返回 True,否则返回 False。
def solve_sudoku(grid):
## 函数代码在此处
pass
查找空白单元格
在 solve_sudoku 函数内部,创建一个辅助函数 find_empty_cell(grid),该函数返回网格中下一个空白单元格的坐标。如果没有空白单元格,则返回 None。
def find_empty_cell(grid):
for row in range(9):
for col in range(9):
if grid[row][col] == 0:
return (row, col)
return None
检查有效移动
在 solve_sudoku 函数内部,创建一个辅助函数 is_valid_move(grid, row, col, num),该函数检查在一个单元格中放置一个数字是否是一个有效的移动。如果移动有效,该函数应返回 True,否则返回 False。
def is_valid_move(grid, row, col, num):
## 函数代码在此处
pass
求解数独网格(续)
在 solve_sudoku 函数内部,使用辅助函数 find_empty_cell 和 is_valid_move 来实现回溯算法。如果找到解决方案,返回 True。如果当前数字导致无效的解决方案,则通过将当前单元格设置为 0 来回溯。
## 尝试用 1 到 9 的数字填充空白单元格
for num in range(1, 10):
if is_valid_move(grid, row, col, num):
grid[row][col] = num
if solve_sudoku(grid):
return True
## 如果当前数字导致无效的解决方案,回溯
grid[row][col] = 0
return False
根据难度移除数字
在 generate_sudoku 函数内部,根据指定的难度级别从网格中移除数字。难度级别为:1(简单)、2(中等)、3(困难)。要移除的数字数量计算为 num_to_remove = 45 + 10 * difficulty。
## 根据难度级别移除数字
num_to_remove = 45 + 10 * difficulty
for _ in range(num_to_remove):
row = random.randint(0, 8)
col = random.randint(0, 8)
grid[row][col] = 0
return grid
绘制网格
创建一个函数 draw_grid(grid, selected_cell),该函数在游戏窗口上绘制数独网格。此函数应负责绘制单元格、数字以及突出显示所选单元格。
def draw_grid(grid, selected_cell):
## 函数代码在此处
pass
填充单元格并绘制数字
在 draw_grid 函数内部,遍历网格,并使用 Pygame 函数 pygame.draw.rect 和 screen.blit 来绘制单元格和数字。
## 绘制单元格
for row in range(9):
for col in range(9):
cell_rect = pygame.Rect(
col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE
)
pygame.draw.rect(screen, GRAY, cell_rect)
## 绘制数字
if grid[row][col]!= 0:
number = font_small.render(str(grid[row][col]), True, BLACK)
text_rect = number.get_rect(
center=(
col * CELL_SIZE + CELL_SIZE // 2,
row * CELL_SIZE + CELL_SIZE // 2,
)
)
screen.blit(number, text_rect)
突出显示所选单元格
在 draw_grid 函数内部,如果所选单元格不为 None,则使用 pygame.draw.rect 函数突出显示它。
## 突出显示所选单元格
if (row, col) == selected_cell:
pygame.draw.rect(screen, LIGHT_BLUE, cell_rect, 3)
绘制网格线
在 draw_grid 函数内部,使用 pygame.draw.line 函数绘制线条以创建网格。
## 绘制线条
for i in range(10):
if i % 3 == 0:
thickness = 4
else:
thickness = 1
pygame.draw.line(
screen,
BLACK,
(0, i * CELL_SIZE),
(WINDOW_SIZE[0], i * CELL_SIZE),
thickness,
)
pygame.draw.line(
screen,
BLACK,
(i * CELL_SIZE, 0),
(i * CELL_SIZE, WINDOW_SIZE[1]),
thickness,
)
更新显示
在 draw_grid 函数内部,使用 pygame.display.update 函数更新显示。
pygame.display.update()
检查网格是否已满
创建一个函数 is_grid_full(grid),用于检查数独网格是否已完全填满。这可以通过遍历网格并检查是否有任何单元格的值为 0 来实现。
def is_grid_full(grid):
## 函数代码写在这里
pass
获取鼠标下方的单元格
创建一个函数 get_cell_under_mouse(pos),该函数返回鼠标位置下方的单元格坐标。这可以通过将鼠标位置除以单元格大小来计算。
def get_cell_under_mouse(pos):
## 函数代码写在这里
pass
选择难度
创建一个函数 select_difficulty(),该函数允许玩家在开始游戏前选择难度级别。此函数应显示难度选项并返回所选的难度级别。
def select_difficulty():
## 函数代码写在这里
pass
实现难度选择
在 select_difficulty 函数内部,使用 Pygame 函数 pygame.mouse.get_pos 和 pygame.event.get 来实现难度选择逻辑。
## 显示难度选择文本
title_text = font_large.render("Select Difficulty", True, BLACK)
screen.blit(title_text, (110, 200))
## 显示难度选项
option_y = 300
for difficulty, label in difficulties.items():
option_text = font_small.render(f"{difficulty}. {label}", True, BLACK)
text_rect = option_text.get_rect(center=(WINDOW_SIZE[0] // 2, option_y))
screen.blit(option_text, text_rect)
option_y += 70
pygame.display.update()
## 等待难度选择
difficulty_selected = False
difficulty = 1
while not difficulty_selected:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: ## 鼠标左键
pos = pygame.mouse.get_pos()
if 180 <= pos[0] <= 380:
if 300 <= pos[1] <= 350:
difficulty = 1
difficulty_selected = True
elif 370 <= pos[1] <= 420:
difficulty = 2
difficulty_selected = True
elif 440 <= pos[1] <= 490:
difficulty = 3
difficulty_selected = True
return difficulty
主游戏循环
通过定义一个函数 main() 来创建主游戏循环。
def main():
## 函数代码写在这里
pass
选择难度并生成网格
在 main 函数内部,通过调用 select_difficulty() 来选择难度级别,如果难度为 None 则返回。然后,通过调用 generate_sudoku(difficulty) 使用所选难度级别生成一个数独网格。
## 选择难度级别
difficulty = select_difficulty()
if difficulty is None:
return
## 生成一个新的数独网格
grid = generate_sudoku(difficulty)
游戏循环变量
在 main 函数内部,创建游戏循环变量 selected_cell 和 running。将 selected_cell 设置为 None,将 running 设置为 True。
selected_cell = None
running = True
启用按键重复
在 main 函数内部,使用 pygame.key.set_repeat 函数启用按键重复。我们将延迟和间隔设置为 100 毫秒,以便玩家更轻松地填写单元格。
## 启用按键重复
pygame.key.set_repeat(100, 100)
处理事件
在游戏循环中,使用 pygame.event.get 函数处理事件。检查退出事件、鼠标按钮事件和键盘事件。
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
## 处理鼠标按钮事件
pass
elif event.type == pygame.KEYDOWN:
## 处理键盘事件
pass
处理鼠标按钮事件
在鼠标按钮事件的事件处理中,检查是否点击了鼠标左键或右键。
if event.button == 1: ## 鼠标左键
## 处理鼠标左键点击
pass
elif event.button == 3: ## 鼠标右键
## 处理鼠标右键点击
pass
处理鼠标左键点击
在鼠标左键点击处理中,通过调用 get_cell_under_mouse(pos) 获取鼠标位置下方的单元格坐标。
## 获取鼠标位置下方的单元格
pos = pygame.mouse.get_pos()
row, col = get_cell_under_mouse(pos)
处理鼠标左键点击(续)
在鼠标左键点击处理中,如果单元格为空(通过检查 grid[row][col] == 0),则选择该单元格。
## 如果单元格为空,则选择它
if grid[row][col] == 0:
selected_cell = (row, col)
处理鼠标右键点击
在鼠标右键点击处理中,如果所选单元格不为 None 且为空(通过检查 grid[selected_cell[0]][selected_cell[1]] == 0),则清除该单元格。
if selected_cell:
## 如果所选单元格为空,则清除它
if grid[selected_cell[0]][selected_cell[1]] == 0:
grid[selected_cell[0]][selected_cell[1]] = 0
处理按键事件
在按键事件的事件处理中,检查按下了哪个数字键,并相应地设置所选单元格的值。
if selected_cell:
## 获取按下的键
if event.key == pygame.K_1:
grid[selected_cell[0]][selected_cell[1]] = 1
elif event.key == pygame.K_2:
grid[selected_cell[0]][selected_cell[1]] = 2
elif event.key == pygame.K_3:
grid[selected_cell[0]][selected_cell[1]] = 3
elif event.key == pygame.K_4:
grid[selected_cell[0]][selected_cell[1]] = 4
elif event.key == pygame.K_5:
grid[selected_cell[0]][selected_cell[1]] = 5
elif event.key == pygame.K_6:
grid[selected_cell[0]][selected_cell[1]] = 6
elif event.key == pygame.K_7:
grid[selected_cell[0]][selected_cell[1]] = 7
elif event.key == pygame.K_8:
grid[selected_cell[0]][selected_cell[1]] = 8
elif event.key == pygame.K_9:
grid[selected_cell[0]][selected_cell[1]] = 9
绘制网格并检查完成情况
在游戏循环中,通过调用 draw_grid(grid, selected_cell) 来绘制网格。然后,通过调用 is_grid_full(grid) 检查网格是否已填满。
## 绘制网格
draw_grid(grid, selected_cell)
## 检查网格是否已填满
if is_grid_full(grid):
print("Congratulations! You solved the Sudoku puzzle.")
更新显示并退出
在游戏循环之后,使用 pygame.display.update 函数更新显示,并使用 pygame.quit() 退出游戏。
## 更新显示
pygame.display.update()
## 退出游戏
pygame.quit()
运行游戏
最后,添加一个条件来检查当前文件是否为程序的主入口点。如果是,则通过调用 main() 函数来运行游戏。
if __name__ == "__main__":
main()
使用 python 命令运行游戏。
python sudoku_game.py

总结
恭喜你!你已经成功地使用 Python 和 Pygame 库创建了一个数独游戏。这个游戏允许玩家选择难度级别,用数字填充空白单元格,并检查网格是否完成。享受玩数独游戏和解谜的乐趣吧!



