Python桌面版数独游戏(三版)-增加难易度模式

数独游戏难度模式解析

在数独游戏中,难度通常由已知数字(提示数)的数量决定。难度越高,已知数字越少,玩家需要推理的步骤越多。以下是不同模式下的算法区别和核心代码解析。
在这里插入图片描述


1. 难度模式配置

我们定义了三种难度模式:

  • 简单模式:提示数字较多,玩家容易完成。
  • 中等模式:提示数字适中,需要一定的推理能力。
  • 困难模式:提示数字较少,需要较强的逻辑推理。
1.1 难度与移除数量的关系
  • 简单模式:移除较少数字,保留较多提示。
  • 中等模式:移除中等数量数字,提示适中。
  • 困难模式:移除较多数字,提示较少。
MODE_CONFIG = {
    "9x9": {"base": 3, "remove_count": {"easy": 30, "medium": 40, "hard": 50}, "size": 9},  # 9x9棋盘,数字范围1-9
    "6x6": {"base": 2, "remove_count": {"easy": 15, "medium": 20, "hard": 25}, "size": 6},  # 6x6棋盘,数字范围1-6
    "4x4": {"base": 2, "remove_count": {"easy": 5, "medium": 8, "hard": 10}, "size": 4}    # 4x4棋盘,数字范围1-4
}
1.2 难度选择界面

我们为难度模式添加了选择界面,用户可以选择不同的难度:

def create_difficulty_selection(self):
    # 创建难度选择区域
    self.difficulty_frame = tk.Frame(self.root)
    self.difficulty_frame.grid(row=0, column=1, pady=10)

    tk.Label(self.difficulty_frame, text="选择难度:").pack(side="left", padx=5)
    
    self.difficulty_var = tk.StringVar(value="easy")
    
    for difficulty in ["easy", "medium", "hard"]:
        tk.Radiobutton(self.difficulty_frame, text=self.DIFFICULTY_LABELS[difficulty], 
                      variable=self.difficulty_var, value=difficulty, 
                      command=self.update_difficulty).pack(side="left", padx=5)

2. 核心算法解析

2.1 数独生成算法

数独生成算法基于 拉丁方阵(Latin Square) 的概念,通过以下步骤生成:

  1. 模式函数:用于生成数独的初始填充。
  2. 随机化:通过随机化行、列和数字,确保生成的数独是随机的。
  3. 移除数字:根据难度设置移除一定数量的数字,生成题目。
def pattern(r, c): return (base * (r % base) + r // base + c) % side

from random import sample

def shuffle(s): return sample(s, len(s))

rBase = range(base)
rows = [g * base + r for g in shuffle(rBase) for r in shuffle(rBase)]
cols = [g * base + c for g in shuffle(rBase) for c in shuffle(rBase)]
nums = shuffle(range(1, size + 1))  # 数字范围根据棋盘大小调整

# 创建棋盘时使用正确的尺寸
board = [[0 for _ in range(side)] for _ in range(side)]

# 填充棋盘
for r in range(side):
    for c in range(side):
        board[r][c] = nums[pattern(r, c)]
2.2 数字移除算法

数字移除是根据难度设置的参数进行的,确保不会移除所有数字,并且不会超出棋盘范围:

# 移除部分数字,生成题目
squares = side * side
# 确保不会尝试移除超过可用数量的数字
remove_count = min(remove_count, squares - self.MIN_REMOVE_COUNT)  # 至少保留一个数字

# 安全地随机移除数字
available_positions = [(r, c) for r in range(side) for c in range(side) if r < side and c < side]
if available_positions:
    # 确保至少保留一个数字
    safe_remove_count = min(remove_count, len(available_positions))
    for r, c in sample(available_positions, safe_remove_count):
        if r < side and c < side:  # 再次检查边界
            board[r][c] = 0

3. 完整代码在资源中可以下载

以下是包含难度模式的部分代码:


class SudokuGame:
    # 常量定义
    CELL_WIDTH = 3
    CELL_FONT = ('Arial', 18)
    ORIGINAL_COLOR = 'black'
    PLAYER_COLOR = 'blue'
    BG_COLOR = 'white'
    READONLY_BG_COLOR = 'lightgray'
    MIN_REMOVE_COUNT = 1  # 至少保留一个数字

    MODE_CONFIG = {
        "9x9": {"base": 3, "remove_count": {"easy": 30, "medium": 40, "hard": 50}, "size": 9},  # 9x9棋盘,数字范围1-9
        "6x6": {"base": 2, "remove_count": {"easy": 15, "medium": 20, "hard": 25}, "size": 6},  # 6x6棋盘,数字范围1-6
        "4x4": {"base": 2, "remove_count": {"easy": 5, "medium": 8, "hard": 10}, "size": 4}    # 4x4棋盘,数字范围1-4
    }

    DIFFICULTY_LABELS = {
        "easy": "简单",
        "medium": "中等",
        "hard": "困难"
    }

    def __init__(self, root):
        self.root = root
        self.root.title("数独游戏")
        self.board = []
        self.original = []
        self.entries = [[None for _ in range(9)] for _ in range(9)]
        self.mode = "9x9"  # 默认模式改为9x9
        self.difficulty = "easy"  # 默认难度
        self.create_mode_selection()
        self.create_difficulty_selection()
        self.create_widgets()
        self.generate_sudoku()  # 默认生成9x9棋盘和棋局

    def create_mode_selection(self):
        # 创建模式选择区域
        self.mode_frame = tk.Frame(self.root)
        self.mode_frame.grid(row=0, column=0, pady=10)

        tk.Label(self.mode_frame, text="选择模式:").pack(side="left", padx=5)
        
        self.mode_var = tk.StringVar(value="9x9")
        modes = ["4x4", "6x6", "9x9"]
        
        for mode in modes:
            tk.Radiobutton(self.mode_frame, text=mode, variable=self.mode_var, 
                          value=mode, command=self.update_mode).pack(side="left", padx=5)

    def create_difficulty_selection(self):
        # 创建难度选择区域
        self.difficulty_frame = tk.Frame(self.root)
        self.difficulty_frame.grid(row=0, column=1, pady=10)

        tk.Label(self.difficulty_frame, text="选择难度:").pack(side="left", padx=5)
        
        self.difficulty_var = tk.StringVar(value="easy")
        
        for difficulty in ["easy", "medium", "hard"]:
            tk.Radiobutton(self.difficulty_frame, text=self.DIFFICULTY_LABELS[difficulty], 
                          variable=self.difficulty_var, value=difficulty, 
                          command=self.update_difficulty).pack(side="left", padx=5)

    def update_mode(self):
        # 更新模式
        self.mode = self.mode_var.get()

    def update_difficulty(self):
        # 更新难度
        self.difficulty = self.difficulty_var.get()

    def clear_board(self):
        # 清理现有的输入框
        for row in range(9):
            for col in range(9):
                if self.entries[row][col]:
                    self.entries[row][col].destroy()
                    self.entries[row][col] = None

    def create_widgets(self):
        # 创建数独棋盘
        self.frame = tk.Frame(self.root)
        self.frame.grid(row=1, column=0, padx=10, pady=10, columnspan=2)

        # 根据模式设置棋盘大小
        config = self.MODE_CONFIG.get(self.mode, self.MODE_CONFIG["9x9"])
        size = config["size"]

        for row in range(size):
            for col in range(size):
                entry = tk.Entry(self.frame, width=self.CELL_WIDTH, font=self.CELL_FONT, justify='center')
                entry.grid(row=row, column=col, padx=(0 if col % 3 != 2 else 5),
                           pady=(0 if row % 3 != 2 else 5))
                self.entries[row][col] = entry

        # 按钮
        self.btn_frame = tk.Frame(self.root)
        self.btn_frame.grid(row=2, column=0, pady=10, columnspan=2)

        self.reset_button = tk.Button(self.btn_frame, text="重新开始本局", command=self.reset_board)
        self.reset_button.pack(side="left", padx=10)

        self.new_button = tk.Button(self.btn_frame, text="生成新棋局", command=self.generate_sudoku)
        self.new_button.pack(side="left", padx=10)

    def generate_sudoku(self):
        # 根据选择的模式和难度生成数独
        config = self.MODE_CONFIG.get(self.mode, self.MODE_CONFIG["9x9"])
        base = config["base"]
        remove_count = config["remove_count"][self.difficulty]
        size = config["size"]
        
        side = size  # 棋盘边长等于size

        def pattern(r, c): return (base * (r % base) + r // base + c) % side

        from random import sample

        def shuffle(s): return sample(s, len(s))

        rBase = range(base)
        rows = [g * base + r for g in shuffle(rBase) for r in shuffle(rBase)]
        cols = [g * base + c for g in shuffle(rBase) for c in shuffle(rBase)]
        nums = shuffle(range(1, size + 1))  # 数字范围根据棋盘大小调整

        # 创建棋盘时使用正确的尺寸
        board = [[0 for _ in range(side)] for _ in range(side)]
        
        # 填充棋盘
        for r in range(side):
            for c in range(side):
                board[r][c] = nums[pattern(r, c)]

        # 移除部分数字,生成题目
        squares = side * side
        # 确保不会尝试移除超过可用数量的数字
        remove_count = min(remove_count, squares - self.MIN_REMOVE_COUNT)  # 至少保留一个数字
        
        # 安全地随机移除数字
        available_positions = [(r, c) for r in range(side) for c in range(side) if r < side and c < side]
        if available_positions:
            # 确保至少保留一个数字
            safe_remove_count = min(remove_count, len(available_positions))
            for r, c in sample(available_positions, safe_remove_count):
                if r < side and c < side:  # 再次检查边界
                    board[r][c] = 0

        # 清空未使用区域
        self.clear_board()
        
        # 重新创建对应当前模式的输入框
        # 使用安全的size值
        current_size = config["size"]
        for row in range(current_size):
            for col in range(current_size):
                # 确保不会越界访问
                if row < len(self.entries) and col < len(self.entries[row]):
                    entry = tk.Entry(self.frame, width=self.CELL_WIDTH, font=self.CELL_FONT, justify='center')
                    entry.grid(row=row, column=col, padx=(0 if col % 3 != 2 else 5),
                               pady=(0 if row % 3 != 2 else 5))
                    entry.bind("<Key>", self.on_key)
                    self.entries[row][col] = entry

        self.board = board
        self.original = copy.deepcopy(board)
        self.update_gui()

    def update_gui(self):
        # 根据模式更新GUI
        config = self.MODE_CONFIG.get(self.mode, self.MODE_CONFIG["9x9"])
        size = config["base"] ** 2
        
        # 确保board已初始化
        if not self.board:
            return

        for row in range(size):
            for col in range(size):
                val = 0
                # 安全访问board元素
                if row < len(self.board) and col < len(self.board[row]):
                    val = self.board[row][col]
                
                entry = self.entries[row][col]
                if entry:
                    entry.delete(0, tk.END)
                    # 安全访问board元素
                    if row < len(self.board) and col < len(self.board[row]):
                        val = self.board[row][col]
                        if val != 0:
                            entry.insert(0, str(val))
                            entry.config(fg=self.ORIGINAL_COLOR, readonlybackground=self.READONLY_BG_COLOR)
                            entry.bind("<Key>", lambda e: "break")  # 不可编辑原始数字
                        else:
                            entry.config(fg=self.PLAYER_COLOR, bg=self.BG_COLOR)
                            entry.bind("<Key>", self.on_key)

    def on_key(self, event):
        widget = event.widget
        if event.char.isdigit() or event.keysym in ('BackSpace', 'Delete'):
            # 允许输入数字或删除
            pass
        else:
            return "break"

    def reset_board(self):
        self.board = copy.deepcopy(self.original)
        self.update_gui()


4. 总结

  • 难度模式:通过配置文件定义不同模式下的难度参数,确保代码的可维护性。
  • 数独生成算法:基于拉丁方阵生成数独,并通过随机化确保生成的数独是随机的。
  • 数字移除算法:根据难度设置移除数字的数量,并确保不会移除所有数字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

香蕉可乐荷包蛋

努力写有用的code

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值