欢迎访问宙启技术站
智能推送

利用Python函数生成一个九宫格数独游戏

发布时间:2023-06-15 16:28:53

数独游戏是一种热门的思维训练游戏,也称为“九宫格”。这个游戏由一个$9\times 9$的方阵组成,每个方格中填写一个数字,一般是$1$到$9$之间的整数,使得每行、每列和每个$3\times3$的小方格内数字都不能重复。本文将介绍如何用Python函数生成一个九宫格数独游戏。

1. 导入模块

在编写程序之前,我们需要导入两个Python模块,分别为randomnumpyrandom模块用于随机生成数独游戏的初始状态,numpy模块用于创建九宫格数组和打印游戏界面。

import random
import numpy as np

2. 生成初始状态的数独游戏

我们可以通过random模块生成一个$9\times 9$的二维数组,其中每个元素随机填写一个$1$到$9$之间的整数,并且需要满足数独游戏的规则,即每行、每列和每个$3\times3$的小方格内数字都不能重复。

为了方便判断行、列和小方格内是否已经存在某个数字,我们可以创建三个空的列表rowscolsboxes。分别表示每行、每列和每个小方格,元素为一个集合,存储该行、列或小方格内已经存在的数字。

def generate_sudoku():
    sudoku = [[0 for x in range(9)] for y in range(9)]
    rows = [set() for x in range(9)]
    cols = [set() for x in range(9)]
    boxes = [set() for x in range(9)]
    
    # 随机填写每个方格,并满足数独游戏的规则
    for i in range(9):
        for j in range(9):
            # 计算当前方格所在的小方格的编号
            box_index = (i // 3) * 3 + j // 3
            # 生成一个随机数字
            num = random.randint(1, 9)
            # 如果该数字已经存在于该行、该列或该小方格中,则重新生成一个数字
            while num in rows[i] or num in cols[j] or num in boxes[box_index]:
                num = random.randint(1, 9)
            # 将该数字填写到当前方格中,并记录到对应的行、列和小方格中
            sudoku[i][j] = num
            rows[i].add(num)
            cols[j].add(num)
            boxes[box_index].add(num)
    
    return sudoku

在以上代码中,我们使用双重循环遍历每个方格,计算当前方格所在的小方格的编号,然后生成一个随机数字,如果该数字已经存在于该行、该列或该小方格中,则重新生成一个数字,最后将该数字填写到当前方格中,并记录到对应的行、列和小方格中。最终返回一个初始状态的数独游戏。

3. 检查数独游戏的解

我们需要编写一个函数来检查数独游戏的解是否正确。函数的输入为一个数独游戏的二维数组,输出为一个布尔值,表示该游戏是否有唯一解。

def check_solution(sudoku):
    # 检查行
    for i in range(9):
        used = set()
        for j in range(9):
            if sudoku[i][j] in used:
                return False
            used.add(sudoku[i][j])
    
    # 检查列
    for j in range(9):
        used = set()
        for i in range(9):
            if sudoku[i][j] in used:
                return False
            used.add(sudoku[i][j])
    
    # 检查小方格
    for box_i in range(3):
        for box_j in range(3):
            used = set()
            for i in range(box_i*3, (box_i+1)*3):
                for j in range(box_j*3, (box_j+1)*3):
                    if sudoku[i][j] in used:
                        return False
                    used.add(sudoku[i][j])
    
    return True

在以上代码中,我们使用三个循环分别检查每一行、每一列和每一个小方格。对于每一行、每一列和每一个小方格,使用一个集合used来存储已经使用的数字,如果当前方格的数字已经存在于used集合中,则返回False表示该游戏无解,否则将该数字添加到used集合中,继续检查下一个方格。最终返回True表示该游戏有唯一解。

4. 生成数独游戏的解

为了生成数独游戏的解,我们可以使用递归 Backtracking 算法来求解数独游戏。

def solve_sudoku(sudoku):
    # 查找未填写的方格
    for i in range(9):
        for j in range(9):
            if sudoku[i][j] == 0:
                # 枚举每个可以填写的数字
                for num in range(1, 10):
                    # 判断该数字是否可以填写到当前方格中
                    if num not in get_used_nums(sudoku, i, j):
                        sudoku[i][j] = num
                        # 递归求解数独游戏
                        if solve_sudoku(sudoku):
                            return True
                        # 回溯
                        sudoku[i][j] = 0
                return False
    return True

在以上代码中,我们首先查找未填写的方格,如果当前方格的数值为$0$,则说明该方格未填写。我们枚举该方格可以填写的数字,判断该数字是否可以填写到当前方格中,注意要使用get_used_nums函数来获取当前行、当前列和当前小方格中已经存在的数字,如果可以填写,则将该数字填写到当前方格中,并递归求解数独游戏。如果递归求解成功,则返回True表示该游戏有解,如果递归求解失败,则回溯,将当前方格的数值重新设置为$0$,并继续枚举下一个数字。如果枚举完所有可能的数字后,仍然没有找到解,则返回False表示该游戏无解,继续回溯。

我们还需要编写一个函数get_used_nums来获取当前行、当前列和当前小方格中已经存在的数字。

def get_used_nums(sudoku, i, j):
    used = set()
    # 获取当前行中已经存在的数字
    used |= set(sudoku[i])
    # 获取当前列中已经存在的数字
    used |= set(sudoku[x][j] for x in range(9))
    # 获取当前小方格中已经存在的数字
    box_i = i // 3
    box_j = j // 3
    used |= set(sudoku[x][y] for x in range(box_i*3, (box_i+1)*3) for y in range(box_j*3, (box_j+1)*3))
    return used

在以上代码中,我们首先创建一个空的集合used,然后获取当前行中已经存在的数字并添加到used集合中,获取当前列中已经存在的数字并添加到used集合中,获取当前小方格中已经存在的数字并添加到used集合中,最终返回used集合。

5. 生成数独游戏

在以上步骤中,我们已经能够生成一个