✨ 项目亮点
-
✅ 三种难度可选(初级30空/中级45空/高级60空)
-
✅ 实时颜色提示(正确蓝色/错误红色)
-
✅ 内置"提示"功能(显示答案2秒)
-
✅ 成绩记录系统(分难度保存最快用时)
-
✅ 流畅界面 + 加载进度优化
-
✅ 完整游戏交互(键盘+鼠标)
📌 一、功能概览
| 功能 | 说明 |
|---|---|
| 难度切换 | 初级/中级/高级,对应不同空格数量 |
| 实时验证 | 输入后立即颜色反馈(蓝/红) |
| 提示系统 | 按T键或按钮显示当前格答案(持续2秒) |
| 成绩记录 | 自动保存各难度最佳用时,支持查看历史记录 |
| 重置游戏 | 可随时重置当前难度盘面 |
| 帮助说明 | 内置游戏规则与操作指南 |
🧩 二、核心技术实现
1. 游戏状态管理
python
game_states = {
"初级": {"matrix": ..., "blank_ij": ..., "start_time": ...},
"中级": {...},
"高级": {...}
}
-
每个难度独立存储状态,切换时不丢失进度
-
包含盘面、空白格坐标、开始时间等信息
2. 多线程加载优化
python
def initialize_game_in_background():
thread = threading.Thread(target=background_task)
thread.daemon = True
thread.start()
-
后台生成所有难度初始盘面,避免主界面卡顿
-
显示加载动画,提升用户体验
3. 实时视觉反馈
-
根据数独规则实时检查输入数字合法性
-
正确数字显示为蓝色,错误显示为红色
-
提示数字使用紫色高亮(2秒后消失)
4. 成绩持久化存储
python
best_times = {"初级": 120.5, "中级": 300.0, "高级": float('inf')}
-
使用文本文件保存各难度最佳成绩
-
首次完成时自动记录,支持破纪录提示
📁 三、代码结构
text
sudoku/
│ main.py # 主程序入口
│ bulid.py # 数独生成与验证核心
│ resource/
│ └── sudoku_best_times.txt # 成绩记录文件
🧠 核心函数说明
-
give_me_a_game(blank_size)-- 生成数独题目与答案 -
check(matrix, i, j, num)-- 验证数字是否合法 -
draw_background()-- 绘制游戏界面与网格 -
show_hint()-- 显示当前格提示答案 -
show_records()-- 弹出最佳成绩窗口 -
reset_game(difficulty)-- 重置指定难度游戏
🎯 四、运行说明
环境要求
bash
pip install pygame
启动游戏
bash
python main.py
操作方式
-
方向键/鼠标点击 -- 移动选择格子
-
数字键1-9 -- 填入数字
-
空格/DEL/Backspace -- 清除数字
-
重置游戏
-
请求提示
-
查看帮助
🔧 五、可扩展方向
-
主题切换 -- 增加深色/彩色主题选项
-
音效反馈 -- 添加正确/错误提示音
-
解题器 -- 集成自动求解功能
-
云同步 -- 将成绩上传至网络排行榜
📦 六、项目总结
该项目展示了如何用 Python + PyGame 构建一个完整的桌面游戏应用,涉及:
-
✅ 状态管理 -- 多难度独立存储
-
✅ UI交互 -- 按钮、提示、对话框等组件
-
✅ 文件存储 -- 简单数据持久化方案
-
✅ 性能优化 -- 多线程避免界面卡顿
-
✅ 代码结构 -- 模块化设计,便于扩展
适合作为 Python GUI编程、游戏开发入门 的实战案例。
完整代码如下:
SuDoKu.py
python
import sys
import pygame
from pygame.color import THECOLORS as COLORS # 使用预定义颜色
import random
from bulid import give_me_a_game, check # 数独生成和验证模块
import time
import os
import threading # 多线程用于后台加载
# 全局变量
# 难度级别配置:空白格子数量、是否启用提示、是否实时检查
DIFFICULTY_LEVELS = {
"初级": {"blank_size": 30, "hint": True, "real_time_check": True},
"中级": {"blank_size": 45, "hint": True, "real_time_check": True},
"高级": {"blank_size": 60, "hint": True, "real_time_check": True}
}
# 存储每个难度的最佳时间
best_times = {
"初级": float('inf'), # 初始值为无穷大
"中级": float('inf'),
"高级": float('inf')
}
# 各难度的游戏状态存储
game_states = {
"初级": {
"start_time": 0, # 添加独立计时器
"elapsed_time": 0,
"completed": False
},
"中级": {
"start_time": 0,
"elapsed_time": 0,
"completed": False
},
"高级": {
"start_time": 0,
"elapsed_time": 0,
"completed": False
}
}
# 隐藏控制台窗口(仅Windows有效)
if os.name == 'nt':
import ctypes
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
def resource_path(relative_path):
""" 获取资源绝对路径 """
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
# 最佳成绩文件路径
BEST_TIMES_FILE = resource_path("resource/sudoku_best_times.txt")
# 添加读取和保存最佳成绩的函数
def load_best_times():
"""从文件加载最佳成绩记录"""
try:
if os.path.exists(BEST_TIMES_FILE):
with open(BEST_TIMES_FILE, 'r', encoding='utf-8') as f:
for line in f:
difficulty, time_str = line.strip().split(':')
best_times[difficulty] = float(time_str)
except Exception as e:
print(f"加载最佳成绩失败: {e}")
pass # 如果文件损坏,忽略错误
def save_best_times():
"""保存最佳成绩到文件"""
try:
with open(BEST_TIMES_FILE, 'w', encoding='utf-8') as f:
for difficulty, time_val in best_times.items():
if time_val != float('inf'):
f.write(f"{difficulty}:{time_val}\n")
except Exception as e:
print(f"保存最佳成绩失败: {e}")
def draw_background():
# 背景色:浅灰色
screen.fill(COLORS['whitesmoke'])
# 绘制难度选择背景
pygame.draw.rect(screen, COLORS['lightgray'], (0, 0, 540, 40))
pygame.draw.line(screen, COLORS['darkgray'], (0, 40), (540, 40), 2)
# 绘制游戏区域背景:白色
pygame.draw.rect(screen, COLORS['white'], (0, 40, 540, 540))
# 绘制游戏区域网格
# 绘制粗线(3x3的大格子)
for i in range(0, 4):
# 横线
pygame.draw.line(screen, COLORS['black'], (0, 40 + i * 180), (540, 40 + i * 180), 3)
# 竖线
pygame.draw.line(screen, COLORS['black'], (i * 180, 40), (i * 180, 580), 3)
# 绘制细线(每个小格子)
for i in range(0, 10):
if i % 3 != 0:
# 横线
pygame.draw.line(screen, COLORS['lightgray'], (0, 40 + i * 60), (540, 40 + i * 60), 1)
# 竖线
pygame.draw.line(screen, COLORS['lightgray'], (i * 60, 40), (i * 60, 580), 1)
def draw_choose():
"""绘制选中格子的状态"""
# 绘制选中格子的背景(半透明绿色)
s = pygame.Surface((60, 60), pygame.SRCALPHA)
s.fill((0, 255, 0, 50))
screen.blit(s, (cur_j * 60, cur_i * 60 + 40))
def check_win(matrix_all, matrix):
"""检测是否完成"""
return matrix_all == matrix
def check_color(matrix, i, j):
"""绘制不同格子的颜色"""
_matrix = [[col for col in row] for row in matrix]
_matrix[i][j] = 0
if check(_matrix, i, j, matrix[i][j]):
return COLORS['blue']
return COLORS['red']
def draw_number():
"""绘制格子上的数字"""
game_state = game_states[current_difficulty]
if not game_state:
return
MATRIX = game_state["matrix"] # 整个数独
MATRIX_ANSWER = game_state["matrix_answer"] # 答案
BLANK_IJ = game_state["blank_ij"] # 空白格子坐标
for i in range(9):
for j in range(9):
# 如果是提示格子且当前在提示时间内,显示正确答案
if game_state.get("hint_cell") == (i, j) and pygame.time.get_ticks() < game_state.get("hint_end_time", 0):
value = MATRIX_ANSWER[i][j]
color = COLORS['purple'] # 提示的数字使用紫色
txt = font.render(str(value), True, color)
x, y = j * 60 + 30, i * 60 + 70
screen.blit(txt, (x - txt.get_width() // 2, y - txt.get_height() // 2))
continue
value = MATRIX[i][j]
if value == 0:
continue # 如果是0就不绘制
# 初始题目数字用黑色
if (i, j) not in BLANK_IJ:
color = COLORS['black']
else:
color = COLORS['blue']
# 如果开启了实时检查并且有提示
if DIFFICULTY_SETTINGS['real_time_check'] and DIFFICULTY_SETTINGS['hint']:
color = check_color(MATRIX, i, j)
txt = font.render(str(value), True, color)
x, y = j * 60 + 30, i * 60 + 70
screen.blit(txt, (x - txt.get_width() // 2, y - txt.get_height() // 2))
def draw_difficulty_buttons():
"""绘制等级按钮"""
for i, level in enumerate(["初级", "中级", "高级"]):
color = COLORS['lightgreen'] if current_difficulty == level else COLORS['lightblue']
pygame.draw.rect(screen, color, (i * 180, 0, 180, 40))
pygame.draw.rect(screen, COLORS['black'], (i * 180, 0, 180, 40), 1) # 边框
try:
# 使用支持中文的字体
txt = font_small.render(level, True, COLORS['black'])
except:
# 如果字体不支持中文,使用英文
level_en = ["Easy", "Medium", "Hard"][i]
txt = font_small.render(level_en, True, COLORS['black'])
x, y = i * 180 + 90, 20
screen.blit(txt, (x - txt.get_width() // 2, y - txt.get_height() // 2))
def draw_context():
"""绘制主界面下方的内容"""
game_state = game_states[current_difficulty]
if not game_state:
return
# 显示空白格子数量
txt = font_tiny.render(f'空白: {game_state["cur_blank_size"]}', True, COLORS['black'])
screen.blit(txt, (10, 610))
# 显示操作次数
txt = font_tiny.render(f'操作: {game_state["cur_change_size"]}', True, COLORS['black'])
screen.blit(txt, (150, 610))
# 重置按钮
pygame.draw.rect(screen, COLORS['lightblue'], (350, 585, 80, 30))
pygame.draw.rect(screen, COLORS['black'], (350, 585, 80, 30), 1) # 边框
try:
txt_reset = font_tiny.render('重置(R)', True, COLORS['black'])
except:
txt_reset = font_tiny.render('Reset', True, COLORS['black'])
screen.blit(txt_reset, (390 - txt_reset.get_width() // 2, 600 - txt_reset.get_height() // 2))
# 成绩按钮
pygame.draw.rect(screen, COLORS['lightgreen'], (440, 585, 80, 30))
pygame.draw.rect(screen, COLORS['black'], (440, 585, 80, 30), 1) # 边框
try:
txt_records = font_tiny.render('成绩', True, COLORS['black'])
except:
txt_records = font_tiny.render('Records', True, COLORS['black'])
screen.blit(txt_records , (480 - txt_records .get_width() // 2, 600 - txt_records .get_height() // 2))
# 提示按钮
pygame.draw.rect(screen, COLORS['lightyellow'], (350, 625, 80, 25))
pygame.draw.rect(screen, COLORS['black'], (350, 625, 80, 25), 1)
try:
txt_hint = font_tiny.render('提示(T)', True, COLORS['black'])
except:
txt_hint = font_tiny.render('Hint', True, COLORS['black'])
screen.blit(txt_hint, (390 - txt_hint.get_width() // 2, 637 - txt_hint.get_height() // 2))
# 帮助(说明)按钮
pygame.draw.rect(screen, COLORS['lightpink'], (440, 625, 80, 25))
pygame.draw.rect(screen, COLORS['black'], (440, 625, 80, 25), 1)
try:
txt_help = font_tiny.render('说明(H)', True, COLORS['black'])
except:
txt_help = font_tiny.render('Help', True, COLORS['black'])
screen.blit(txt_help, (480 - txt_help.get_width() // 2, 637 - txt_help.get_height() // 2))
# 操作提示文字
try:
help_txt = font_tiny.render('方向键移动 | 数字键填写 | 空格/DEL清除', True, COLORS['darkgray'])
except:
help_txt = font_tiny.render('Arrow:Move | Num:Input | Space/DEL:Clear', True, COLORS['darkgray'])
# 将提示文本上移,避免超出窗口
screen.blit(help_txt, (10, 655))
def draw_message(message, color):
"""绘制提示信息"""
msg_surface = font_small.render(message, True, color)
msg_rect = msg_surface.get_rect(center=(270, 610))
# 用半透明背景
s = pygame.Surface((540, 40), pygame.SRCALPHA)
s.fill((255, 255, 255, 200))
screen.blit(s, (0, 590))
screen.blit(msg_surface, msg_rect)
pygame.display.flip()
pygame.time.delay(1500)
# 清空消息区域
pygame.draw.rect(screen, COLORS['whitesmoke'], (0, 590, 540, 110))
def reset_game(difficulty):
"""重置游戏"""
# 显示"正在生成"的消息
try:
draw_message("正在生成新游戏...", COLORS['blue'])
except:
draw_message("Generating...", COLORS['blue'])
pygame.display.flip()
# 生成新游戏
start_time = time.perf_counter() # 使用高精度计时器
MATRIX_ANSWER, MATRIX, BLANK_IJ = give_me_a_game(blank_size=DIFFICULTY_LEVELS[difficulty]["blank_size"])
elapsed = time.perf_counter() - start_time
print(f"生成时间: {elapsed:.6f}秒") # 显示6位小数
cur_blank_size = sum(1 for row in MATRIX for cell in row if cell == 0)
cur_change_size = 0
# 保存游戏状态
game_states[difficulty] = {
"matrix_answer": MATRIX_ANSWER,
"matrix": MATRIX,
"blank_ij": set(BLANK_IJ),
"cur_blank_size": cur_blank_size,
"cur_change_size": cur_change_size,
"hint_cell": None,
"hint_end_time": 0,
"show_reset_confirm": False, # 重置确认对话框状态
"start_time": pygame.time.get_ticks(), # 添加游戏开始时间
"completed": False # 添加完成状态
}
return game_states[difficulty]
def check_solution():
"""校验数独"""
game_state = game_states[current_difficulty]
if not game_state:
return False
MATRIX = game_state["matrix"]
MATRIX_ANSWER = game_state["matrix_answer"]
for i in range(9):
for j in range(9):
if MATRIX[i][j] != MATRIX_ANSWER[i][j]:
return False
return True
def show_hint():
"""显示当前选中格子的正确答案"""
game_state = game_states[current_difficulty]
if not game_state:
return
# 确保选中的格子是空白格
if (cur_i, cur_j) not in game_state["blank_ij"]:
try:
draw_message("请选择空白格子", COLORS['red'])
except:
draw_message("Select a blank cell", COLORS['red'])
return
# 设置提示状态
game_state["hint_cell"] = (cur_i, cur_j)
game_state["hint_end_time"] = pygame.time.get_ticks() + 2000 # 2秒后结束提示
try:
draw_message("显示正确答案1秒", COLORS['purple'])
except:
draw_message("Showing answer for 2 seconds", COLORS['purple'])
def show_help_screen():
"""显示游戏帮助说明"""
# 创建一个新窗口
help_screen = pygame.display.set_mode((540, 700))
pygame.display.set_caption("游戏说明")
# 设置字体
try:
title_font = pygame.font.SysFont('Microsoft YaHei', 30)
text_font = pygame.font.SysFont('Microsoft YaHei', 20) # 缩小字体
except:
title_font = pygame.font.SysFont(None, 30)
text_font = pygame.font.SysFont(None, 20)
# 帮助文本
try:
title = title_font.render("数独游戏规则", True, COLORS['black'])
except:
title = title_font.render("Sudoku Rules", True, COLORS['black'])
texts = [
"游戏目标:在9x9的格子中填入数字1-9,使得:",
"- 每一行包含1-9的所有数字,且不重复;",
"- 每一列包含1-9的所有数字,且不重复;",
"- 每一个3x3的宫格包含1-9的所有数字,且不重复。",
"",
"操作说明:",
"- 方向键或鼠标点击选择格子;",
"- 数字键1-9填写数字;",
"- 空格键、DEL或Backspace删除数字;",
"- 顶部按钮切换难度;",
"- 重置:生成新游戏;",
"- 提交:检查解答是否正确;",
"- 提示:显示当前格子的正确答案(1秒)。",
"",
"难度说明:",
"- 初级:30个空格",
"- 中级:45个空格",
"- 高级:60个空格",
"",
"所有难度都有实时提示:",
"- 正确数字显示为蓝色",
"- 错误数字显示为红色",
"",
"按任意键返回游戏..."
]
running = True # 游戏运行状态
while running:
help_screen.fill(COLORS['whitesmoke'])
help_screen.blit(title, (270 - title.get_width() // 2, 10))
y_pos = 70
for text in texts:
try:
rendered = text_font.render(text, True, COLORS['black'])
except:
rendered = text_font.render(text, True, COLORS['black'])
help_screen.blit(rendered, (20, y_pos))
y_pos += 25 # 行距缩小
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
running = False
# 恢复主屏幕
pygame.display.set_mode(SIZE)
pygame.display.set_caption("数独游戏")
def show_loading_screen():
"""显示加载界面"""
# 绘制背景
screen.fill(COLORS['whitesmoke'])
# 显示加载文本
try:
loading_text = font_large.render("数独游戏加载中...", True, COLORS['blue'])
except:
loading_text = font_large.render("Sudoku Loading...", True, COLORS['blue'])
text_rect = loading_text.get_rect(center=(SIZE[0] // 2, SIZE[1] // 2))
screen.blit(loading_text, text_rect)
pygame.display.flip()
def initialize_game_in_background():
"""在后台线程中初始化游戏"""
# 在后台初始化游戏
def background_task():
for difficulty in DIFFICULTY_LEVELS:
reset_game(difficulty)
# 创建并启动后台线程
thread = threading.Thread(target=background_task)
thread.daemon = True # 设置为守护线程
thread.start()
# 等待初始化完成
while thread.is_alive():
# 显示加载界面
show_loading_screen()
# 处理事件,防止程序无响应
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.time.delay(50) # 短暂延迟,减少CPU占用
def show_records():
"""显示最佳成绩记录"""
# 创建新窗口 - 修改为540x700
records_screen = pygame.display.set_mode((540, 700))
pygame.display.set_caption("成绩记录")
# 设置字体
try:
title_font = pygame.font.SysFont('Microsoft YaHei', 40)
text_font = pygame.font.SysFont('Microsoft YaHei', 30)
hint_font = pygame.font.SysFont('Microsoft YaHei', 24)
except:
title_font = pygame.font.SysFont(None, 40)
text_font = pygame.font.SysFont(None, 30)
hint_font = pygame.font.SysFont(None, 24)
# 加载最佳成绩
load_best_times()
running = True
while running:
records_screen.fill(COLORS['whitesmoke'])
# 标题
try:
title = title_font.render("最佳成绩记录", True, COLORS['blue'])
except:
title = title_font.render("Best Records", True, COLORS['blue'])
records_screen.blit(title, (270 - title.get_width() // 2, 40))
y_pos = 120
for level in ["初级", "中级", "高级"]:
time_val = best_times[level]
if time_val == float('inf'):
time_str = "暂无记录"
else:
time_str = f"{time_val:.3f}秒"
try:
level_text = text_font.render(f"{level}:", True, COLORS['black'])
time_text = text_font.render(time_str, True, COLORS['darkblue'])
except:
level_text = text_font.render(f"{level}:", True, COLORS['black'])
time_text = text_font.render(time_str, True, COLORS['darkblue'])
records_screen.blit(level_text, (150, y_pos))
records_screen.blit(time_text, (260, y_pos))
y_pos += 70
# 添加分隔线
pygame.draw.line(records_screen, COLORS['lightgray'], (50, y_pos + 20), (490, y_pos + 20), 2)
y_pos += 50
# 添加说明文本
try:
hint1 = hint_font.render("提示:", True, COLORS['darkred'])
hint2 = hint_font.render("1. 成绩按不同难度分别记录", True, COLORS['black'])
hint3 = hint_font.render("2. 只记录最快完成时间", True, COLORS['black'])
hint4 = hint_font.render("3. 空白格子越多难度越高", True, COLORS['black'])
except:
hint1 = hint_font.render("Note:", True, COLORS['darkred'])
hint2 = hint_font.render("1. Records are saved per difficulty", True, COLORS['black'])
hint3 = hint_font.render("2. Only the fastest time is saved", True, COLORS['black'])
hint4 = hint_font.render("3. More blanks = higher difficulty", True, COLORS['black'])
records_screen.blit(hint1, (90, y_pos))
records_screen.blit(hint2, (120, y_pos + 40))
records_screen.blit(hint3, (120, y_pos + 80))
records_screen.blit(hint4, (120, y_pos + 120))
# 关闭提示
try:
hint = hint_font.render("按任意键返回...", True, COLORS['darkgray'])
except:
hint = hint_font.render("Press any key to return...", True, COLORS['darkgray'])
records_screen.blit(hint, (270 - hint.get_width() // 2, 620))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN or event.type == pygame.MOUSEBUTTONDOWN:
running = False
# 恢复主屏幕
pygame.display.set_mode(SIZE)
pygame.display.set_caption("数独游戏")
def draw_reset_confirm_dialog():
"""绘制重置确认对话框"""
# 半透明背景
s = pygame.Surface(SIZE, pygame.SRCALPHA)
s.fill((0, 0, 0, 128)) # 半透明黑色
screen.blit(s, (0, 0))
# 对话框背景
dialog_rect = pygame.Rect(120, 250, 300, 150)
pygame.draw.rect(screen, COLORS['white'], dialog_rect)
pygame.draw.rect(screen, COLORS['black'], dialog_rect, 2)
# 对话框标题
try:
title = font_small.render("确认重置游戏吗?", True, COLORS['black'])
except:
title = font_small.render("Confirm Reset Game?", True, COLORS['black'])
screen.blit(title, (dialog_rect.centerx - title.get_width() // 2, dialog_rect.y + 20))
# 确认按钮
confirm_rect = pygame.Rect(dialog_rect.x + 50, dialog_rect.y + 80, 80, 40)
pygame.draw.rect(screen, COLORS['lightgreen'], confirm_rect)
pygame.draw.rect(screen, COLORS['black'], confirm_rect, 1)
try:
confirm_text = font_tiny.render("确认", True, COLORS['black'])
except:
confirm_text = font_tiny.render("Yes", True, COLORS['black'])
screen.blit(confirm_text, (confirm_rect.centerx - confirm_text.get_width() // 2,
confirm_rect.centery - confirm_text.get_height() // 2))
# 取消按钮
cancel_rect = pygame.Rect(dialog_rect.x + 170, dialog_rect.y + 80, 80, 40)
pygame.draw.rect(screen, COLORS['lightcoral'], cancel_rect)
pygame.draw.rect(screen, COLORS['black'], cancel_rect, 1)
try:
cancel_text = font_tiny.render("取消", True, COLORS['black'])
except:
cancel_text = font_tiny.render("No", True, COLORS['black'])
screen.blit(cancel_text, (cancel_rect.centerx - cancel_text.get_width() // 2,
cancel_rect.centery - cancel_text.get_height() // 2))
return confirm_rect, cancel_rect
if __name__ == "__main__":
# 初始化
pygame.init()
# 设置支持中文的字体
try:
# 尝试使用微软雅黑
font = pygame.font.SysFont('Microsoft YaHei', 36)
font_small = pygame.font.SysFont('Microsoft YaHei', 24)
font_tiny = pygame.font.SysFont('Microsoft YaHei', 20) # 更小的字体
font_large = pygame.font.SysFont('Microsoft YaHei', 48) # 大字体
except:
try:
# 尝试使用黑体
font = pygame.font.SysFont('SimHei', 36)
font_small = pygame.font.SysFont('SimHei', 24)
font_tiny = pygame.font.SysFont('SimHei', 20)
font_large = pygame.font.SysFont('SimHei', 48)
except:
# 如果失败,使用默认字体
font = pygame.font.SysFont(None, 36)
font_small = pygame.font.SysFont(None, 24)
font_tiny = pygame.font.SysFont(None, 20)
font_large = pygame.font.SysFont(None, 48)
# 默认难度
current_difficulty = "初级"
DIFFICULTY_SETTINGS = DIFFICULTY_LEVELS[current_difficulty]
# 窗口尺寸:增加高度
SIZE = (540, 700)
screen = pygame.display.set_mode(SIZE)
pygame.display.set_caption("数独游戏")
# 显示加载界面
show_loading_screen()
# 在后台初始化游戏
initialize_game_in_background()
# 变量初始化
cur_i, cur_j = 0, 0
reset_confirm_active = False # 重置确认对话框状态
# 主循环
running = True
# 在游戏开始前加载最佳成绩
load_best_times()
while running:
game_state = game_states[current_difficulty]
if not game_state:
continue
DIFFICULTY_SETTINGS = DIFFICULTY_LEVELS[current_difficulty]
# 检查提示是否结束
if game_state.get("hint_end_time", 0) > 0 and pygame.time.get_ticks() > game_state["hint_end_time"]:
game_state["hint_cell"] = None
game_state["hint_end_time"] = 0
# 绘制背景
draw_background()
draw_difficulty_buttons()
draw_number()
draw_choose() # 选中框在数字上层
draw_context()
# 检查是否需要显示重置确认对话框
confirm_rect = cancel_rect = None
if reset_confirm_active:
confirm_rect, cancel_rect = draw_reset_confirm_dialog()
# 更新
pygame.display.flip()
# 事件处理
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
# 如果重置确认对话框激活,优先处理对话框事件
if reset_confirm_active:
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
if confirm_rect.collidepoint(x, y):
# 确认重置
game_state = reset_game(current_difficulty)
try:
draw_message("游戏已重置", COLORS['blue'])
except:
draw_message("Game Reset", COLORS['blue'])
reset_confirm_active = False
elif cancel_rect.collidepoint(x, y) or not confirm_rect.collidepoint(x, y):
# 取消重置或点击对话框外部
reset_confirm_active = False
continue
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
# 检查是否点击了难度按钮
if y <= 40:
if x < 180:
current_difficulty = "初级"
elif x < 360:
current_difficulty = "中级"
else:
current_difficulty = "高级"
cur_i, cur_j = 0, 0
DIFFICULTY_SETTINGS = DIFFICULTY_LEVELS[current_difficulty]
try:
draw_message(f"已切换到{current_difficulty}难度", COLORS['blue'])
except:
draw_message(f"Switched to {current_difficulty}", COLORS['blue'])
# 检查是否点击了游戏区域
elif 40 <= y < 580:
cur_j = x // 60
cur_i = (y - 40) // 60
# 检查重置按钮 (350,585) 到 (430,615)
elif 350 <= x <= 430 and 585 <= y <= 615:
reset_confirm_active = True # 激活重置确认对话框
# 检查记录按钮 (440,585) to (520,615)
elif 440 <= x <= 520 and 585 <= y <= 615:
show_records()
# 检查提示按钮 (350,625) to (430,650)
elif 350 <= x <= 430 and 625 <= y <= 650:
show_hint()
# 检查帮助按钮 (440,625) to (520,650)
elif 440 <= x <= 520 and 625 <= y <= 650:
show_help_screen()
elif event.type == pygame.KEYUP:
# 方向键移动
if event.key == pygame.K_UP and cur_i > 0:
cur_i -= 1
elif event.key == pygame.K_DOWN and cur_i < 8:
cur_i += 1
elif event.key == pygame.K_LEFT and cur_j > 0:
cur_j -= 1
elif event.key == pygame.K_RIGHT and cur_j < 8:
cur_j += 1
# 数字键输入
elif event.key in [pygame.K_1, pygame.K_2, pygame.K_3, pygame.K_4, pygame.K_5,
pygame.K_6, pygame.K_7, pygame.K_8, pygame.K_9] and (cur_i, cur_j) in game_state[
"blank_ij"]:
# 只在值变化时更新
if game_state["matrix"][cur_i][cur_j] != int(chr(event.key)):
game_state["matrix"][cur_i][cur_j] = int(chr(event.key))
game_state["cur_blank_size"] = sum(
1 for row in game_state["matrix"] for cell in row if cell == 0)
game_state["cur_change_size"] += 1
# 删除/清除:空格键、DEL、Backspace
elif event.key in [pygame.K_DELETE, pygame.K_BACKSPACE, pygame.K_SPACE, pygame.K_0] and (
cur_i, cur_j) in game_state["blank_ij"]:
# 只在值非0时更新
if game_state["matrix"][cur_i][cur_j] != 0:
game_state["matrix"][cur_i][cur_j] = 0
game_state["cur_blank_size"] = sum(
1 for row in game_state["matrix"] for cell in row if cell == 0)
game_state["cur_change_size"] += 1
# 重置游戏 (R键)
elif event.key == pygame.K_r:
reset_confirm_active = True # 激活重置确认对话框
# 提示 (T键)
elif event.key == pygame.K_t:
show_hint()
# 帮助 (H键)
elif event.key == pygame.K_h:
show_help_screen()
# 取消重置确认 (ESC键)
elif event.key == pygame.K_ESCAPE and reset_confirm_active:
reset_confirm_active = False
# 检查是否胜利
if not reset_confirm_active and check_win(game_state["matrix_answer"], game_state["matrix"]):
# try:
# draw_message("恭喜!你赢了!", COLORS['green'])
# except:
# draw_message("You Win! Congratulations!", COLORS['green'])
game_state["completed"] = True
elapsed = (pygame.time.get_ticks() - game_state["start_time"]) / 1000.0 # 转换为秒
# 更新最佳成绩
if elapsed < best_times[current_difficulty]:
best_times[current_difficulty] = elapsed
save_best_times()
message = f"恭喜!新纪录: {elapsed:.1f}秒"
else:
message = f"完成时间: {elapsed:.1f}秒"
draw_message(message, COLORS['green'])
pygame.time.delay(3000)
# 重置游戏
game_state = reset_game(current_difficulty)
pygame.quit()
bulid.py
python
import random
import time
def print_matrix(matrix):
"""格式化打印数独矩阵"""
print('---' * 19)
for row in matrix:
print('|' + ' '.join([str(col) if col != 0 else ' ' for col in row]) + '|')
print('---' * 19)
def shuffle_number(_list):
"""随机打乱数字列表"""
random.shuffle(_list)
return _list
def check(matrix, i, j, number):
"""检查在位置(i,j)放置number是否合法"""
# 检查行
if number in matrix[i]:
return False
# 检查列
for row in matrix:
if row[j] == number:
return False
# 检查3x3宫格
group_i, group_j = i // 3, j // 3
for x in range(group_i * 3, group_i * 3 + 3):
for y in range(group_j * 3, group_j * 3 + 3):
if matrix[x][y] == number:
return False
return True
def solve_sudoku(matrix):
"""使用回溯法求解数独,返回一个解"""
for i in range(9):
for j in range(9):
if matrix[i][j] == 0:
for num in shuffle_number(number_list[:]): # 尝试每个数字
if check(matrix, i, j, num):
matrix[i][j] = num
if solve_sudoku(matrix): # 递归尝试
return matrix
matrix[i][j] = 0 # 回溯
return None # 无解
return matrix # 全部填满
def generate_full_sudoku():
"""生成一个完整的数独解决方案"""
# 从一个空的数独开始
empty_grid = [[0] * 9 for _ in range(9)]
# 随机填充第一行
first_row = shuffle_number(number_list[:])
empty_grid[0] = first_row
# 使用回溯法求解
solution = solve_sudoku(empty_grid)
return solution
def give_me_a_game(blank_size=30):
"""生成可玩的数独游戏"""
# 生成一个完整的数独
solution = generate_full_sudoku()
if solution is None:
# 如果生成失败,使用备用方案
solution = [
[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 5, 3, 4, 8],
[1, 9, 8, 3, 4, 2, 5, 6, 7],
[8, 5, 9, 7, 6, 1, 4, 2, 3],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 6, 1, 5, 3, 7, 2, 8, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[3, 4, 5, 2, 8, 6, 1, 7, 9]
]
# 创建空白位置
blank_grid = [row[:] for row in solution] # 深拷贝
# 确保空白数量在合理范围内
blank_size = min(max(blank_size, 10), 60)
# 随机选择空白位置
all_positions = [(i, j) for i in range(9) for j in range(9)]
blank_positions = random.sample(all_positions, blank_size)
# 设置空白格子
for i, j in blank_positions:
blank_grid[i][j] = 0
time.sleep(0.001) # 微小延迟
# 打印数独
print_matrix(solution)
return solution, blank_grid, blank_positions
# 可用数字列表
number_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]