微信小程序:数独游戏九宫格
#算法1.py
import numpy as np
import copy
from itertools import combinations
class point:
def __init__(self,行,列):
self.行=行
self.列=列
self.宫=行//3*3+列//3
self.中的候选数=[*range(1,10)]
class 数独:
def __init__(self,sudoku):
self.sudoku=sudoku
self.待填单元格=initPoint(sudoku)
self.已填单元格=[]
self.存在有效方法=True
class 无候选数错误(Exception):
def __init__(self, msg=''):
self.message = msg
def __str__(self):
return self.message
def 该行数字(行,sudoku):temp=set(sudoku[行]);temp.remove(0);return(list(temp))
def 该列数字(列,sudoku):temp=set(sudoku[:,列]);temp.remove(0);return(list(temp))
def 该宫数字(行,列,sudoku):temp=set(sudoku[行-行%3:行-行%3+3,列-列%3:列-列%3+3].flatten());temp.remove(0);return(list(temp))
def initPoint(sudoku):
待填单元格=[]
i=j=0
while i!=9:
if sudoku[i][j]==0:
单元格=point(i,j)
# print([*该行数字(单元格.行,sudoku),*该列数字(单元格.列,sudoku),*该宫数字(单元格.行,单元格.列,sudoku)])
for 格中不允许填的数字 in [*该行数字(单元格.行,sudoku),*该列数字(单元格.列,sudoku),*该宫数字(单元格.行,单元格.列,sudoku)]:
# print(单元格.中的候选数,格中不允许填的数字)
if 格中不允许填的数字 in 单元格.中的候选数:
单元格.中的候选数.remove(格中不允许填的数字)
待填单元格+=[单元格]
if j==8:i+=1
j=(j+1 if j!=8 else 0)
return 待填单元格
def 该行待填单元格(行,数独里的待填单元格):return([i for i in 数独里的待填单元格 if i.行==行 and hasattr(i,'中的候选数')])
def 该列待填单元格(列,数独里的待填单元格):return([i for i in 数独里的待填单元格 if i.列==列 and hasattr(i,'中的候选数')])
def 该宫待填单元格(宫,数独里的待填单元格):return([i for i in 数独里的待填单元格 if i.宫==宫 and hasattr(i,'中的候选数')])
def 这里面的候选数(待填单元格):return([i for p in 待填单元格 for i in p.中的候选数])
def 消除相关候选数(已填单元格,sudoku):
for 单元格 in 该行待填单元格(已填单元格.行,sudoku.待填单元格)+该列待填单元格(已填单元格.列,sudoku.待填单元格)+该宫待填单元格(已填单元格.宫,sudoku.待填单元格):
if 已填单元格.value in 单元格.中的候选数:单元格.中的候选数.remove(已填单元格.value)
def 填写(sudoku,单元格,值):
起到效果(sudoku)
del 单元格.中的候选数
单元格.value=sudoku.sudoku[单元格.行][单元格.列]=值
消除相关候选数(单元格,sudoku)
sudoku.已填单元格.append(单元格)
sudoku.待填单元格.remove(单元格)
def 唯一候选数法(sudoku):
for 单元格 in sudoku.待填单元格[:]:#[:]防止for remove bug
if len(单元格.中的候选数)==0:raise 无候选数错误('{}行{}列中无候选数'.format(单元格.行+1,单元格.列+1))
if hasattr(单元格,'中的候选数') and len(单元格.中的候选数)==1:填写(sudoku,单元格,单元格.中的候选数[0])
def 隐性唯一候选数法(sudoku):
for 单元格 in sudoku.待填单元格[:]:#[:]防止for remove bug
# if len(单元格.中的候选数)==0:raise 无候选数错误('{}{}格中无候选数'.format(单元格.行,单元格.列))
for 候选数 in 单元格.中的候选数:
if 1 in (这里面的候选数(该行待填单元格(单元格.行,sudoku.待填单元格)).count(候选数),这里面的候选数(该列待填单元格(单元格.列,sudoku.待填单元格)).count(候选数),这里面的候选数(该宫待填单元格(单元格.宫,sudoku.待填单元格)).count(候选数)):
填写(sudoku,单元格,候选数)
break
def 显式数集删减法(sudoku):
def 数集删减(待填单元格,plist,set):
for 单元格 in [i for i in 待填单元格 if i not in plist]:
temp=copy.deepcopy(单元格.中的候选数)
# if temp==0:raise 无候选数错误('{}{}格中无候选数'.format(单元格.行,单元格.列))
单元格.中的候选数=[i for i in 单元格.中的候选数 if i not in set]
if temp!=单元格.中的候选数:起到效果(sudoku)
def 显式数集(待填单元格):
for 数集数 in range(2,6):
可能的格子=[单元格 for 单元格 in 待填单元格 if len(单元格.中的候选数)<=数集数]
avail=这里面的候选数(可能的格子)
if len(可能的格子)<数集数:pass
elif len(可能的格子)==数集数 and len(set(avail))==数集数:数集删减(待填单元格,可能的格子,set(avail))
elif len(可能的格子)>数集数:
for c in combinations(可能的格子,数集数):
smallset=set([i for 单元格 in c for i in 单元格.中的候选数])
if len(smallset)==数集数:数集删减(待填单元格,c,smallset)
for i in range(9):
显式数集(该行待填单元格(i,sudoku.待填单元格))
显式数集(该列待填单元格(i,sudoku.待填单元格))
显式数集(该宫待填单元格(i,sudoku.待填单元格))
def 候选数区块删减法(sudoku):
def 直宫剪切(sudoku,某个候选数字,aplist,bplist):
a中有该数字的单元格=[单元格 for 单元格 in aplist if 某个候选数字 in 单元格.中的候选数]
temp=[单元格 in bplist for 单元格 in a中有该数字的单元格]
if all(temp) and temp:#a中所有有该数字的单元格都在b中,all()对空列表会返回Ture
for b的单元格 in bplist:
if b的单元格 not in a中有该数字的单元格 and 某个候选数字 in b的单元格.中的候选数:
b的单元格.中的候选数.remove(某个候选数字)
起到效果(sudoku)
def 该宫的行列(宫):return(zip(*[(i+宫//3*3,i+宫%3*3) for i in [0,1,2]]))
宫=0;某个候选数字=1
while 宫!=9:
宫行,宫列=该宫的行列(宫)
for 行 in 宫行:
直宫剪切(sudoku,某个候选数字,该行待填单元格(行,sudoku.待填单元格),该宫待填单元格(宫,sudoku.待填单元格))
直宫剪切(sudoku,某个候选数字,该宫待填单元格(宫,sudoku.待填单元格),该行待填单元格(行,sudoku.待填单元格))
for 列 in 宫列:
直宫剪切(sudoku,某个候选数字,该列待填单元格(列,sudoku.待填单元格),该宫待填单元格(宫,sudoku.待填单元格))
直宫剪切(sudoku,某个候选数字,该宫待填单元格(宫,sudoku.待填单元格),该列待填单元格(列,sudoku.待填单元格))
if 某个候选数字==9:宫+=1
某个候选数字=(某个候选数字+1 if 某个候选数字!=9 else 1)
def 隐性数集删减法(sudoku):
def 数集删减(plist,set):
for 单元格 in plist:单元格.中的候选数=[i for i in 单元格.中的候选数 if i in set]
def 隐式数集(待填单元格):
for 数集数 in range(2,5):
for c in combinations(range(1,10),数集数):
可能的格子=[单元格 for 单元格 in 待填单元格 if any(ci in 单元格.中的候选数 for ci in c)]
if len(可能的格子)==数集数 and all(ci in set(这里面的候选数(可能的格子)) for ci in c):数集删减(可能的格子,c)
for i in range(9):
隐式数集(该行待填单元格(i,sudoku.待填单元格))
隐式数集(该列待填单元格(i,sudoku.待填单元格))
隐式数集(该宫待填单元格(i,sudoku.待填单元格))
def 候选数矩形删减法(sudoku):
def 数集删减行(行,列,数,sudoku):#是这两行但不是这两列的格子删掉数
# # print(行,列)
for i in [单元格 for 单元格 in sudoku.待填单元格 if 单元格.行 in 行 and 单元格.列 not in 列]:
try:
i.中的候选数.remove(数)
起到效果(sudoku)
except(ValueError):pass
def 数集删减列(列,行,数,sudoku):#是这两列但不是这两行的格子删掉数
for i in [单元格 for 单元格 in sudoku.待填单元格 if 单元格.列 in 列 and 单元格.行 not in 行]:
try:
i.中的候选数.remove(数)
起到效果(sudoku)
except(ValueError):pass
def 矩形数集(alist,blist,数,行列='行'):#给定两列,如果都有两个该候选数且在两行中,删除行中其他数
alist=[单元格 for 单元格 in alist if 数 in 单元格.中的候选数]#列中含有候选数的单元格
blist=[单元格 for 单元格 in blist if 数 in 单元格.中的候选数]
#if列中含有候选数的单元格只有两个,且他们都集中在两行内
if not (alist or blist):return()
if 行列=='行':
要删减的列=set([单元格.列 for 单元格 in alist]+[单元格.列 for 单元格 in blist])
if len(alist)==len(blist)==len(要删减的列)==2:数集删减列(要删减的列,[alist[0].行,blist[0].行],数,sudoku)
else:
要删减的行=set([单元格.行 for 单元格 in alist]+[单元格.行 for 单元格 in blist])
if len(alist)==len(blist)==len(要删减的行)==2:数集删减行(要删减的行,[alist[0].列,blist[0].列],数,sudoku)
i=1;j=0
while j!=36:
a,b=[c for c in combinations(range(9),2)][j]
矩形数集(该行待填单元格(a,sudoku.待填单元格),该行待填单元格(b,sudoku.待填单元格),i)
矩形数集(该列待填单元格(a,sudoku.待填单元格),该列待填单元格(b,sudoku.待填单元格),i,'列')
if i==9:j+=1
i=(i+1 if i!=9 else 1)
迭代次数=0
def 暴力破解(sudoku):
# # print('爆破')
global 迭代次数
迭代次数+=1
# print(迭代次数)
while sudoku.待填单元格:
数独副本=copy.deepcopy(sudoku)
最小单元格候选数数量=9
for 单元格 in sudoku.待填单元格:
if len(单元格.中的候选数)==2:
尝试的单元格=单元格
break
elif len(单元格.中的候选数)<最小单元格候选数数量:
最小单元格候选数数量=len(单元格.中的候选数)
尝试的单元格=单元格
尝试的单元格坐标存档=copy.deepcopy([尝试的单元格.行,尝试的单元格.列])
if not 尝试的单元格.中的候选数:raise 无候选数错误
# print('尝试的数',尝试的单元格坐标存档,尝试的单元格.中的候选数[-1],'\n',sudoku.sudoku)
尝试的单元格.中的候选数=[尝试的单元格.中的候选数[-1]]
try:
# print('计算')
sudoku=计算(sudoku)
except 无候选数错误:
# print('无候选数错误')
# print('尝试的数',尝试的单元格坐标存档,'\n',sudoku.sudoku)
# # print(sudoku.sudoku)
sudoku=数独副本
# # print(sudoku.sudoku)
# # print('?')
for 单元格 in sudoku.待填单元格:
# print('for')
if [单元格.行,单元格.列]==尝试的单元格坐标存档:
# print('if')
# print(单元格.中的候选数)
del 单元格.中的候选数[-1]
if len(单元格.中的候选数)==1:填写(sudoku,单元格,单元格.中的候选数[0])
# try:# print(单元格.中的候选数)
# except AttributeError:# print(单元格.value)
# # print(sudoku.待填单元格[0].中的候选数)
else:
# print('else')
# print('尝试的数',尝试的单元格坐标存档,'\n',sudoku.sudoku)
# print(sudoku)
for 单元格 in sudoku.待填单元格:
# print('else for')
# print([单元格.行,单元格.列])
if [单元格.行,单元格.列]==尝试的单元格坐标存档:
# print('else for if')
填写(sudoku,单元格,单元格.中的候选数[-1])
# print('else for end')
# print('else end')
finally:
# print('finally')
del 数独副本
del 尝试的单元格坐标存档
# print('迭代次数',迭代次数)
迭代次数-=1
# print(sudoku.sudoku)
return(sudoku)
def 起到效果(sudoku):
sudoku.有效=True
sudoku.存在有效方法=True
def 计算(sudoku):
if type(sudoku)!=数独:sudoku=数独(np.array(sudoku).reshape(9,9))
解题技巧=[候选数矩形删减法,隐性数集删减法 ,候选数区块删减法,显式数集删减法 ,隐性唯一候选数法,唯一候选数法]
# 解题技巧=[显式数集删减法 ,隐性唯一候选数法,唯一候选数法]
while sudoku.存在有效方法:
sudoku.存在有效方法=False
for func in 解题技巧:
sudoku.有效=True
while sudoku.有效:#该方法有效就继续计算至无效
sudoku.有效=False
# # print('前',[p.中的候选数 for p in sudoku.待填单元格])
# # print(这里面的候选数(sudoku.待填单元格))
func(sudoku)#尝试更改有效和存在有效为True
# # print(func)
# # print(这里面的候选数(sudoku.待填单元格))
# # print('已',[p.value for p in sudoku.已填单元格])
# # print('未',[p.中的候选数 for p in sudoku.待填单元格])
if sudoku.待填单元格:#如果还有未解出的
sudoku.存在有效方法=True
# print('爆破')
sudoku=暴力破解(sudoku)
# print('爆破end')
# for i in range(9):# print('未',[(单元格.中的候选数) for 单元格 in sudoku.待填单元格 if 单元格.行==i and 单元格.宫!='a'])#查看某格的候选数
return sudoku
test唯一=[4, 3, 9, 0, 6, 0, 2, 8, 1, 6, 0, 0, 0, 4, 2, 7, 5, 3, 2, 5, 7, 0, 1, 8, 0, 9, 6, 5, 0, 0, 0, 8, 0, 0, 6, 9, 0, 8, 0, 1, 0, 4, 5, 0, 7, 9, 0, 1, 0, 2, 5, 0, 3, 4, 1, 6, 0, 4, 0, 9, 3, 0, 8, 7, 0, 0, 0, 3, 0, 6, 0, 2, 8, 4, 3, 2, 7, 6, 0, 1, 5]
test隐唯=[4, 0, 3, 0, 0, 1, 0, 2, 0, 0, 2, 6, 0, 7, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 8, 0, 6, 0, 3, 0, 3, 6, 0, 0, 0, 9, 2, 0, 8, 0, 0, 0, 2, 0, 0, 6, 9, 4, 7, 0, 0, 3, 0, 0, 4, 8, 0, 0, 5, 0, 0, 8, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
test显式=[0, 0, 9, 0, 8, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 7, 0, 0, 5, 0, 2, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 6, 0]
test区块=[0, 0, 4, 6, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 2, 0, 4, 0, 0, 0, 9, 0, 9, 8, 0, 0, 4, 0, 3, 5, 0, 1, 0, 3, 0, 0, 0, 0, 4, 0, 4, 6, 0, 0, 0, 3, 8, 0, 7, 0, 3, 6, 0, 2, 4, 0, 7, 0, 0, 4, 0, 0, 6, 1, 0, 3, 5, 0, 1, 9, 3, 0, 0, 4, 6, 0]
test隐式=[5, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 7, 0, 0, 0, 0, 0, 4, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 7, 0, 2, 0, 6, 0, 0, 0, 0, 1, 0, 0, 0, 0, 8, 0, 0, 0, 0, 4, 5, 0, 0, 0, 0]
test矩形=[0, 0, 0, 3, 0, 0, 1, 0, 0, 5, 0, 0, 4, 0, 1, 0, 9, 0, 0, 0, 1, 0, 2, 8, 6, 0, 0, 0, 9, 0, 8, 0, 0, 0, 0, 1, 0, 0, 8, 0, 1, 7, 0, 0, 2, 0, 1, 0, 0, 4, 0, 8, 0, 0, 0, 0, 4, 0, 8, 5, 0, 0, 0, 3, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 2, 6, 3, 4, 0, 0, 0]
test爆破=[0, 2, 6, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 9, 0, 2, 0, 0, 3, 0, 0, 0, 8, 0, 6, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 6, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 9, 3, 4, 0, 6, 1, 2, 0, 0, 0, 1, 8, 7, 0, 0, 0, 0, 0, 0, 9, 0, 1, 0, 0, 0, 5, 0, 4, 0, 0, 0, 0, 9, 1, 0]
sudoku=test爆破
# print(计算(sudoku).sudoku)
#主程序
from win32gui import *
from PIL import ImageGrab,Image, ImageEnhance
from ctypes import windll
from win32con import SW_RESTORE
import cv2
import numpy as np
import os
from time import sleep
import pyautogui
def PIL_to_cv2(img):return(cv2.cvtColor(np.array(img),cv2.COLOR_RGB2BGR))
def 窗口置顶(窗口):
if IsIconic(窗口):ShowWindow(窗口, SW_RESTORE)
else:SetForegroundWindow(窗口)
sleep(0.3)
def 题目图片(窗口):
窗口坐标=GetWindowRect(窗口)
# ImageGrab.grab(窗口坐标).show()
return(ImageGrab.grab([窗口坐标[i]+(20,121,-20,-322)[i] for i in range(4)]))
def 图片识别(图片):
def 识别数字(nppic):
res=[cv2.matchTemplate(nppic,template,cv2.TM_CCOEFF_NORMED).max() for template in [cv2.imread("digital/{}".format(x)) for x in os.listdir("digital")]]
if max(res)>=0.65:return(res.index(max(res))+1)
else:return 0
图片=PIL_to_cv2(图片)
upleft=[];lowright=[]
#计算图片角坐标
# for i in range(len(图片)):
# for j in range(len(图片[i])):
# if all(c==1 or np.var([sum(图片[(i+d)%len(图片)][j]) for d in range(40)]+[sum(图片[i][(j+e)%len(图片[i])]) for e in range(40)])==0 if (str(图片[a][b]) in ('[255 255 255]','[225 247 255]'))==c else 0 for a,b,c in [[i,j,1],[i-1,j,0],[i,j-1,0]]): upleft+=[(j,i)]
# elif all(c==1 or np.var([sum(图片[i-d][j]) for d in range(40)]+[sum(图片[i][j-e]) for e in range(40)])==0 if (str(图片[a][b]) in ('[255 255 255]','[225 247 255]'))==c else 0 for a,b,c in [[i,j,1],[i+1,j,0],[i,j+1,0]]):lowright+=[(j,i)]
# print(i)
# print(*zip(upleft,lowright),'len=',len(upleft),sep=',')
w=((2,43),(46,89),(92,133),(139,180),(184,225),(229,270),(276,317),(321,363),(367,407))
h=((3,44),(47,90),(94,134),(140,181),(185,227),(231,272),(279,319),(322,365),(368,409))
# cv2.imshow('t',图片[2:43,3:44])
# cv2.waitKey()
# print(识别数字(图片[2:43,3:44]))
sudoku=[识别数字(图片[a:b,c:d]) for a,b in w for c,d in h]
# print(np.array(sudoku).reshape(9,9))
# print(sudoku)
if len([i for i in sudoku if i!=0])<17:return(False)
return(sudoku)
# print(图片识别(Image.open(r'5.png')))
# print(识别数字(cv2.imread('5.jpg')))
def 填写(sudoku,窗口):
窗口坐标=GetWindowRect(窗口)
九宫格坐标=[窗口坐标[i]+(20,121,-20,-322)[i] for i in range(4)]#(左,上,右,下)
w=[22.5, 67.5, 112.5, 159.5, 204.5, 249.5, 296.5, 342.0, 387.0]#九宫格坐标
h=[23.5, 68.5, 114.0, 160.5, 206.0, 251.5, 299.0, 343.5, 388.5]#九宫格
按钮dict={1:(118,618),2:(192,618),3:(266,618),4:(340,618),5:(414,618),6:(118,667),7:(192,667),8:(266,667),9:(340,667)}#窗口坐标
鼠标坐标表=[[单元格.value,(w[单元格.列]+九宫格坐标[0],h[单元格.行]+九宫格坐标[1]),(按钮dict[单元格.value][0]+窗口坐标[0],按钮dict[单元格.value][1]+窗口坐标[1])] for 单元格 in sudoku.已填单元格]
# print(鼠标坐标表)
# print(1)
new鼠标坐标表=[]
# while 鼠标坐标表:
# print(2)
# for i in range(1,10):
# if not 鼠标坐标表:break
# if 鼠标坐标表[0][0]==i:
# print(i)
# print('格',鼠标坐标表[0][1])
# print('按钮',鼠标坐标表[0][2])
# del 鼠标坐标表[0]
while 鼠标坐标表:
for i in range(1,10):
for j in 鼠标坐标表[:]:
if j[0]==i:
new鼠标坐标表+=[j]
鼠标坐标表.remove(j)
break
# new鼠标坐标表+=["sleep"]
# sleeptime=0
for i in new鼠标坐标表:
# sleeptime+=0.03
# if i=='sleep':
# sleep(sleeptime)
# continue
pyautogui.click(i[1])
pyautogui.click(i[2])
pyautogui.click(new鼠标坐标表[-1][2])#老是最后一个点不上不知道为什么
# sudoku=1
# 游戏窗口=FindWindow(0, "数独游戏九宫格")
# 填写(sudoku,游戏窗口)
from 算法1 import 计算
windll.user32.SetProcessDPIAware()#系统放缩还原
游戏窗口=FindWindow(0, "数独游戏九宫格")
# print(sudoku)
for 自动解题次数 in range(1):
窗口置顶(游戏窗口)
识按钮图=cv2.minMaxLoc(cv2.matchTemplate(cv2.cvtColor(np.asarray(pyautogui.screenshot(region=None)),cv2.COLOR_RGB2BGR),cv2.imread('next.png'),cv2.TM_CCOEFF_NORMED))
if 识按钮图[1]>0.65:pyautogui.click(识按钮图[-1])
try:填写(计算(图片识别(题目图片(游戏窗口))),游戏窗口)
except:pass
# print(a)
# print(sudoku.sudoku)
# 窗口置顶(游戏窗口)
sleep(0.9)
使用的文件:
几个坐标表是我另写程序测的像素