基于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 -- 清除数字

  • 重置游戏

  • 请求提示

  • 查看帮助


🔧 五、可扩展方向

  1. 主题切换 -- 增加深色/彩色主题选项

  2. 音效反馈 -- 添加正确/错误提示音

  3. 解题器 -- 集成自动求解功能

  4. 云同步 -- 将成绩上传至网络排行榜


📦 六、项目总结

该项目展示了如何用 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]
相关推荐
人工智能培训2 小时前
如何持续、安全地向大模型注入新知识?
人工智能·python·算法·大模型·大模型学习·大模型应用工程师·大模型工程师证书
AIFQuant2 小时前
如何快速接入贵金属期货实时行情 API:python 实战分享
开发语言·python·金融·数据分析·restful
Ulyanov2 小时前
PyVista战场可视化实战(二):动态更新与动画——让战场动起来
python·性能优化·tkinter·pyvista·gui开发
深蓝海拓2 小时前
PyQt5/PySide6的moveToThread:移动到线程
笔记·python·qt·学习·pyqt
幻云20102 小时前
Python深度学习:筑基与实践
前端·javascript·vue.js·人工智能·python
被星1砸昏头2 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
avi91112 小时前
简单的Gradio实现一个统计界面+日志输出
python·aigc·gradio
52Hz1182 小时前
力扣240.搜索二维矩阵II、160.相交链表、206.反转链表
python·算法·leetcode
jun_bai2 小时前
conda环境配置nnU-Net生物医学图像分割肺动脉静脉血管
开发语言·python