Python实现数独游戏(三)—— 完整代码

目录

一、文件结构

二、代码

1、configs.py

2、main.py

3、paint.py

4、Generate.py

5、Game_Soduku


一、文件结构

Python实现数独游戏(三)—— 完整代码_第1张图片

二、代码

1、configs.py

import argparse


def parse_args():

    parser = argparse.ArgumentParser(description='Sudoku Game')

    # Form
    """
    screen_width: Width of the form
    screen_height: Height of the form
    """
    parser.add_argument('--screen_width', default=560)
    parser.add_argument('--screen_height', default=692)

    # Selected form
    """
    selected_width: Width of the selected form
    selected_height: Height of the selected form
    """
    parser.add_argument('--selected_width', default=260)
    parser.add_argument('--selected_height', default=300)

    # Difficulty level
    """
    level: The difficulty level of game, default value is 0
    0 means simple; 1 means medium; 2 means hard
    """
    parser.add_argument('--level', default=0)
    # Block
    """
    block_gap: Gap between two blocks
    block_size: Size of a block
    """
    parser.add_argument('--block_gap', default=1)
    parser.add_argument('--block_size', default=60)

    return parser.parse_args()

2、main.py

import configs
from Game_Sudoku import Game_Sudoku


def main(args):
    """
    screen_width: Width of the form
    screen_height: Height of the form
    """
    screen_width = args.screen_width
    screen_height = args.screen_height
    selected_width = args.selected_width
    selected_height = args.selected_height
    block_gap = args.block_gap
    block_size = args.block_size
    level = args.level

    game = Game_Sudoku(screen_width, screen_height, selected_width, selected_height,
                       block_gap, block_size, level)
    game.SelectedForm()
    # game.Form()

if __name__ == '__main__':
    args = configs.parse_args()
    main(args)

3、paint.py

import time
import pygame
import datetime


class Paint(object):
    # 初始化
    def __init__(self):
        self.martix = []  # 九宫格的数字
        self.timing = ""

    # 绘制选择窗口
    def PaintSelected(self, selected_form, move_x, move_y):
        """ 游戏背景 """
        # fill(color): 填充某一种颜色
        selected_form.fill((220, 220, 220))

        """ 字体设置 """
        # 初始化字体
        pygame.font.init()

        # part_1: easy
        # pygame.draw.rect(self.selected_form, (128, 128, 128), (0, 0, 260, 100))
        selected_font = pygame.font.SysFont('simsunnsimsun', 30, True)
        easy_text = selected_font.render('简 单', True, (0, 0, 0))
        selected_form.blit(easy_text, (90, 30))

        # part_2: medium
        # pygame.draw.rect(self.selected_form, (128, 128, 128), (0, 100, 260, 100))
        medium_text = selected_font.render('中 等', True, (0, 0, 0))
        selected_form.blit(medium_text, (90, 130))

        # part_3: hard
        # pygame.draw.rect(self.selected_form, (128, 128, 128), (0, 200, 260, 100))
        hard_text = selected_font.render('困 难', True, (0, 0, 0))
        selected_form.blit(hard_text, (90, 230))

        """ 鼠标移动事件 """
        if 0 < move_x < 260 and 0 < move_y < 100:
            pygame.draw.rect(selected_form, (128, 128, 128), (0, 0, 260, 100))
            easy_text = selected_font.render('简 单', True, (255, 255, 255))
            selected_form.blit(easy_text, (90, 30))
        elif 0 < move_x < 260 and 100 < move_y < 200:
            pygame.draw.rect(selected_form, (128, 128, 128), (0, 100, 260, 100))
            medium_text = selected_font.render('中 等', True, (255, 255, 255))
            selected_form.blit(medium_text, (90, 130))
        elif 0 < move_x < 260 and 200 < move_y < 300:
            pygame.draw.rect(selected_form, (128, 128, 128), (0, 200, 260, 100))
            hard_text = selected_font.render('困 难', True, (255, 255, 255))
            selected_form.blit(hard_text, (90, 230))

    # 绘制主窗口
    def PaintForm(self, form, start_time, block_size, block_gap,
                  move_x, move_y, press_x, press_y, martix, empty, is_same, issuccess, start):
        """ 游戏背景 """
        # fill(color): 填充某一种颜色
        form.fill((220, 220, 220))

        """ 主窗口————上 """
        # 初始化字体
        pygame.font.init()
        # 添加标题
        # f = pygame.font.get_fonts()  #: 获取字体样式
        # pygame.font.Font.render(): 在一个新 Surface 对象上绘制文本
        title_font = pygame.font.SysFont('幼圆', 50, True)
        title_text = title_font.render('数 独', True, (0, 0, 0))
        form.blit(title_text, (120, 10))

        # 添加时间: 计时: 0:00:00
        pygame.draw.rect(form, (128, 128, 128), (380, 0, 130, 70))
        time_font = pygame.font.SysFont('幼圆', 28, True)
        time_text = time_font.render('计 时', True, (0, 0, 0))
        form.blit(time_text, (405, 5))

        tmp = round(time.time() - int(start_time), 0)
        self.time = str(datetime.timedelta(seconds=tmp))
        digtial_time = time_font.render(str(self.time), True, (255, 250, 250))
        form.blit(digtial_time, (390, 35))

        # time.sleep(1)

        # 添加游戏说明
        tips_font = pygame.font.SysFont('simsunnsimsun', 20)
        tips_text = tips_font.render('利用逻辑和推理,在其他的空格上填入1-9的数字。', True, (0, 0, 0))
        form.blit(tips_text, (25, 70))

        tips_text = tips_font.render('使1-9每个数字在每一行、每一列和每一宫中都只出现一次。', True, (0, 0, 0))
        form.blit(tips_text, (25, 90))

        tips_text = tips_font.render('按esc键重新开始', True, (0, 0, 0))
        form.blit(tips_text, (25, 110))

        """ 主窗口————中 """
        pygame.draw.rect(form, (0, 0, 0), (5, 140, 550, 550))  # 黑色背景
        pygame.draw.rect(form, (65, 105, 225), (5, 140, 185, 185))  # 蓝色背景, 左上1格
        pygame.draw.rect(form, (65, 105, 225), (370, 140, 185, 185))  # 蓝色背景, 右上3格
        pygame.draw.rect(form, (65, 105, 225), (188, 322, 185, 185))  # 蓝色背景, 中间5格
        pygame.draw.rect(form, (65, 105, 225), (5, 505, 185, 185))  # 蓝色背景, 左下7格
        pygame.draw.rect(form, (65, 105, 225), (370, 505, 185, 185))  # 蓝色背景, 右下9格

        for i in range(9):
            for j in range(9):
                # (x, y) 方块的初始位置
                x = j * block_size + (j + 1) * block_gap
                y = i * block_size + (i + 1) * block_gap

                """ 鼠标移动事件 """
                # 鼠标移动到相应方块, 方块颜色变化
                if x + 5 < move_x < x + 5 + block_size and y + 140 < move_y < y + 140 + block_size:
                    pygame.draw.rect(form, (220, 220, 220), (x + 5, y + 140, block_size, block_size))
                else:
                    pygame.draw.rect(form, (255, 255, 255), (x + 5, y + 140, block_size, block_size))

                """ 鼠标按键事件 """
                # 鼠标按键相应的方块, 该方块所在的行和列颜色均发生变化
                # 列
                if x + 5 < press_x < x + 5 + block_size and 140 < press_y < 690:
                    pygame.draw.rect(form, (220, 220, 220), (x + 5, y + 140, block_size, block_size))
                # 行
                if y + 140 < press_y < y + 140 + block_size and 5 < press_x < 555:
                    pygame.draw.rect(form, (220, 220, 220), (x + 5, y + 140, block_size, block_size))

                # 绘制数字
                num_font = pygame.font.SysFont('幼圆', 45, True)  # 数字字体类型及大小
                if martix[i][j] != 0:
                    if [i, j] not in empty and [i, j] not in is_same:
                        num_text = num_font.render(str(martix[i][j]), True, (0, 0, 0))
                    elif [i, j] not in empty and [i, j] in is_same:
                        num_text = num_font.render(str(martix[i][j]), True, (255, 0, 0))
                    else:
                        num_text = num_font.render(str(martix[i][j]), True, (65, 105, 225))
                # if martix[i][j] == 0:
                #     num_text = num_font.render(str(martix[i][j]), True, (255, 255, 255))
                # else:
                #     num_text = num_font.render(str(martix[i][j]), True, (0, 0, 0))
                    form.blit(num_text, (x + 22, y + 150))

        """ 如果游戏成功 """
        if issuccess:
            success_font = pygame.font.SysFont("simsunnsimsun", 60, True)
            str_text = success_font.render('Successful!', True, (178, 34, 34))
            form.blit(str_text, (85, 360))

        """ 游戏未开始 """
        if not start:
            pygame.draw.rect(form, (192, 192, 192), (100, 250, 360, 300))
            font = pygame.font.SysFont("幼圆", 40, True)
            str_text = font.render('空格开始', True, (0, 0, 0))
            form.blit(str_text, (190, 360))

4、Generate.py

import random
import numpy as np


class Generate(object):
    # 初始化九宫格
    def __init__(self, n):
        # int8,int16,int32,int64 可替换为等价的字符串 'i1','i2','i4',以及其他
        self.martix = np.zeros((9, 9), dtype='i1')
        self.Nums = {1, 2, 3, 4, 5, 6, 7, 8, 9}
        self.n = n  # 挖洞数目

        # 挖洞时保存挖好的数独
        self.uniqueMartix = []

    # 构建数独
    def build_martix(self):
        # self.LasVegas(11)  # 数独的生成
        while not self.LasVegas(11):
            pass
        self.Generate(self.n)
        return self.martix

    # 拉斯维加斯算法生成数独
    def LasVegas(self, counts):
        """
        :param counts: 生成的数字个数
        :return:
        """
        while counts:
            # [x, y] 为表格位置, 即表格位置是随机生成
            row = random.randint(0, 8)
            col = random.randint(0, 8)

            # 九宫格中的空格
            if self.martix[row, col] == 0:
                # 随机取数
                value = random.sample(self.Get_possible(row, col), 1)[0]
                self.martix[row, col] = value
                counts -= 1

        # 采用深度优先方法继续解数独
        if self.Solve():
            return True
        else:
            return False

    # 求解数独
    def Solve(self):
        for row in range(9):
            for col in range(9):
                if self.martix[row, col] == 0:
                    possible = self.Get_possible(row, col)  # 所有的可能的数字
                    for value in possible:
                        self.martix[row, col] = value
                        if self.Solve():
                            return True
                        self.martix[row, col] = 0
                        self.row, self.col = row, col
                    return False
        return True

    # 数字[1, 9]随机排列
    def Get_possible(self, row, col):
        """
        :param row: 横坐标
        :param col: 纵坐标
        :return: 返回可能的数字集合
        """
        # [x, y] 为大表格位置, 即九个小格子为一个大表格
        x, y = row // 3, col // 3
        """
        self.martix[row, :]: [row, col]表格所在行
        self.martix[:, col]: [row, col]表格所在列
        """
        rowSet = set(self.martix[row, :])  # [row, col] 所在行的数字集合
        colSet = set(self.martix[:, col])  # [row, col] 所在列的数字集合
        blockSet = set(self.martix[x * 3: x * 3 + 3, y * 3: y * 3 + 3].reshape(9))  # [row, col] 所在大表格的数字集合

        return self.Nums - rowSet - colSet - blockSet

    # 根据数独盘生成数独题目
    def Generate(self, n):
        # level 表示要挖掉的数字个数,通常挖掉 50 - 60 个左右,
        # 最多挖去63-64个可以得到有唯一解的宫格,但是需要计算的时间会长很多
        self.uniqueMartix = self.martix.copy()

        counts = 0
        while counts < n:
            row = random.randint(0, 8)
            col = random.randint(0, 8)
            # 该格子已经挖过了
            if self.uniqueMartix[row, col] == 0:
                continue

            # 挖掉该格子后能生成唯一的九宫格。如果有就继续挖,如果没有唯一解就不挖这个格子
            if self.IsUnique(row, col):
                # 保存挖洞后的状态
                self.uniqueMartix[row, col] = 0

                # 挖完洞后继续挖,直到挖出指定数量的格子
                self.martix = self.uniqueMartix.copy()

                counts += 1

    # 挖洞后判断是否该九宫格只有唯一的答案
    def IsUnique(self, row, col):
        for value in range(1, 10):
            if self.martix[row][col] != value:
                # 假设挖掉这个数字
                self.martix[row][col] = 0
                if value in self.Get_possible(row, col):
                    # 更换一个数字之后检查是否还有另一解
                    self.martix[row][col] = value
                    if self.Solve():
                        return False

                # 上面进行深度优先过程已经改变了 self.martix的值,恢复更换这个数字之前的状态
                self.martix = self.uniqueMartix.copy()

        # 已尝试所有其他数字发现无解,即只有唯一解
        return True

5、Game_Soduku

import os
import sys
import time
import pygame
import numpy as np

from paint import Paint
from Generate import Generate


class Game_Sudoku(object):
    # 初始化函数
    def __init__(self, screen_width, screen_height, selected_width, selected_height,
                 block_gap, block_size, level):
        """ 窗口 """
        self.screen_width = screen_width  # 窗口宽度
        self.screen_height = screen_height  # 窗口高度
        self.block_gap = block_gap  # 方块间隙 10
        self.block_size = block_size  # 方块大小 86
        self.form = ''  # 游戏主窗口

        self.martix = []  # 九宫格题目

        self.x, self.y = 0, 0  # 表格起始位置
        self.row, self.col = 0, 0  # 表格的相对位置

        """ 其他 """
        self.tmp = 0  # 时间差
        self.time = ""  # 计时0:00:00
        self.start_time = ""  # 开始时间
        self.nums = ['1', '2', '3', '4', '5', '6', '7', '8', '9']  # 数独游戏中的数字
        self.empty = []  # 数独中的空格位置
        self.is_same = []  # 当同行或同列或同方块出现相同数字, 则数独中与之相同的数字的位置
        self.issuccess = False  # 游戏是否成功
        self.start = False  # 游戏是否开始

        """ 字体 """
        self.title_font = ''  # 窗口标题字体类型及大小: Sudoku
        self.time_font = ''  # 时间字体类型及大小
        self.tips_font = ''  # 说明字体类型及大小
        self.font = ''  # 数字字体

        """ 选择窗口 """
        self.selected_form = ""  # 选择难易程度的窗口
        self.selected_width = selected_width  # 选择窗口的宽度
        self.selected_height = selected_height  # 选择窗口的高度

        self.selected_font = ""  # easy/medium/hard 字体类型及大小

        self.move_x, self.move_y = 0, 0  # 鼠标移动的位置
        self.press_x, self.press_y = 0, 0  # 鼠标按键的位置

        self.level = level  # 游戏难易程度
        self.counts = [30, 40, 50]  # 挖洞个数

    # 窗口的设置
    def Form(self):
        """
        :return:
        """
        pygame.init()  # 初始化所有导入的 pygame 模块
        pygame.display.set_caption("Game_Sudoku")  # 窗口标题
        os.environ['SDL_VIDEO_CENTERED'] = '1'  # 窗口居中显示
        self.form = pygame.display.set_mode([self.screen_width, self.screen_height], 0, 0)  # 窗口大小
        os.environ['SDL_VIDEO_CENTERED'] = '1'  # 窗口居中显示

        self.start_time = time.time()

        """ 游戏初始化 """
        self.init()

        while True:
            self.Action()  # 用户行为: 键盘输入/鼠标
            pygame.display.update()  # 使绘制的显示到窗口上

    # 选择窗口的设置
    def SelectedForm(self):
        """
        :return:
        """
        pygame.init()  # 初始化所有导入的 pygame 模块
        pygame.display.set_caption("Game_Sudoku")  # 窗口标题
        os.environ['SDL_VIDEO_CENTERED'] = '1'  # 窗口居中显示
        self.selected_form = pygame.display.set_mode([self.selected_width, self.selected_height], 0, 0)  # 窗口大小

        while True:
            self.SelectedAction()  # 用户行为: 键盘输入/鼠标
            pygame.display.update()  # 使绘制的显示到窗口上

    # 用户行为(主窗口): 键盘输入/鼠标
    def Action(self):
        for event in pygame.event.get():  # pygame.event.get(): 获取所有消息并将其从队列中删除
            if event.type == pygame.QUIT:  # pygame.QUIT: 窗口右上角的红 ×
                # sys.exit()  # sys.exit()函数是通过抛出异常的方式来终止进程的
                self.SelectedForm()
            elif event.type == pygame.MOUSEMOTION:  # 鼠标移动事件
                pos = pygame.mouse.get_pos()
                self.move_x, self.move_y = pos[0], pos[1]
            elif event.type == pygame.MOUSEBUTTONDOWN:  # 鼠标按键事件
                pos = pygame.mouse.get_pos()
                self.press_x, self.press_y = pos[0], pos[1]
                self.row, self.col = (self.press_y - 140 - 1) // 61, (self.press_x - 5 - 1) // 61
            elif event.type == pygame.KEYDOWN:  # 键盘按键事件
                """
                pygame.KEYDOWN 按下键盘时
                pygame.KEYUP 释放键盘时
                """
                """ 
                K_ESCAPE: ESC
                """
                if event.key == pygame.K_SPACE:
                    self.start = True
                    self.start_time = time.time()
                elif event.key == pygame.K_ESCAPE:
                    """ 游戏初始化 """
                    self.init()
                elif chr(event.key) in self.nums and 0 <= self.row <= 8 and 0 <= self.col <= 8 \
                        and [self.row, self.col] in self.empty:
                    self.IsRight(chr(event.key))
                    self.martix[self.row][self.col] = chr(event.key)
                elif event.key == pygame.K_BACKSPACE:
                    if [self.row, self.col] in self.empty:
                        self.martix[self.row][self.col] = 0

        paint = Paint()
        paint.PaintForm(self.form, self.start_time, self.block_size, self.block_gap,
                        self.move_x, self.move_y, self.press_x, self.press_y, self.martix,
                        self.empty, self.is_same, self.issuccess, self.start)  # 表格绘制

        # 判断游戏是否成功
        self.IsSuccess()

    # 用户行为(选择难度窗口): 键盘输入/鼠标
    def SelectedAction(self):
        for event in pygame.event.get():  # pygame.event.get(): 获取所有消息并将其从队列中删除
            if event.type == pygame.QUIT:  # pygame.QUIT: 窗口右上角的红 ×
                sys.exit()  # sys.exit()函数是通过抛出异常的方式来终止进程的
            elif event.type == pygame.MOUSEMOTION:  # 鼠标移动事件
                pos = pygame.mouse.get_pos()
                self.move_x, self.move_y = pos[0], pos[1]
            elif event.type == pygame.MOUSEBUTTONDOWN:  # 鼠标按键事件
                pos = pygame.mouse.get_pos()
                self.press_x, self.press_y = pos[0], pos[1]
                if 0 < self.press_x < 260 and 0 < self.press_y < 100:
                    self.level = 0
                elif 0 < self.press_x < 260 and 100 < self.press_y < 200:
                    self.level = 1
                elif 0 < self.press_x < 260 and 200 < self.press_y < 300:
                    self.level = 2
            elif event.type == pygame.MOUSEBUTTONUP:  # 鼠标释放事件
                self.Form()
        paint = Paint()
        paint.PaintSelected(self.selected_form, self.move_x, self.move_y)  # 表格绘制

    # 游戏初始化
    def init(self):
        self.empty = []
        # 数独题目、答案
        g = Generate(self.counts[self.level])
        self.martix = g.build_martix()

        # 获取数独中的空格位置
        for i in range(9):
            for j in range(9):
                if self.martix[i][j] == 0:
                    self.empty.append([i, j])

    # 判断填入数字是否符合游戏要求
    def IsRight(self, num):
        """
        :param num: 输入的数字
        :return: 该行、列、大表格是否存在和num相同的数字
        """
        """
        self.martix[self.row, :]: 行
        self.martix[:, self.col]: 列
        self.martix[self.row // 3 * 3: self.row // 3 * 3 + 3, self.col // 3 * 3: self.col // 3 * 3 + 3]: 所处表格
        """
        rowset = self.martix[self.row, :]  # 行
        colset = self.martix[:, self.col]  # 列
        blockset = self.martix[self.row // 3 * 3: self.row // 3 * 3 + 3, self.col // 3 * 3: self.col // 3 * 3 + 3].reshape(9)  # 方格

        num = int(num)
        self.is_same = []
        if num in rowset or num in colset or num in blockset:
            pos = np.where(self.martix == num)
            pos_x, pos_y = pos[0], pos[1]
            for i in range(len(pos_x)):
                self.is_same.append([pos_x[i], pos_y[i]])

    # 判断游戏是否成功
    def IsSuccess(self):
        if self.martix.min() > 0 and not self.is_same:
            self.empty = []
            self.issuccess = True
            self.end = time.time()
        else:
            self.issuccess = False

你可能感兴趣的:(python,python,数独,pygame,游戏)