You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
21 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
@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)
martix_position_mapping = creat_martix_mapping(side)
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
global STEP, loca, loca_o
for key, posit_lt in martix_position_mapping.items():
if posit_lt[0] < x < posit_lt[0] + side and posit_lt[1] < y < posit_lt[1] + side:
print(key)
if key[0] >= 0 and key[1] >= 0 and not martix[key[0], key[1]]:
# print(col, row, "dks")
if loca:#若已经点击过坐标
loca_o = loca # 保留上次点击过的坐标
loca = (key[0] + 1, key[1] + 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(side):
'''
绘制数字按钮
:param side: 全局按钮大小
:return:
'''
for i in range(9):#遍历0~9
top_x, top_y = i * (side + 3) + 50, 12 * (side + 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=side + 3, height=side + 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(side, u_martix, possible):
'''
显示提示信息
:param side: 方格大小
:return:
'''
hint_bc = int(side // 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 * (side + 3) + 50 + side / 2, row * (side + 3) + 50 + side / 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显示在方格中
canvas.create_text(x + ii * hint_bc, y + jj * hint_bc,
text=f"{n}", font=('楷体', 10), fill='purple', tags="mini_num")
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):
def draw_fixation_num():
for i in range(9):
for n in range(9):
x,y = martix_position_mapping[i,n]
canvas.create_text(x+ side / 2, x + side / 2,
text=f"{u_martix[i, n]}", font=('幼圆', 18, 'bold'), fill="black", tags="fixa_num")
num_draw(side,i,n,martix,u_martix)
def draw(loca, martix, u_martix, STEP):
'''
绘制游戏界面
:param loca: 当前位置
:return:
'''
if loca != ():#根据当前选中位置绘制阴影
canvas.delete("rect")
row, col = loca[0] - 1, loca[1] - 1
for i in range(9):
for j in range(9):
if col == i and row == j:#当前选中的方格使用灰色标记
canvas.create_rectangle(col * (side + 3) + 52, row * (side + 3) + 52, col * (side + 3) + 48 + side,
row * (side + 3) + 48 + side, fill='grey', width=0, tags="rect")
elif col == i or row == j:#当前选中方格所在行列使用浅灰色标记
canvas.create_rectangle(i * (side + 3) + 52, j * (side + 3) + 52, i * (side + 3) + 48 + side,
j * (side + 3) + 48 + side, fill='#CCCCCC', width=0, tags="rect")
canvas.delete("num")
num_draw(side, row, col, martix, u_martix)
canvas.delete("mini_num")
show_hint(side, u_martix, possible)# 显示提示信息
# print(u_martix.tolist())
# print(loca)
empty_count = empty(martix)# 计算数组空格
Difficulty(empty_count)# 根据空格数量显示难度
noter(note_mark)# 显示笔记提示
seq_recode(right, STEP)# 显示步骤
win(u_martix, time1, STEP)# 检查游戏是否结束
# canvas.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 # 全局操作次序初始化
draw_all_rect() # 显示底图
control(side) # 绘制五个控制按键
control_draw(side, u_martix) # 绘制9个数字按钮
draw_fixation_num()
time1 = MyTimer() # 实例计时器
# 显示时间信息
show_Time(time1)
draw(loca, martix, u_martix, STEP)
canvas.bind('<Button-1>', left1)
if __name__ == "__main__":
mainloop()