欢迎光临
我们一直在努力

基于Python+PyGame实现的一款功能完整的数独游戏,支持多难度选择、实时验证、提示系统、成绩记录,并采用多线程优化加载体验。(文末附全部代码)

✨ 项目亮点

  • ✅ 三种难度可选(初级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

    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))

    # 绘制游戏区域网格
    # 绘制粗线(3×3的大格子)
    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 = [
    "游戏目标:在9×9的格子中填入数字1-9,使得:",
    "- 每一行包含1-9的所有数字,且不重复;",
    "- 每一列包含1-9的所有数字,且不重复;",
    "- 每一个3×3的宫格包含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():
    """显示最佳成绩记录"""
    # 创建新窗口 – 修改为540×700
    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

    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

    # 检查3×3宫格
    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]

    赞(0)
    未经允许不得转载:171主机测评 » 基于Python+PyGame实现的一款功能完整的数独游戏,支持多难度选择、实时验证、提示系统、成绩记录,并采用多线程优化加载体验。(文末附全部代码)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址