upload sudoku project

master
bettleChen 1 year ago
parent 9d8a3aa15c
commit f706bb341d

238
UI.py

@ -0,0 +1,238 @@
"""
@Author: packy945
@FileName: UI.py
@DateTime: 2023/4/27 17:42
@SoftWare: PyCharm
"""
# -*- encoding: utf-8 -*-
import tkinter as tk
from tkinter import *
from data import *
import time
import numpy as np
from tkinter import scrolledtext
BLACK = "#000000"
HINT = '#6D2DFA'
SILVER = '#FFFFFF'
u_NUMBER = '#6D2DFA'
WHITE = "#FFFFFF"
ORANGE = '#FF8C00'
RED = "#C80000"
YELLOW = "#FAE067"
GREY = '#808080'
GREYLESS = '#CCCCCC'
BC = 40
# 构建主窗口
width, height = 840, 610#设置长宽
root = tk.Tk()#设置主窗口
root.title("数独")#设置窗口标题
root.config(bg=BLACK)#设置背景
root.geometry(f'{width}x{height}')#设置窗口大小
root.resizable(0, 0) # 防止用户调整尺寸
cv = tk.Canvas(root, bg=WHITE, bd=2, relief="sunken", width=width, height=height, background=ORANGE,
borderwidth=0, highlightthickness=0)#设置画布
cv.place(x=0, y=0, anchor=NW)#设置画布位置
bc = 40
"""文字框"""
top_x = (bc + 3) * 11 # 文本框顶点x
top_y = 50 # 文本框顶点y
bc_x = (bc + 3) * 7 + 20 # 文本框宽度
bc_y = (bc + 3) * 12 # 文本框高度
right = tk.Frame(root, bg=WHITE, relief="sunken", width=bc_x, height=bc_y)
right.place(x=top_x, y=top_y, anchor=NW) # 右边栏
class MyTimer:
# 计时器
def __init__(self):
self.elapsed = 0.0
self.running = False
self.last_start_time = None
self.timestr = tk.IntVar() # 创建可变数据类型
self.min = tk.IntVar() # 创建可变数据类型
self.sec = tk.IntVar() # 创建可变数据类型
self.timestr.set(0) # 只能数值不能等于号
self.starttime = 0 # 开始计时时间
self.elapsedtime = 0.0 # 计时器统计到的时间
self.timer = None
self.starttime = time.time() - self.elapsedtime
self.update()
self.win = 0
def update(self):
self.elapsedtime = int(time.time() - self.starttime)
self.timestr.set(self.elapsedtime)
self.min.set(self.elapsedtime // 60)
self.sec.set(self.elapsedtime - self.min.get() * 60)
self.timer = root.after(100, self.update)
def restart(self):
self.win = 0
self.elapsedtime = 0.0
self.starttime = time.time() - self.elapsedtime
self.update()
def stop(self):
if self.win:
pass
else:
self.win = 1
root.after_cancel(self.timer)
self.elapsedtime = int(time.time() - self.starttime)
now = int(time.time() - self.starttime)
self.timestr.set(now)
self.min.set(now // 60)
self.sec.set(now - self.min.get() * 60)
def rect_draw(bc, row, col):
'''
绘制一个方格
:param bc: 方格大小
:param row:
:param col:
:return:
'''
block_color = [3, 4, 5] # 显色宫格依据
logi_row = row in block_color and col in block_color # 显色逻辑
logi_col = row in block_color or col in block_color # 显色逻辑
if not logi_row and logi_col:
color = "#FAE067"
else:
color = 'white'
cv.create_rectangle(row * (bc + 3) + 50, col * (bc + 3) + 50, row * (bc + 3) + 50 + bc, col * (bc + 3) + 50 + bc,
fill=color, width=0) # 绘制一个方格
def show_Time(timer: MyTimer):
'''
显示计时器
:param timer:计时器示例
:return:
'''
Time = tk.Label(right, text="运行时间: 分 秒", bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold'), anchor=W)
Time.place(x=30, y=30, width=300, height=30)
Min = tk.Label(right, textvariable=timer.min, bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold')) # 分钟计数
Min.place(x=150, y=30, width=30, height=30)
Sec = tk.Label(right, textvariable=timer.sec, bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold')) # 秒钟计数
Sec.place(x=220, y=30, width=30, height=30)
def num_draw(bc, row, col, martix, u_martix):
'''
绘制方格中的数字
:param bc: 方格大小
:param row:
:param col:
:param martix: 原矩阵
:param u_martix: 用户矩阵
:return:
'''
if u_martix[row, col] == 10:# 如果该方格为提示信息,则不显示大数字
return
if not martix[row, col] == 0: # 如果该方格为题目所给数字(不可更改),则显示为黑色
color = 'black' # 将颜色设置为黑色
else:
if u_martix[row, col] not in GetPossible(u_martix, row, col): # 判断这一格的数字是否符合数独要求
color = 'red' # 不符合要求显示红色
else:
color = 'purple' # 用户填的数字的颜色
if martix[row, col] != 0 or u_martix[row, col] != 0: #如果该格子有数据要显示
cv.create_text(col * (bc + 3) + 50 + bc / 2, row * (bc + 3) + 50 + bc / 2,
text=f"{u_martix[row, col]}", font=('幼圆', 18, 'bold'), fill=color)
def Difficulty(empty):
"""
难度显示
:param empty: 原始空格数目
:return:
"""
Level = tk.Label(right, text="难度:{:.2f}".format(empty / 81), bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold'), anchor=W)
Level.place(x=30, y=60, width=300, height=30)
def noter(tag):
'''
显示笔记提示
:param tag: 笔记模式是否开启
:return:
'''
note_help1 = tk.Label(right, text=f"False 时保存笔记,当前笔记:{tag}", bg=WHITE, fg=RED, font=('幼圆', 12, 'bold'), anchor=W)
note_help1.place(x=30, y=90, width=300, height=30)
def seq_recode(right, STEP):
'''
显示步骤
:param STEP: 步骤
:return:
'''
note_help1 = tk.Label(right, text=f"步骤记录:", bg=WHITE, fg=RED, font=('幼圆', 12, 'bold'), anchor=W)
note_help1.place(x=30, y=120, width=300, height=30)
scr = scrolledtext.ScrolledText(right, fg='red', font=('幼圆', 16, 'bold'))#设置一个可滚动文本框
scr.place(x=30, y=150, width=280, height=300)#
for i in range(len(STEP)):
if i != 0:#将步骤显示到文本框中
scr.insert('end', f"{STEP[i][:3]}\n") # 末尾插入
scr.config(state=DISABLED) # 设置文本框为 “不能编辑”
scr.see(END)#将视图移到末尾
def empty(u_martix):
count = 0 # 计数
for i in range(9):
for j in range(9):
if u_martix[i][j] == 0 or u_martix[i][j] == 10:
count += 1
return count
if __name__ == "__main__":
martix = M = np.array([[1, 3, 4, 0, 0, 6, 0, 0, 7],
[0, 9, 2, 0, 0, 7, 0, 5, 6],
[0, 6, 7, 8, 9, 4, 0, 2, 3],
[0, 8, 1, 2, 4, 9, 6, 0, 0],
[9, 4, 5, 6, 0, 8, 2, 3, 0],
[2, 7, 6, 0, 1, 5, 9, 0, 4],
[0, 2, 8, 0, 5, 1, 0, 0, 9],
[0, 1, 3, 0, 6, 0, 0, 4, 8],
[0, 5, 0, 4, 0, 0, 0, 1, 2]])
u_M = np.array([[1, 3, 4, 0, 0, 6, 0, 0, 7],
[0, 9, 2, 0, 0, 7, 0, 5, 6],
[0, 6, 7, 8, 9, 4, 0, 2, 3],
[0, 8, 1, 2, 4, 9, 6, 0, 0],
[9, 4, 5, 6, 0, 8, 2, 3, 0],
[2, 7, 6, 1, 1, 5, 9, 0, 4],
[0, 2, 8, 0, 5, 1, 0, 6, 9],
[0, 1, 3, 0, 6, 0, 7, 4, 8],
[0, 5, 0, 4, 0, 0, 0, 1, 2]])
# text_enter(BC)
# time1.start() # 计时开始
# draw((5, 5),M,u_M)
BC = 40
def left1(event):
print(event.x, event.y)
cv.bind('<Button-1>', left1)
mainloop()

@ -0,0 +1,124 @@
"""
@Author: packy945
@FileName: data.py
@DateTime: 2023/4/27 17:42
@SoftWare: PyCharm
"""
# -*- encoding: utf-8 -*-
import numpy as np
import random
from collections import Counter
def Init():
# 构建9*9数组
martix = np.zeros((9, 9), dtype='i1') # 初始化9*9数组并赋值0
for row in range(0, 9):
for col in range(0, 9):
value = random.randint(0, 9)
martix[row, col] = value # 给9*9数组martix随机赋值
return martix
def Judge(martix):
# 计算整个数组是否符合要求
for row in range(0, 9):
for col in range(0, 9):
if martix[row][col] == 0:
continue
m, n = row // 3, col // 3 # [m, n] 为宫位置, 即九个小格子为一个宫
row_martix = martix[row, :] # [row, col] 所在行的数字数组
row_set = set(row_martix) - {0} # [row, col] 所在行的数字集合1~9
col_martix = martix[:, col] # [row, col] 所在列的数字数组
col_set = set(col_martix) - {0} # [row, col] 所在列的数字集合1~9
block_martix = martix[m * 3: m * 3 + 3, n * 3: n * 3 + 3].reshape(9) # [row, col] 所在的宫的数字数组
block_set = set(block_martix) - {0} # [row, col] 所在的宫的数字集合1~9
if len(row_martix) != (len(row_set) + Counter(row_martix)[0]):
return False #若集合里的数字个数不包括0加上0的个数小于9说明有非0的数字重复
if len(col_martix) != (len(col_set) + Counter(col_martix)[0]):
return False #若行、列、九宫格有非0的数字重复则说明不满足数独要求
if len(block_martix) != (len(block_set) + Counter(block_martix)[0]):
return False
return True #若全部满足要求,则返回判断成功
def GetPossible(martix, row, col):# 获取一个格子里可能可以填的值
Nums = {1, 2, 3, 4, 5, 6, 7, 8, 9}
Set = set()
m, n = row // 3, col // 3 # 计算所在九宫格的编号
for i in range(9):
if i == row:
continue
Set.add(martix[i][col])# 将所在列出现数字加入集合
for j in range(9):
if j == col:
continue
Set.add(martix[row][j])# 将所在行出现数字加入集合
for i in range(m * 3, m * 3 + 3):
for j in range(n * 3, n * 3 + 3):
if row == i and j == col:
continue
Set.add(martix[i][j])# 将所在九宫格出现数字加入集合
return Nums - Set # 返回1~9中没出现过的数字即可选的值
def Solve(martix):
"""求解数组"""
for i in range(9):
row = (i + 5) % 9
for j in range(9):
col = (j + 4) % 9
if martix[row, col] == 0:
possible = GetPossible(martix, row, col) # 所有的可能的数字
for value in possible:
martix[row, col] = value # 将可能的数组填入
if Solve(martix): # 继续深度优先遍历填入数字
return True # 填完最后一个数字
martix[row, col] = 0 # 如果当前状况无解则归位,然后回溯
return False
# 当所有的数字填完,数独求解完毕
return True
def question(martix):#随机生成一个数独
for row in range(9):
for col in range(9):
if martix[row, col] == 0:
possible = GetPossible(martix, row, col) # 所有的可能的数字
ran_possible = list(possible) #将可能值集合转为列表
random.shuffle(ran_possible)#随机打乱可能值列表
for value in ran_possible:#逐个选择可能值列表
martix[row, col] = value # 将可能的数组填入
if question(martix): # 继续深度优先遍历填入数字
return True # 填完最后一个数字
martix[row, col] = 0 # 如果当前状况无解则归位,然后回溯
return False
return True# 当所有的数字填完,数独求解完毕
def InitMartix(n):
'''
创建一个符合要求的带空数组
:return: 生产完成的数组
'''
martix = np.zeros((9, 9), dtype='i1')
question(martix) # 随机生成一个数独题目
while n:
row = random.randint(0, 8)#随机生成一个行数
col = random.randint(0, 8)#随机生成一个列数
if not martix[row][col] == 0:
martix[row][col] = 0#若该格不为空,则置为空
n -= 1#空格数减一
return martix
def InitMartix_new():
'''
创建一个符合要求的带空数组
:return: 生产完成的数组
'''
martix = np.zeros((9, 9), dtype='i1')
question(martix) # 随机生成一个数独题目
for i in range(9):
for j in range(9):
x = random.randint(-50, 100)#生成随机数
if x < 0:#根据随机数来决定该格子是否显示
martix[i][j] = 0 # 若该格不为空,则置为空
return martix

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

@ -0,0 +1,499 @@
"""
@Author: packy945
@FileName: sudoku.py
@DateTime: 2023/4/27 17:41
@SoftWare: PyCharm
"""
# -*- encoding: utf-8 -*-
from data import *
from UI import *
def game_menu(menu:tk.Menu):
"""游戏菜单显示"""
menu_node = Menu(menu, tearoff=False)
menu_node.add_command(label="重新开始", command=lambda: restart(N))
menu_node.add_command(label="暂停计时", command=lambda: stop_time())
menu_node.add_command(label="载入题目", command=lambda: load_game())
# 在主目录菜单上新增"菜单"选项并通过menu参数与下拉菜单绑定
menu.add_cascade(label="游戏设置", menu=menu_node)
def auto_menu(menu:tk.Menu):
"""自动求解菜单显示"""
menu_node = Menu(menu, tearoff=False)
menu_node.add_command(label="自动求解-顺序求解", command=lambda: [init_u_M(), auto_Solve(u_martix, STEP.copy())])
menu_node.add_command(label="自动求解-优化", command=lambda: [init_u_M(), auto_Solve_new(u_martix, STEP.copy())])
fmenu = Menu(menu, tearoff=False)
fmenu.add_command(label='低速', command=lambda: speed_set(1))
fmenu.add_command(label='中速', command=lambda: speed_set(0.5))
fmenu.add_command(label='高速', command=lambda: speed_set(0.2))
menu_node.add_cascade(label="求解速度选择", menu=fmenu)
# 在主目录菜单上新增"菜单"选项并通过menu参数与下拉菜单绑定
menu.add_cascade(label="自动求解", menu=menu_node)
main_menu = tk.Menu(root)
root.config(menu=main_menu)
game_menu(main_menu)
auto_menu(main_menu)
def speed_set(spd):
global wait_time
wait_time = spd
def stop_time():
global time1
time1.stop()
def init_u_M():
global martix, u_martix, step, STEP, seq
u_martix = martix.copy()
step = [] # 操作步骤
STEP = [[0, (0, 0), 0, martix]] # 记录每一步的状态
seq = 1 # 全局操作次序初始化
def restart(N):
'''
重新生成一个游戏
:return:
'''
global martix, u_martix, time1, show_possible, loca_o, loca, note_mark, step, STEP, seq
time1.restart()
martix = InitMartix(N) # 生成一个数独数组
u_martix = martix.copy() # 复制用户martix数组
show_possible = 0 # 是否显示提示
p = []# 初始化可能值数组
possible = []
for i in range(9):
p.append({}.copy())
for i in range(9):
possible.append(p.copy())
loca_o = tuple() # martix 数组上次滞留坐标
loca = tuple()# martix 数组当前坐标
note_mark = False # 笔记开启标志
show_Time(time1)# 显示时间信息
step = [] # 操作步骤
STEP = [[0, (0, 0), 0, martix]] # 记录每一步的状态
seq = 1 # 全局操作次序初始化
draw(loca, martix, u_martix, STEP)
def auto_Solve(u_M, STEP):
'''
自动解题算法按顺序求解
:param u_M:待解答的数独矩阵
:return:求解完毕的矩阵
'''
for row in range(9):
for col in range(9):
if u_M[row, col] == 0:
possible = GetPossible(u_M, row, col) # 计算所有的可能的数字
for value in possible:#尝试所有可能数字
u_M[row, col] = value # 将可能的数组填入
seq = STEP[-1][0] + 1 # 记录顺序
step = [seq, (row + 1, col + 1), value, u_M.copy()] # 记录一步
STEP.append(step)#将操作记录加入Step中
draw((row + 1, col + 1), martix, u_M, STEP)#更新显示界面
time.sleep(wait_time) # 防止运算过快,减慢演算步骤
if auto_Solve(u_M, STEP.copy()): # 继续深度优先遍历填入数字
return True # 求解完成,直接返回
u_M[row, col] = 0 # 若用这个数无解则重置为0进行回溯
draw((row + 1, col + 1), martix, u_M, STEP)#更新显示界面
time.sleep(wait_time) # 停顿一段时间,显示求解步骤
return False #当前无解返回False
return True# 当所有的数字填完,数独求解完毕
def auto_Solve_new(u_M, STEP):
'''
自动求解优化
:param u_M:待求解数独矩阵
:return:求解完成的矩阵
'''
flag = 0
# 优化:如果某个位置只有一个可能值,则先填这个格子
for row in range(9):
for col in range(9):
if u_M[row, col] == 0:
possible = GetPossible(u_M, row, col) # 所有的可能的数字
if len(possible) == 1:
flag = 1#标记可以进行求解优化
value = possible.pop()#只有一个可能数组,则直接将该数字填入
u_M[row, col] = value # 将确定的数字填入填入
seq = STEP[-1][0] + 1 # 记录顺序
step = [seq, (row + 1, col + 1), value, u_M.copy()] # 记录一步
STEP.append(step)#将步骤记录加到记录中
draw((row + 1, col + 1), martix, u_M, STEP) #重新绘图
time.sleep(wait_time) # 停顿一段时间,显示求解步骤
if auto_Solve_new(u_M, STEP): # 继续深度优先遍历填入数字
return True # 填完最后一个数字
u_M[row, col] = 0 # 如果当前填入的数字会导致后面无解则依然填入0表示空白待填
draw((row + 1, col + 1), martix, u_M, STEP)#重新绘图
time.sleep(wait_time) # 停顿一段时间,显示求解步骤
if not flag:#若没有能直接确定的格子,则按原方法进行求解
for row in range(9):
for col in range(9):
if u_M[row, col] == 0:
possible = GetPossible(u_M, row, col) # 所有的可能的数字
for value in possible:
u_M[row, col] = value # 将可能的数组填入
seq = STEP[-1][0] + 1 # 记录顺序
step = [seq, (row + 1, col + 1), value, u_M.copy()] # 记录一步
STEP.append(step)
draw((row + 1, col + 1), martix, u_M, STEP)
time.sleep(wait_time) # 停顿一段时间,显示求解步骤
if auto_Solve_new(u_M, STEP): # 继续深度优先遍历填入数字
return True # 填完最后一个数字
u_M[row, col] = 0 # 如果当前填入的数字会导致后面无解则依然填入0表示空白待填
draw((row + 1, col + 1), martix, u_M, STEP)
time.sleep(wait_time) # 停顿一段时间,显示求解步骤
return False
return True# 当所有的数字填完,数独求解完毕
def load_game():
'''
载入存储的游戏
:return:
'''
global martix, u_martix, time1, show_possible, loca_o, loca, note_mark, step, STEP, seq
time1.restart()
martix = np.load("martix.npy")
u_martix = martix.copy() # 复制用户martix数组
show_possible = 0 # 是否显示提示
# 初始化可能值数组
p = []
possible = []
for i in range(9):
p.append({}.copy())
for i in range(9):
possible.append(p.copy())
loca_o = tuple() # martix 数组上次滞留坐标
loca = tuple()
note_mark = False # 笔记开启标志
# 显示时间信息
show_Time(time1)
step = [] # 操作步骤
STEP = [[0, (0, 0), 0, martix]] # 记录每一步的状态
seq = 1 # 全局操作次序初始化
draw(loca, martix, u_martix, STEP)
def left1(event):
'''
martix坐标映射
:param event: 鼠标左键点击的坐标值
:return: 点击的方格坐标
'''
""""""
x = event.x
y = event.y
row = -1
col = -1
global STEP, loca, loca_o
for cur_col in range(9):
for cur_row in range(9):
top_x, top_y = cur_col * (bc + 3) + 50, cur_row * (bc + 3) + 50# 计算每个方格的四个角
ud_x, ud_y = (cur_col + 1) * (bc + 3) + 50, (cur_row + 1) * (bc + 3) + 50# 计算每个方格的四个角
if x < ud_x and x > top_x:#计算点击的列数
col = cur_col
if y < ud_y and y > top_y:#计算点击的行数
row = cur_row
if row >= 0 and col >= 0 and not martix[row, col]:
if loca:#若已经点击过坐标
loca_o = loca # 保留上次点击过的坐标
loca = (row + 1, col + 1) # 更新选中的格子位置
draw(loca, martix, u_martix, STEP)#绘制界面
return
def win(u_martix, timer: MyTimer, STEP):
'''
检查游戏是否结束若结束则输出结束界面
:param u_martix:用户矩阵
:param timer:计时器
:return:
'''
count = 0 # 计数
for i in range(9):
for j in range(9):
if u_martix[i][j] == 0 or u_martix[i][j] == 10:
count += 1
if count == 0:
if Judge(u_martix):
for w in right.winfo_children():
w.destroy()
timer.stop()
win_words = tk.Label(right, text=f"游戏结束,恭喜通过", bg='white', fg='red', font=('幼圆', 16, 'bold'),
anchor=W)
win_words.place(relx=0.1, rely=0.3)
win_time = tk.Label(right, text=f"耗时:{timer.min.get()}{timer.sec.get()}", bg='white', fg='red',
font=('幼圆', 16, 'bold'), anchor=W)
win_time.place(relx=0.1, rely=0.4)
win_step = tk.Label(right, text=f"花费步数:{len(STEP)}", bg='white', fg='red',
font=('幼圆', 16, 'bold'), anchor=W)
win_step.place(relx=0.1, rely=0.5)
re = tk.Button(right, text=f"重新开始", bg='white', fg='black',
font=('幼圆', 14, 'bold'), command=lambda: restart(N))
re.place(relx=0.1, rely=0.6, relwidth=0.5)
def u_number(num, loca):
'''
添加功能
:param num: 用户点击数字按钮
:param loca: 当前位置
:return:
'''
if loca != ():#若选择了一个格子
row, col = loca[0] - 1, loca[1] - 1
global step, seq
if num: # 直接点击了数字按钮就添加num
if martix[row][col] == 0 and not note_mark: # 空格且笔记模式为False才可以填写
if u_martix[row][col] == 10: # 如果修改了这个格子的值,删除原来的记录
possible[row][col].clear()
# for note in note_martix: # 如果这个空格有笔记直接覆盖清空笔记
# if note[0] == row and note[1] == col:
# note_martix.remove(note)
u_martix[row][col] = num # 为用户数组添加数值
seq = STEP[-1][0] + 1 # 记录顺序
step = [seq, loca, num, u_martix.copy()] # 记录一步
# print(step[:3])
if len(step) != 0: # 添加步骤
STEP.append(step)
step = []
print(loca, num, "添加")
elif note_mark: # 笔记模式打开时
if u_martix[row][col] == 0:# 若当前没有笔记则只有num
u_martix[row][col] = 10#标记该格为笔记格子
possible[row][col] = {num}# 将笔记加入记录中
elif u_martix[row][col] == 10:# 若已有笔记则添加num
possible[row][col].add(num)
draw(loca, martix, u_martix, STEP)#重新绘制界面
def control_draw_old(bc):
'''
绘制数字按钮
:param bc: 全局按钮大小
:return:
'''
for i in range(9):#遍历0~9
top_x, top_y = i * (bc + 3) + 50, 12 * (bc + 3) + 10#计算每个按钮左上角坐标
B = tk.Button(root, text=f"{i + 1}", bg='white', fg='black',
font=('幼圆', 14, 'bold'), command=lambda num=i + 1: u_number(num, loca))
B.place(x=top_x, y=top_y, width=bc + 3, height=bc + 3)#绘制按钮到图上
def control_draw(bc, u_martix):
'''
绘制数字按钮
:param bc: 全局按钮大小
:param u_martix: 当前用户矩阵
:return:
'''
num = [9] * 11#记录每个数字缺失的个数为9
for i in range(9):
for j in range(9):#遍历当前数组,每有一个数字,则该数字的缺失值减一
num[u_martix[i][j]] = num[u_martix[i][j]] - 1
for i in range(9):
top_x, top_y = i * (bc + 3) + 50, 12 * (bc + 3) + 10#计算每个按钮左上角坐标
if num[i + 1]:#如果该数字还有缺失图中不到9个则按钮可以显示
B = tk.Button(root, text=f"{i + 1}", bg='white', fg='black',
font=('幼圆', 14, 'bold'), command=lambda num=i + 1: u_number(num, loca))
else:#若某个数字已经有9个说明已经不能选择该数字设置这个按钮为不可按
B = tk.Button(root, text=f"{i + 1}", bg='grey', fg='black', state=DISABLED,
font=('幼圆', 14, 'bold'), command=lambda num=i + 1: u_number(num, loca))
B.place(x=top_x, y=top_y, width=bc + 3, height=bc + 3)#绘制按钮到图上
def listen_del(loca, possible):
'''
擦除功能
:param loca: 当前位置
:return:
'''
if loca != ():
# 选定了位置才能擦除
row, col = loca[0] - 1, loca[1] - 1
if not u_martix[row][col] == 0 and martix[row][col] == 0: # 原题目中没有数字才可以删除
u_martix[row][col] = 0#设置为空
possible[row][col].clear()#清除可能值
print(loca, 2, "删除")#输出删除信息
draw(loca, martix, u_martix, STEP)#显示界面
def listen_all_note(possible, u_martix):
'''
一键提示功能
:param pos:
:return:
'''
print('all note')#输出提示信息
global show_possible, loca
show_possible = 1 # 显示提示信息
for row in range(9):
for col in range(9):
if u_martix[row][col] == 10 or u_martix[row][col] == 0:# 只有空格才会更新提示
possible[row][col] = GetPossible(u_martix, row, col)#计算可能取值
u_martix[row][col] = 10#标记这个格子为存在提示
draw(loca, martix, u_martix, STEP)#重新绘制界面
def show_hint(bc, u_martix, possible):
'''
显示提示信息
:param bc: 方格大小
:return:
'''
hint_bc = int(bc // 3)# 小数字的间距
for row in range(9):
for col in range(9):
if not u_martix[row][col] == 10:
possible[row][col].clear()# 只有u_martix[j][i]为10才表示该格子有笔记存在
continue# 若该格无笔记,则跳过该格
x, y = col * (bc + 3) + 50 + bc / 2, row * (bc + 3) + 50 + bc / 2# 计算大方格中点坐标
x = x - hint_bc#计算左上角第一个数字坐标
y = y - hint_bc#计算左上角第一个数字坐标
for ii in range(3):# 按照九宫格显示小数字
for jj in range(3):
n = ii * 3 + jj + 1#遍历1~9
if possible[row][col]: # 在用户已填的部分有可能导致部分格子无解,所以先加一个判断
for k in possible[row][col]:
if n == k:# 如果possible[row][col]中含有k则说明该格子可以填k则将k显示在方格中
cv.create_text(x + ii * hint_bc, y + jj * hint_bc,
text=f"{n}", font=('楷体', 10), fill='purple')
cv.update()#更新画布
def listen_hint(loca, u_martix):
'''
提示功能显示当前格子的可能值
:param loca: 当前格子
:return:
'''
if loca != ():
row, col = loca[0] - 1, loca[1] - 1
if u_martix[row][col] == 0 or u_martix[row][col] == 10:#只有该方格没有数字时才能提示
possible[row][col] = GetPossible(u_martix, row, col)#计算该方格可能取值
u_martix[row][col] = 10#标记该方格为存在提示
draw(loca, martix, u_martix, STEP)#重新绘制界面
def listen_note(loca):
'''
笔记功能
:param loca: 当前位置
:return:
'''
if loca != ():#若已经选中一个方格
global note_mark # 函数内修改全局变量
note_mark = not note_mark#切换笔记模式
print(f"笔记功能:{note_mark}")#输出信息
draw(loca, martix, u_martix, STEP)#绘制界面
def listen_backspace():
'''
撤回功能
:return:
'''
global u_martix, STEP
if len(STEP) > 1:# 如果操作记录中有操作
STEP.pop()# 删除操作记录最后一步操作
u_martix = STEP[-1][-1].copy()#将数独状态复位到上一步操作
loca = STEP[-1][1]#将当前选定方格复位到上一步操作
draw(loca, martix, u_martix, STEP)#重新绘制界面
else:#若已经没有操作步骤(已经回到游戏初始状态)
u_martix = STEP[-1][-1].copy()#将数独状态复制到初始状态
def control(bc):
'''
绘制五个控制按键
:param bc:全局按钮大小
:return:
'''
global loca, loca_o
lon = 9 * (bc + 3) + 50
lon = lon // 5 - 12 # 控制按键长度
ctl = ["撤回", "擦除", "笔记", "一键笔记", "提示"]
# 撤回
B1 = tk.Button(root, text=f"{ctl[0]}", bg='white', fg='black',
font=('幼圆', 12, 'bold'), command=lambda: listen_backspace())
B1.place(x=(lon + 2) * 0 + 50, y=(bc + 3) * 11 + 8, width=lon, height=bc)
# 擦除
B2 = tk.Button(root, text=f"{ctl[1]}", bg='white', fg='black',
font=('幼圆', 12, 'bold'), command=lambda: listen_del(loca, possible))
B2.place(x=(lon + 2) * 1 + 50, y=(bc + 3) * 11 + 8, width=lon, height=bc)
# 笔记
B3 = tk.Button(root, text=f"{ctl[2]}", bg='white', fg='black',
font=('幼圆', 12, 'bold'), command=lambda: listen_note(loca))
B3.place(x=(lon + 2) * 2 + 50, y=(bc + 3) * 11 + 8, width=lon, height=bc)
# 一键笔记
B4 = tk.Button(root, text=f"{ctl[3]}", bg='white', fg='black',
font=('幼圆', 12, 'bold'), command=lambda: listen_all_note(possible, u_martix))
B4.place(x=(lon + 2) * 3 + 50, y=(bc + 3) * 11 + 8, width=lon, height=bc)
# 提示
B5 = tk.Button(root, text=f"{ctl[4]}", bg='white', fg='black',
font=('幼圆', 12, 'bold'), command=lambda: listen_hint(loca, u_martix))
B5.place(x=(lon + 2) * 4 + 50, y=(bc + 3) * 11 + 8, width=lon, height=bc)
def draw(loca, martix, u_martix, STEP):
'''
绘制游戏界面
:param loca: 当前位置
:return:
'''
for i in range(9):
for j in range(9):# 绘制小方格
rect_draw(BC, i, j)
if loca != ():#根据当前选中位置绘制阴影
row, col = loca[0] - 1, loca[1] - 1
for i in range(9):
for j in range(9):
if col == i and row == j:#当前选中的方格使用灰色标记
cv.create_rectangle(col * (BC + 3) + 52, row * (BC + 3) + 52, col * (BC + 3) + 48 + BC,
row * (BC + 3) + 48 + BC, fill='grey', width=0)
elif col == i or row == j:#当前选中方格所在行列使用浅灰色标记
cv.create_rectangle(i * (BC + 3) + 52, j * (BC + 3) + 52, i * (BC + 3) + 48 + BC,
j * (BC + 3) + 48 + BC, fill='#CCCCCC', width=0)
show_hint(BC, u_martix, possible)# 显示提示信息
for row in range(9):
for col in range(9):# 在小方格上绘制数字
num_draw(BC, row, col, martix, u_martix)
print(u_martix.tolist())
print(loca)
control(BC)# 绘制五个控制按键
control_draw(BC, u_martix)# 绘制9个数字按钮
empty_count = empty(martix)# 计算数组空格
Difficulty(empty_count)# 根据空格数量显示难度
noter(note_mark)# 显示笔记提示
seq_recode(right, STEP)# 显示步骤
win(u_martix, time1, STEP)# 检查游戏是否结束
cv.update()# 更新画布
N = 30 # 空白格子数
martix = InitMartix(N)
u_martix = martix.copy() # 复制用户martix数组
BC = 40
show_possible = 0 # 是否显示提示
# 初始化可能值数组
p = []
possible = []
for i in range(9):
p.append({}.copy())
for i in range(9):
possible.append(p.copy())
# print(possible)
loca_o = tuple() # martix 数组上次滞留坐标
loca = tuple()
note_mark = False # 笔记开启标志
# note_list = [] # 笔记数字记录
# note_martix = [] # 笔记位置记录列表
wait_time = 0.5 # 自动求解速度
step = [] # 操作步骤
STEP = [[0, (0, 0), 0, martix]] # 记录每一步的状态
seq = 1 # 全局操作次序初始化
time1 = MyTimer() # 实例计时器
# 显示时间信息
show_Time(time1)
draw(loca, martix, u_martix, STEP)
cv.bind('<Button-1>', left1)
if __name__ == "__main__":
mainloop()
Loading…
Cancel
Save