數獨游戲難度模式解析
在數獨游戲中,難度通常由已知數字(提示數)的數量決定。難度越高,已知數字越少,玩家需要推理的步驟越多。以下是不同模式下的算法區別和核心代碼解析。
文章目錄
- 數獨游戲難度模式解析
- 1. **難度模式配置**
- **1.1 難度與移除數量的關系**
- **1.2 難度選擇界面**
- 2. **核心算法解析**
- **2.1 數獨生成算法**
- **2.2 數字移除算法**
- 3. **完整代碼在資源中可以下載**
- 4. **總結**
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) 的概念,通過以下步驟生成:
- 模式函數:用于生成數獨的初始填充。
- 隨機化:通過隨機化行、列和數字,確保生成的數獨是隨機的。
- 移除數字:根據難度設置移除一定數量的數字,生成題目。
def pattern(r, c): return (base * (r % base) + r // base + c) % sidefrom random import sampledef 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 = 3CELL_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 = rootself.root.title("數獨游戲")self.board = []self.original = []self.entries = [[None for _ in range(9)] for _ in range(9)]self.mode = "9x9" # 默認模式改為9x9self.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] = Nonedef 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 # 棋盤邊長等于sizedef pattern(r, c): return (base * (r % base) + r // base + c) % sidefrom random import sampledef 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] = entryself.board = boardself.original = copy.deepcopy(board)self.update_gui()def update_gui(self):# 根據模式更新GUIconfig = self.MODE_CONFIG.get(self.mode, self.MODE_CONFIG["9x9"])size = config["base"] ** 2# 確保board已初始化if not self.board:returnfor 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.widgetif event.char.isdigit() or event.keysym in ('BackSpace', 'Delete'):# 允許輸入數字或刪除passelse:return "break"def reset_board(self):self.board = copy.deepcopy(self.original)self.update_gui()
4. 總結
- 難度模式:通過配置文件定義不同模式下的難度參數,確保代碼的可維護性。
- 數獨生成算法:基于拉丁方陣生成數獨,并通過隨機化確保生成的數獨是隨機的。
- 數字移除算法:根據難度設置移除數字的數量,并確保不會移除所有數字。