diff --git a/UI.py b/UI.py new file mode 100644 index 0000000..633b797 --- /dev/null +++ b/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('', left1) + + mainloop() \ No newline at end of file diff --git a/data.py b/data.py new file mode 100644 index 0000000..3df7127 --- /dev/null +++ b/data.py @@ -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 \ No newline at end of file diff --git a/martix.npy b/martix.npy new file mode 100644 index 0000000..802949d Binary files /dev/null and b/martix.npy differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0408cb4 Binary files /dev/null and b/requirements.txt differ diff --git a/sudoku.png b/sudoku.png new file mode 100644 index 0000000..ab6b004 Binary files /dev/null and b/sudoku.png differ diff --git a/sudoku.py b/sudoku.py new file mode 100644 index 0000000..5638bf5 --- /dev/null +++ b/sudoku.py @@ -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('', left1) + +if __name__ == "__main__": + mainloop() \ No newline at end of file