目录
一、文件结构
二、代码
1、configs.py
2、main.py
3、paint.py
4、Generate.py
5、Game_Soduku
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()
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)
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))
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
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