数独小游戏

天青色 发布于 2024-12-17


今天我妹突然拿了个题目给我写,不难,但挺有意思的

经典的数独游戏

突发奇想尝试写了个py脚本,调用gui库实现面板功能,求数独:

import tkinter as tk
from tkinter import messagebox
 
def is_valid(board, row, col, num):
    for i in range(9):
        if board[row][i] == num:
            return False
    for i in range(9):
        if board[i][col] == num:
            return False
    start_row = (row // 3) * 3
    start_col = (col // 3) * 3
    for i in range(3):
        for j in range(3):
            if board[start_row + i][start_col + j] == num:
                return False
    return True
 
def solve_sudoku(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == '*':
                for num in range(1, 10):
                    num = str(num)
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        if solve_sudoku(board):
                            return True
                        board[row][col] = '*'
                return False
    return True
 
def get_board_from_input(entries):
    board = []
    for row in range(9):
        board_row = []
        for col in range(9):
            value = entries[row][col].get().strip()
            if value == '' or value == '*':
                board_row.append('*')
            else:
                board_row.append(value)
        board.append(board_row)
    return board
 
def show_result(board):
    result = '\n'.join([' '.join(row) for row in board])
    messagebox.showinfo("数独解", f"解答结果:\n{result}")
 
def on_submit(entries):
    board = get_board_from_input(entries)
    solve_sudoku(board)
    show_result(board)
 
def create_gui():
    window = tk.Tk()
    window.title("数独解答器")
 
    entries = []
    for row in range(9):
        row_entries = []
        for col in range(9):
            entry = tk.Entry(window, width=5, font=("Arial", 14), justify='center')
            entry.grid(row=row, column=col, padx=5, pady=5)
            row_entries.append(entry)
        entries.append(row_entries)
 
    default_board = [
        ['8', ' ', '6', ' ', ' ', '3', ' ', '9', ' '],
        [' ', '4', ' ', ' ', '1', ' ', ' ', '6', '8'],
        ['2', ' ', ' ', '8', '7', ' ', ' ', ' ', '5'],
        ['1', ' ', '8', ' ', ' ', '5', ' ', '2', ' '],
        [' ', '3', ' ', '1', ' ', ' ', ' ', '5', ' '],
        ['7', ' ', '5', ' ', '3', ' ', '9', ' ', ' '],
        [' ', '2', '1', ' ', ' ', '7', ' ', '4', ' '],
        ['6', ' ', ' ', ' ', '2', ' ', '8', ' ', ' '],
        [' ', '8', '7', '6', ' ', '4', ' ', ' ', '3']
    ]
 
    for i in range(9):
        for j in range(9):
            entries[i][j].insert(0, default_board[i][j])
 
    submit_button = tk.Button(window, text="确定", font=("Arial", 14), command=lambda: on_submit(entries))
    submit_button.grid(row=9, column=4, columnspan=2, pady=10)
 
    window.mainloop()
 
create_gui()

gui样式:

以下为代码详解:

1. 导入库

import tkinter as tk
from tkinter import messagebox
  • tkinter 是 Python 标准库中的一个 GUI 库,用于创建图形界面。这里导入 tkinter 和其中的 messagebox 模块,后者用来显示弹出式消息框。

2. 检查数独是否合法

def is_valid(board, row, col, num):
    for i in range(9):
        if board[row][i] == num:
            return False
    for i in range(9):
        if board[i][col] == num:
            return False
    start_row = (row // 3) * 3
    start_col = (col // 3) * 3
    for i in range(3):
        for j in range(3):
            if board[start_row + i][start_col + j] == num:
                return False
    return True

这个函数用来检查当前数独棋盘上放置的数字是否符合数独规则。

数独规则要求:

  • 同一行不能有重复的数字。
  • 同一列不能有重复的数字。
  • 同一个 3x3 的小宫格里不能有重复的数字。

3. 求解数独(回溯法)

def solve_sudoku(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == '*':
                for num in range(1, 10):
                    num = str(num)
                    if is_valid(board, row, col, num):
                        board[row][col] = num
                        if solve_sudoku(board):
                            return True
                        board[row][col] = '*'
                return False
    return True

回溯法是用来求解数独的常见算法,当然,你也基本上可以把它理解为穷举法

它的基本思路是:

  1. 从数独的每个空格(*)开始,尝试填入一个合法的数字(1 到 9)。
  2. 如果填入的数字合法(通过 is_valid 函数验证),继续递归填充下一个空格。
  3. 如果所有空格都成功填充完毕,则返回 True,表示找到了一个解。
  4. 如果某个数字不合法,回溯到上一步,尝试其他可能的数字。
  5. 如果没有数字可填,则返回 False

非常的简单对吧,就是穷举,简单暴力~

4. 获取用户输入的棋盘数据

def get_board_from_input(entries):
    board = []
    for row in range(9):
        board_row = []
        for col in range(9):
            value = entries[row][col].get().strip()
            if value == '' or value == '*':
                board_row.append('*')
            else:
                board_row.append(value)
        board.append(board_row)
    return board

这个函数用于从 GUI 界面的输入框中获取数独棋盘的值。每个输入框的值通过 .get() 方法获得,并去掉两端的空格(.strip())。

代码逻辑解释:

  • 如果用户没有输入值,或输入的是 *,就将这个位置标记为空格(*)。
  • 如果用户输入的是数字,就将该数字作为当前格子的值。

(用户使用”*“来填写到空缺位作为标记,以免看错看掉)

5. 显示解答结果

def show_result(board):
    result = '\n'.join([' '.join(row) for row in board])
    messagebox.showinfo("数独解", f"解答结果:\n{result}")
  • show_result 函数用于将数独的解答通过弹窗的方式展示给用户。
  • 该函数将 board(数独棋盘)中的每一行转换成字符串,并用换行符连接各行,形成最终的字符串 result
  • 然后通过 messagebox.showinfo 显示弹出窗口,告诉用户解答结果。

6. 处理用户点击“确定”按钮的事件

def on_submit(entries):
    board = get_board_from_input(entries)
    solve_sudoku(board)
    show_result(board)
  • on_submit 函数会在用户点击“确定”按钮时触发。
  • 首先调用 get_board_from_input 从界面输入框中获取数独棋盘数据。
  • 然后调用 solve_sudoku 来求解数独。
  • 最后,调用 show_result 显示解答结果。

7. 创建 GUI 界面

def create_gui():
    window = tk.Tk()
    window.title("数独解答器")
  • create_gui 函数创建了一个 Tk 窗口实例,作为数独游戏的主界面。
  • 窗口标题设置为“数独解答器”。

8. 创建输入框和“确定”按钮

entries = []
for row in range(9):
    row_entries = []
    for col in range(9):
        entry = tk.Entry(window, width=5, font=("Arial", 14), justify='center')
        entry.grid(row=row, column=col, padx=5, pady=5)
        row_entries.append(entry)
    entries.append(row_entries)
  • 这段代码创建了一个 9x9 的输入框网格。每个输入框用于输入数独棋盘中的数字。
  • tk.Entry 创建了一个单独的输入框,设置宽度为 5,字体为 Arial,字号为 14,并且文字居中。
  • 使用 grid 布局方法将输入框放置在窗口中,padxpady 控制输入框之间的间距。
  • entries 是一个二维列表,用来存储每个输入框的引用。

9. 填充默认数独棋盘

default_board = [
    ['8', ' ', '6', ' ', ' ', '3', ' ', '9', ' '],
    [' ', '4', ' ', ' ', '1', ' ', ' ', '6', '8'],
    ['2', ' ', ' ', '8', '7', ' ', ' ', ' ', '5'],
    ['1', ' ', '8', ' ', ' ', '5', ' ', '2', ' '],
    [' ', '3', ' ', '1', ' ', ' ', ' ', '5', ' '],
    ['7', ' ', '5', ' ', '3', ' ', '9', ' ', ' '],
    [' ', '2', '1', ' ', ' ', '7', ' ', '4', ' '],
    ['6', ' ', ' ', ' ', '2', ' ', '8', ' ', ' '],
    [' ', '8', '7', '6', ' ', '4', ' ', ' ', '3']
]
  • default_board 是一个预设的数独棋盘,用户可以修改这些空格。

10. 确定按钮

submit_button = tk.Button(window, text="确定", font=("Arial", 14), command=lambda: on_submit(entries))
submit_button.grid(row=9, column=4, columnspan=2, pady=10)
  • 创建了一个“确定”按钮,点击时调用 on_submit(entries) 函数。
  • command 参数将按钮的点击事件与 on_submit 函数绑定起来。

11. 启动 GUI 界面

window.mainloop()
  • window.mainloop() 启动了 tkinter 的事件循环,显示窗口并等待用户与界面交互。

OK,就这,over。