From 70c23c0e68d9e2097a275adec6646ec492a652ad Mon Sep 17 00:00:00 2001 From: zart Date: Tue, 16 Apr 2024 15:53:27 +0800 Subject: [PATCH] Initial commit --- .gitignore | 8 + README.md | 42 ++ data.py | 124 ++++++ main.py | 1115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ martix.npy | Bin 0 -> 209 bytes 5 files changed, 1289 insertions(+) create mode 100644 .gitignore create mode 100644 data.py create mode 100644 main.py create mode 100644 martix.npy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..baf4912 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ + +# Ignore all files in this directory + +.idea/ +*.iml +*.ipr +*.iws + diff --git a/README.md b/README.md index ff27f8a..0f85e1f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ # Sudoku_v1 +# 说明 + +这是一个用 Python 编写的数独游戏。 + +# 目录结构 + +``` +. +├── .gitignore +├── LICENSE +├── martix.npy +├── main.py +├── data.py +└── README.md +``` + + + +# 运行 + +``` +python main.py +``` + + +# 依赖 +### 环境要求: +- Python 3.6+ + +### 第三方依赖: +- numpy 1.19.2+ + + + +# 许可证 + +本项目使用 AGPL-3.0-only 许可证。 + + + + + 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/main.py b/main.py new file mode 100644 index 0000000..2ba3928 --- /dev/null +++ b/main.py @@ -0,0 +1,1115 @@ +# -*- encoding: utf-8 -*- +""" +@File : main.py +@License : (C)Copyright 2021-2023 + +@Modify Time @Author @Version @Description +------------ ------- -------- ----------- +2023/10/11 16:14 zart20 1.0 None +""" + + +import re +import time +import tkinter as tk +from pprint import pprint +from tkinter import scrolledtext + +import data +from data import * + +BLACK = "#000000" +HINT = '#6D2DFA' +SILVER = '#FFFFFF' +u_NUMBER = '#6D2DFA' +WHITE = "#FFFFFF" +ORANGE = '#FF8C00' +RED = "#C80000" +YELLOW = "#FAE067" +GREY = '#808080' +GREYLESS = '#CCCCCC' + +martix_position_mapping = dict() +side = 40 # 方格边长 +# martix = np.load("martix.npy") + +quest = [[0, 1, 0, 8, 5, 2, 0, 6, 9], + [0, 6, 5, 7, 9, 3, 0, 0, 0], + [9, 0, 8, 0, 0, 4, 0, 5, 7], + [0, 7, 2, 4, 3, 0, 9, 0, 0], + [6, 0, 9, 2, 0, 8, 1, 3, 0], + [0, 8, 0, 1, 0, 0, 0, 2, 5], + [7, 9, 1, 5, 2, 6, 8, 4, 3], + [5, 4, 0, 9, 0, 1, 6, 7, 2], + [8, 0, 0, 3, 4, 7, 0, 0, 0]] + +martix = np.array(quest) + +u_martix = martix.copy() +info_labels = [] # label对象列表 +note_mark = False # 笔记标志 +all_hint_mark = False # 一键笔记标记 +answer_martix = [] # 回撤存储列表 +wait_time = 0.1 # 运行阻滞时间 +set_question_mark = False # 手动修改数组标志 +running = True # 运行状态 +r_step = list() # 单步记录列表 +hint_mark = None # 提示状态 +possible_num_mark = False # 手动预期功能标志 + +row = None +col = None + +root = tk.Tk() +root.title("数独") # 设置窗口标题 +width, height = 840, 610 # 设置长宽 +root.geometry(f'{width}x{height}') # 设置窗口大小 +canvas = tk.Canvas(root, bg=WHITE, bd=2, width=width, height=height, background=ORANGE, + highlightthickness=0) # 设置画布 +canvas.place(x=0, y=0, anchor=tk.NW) # 设置画布位置 + +def creat_martix_mapping(side): + mapping_dict = dict() + offeset_x = 50 + side = side + 3 + for i in range(9): # 坐标映射 + for n in range(9): + x = side * i + offeset_x + y = side * n + offeset_x + mapping_dict[n, i] = [x, y] + return mapping_dict +mapping = creat_martix_mapping(side) + +def draw_info(canvas): + top_x = (side + 3) * 11 # 文本框顶点x + top_y = 50 # 文本框顶点y + bc_x = (side + 3) * 7 + 20 # 文本框宽度 + bc_y = (side + 3) * 12 # 文本框高度 + + info = tk.Frame(canvas, bg=WHITE, relief="sunken", width=bc_x, height=bc_y) + info.place(x=top_x, y=top_y, anchor=tk.NW) # 右边栏 + info_labels.append(info) + return info + + +def draw_all_rect(canvas): + rect_mapping = dict() + block_color = [3, 4, 5] # 显色宫格依据 + for row in range(9): + for col in range(9): + 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' + x, y = mapping[row, col] + rect = canvas.create_rectangle(x, y, x + side, y + side, fill=color, width=0) # 绘制一个方格 + rect_mapping[row, col] = rect + return rect_mapping + +rect_mapping = draw_all_rect(canvas) +main_menu = tk.Menu(root) +info = draw_info(canvas) + + +class MyTimer: + # 计时器 + + def __init__(self, root): + self.root = root + 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 = self.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 + self.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) + + +time1 = MyTimer(root) + + +def creat_note_dict(): + usr_note_dict = dict() + for i in range(9): + for n in range(9): + usr_note_dict[i, n] = set() + return usr_note_dict + + +usr_note_dict = creat_note_dict() # 记录对应单元格笔记数字 + + + + + + + + + + +def label_destroy_info(labels: list[tk.Label], canvas): + """销毁label""" + for label in labels: + label.destroy() + draw_info(canvas) + + +def show_Time(right: tk.Frame, timer: MyTimer): + ''' + 显示计时器 + :param timer:计时器示例 + :return: + ''' + Time = tk.Label(right, text="运行时间: 分 秒", bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold'), anchor=tk.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) + info_labels.append(Time) + info_labels.append(Min) + info_labels.append(Sec) + + +def difficulty(): + """ + 难度显示: + :param empty: 原始空格数目 + :return: + """ + count = 0 # 计数 + for i in range(9): + for j in range(9): + if martix[i][j] == 0 or martix[i][j] == 10: + count += 1 + Level = tk.Label(info, text="难度:{:.2f}".format(count / 81), bg=WHITE, fg=BLACK, font=('幼圆', 16, 'bold'), + anchor=tk.W) + Level.place(x=30, y=60, width=300, height=30) + info_labels.append(Level) + + +def noter(): + ''' + 显示笔记提示 + :param mark: 笔记模式是否开启 + :return: + ''' + note_help1 = tk.Label(info, text=f"False 时保存笔记,当前笔记:{note_mark}", bg=WHITE, fg=RED, + font=('幼圆', 12, 'bold'), anchor=tk.W) + note_help1.place(x=30, y=90, width=300, height=30) + info_labels.append(note_help1) + + +def seq_recode(): + ''' + 显示步骤 + ''' + note_help1 = tk.Label(info, text=f"步骤记录:", bg=WHITE, fg=RED, font=('幼圆', 12, 'bold'), anchor=tk.W) + note_help1.place(x=30, y=120, width=300, height=30) + scr = scrolledtext.ScrolledText(info, fg='red', font=('幼圆', 16, 'bold')) # 设置一个可滚动文本框 + scr.place(x=30, y=150, width=280, height=300) # + + for i in range(len(answer_martix)): + scr.insert('end', f"{i + 1} {answer_martix[i][:2]}\n") # 末尾插入 + scr.config(state=tk.DISABLED) # 设置文本框为 “不能编辑” + scr.see(tk.END) # 将视图移到末尾 + + info_labels.append(note_help1) + info_labels.append(scr) + + +def basics_info(): + difficulty() + noter() + seq_recode() + + +def draw_one_num(x, y, num, color="black", tags=None): + """显示一个数字""" + if num != 0: + canvas.create_text(x + side / 2, y + side / 2, text=num, + font=('幼圆', 18, 'bold'), fill=color, tags=tags) + + +def show_usr_num(): + """显示所有用户数字""" + canvas.delete("usr_input") + canvas.delete("usr_num") + for row in range(9): + for col in range(9): + if martix[row, col] == 0: + x, y = mapping[row, col] + draw_one_num(x, y, u_martix[row, col], "blue", tags="usr_num") + if all_hint_mark: + show_all_hint() + +def show_fixa_num(): + """显示固定数字""" + canvas.delete("fixa_num") + canvas.delete("set_fixa_num") + for i in range(9): + for n in range(9): + x, y = mapping[i, n] + draw_one_num(x, y, martix[i, n], tags="fixa_num") + + +def show_set_all_num(): + global set_martix + canvas.delete("fixa_num") + canvas.delete("set_fixa_num") + for i in range(9): + for n in range(9): + x, y = mapping[i, n] + draw_one_num(x, y, set_martix[i, n], tags="set_fixa_num") + + +def click_event(event): + print(event.x, event.y) + global row, col, set_martix, cell_info_label # 全局坐标映射,方便其他功能知道操作单元格 + x = event.x + y = event.y + for key, posit_lt in mapping.items(): + if posit_lt[0] < x < posit_lt[0] + side and posit_lt[1] < y < posit_lt[1] + side: + row, col = key[0], key[1] + if set_question_mark: + set_martix[row, col] = 0 + show_set_all_num() + else: + draw(row, col) + print(key) + if possible_num_mark: + if row >= 0: + if cell_info_label: + # cell_info_label = tk.Label(info, text=f"当前选中单元格:{row}-{col}", bg=WHITE, font=("幼圆", 12, "bold")) + cell_info_label.config(text=f"当前选中单元格:{row}-{col}") + lebel_width = cell_info_label.winfo_reqwidth() + x = 160 + x = x - lebel_width / 2 + cell_info_label.place(x=x, y=105, ) + else: + if cell_info_label: + cell_info_label.config(text=f"请选中一个单元格以开始实验") + lebel_width = cell_info_label.winfo_reqwidth() + x = 160 + x = x - lebel_width / 2 + cell_info_label.place(x=x, y=105, ) + + +def draw_mini_num(row, col, possible: set, color: str, tags="mini_num"): + hint_bc = int(side // 3) # 小数字的间距 + x, y = mapping[row, col] + x, y = x + side / 2, y + side / 2 # 计算大方格中点坐标 + x = x - hint_bc # 计算左上角第一个数字坐标 + y = y - hint_bc # 计算左上角第一个数字坐标 + for i in range(3): # 按照九宫格显示小数字 + for j in range(3): + n = i * 3 + j + 1 # 遍历1~9 + if len(possible) != 0: # 在用户已填的部分有可能导致部分格子无解,所以先加一个判断 + for k in possible: + if n == k: # 如果possible[row][col]中含有k,则说明该格子可以填k,则将k显示在方格中 + canvas.create_text(x + i * hint_bc, y + j * hint_bc, + text=f"{n}", font=('hei', 12, "bold"), fill=color, tags=tags) + + +def hint_on_off(): + global hint_mark + if hint_mark == None: + show_hint() + hint_mark = 1 + else: + canvas.delete("one_hint") + hint_mark = None + + +def show_hint(): + """提示""" + global row, col + tags = "one_hint" + canvas.delete(tags) + if row != None and u_martix[row, col] == 0: + possible = GetPossible(u_martix, row, col) + draw_mini_num(row, col, possible, "green", tags) + + +def all_note_on_off(): + """一键笔记控制""" + global all_hint_mark + if not all_hint_mark: + show_all_hint() + all_hint_mark = True + else: + canvas.delete("mini_num") + all_hint_mark = False + + +def show_all_hint(): + """一键笔记""" + canvas.delete("mini_num") + for row in range(9): + for col in range(9): + if set_question_mark: + if set_martix[row, col] == 0: + possible = GetPossible(set_martix, row, col) + draw_mini_num(row, col, possible, "grey") + else: + if u_martix[row, col] == 0: + possible = GetPossible(u_martix, row, col) + draw_mini_num(row, col, possible, "grey") + + +def note_on_off(): + global note_mark, usr_note_dict + if not note_mark: + note_mark = True + else: + note_mark = False + noter() + + +def number_button(root): + ''' + 绘制数字按钮 + :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: usr_input(row, col, num)) + B.place(x=top_x, y=top_y, width=side + 3, height=side + 3) # 绘制按钮到图上 + + +def control_button(root, side): + ''' + 绘制五个控制按键 + :param side:全局按钮大小 + :return: + ''' + global loca, loca_o + lon = 9 * (side + 3) + 50 + lon = lon // 5 - 12 # 控制按键长度 + ctl = ["撤回", "擦除", "笔记", "一键笔记", "提示"] + # 撤回 + B1 = tk.Button(root, text=f"撤回", bg='white', fg='black', + font=('幼圆', 12, 'bold'), command=backspace) + B1.place(x=(lon + 2) * 0 + 50, y=(side + 3) * 11 + 8, width=lon, height=side) + # 擦除 + B2 = tk.Button(root, text=f"擦除", bg='white', fg='black', + font=('幼圆', 12, 'bold'), command=delete_ctrl) + B2.place(x=(lon + 2) * 1 + 50, y=(side + 3) * 11 + 8, width=lon, height=side) + # 笔记 + B3 = tk.Button(root, text=f"笔记", bg='white', fg='black', + font=('幼圆', 12, 'bold'), command=note_on_off) + B3.place(x=(lon + 2) * 2 + 50, y=(side + 3) * 11 + 8, width=lon, height=side) + # 一键笔记 + B4 = tk.Button(root, text=f"一键笔记", bg='white', fg='black', + font=('幼圆', 12, 'bold'), command=all_note_on_off) + B4.place(x=(lon + 2) * 3 + 50, y=(side + 3) * 11 + 8, width=lon, height=side) + # 提示 + B5 = tk.Button(root, text=f"提示", bg='white', fg='black', + font=('幼圆', 12, 'bold'), command=hint_on_off) + B5.place(x=(lon + 2) * 4 + 50, y=(side + 3) * 11 + 8, width=lon, height=side) + + +def restart(): + for child in info.winfo_children(): + child.destroy() + canvas.delete("usr_input") + global u_martix, info_labels, note_mark, all_hint_mark, answer_martix, row, col + global usr_note_dict, mapping, time1, set_question_mark, r_step, hint_mark + global possible_num_mark + u_martix = martix.copy() + info_labels = [] # label对象列表 + note_mark = False # 笔记标志 + all_hint_mark = False # 一键笔记标记 + answer_martix = [] # 回撤存储列表 + set_question_mark = False # 手动挖空标志 + r_step = list() # 单步记录列表 + hint_mark = None # 提示状态 + possible_num_mark = False + + row = None + col = None + + usr_note_dict = creat_note_dict() # 记录对应单元格笔记数字 + mapping = creat_martix_mapping(side) + basics_info() + + seq_recode() + show_usr_num() + show_fixa_num() + canvas.delete("mini_num") + time1 = MyTimer(root) + show_Time(info, time1) + + +def game_menu(menu: tk.Menu): + """游戏菜单显示""" + menu_node = tk.Menu(menu, tearoff=False) + menu_node.add_command(label="重新开始", command=lambda: restart()) + menu_node.add_command(label="暂停计时", command=lambda: time1.stop()) + 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 = tk.Menu(menu, tearoff=False) + menu_node.add_command(label="自动求解-顺序求解", command=launch_auto) + menu_node.add_command(label="自动求解-优化", command=launch_auto_better) + fmenu = tk.Menu(menu, tearoff=False) + fmenu.add_command(label='低速', command=lambda: speed_set(0.5)) + fmenu.add_command(label='中速', command=lambda: speed_set(0.2)) + fmenu.add_command(label='高速', command=lambda: speed_set(0)) + menu_node.add_cascade(label="求解速度选择", menu=fmenu) + # 在主目录菜单上新增"菜单"选项,并通过menu参数与下拉菜单绑定 + menu.add_cascade(label="自动求解", menu=menu_node) + + +def question_menu(menu: tk.Menu): + menu_node = tk.Menu(menu, tearoff=False) + menu_node.add_command(label="手动出题", command=lambda: auto_question()) + menu_node.add_command(label="挖空出题", command=lambda: manual_question()) + menu_node.add_command(label="单步求解模式", command=lambda: one_step_ctrl()) + menu_node.add_command(label="手动解预期数", command=lambda: show_possible_num()) + menu.add_cascade(label="手动模式", menu=menu_node) + + +def one_step_ctrl(): + global martix, u_martix + for w in info.winfo_children(): + w.destroy() + + info_label = tk.Label(info, text="单步求解模式", bg=WHITE, font=("幼圆", 12, "bold")) + info_label.place(x=120, y=21) + + next_step_button = tk.Button(info, text="下一步", font=("幼圆", 12, "bold"), command=lambda: one_step_start(1)) + next_step_button.place(x=100, y=60) + + next_step_button = tk.Button(info, text="上一步", font=("幼圆", 12, "bold"), command=lambda: one_step_start(-1)) + next_step_button.place(x=180, y=60) + + one_martix = martix.copy() + one_step_solve(one_martix) # 运行单步求解函数,获得满记录的r_step列表 + + +def one_step_start(step): + global answer_martix, u_martix, row, col + index = len(answer_martix) - 1 + if step == 1 and index + 1 < len(r_step): + item = r_step[index + 1] + u_martix = item[2] + row, col = item[0][0], item[0][1] + answer_martix.append(item) + seq_recode() + draw(row, col) + show_usr_num() + + + elif step == -1: + if len(answer_martix) > 1: + item = r_step[index - 1] + u_martix = item[2] + row, col = item[0][0], item[0][1] + else: + u_martix = martix + try: + answer_martix.pop() + except: + pass + seq_recode() + draw(row, col) + show_usr_num() + + +def one_step_solve(martix): + for row in range(9): + for col in range(9): + if martix[row, col] == 0: + possible = GetPossible(martix, row, col) # 所有的可能的数字 + for value in possible: + martix[row, col] = value # 将可能的数组填入 + recoder_one(row, col, value, martix) + if one_step_solve(martix): # 继续深度优先遍历填入数字 + return True # 填完最后一个数字 + martix[row, col] = 0 # 如果当前状况无解则归位,然后回溯 + recoder_one(row, col, value, martix) + return False + # 当所有的数字填完,数独求解完毕 + return True + + +def get_user_input(entry: tk.Entry): + global martix, u_martix + num = entry.get() + martix = data.InitMartix(int(num)) + u_martix = martix.copy() + restart() + + +def auto_question(): + global martix, u_martix + for w in info.winfo_children(): + w.destroy() + num_info = tk.Label(info, text="输入挖空数", bg=WHITE, font=('幼圆', 12, 'bold'), anchor=tk.W) + num_info.place(x=15, y=21) + num_entry = tk.Entry(info) + num_entry.config(font=('幼圆', 12, 'bold'), width=10) + num_entry.place(x=110, y=23) + get_input_button = tk.Button(info, text="出题", font=('幼圆', 12, 'bold'), + command=lambda: get_user_input(num_entry)) + get_input_button.place(x=90, y=60) + + +def question_ok(set_martix): + global martix, u_martix, set_question_mark + martix = set_martix + set_question_mark = False + restart() + + +def manual_question(): + global martix, u_martix, set_question_mark, set_martix + martix = data.InitMartix(0) + set_martix = martix.copy() + show_fixa_num() + + for w in info.winfo_children(): + w.destroy() + num_info = tk.Label(info, text="鼠标左键点击你想挖空的单元格", bg=WHITE, font=('幼圆', 12, 'bold'), anchor=tk.W) + num_info.place(x=30, y=21) + + set_question_mark = True + + get_input_button = tk.Button(info, text="确认", font=('幼圆', 12, 'bold'), + command=lambda: question_ok(set_martix=set_martix)) + get_input_button.place(x=90, y=60) + + get_input_button = tk.Button(info, text="取消", font=('幼圆', 12, 'bold'), + command=manual_question) + get_input_button.place(x=150, y=60) + + # pprint(martix) + + +def show_possible_num(): + global martix, u_martix, row_entry, col_entry, big_cell_entry, cell_entry, cell_info_label + global possible_num_mark + + possible_num_mark = True + for w in info.winfo_children(): + w.destroy() + jx = 75 + info_label = tk.Label(info, text="手动解预期数", bg=WHITE, font=("幼圆", 12, "bold")) + info_label.place(x=120, y=21) + + if row and col: + cell_info_label = tk.Label(info, text=f"当前选中单元格:{row}-{col}", bg=WHITE, font=("幼圆", 12, "bold")) + lebel_width = cell_info_label.winfo_reqwidth() + x = 160 + x = x - lebel_width / 2 + cell_info_label.place(x=x, y=105, ) + else: + cell_info_label = tk.Label(info, text=f"请选中一个单元格以开始实验", bg=WHITE, fg=RED, + font=("幼圆", 12, "bold")) + lebel_width = cell_info_label.winfo_reqwidth() + x = 160 + x = x - lebel_width / 2 + cell_info_label.place(x=x, y=105, ) + + next_cell_button = tk.Button(info, text="自动解", font=("幼圆", 12, "bold"), command=lambda: auto_possible_verify()) + next_cell_button.place(x=100, y=60) + + next_cell_button1 = tk.Button(info, text="验证", font=("幼圆", 12, "bold"), command=lambda: possible_verify()) + next_cell_button1.place(x=180, y=60) + + row_label = tk.Label(info, text=f"当前行可填数字:", bg=WHITE, font=("幼圆", 12, "bold")) + row_label.place(x=20, y=150) + + row_entry = tk.Entry(info) + row_entry.place(x=20, y=175) + row_entry.config(font=('幼圆', 12, 'bold'), width=10) + + col_label = tk.Label(info, text=f"当前列可填数字:", bg=WHITE, font=("幼圆", 12, "bold")) + col_label.place(x=20, y=150 + jx) + + col_entry = tk.Entry(info) + col_entry.place(x=20, y=175 + jx) + col_entry.config(font=('幼圆', 12, 'bold'), width=10) + + big_cell = tk.Label(info, text=f"当前宫格可填数字:", bg=WHITE, font=("幼圆", 12, "bold")) + big_cell.place(x=20, y=150 + 2 * jx) + + big_cell_entry = tk.Entry(info) + big_cell_entry.place(x=20, y=175 + 2 * jx) + big_cell_entry.config(font=("幼圆", 12, "bold"), width=10) + + cell = tk.Label(info, text=f"当前格可能填写数字:", bg=WHITE, font=("幼圆", 12, "bold")) + cell.place(x=20, y=150 + 3 * jx) + + cell_entry = tk.Entry(info) + cell_entry.place(x=20, y=175 + 3 * jx) + cell_entry.config(font=("幼圆", 12, "bold"), width=10) + + +def possible_verify(): + """验证功能""" + global martix, u_martix, row_entry, col_entry, big_cell_entry, cell_entry + global row, col + + if row is not None: + row_nums = row_entry.get() + row_nums = trans_int_set(row_nums) + + col_nums = col_entry.get() + col_nums = trans_int_set(col_nums) + + big_cell_nums = big_cell_entry.get() + big_cell_nums = trans_int_set(big_cell_nums) + + cell_nums = cell_entry.get() + cell_nums = trans_int_set(cell_nums) + + row_entry.delete(0, tk.END) + if get_row_possible(u_martix, row, col) == row_nums: + row_entry.insert(0, "正确") + else: + row_entry.insert(0, "错误") + + col_entry.delete(0, tk.END) + if get_col_possible(u_martix, row, col) == col_nums: + col_entry.insert(0, "正确") + else: + col_entry.insert(0, "错误") + + big_cell_entry.delete(0, tk.END) + if get_big_cell_possible(u_martix, row, col) == big_cell_nums: + big_cell_entry.insert(0, "正确") + else: + big_cell_entry.insert(0, "错误") + + cell_entry.delete(0, tk.END) + if GetPossible(u_martix, row, col) == cell_nums: + cell_entry.insert(0, "正确") + else: + cell_entry.insert(0, "错误") + + print(row_nums, col_nums, big_cell_nums, cell_nums) + print(get_row_possible(u_martix, row, col), get_col_possible(u_martix, row, col), + get_big_cell_possible(u_martix, row, col), GetPossible(u_martix, row, col)) + + +def auto_possible_verify(): + """验证功能""" + global martix, u_martix, row_entry, col_entry, big_cell_entry, cell_entry + global row, col + + if row is not None: + row_entry.delete(0, tk.END) + row_nums = get_row_possible(u_martix, row, col) + row_nums = [str(i) for i in row_nums] + row_nums = ",".join(row_nums) + row_entry.insert(0, row_nums) + + col_entry.delete(0, tk.END) + col_nums = get_col_possible(u_martix, row, col) + col_nums = [str(i) for i in col_nums] + col_nums = ",".join(col_nums) + col_entry.insert(0, col_nums) + + big_cell_entry.delete(0, tk.END) + big_cell_nums = get_big_cell_possible(u_martix, row, col) + big_cell_nums = [str(i) for i in big_cell_nums] + big_cell_nums = ",".join(big_cell_nums) + big_cell_entry.insert(0, big_cell_nums) + + cell_entry.delete(0, tk.END) + cell_nums = GetPossible(u_martix, row, col) + cell_nums = [str(i) for i in cell_nums] + cell_nums = ",".join(cell_nums) + cell_entry.insert(0, cell_nums) + + print(row_nums, col_nums, big_cell_nums, cell_nums) + print(get_row_possible(u_martix, row, col), get_col_possible(u_martix, row, col), + get_big_cell_possible(u_martix, row, col), GetPossible(u_martix, row, col)) + + +def trans_int_set(nums: str): + nums = re.findall(r"\d+", nums) + nums = "".join(nums) + Set = set() + for num in list(nums): + Set.add(int(num)) + return Set + + +def get_row_possible(martix, row, col): + """当前行的可能数字""" + Nums = {1, 2, 3, 4, 5, 6, 7, 8, 9} + Set = set() + for i in range(9): + if i == row: + continue + Set.add(int(martix[i, col])) # 将所在列出现数字加入集合 + # print(martix[i,col]) + return Nums - Set + + +def get_col_possible(martix, row, col): + """当前列的可能数字""" + Nums = {1, 2, 3, 4, 5, 6, 7, 8, 9} + Set = set() + for i in range(9): + if i == col: + continue + Set.add(int(martix[row, i])) # 将所在列出现数字加入集合 + # print(martix[row,i]) + return Nums - Set + + +def get_big_cell_possible(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(m * 3, m * 3 + 3): + for j in range(n * 3, n * 3 + 3): + if row == i and j == col: + continue + Set.add(int(martix[i, j])) # 将所在九宫格出现数字加入集合 + # print(martix[i,j]) + return Nums - Set # 返回1~9中没出现过的数字,即可选的 + + +def next_cell_query(): + for row in range(9): + for col in range(9): + if martix[row, col] == 0: + return row, col + + +def speed_set(num): + global wait_time + wait_time = num + + +def launch_auto(): + time1.restart() + auto_Solve(u_martix) + win(u_martix, time1) + + +def auto_Solve(martix): + for row in range(9): + for col in range(9): + if martix[row, col] == 0: + possible = GetPossible(martix, row, col) # 所有的可能的数字 + for value in possible: + martix[row, col] = value # 将可能的数组填入 + + draw(row, col) + recoder(row, col, value, u_martix) + show_usr_num() + canvas.update_idletasks() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + + if auto_Solve(martix): # 继续深度优先遍历填入数字 + return True # 填完最后一个数字 + + martix[row, col] = 0 # 如果当前状况无解则归位,然后回溯 + recoder(row, col, value, u_martix) # 发生回溯时记录 + draw(row, col) + show_usr_num() + canvas.update() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + + return False + # 当所有的数字填完,数独求解完毕 + return True + + +def recoder_one(row, col, num, u_martix): + # 返回记录表 + global r_step + step_martix = u_martix.copy() + r_step.append([(row, col), num, step_martix]) + + +def recoder(row, col, num, u_martix): + global answer_martix + step_martix = u_martix.copy() + if len(answer_martix) > 0: + old_row, old_col = answer_martix[-1][0] + if old_row == row and old_col == col: + answer_martix[-1] = [(row, col), num, step_martix] + else: + answer_martix.append([(row, col), num, step_martix]) + else: + answer_martix.append([(row, col), num, step_martix]) + seq_recode() + + +def launch_auto_better(): + time1.restart() + auto_Solve_better(u_martix) + win(u_martix, time1) + noter() + + +def auto_Solve_better(u_M): + global note_mark + note_mark = False # 关闭笔记 + + 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 # 将确定的数字填入填入 + + draw(row, col) + canvas.update() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + recoder(row, col, value, u_martix) + show_usr_num() + + time.sleep(wait_time) # 停顿一段时间,显示求解步骤 + if auto_Solve_better(u_M): # 继续深度优先遍历填入数字 + return True # 填完最后一个数字 + u_M[row, col] = 0 # 如果当前填入的数字会导致后面无解则依然填入0表示空白待填 + + draw(row, col) + canvas.update() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + recoder(row, col, value, u_martix) + + show_usr_num() + 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 # 将可能的数组填入 + recoder(row, col, value, u_martix) + draw(row, col) + + canvas.update() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + + show_usr_num() + + time.sleep(wait_time) # 停顿一段时间,显示求解步骤 + if auto_Solve_better(u_M): # 继续深度优先遍历填入数字 + return True # 填完最后一个数字 + u_M[row, col] = 0 # 如果当前填入的数字会导致后面无解则依然填入0表示空白待填 + + draw(row, col) + + canvas.update() + time.sleep(wait_time) # 防止运算过快,减慢演算步骤 + recoder(row, col, value, u_martix) + + show_usr_num() + + time.sleep(wait_time) # 停顿一段时间,显示求解步骤 + return False + return True # 当所有的数字填完,数独求解完毕 + + +def win(u_martix, timer: MyTimer): + 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 info.winfo_children(): + w.destroy() + timer.stop() + win_words = tk.Label(info, text=f"游戏结束,恭喜通过", bg='white', fg='red', font=('幼圆', 16, 'bold'), + anchor=tk.W) + win_words.place(relx=0.1, rely=0.3) + win_time = tk.Label(info, text=f"耗时:{timer.min.get()}分{timer.sec.get()}秒", bg='white', fg='red', + font=('幼圆', 16, 'bold'), anchor=tk.W) + win_time.place(relx=0.1, rely=0.4) + win_step = tk.Label(info, text=f"花费步数:{len(answer_martix)}步", bg='white', fg='red', + font=('幼圆', 16, 'bold'), anchor=tk.W) + win_step.place(relx=0.1, rely=0.5) + re = tk.Button(info, text=f"重新开始", bg='white', fg='black', + font=('幼圆', 14, 'bold'), command=lambda: restart()) + re.place(relx=0.1, rely=0.6, relwidth=0.5) + + +def load_game(): + global martix, u_martix + martix = data.InitMartix(30) + pprint(martix) + restart() + + +def draw(row, col): + rect_color(row, col) + select_rect_color(row, col) + # show_usr_num() + + +def show_all_note(): + canvas.delete("usr_note") + for i in range(9): + for n in range(9): + possible = usr_note_dict[i, n] + draw_mini_num(i, n, possible, "red", "usr_note") + + +def delete_ctrl(): + """擦除功能""" + global u_martix, usr_note_dict + if row != None: + if martix[row, col] == 0: + canvas.delete("usr_input") + u_martix[row, col] = 0 + show_usr_num() + if len(usr_note_dict[row, col]) != 0: + usr_note_dict[row, col] = set() + show_all_note() + + +def backspace(): + """撤回功能""" + global answer_martix, u_martix, row, col + if len(answer_martix) > 0: + loca, num, step_martix = answer_martix.pop() + u_martix = step_martix + row, col = loca + seq_recode() + delete_ctrl() + draw(row, col) + + +def usr_input(row, col, num, color="blue"): + global answer_martix + if not note_mark: + if row != None and martix[row, col] == 0: + canvas.delete("usr_input") + canvas.delete("usr_note") + canvas.delete("mini_num") + + step_martix = u_martix.copy() + + u_martix[row, col] = num + usr_note_dict[row, col] = set() + x, y = mapping[row, col] + draw_one_num(x, y, num, color, "usr_input") + u_martix[row, col] = num + show_all_note() + show_usr_num() + + if len(answer_martix) > 0: + old_row, old_col = answer_martix[-1][0] + if old_row == row and old_col == col: + answer_martix[-1] = [(row, col), num, step_martix] + else: + answer_martix.append([(row, col), num, step_martix]) + else: + answer_martix.append([(row, col), num, step_martix]) + seq_recode() + win(u_martix, time1) + else: + if row != None and martix[row, col] == 0: + u_martix[row, col] = 0 + usr_note_dict[row, col].add(num) + draw_mini_num(row, col, usr_note_dict[row, col], "red", "usr_note") + show_usr_num() + + +def rect_color(row, col): # 显示当前选中格之外所有颜色 + block_color = [3, 4, 5] # 显色宫格依据 + for key, item in rect_mapping.items(): + if key[0] != row and key[1] != col: + logi_row = key[0] in block_color and key[1] in block_color # 显色逻辑 + logi_col = key[0] in block_color or key[1] in block_color # 显色逻辑 + if not logi_row and logi_col: + color = "#FAE067" + canvas.itemconfig(item, fill=color) + else: + color = 'white' + canvas.itemconfig(item, fill=color) + + +def select_rect_color(row, col): # 突出显示当前选中格 + for key, item in rect_mapping.items(): + if key[0] == row and key[1] == col: + canvas.itemconfig(item, fill='#DCA0DC') + elif key[0] == row or key[1] == col: + canvas.itemconfig(item, fill='#ECD0EC') + + +def main(): + show_Time(info, time1) + root.config(menu=main_menu) + game_menu(main_menu) + auto_menu(main_menu) + question_menu(main_menu) + number_button(root) + control_button(root, side) + basics_info() + canvas.bind('', click_event) + show_fixa_num() + + + +if __name__ == '__main__': + main() + tk.mainloop() + # config = Config() + # config.max_depth = 5 + + + + + + + diff --git a/martix.npy b/martix.npy new file mode 100644 index 0000000000000000000000000000000000000000..802949dac0ce556f6e0d1a5e37f82d4e2c7c4713 GIT binary patch literal 209 zcmbV@JqiLb6olvHKb|686w+9(2e4CYEUYBBkrl+1L~PVkcwygO!6^pj`i0{iXrV!6#ebjtAj^fWm6oo literal 0 HcmV?d00001