还不给开学。。。太无聊了
需要用到的库
import pyautogui
from PIL import ImageGrab, Image
import win32gui,win32api,win32con
import cv2
import numpy
import time
首先打开4399,挑选一个数独游戏,点击全屏,用picpick软件获取数独左上角和右下角的屏幕坐标,记录下来,然后按下F12获取该窗口的title = "九宫格数独小游戏,在线玩,4399小游戏"
具体思路是
利用win32gui库根据窗口名字将数独游戏的窗口显示在屏幕最前面,由于我是用谷歌浏览器打开的,所以title = u"九宫格数独小游戏,在线玩,4399小游戏 - Google Chrome"
将鼠标移到左上角防止干扰
根据数独游戏的位置信息裁剪数独图片
然后将数独图片切割成9 * 9个小块图片,对每个图片进行识别,将图片转为一个二维的数独数组。不过这里的数字识别我没有用深度学习的方法(懒得训练,而且其数字很规则),尝试用开源tesseract来对图片识别,奈何tesseract识别率太低,就用了比较投机取巧的方法:首先切割成9 * 9个小图片,再把小图片周围的蓝边去掉,利用opencv对小图片进行边缘检测,边缘检测算子是canny,最后统计边缘检测结果图片中白色像素的数量,发现数字“1”的白色像素有49个,数字“2”的白色像素有72个……,将数据放入字典cannydict中,cannydict = {49:1, 72:2, 75:3, 59:4, 73:5, 82:6, 61:7, 87:8, 81:9, 0:0}, 所以往后只需要判断边缘检测图片的白色像素为多少就可以确定该图片写的数字是啥了。(这里的数据只对该数独游戏有效,其他游戏还是自己测试吧)
将图片转为二维数独数组之后,还需要保存一个一样的二维数组做备份,毕竟后面解答的时候你需要知道在哪些位置填数字
然后便是解数独,采用回溯法:找出第一个空白格,根据行、列、所在九宫格判断该空白格可填入的数字(是一个list1),在list1中顺序挑一个数字、再找出下一个空白格的可填入数字(list2)...不断递归,如果下一个空白格可填入数字为0说明该数独解答有误,回溯回去,并把数字改为0,在上一层递归中重新挑选数字...如此往复,直到没有下一个空白格,解答完毕,输出答案。 这里解数独的代码参考B站以为up主, 视频号:BV1gE411h7jh
解完数独,根据之前保留的备份二维数组,就可以在4399游戏界面中的指定位置输入答案了,
pyautogui.click(clickPos) # 鼠标点击
pyautogui.typewrite(message=str(self.matrix[numH][numL])) # 键盘输入
完整代码如下:
import pyautogui
from PIL import ImageGrab, Image
import win32gui,win32api,win32con
import cv2
import numpy
import time
pyautogui.FAILSAFE = False
class GameAssist:
# 初始化
def __init__(self, winName):
self.handwin = win32gui.FindWindow(0, winName)
if not self.handwin:
print("无法找到窗口")
exit()
# 放置最前
win32gui.SetForegroundWindow(self.handwin)
# 数独位置信息
self.shuduRect = (379, 243, 819, 683)
# 数独矩阵 9 * 9
self.matrix = [[0 for i in range(9)] for i in range(9)]
# 再记录原始矩阵,方便后面填入位置
self.matrixOrigin = [[0 for i in range(9)] for i in range(9)]
# canny白色像素数量对应数字
self.cannydict = {49:1, 72:2, 75:3,
59:4, 73:5, 82:6,
61:7, 87:8, 81:9,
0:0}
self.runTime = 0
# canny边缘检测并计算分数
def canny2score(self, img):
img = cv2.cvtColor(numpy.asarray(img), cv2.COLOR_RGB2BGR)
canny = cv2.Canny(img, 50, 150)
score = 0
for row in range(canny.shape[0]): # 遍历高
for col in range(canny.shape[1]): # 遍历宽
if canny[row, col] == 255:
score += 1
return score
# 将数独图片转为数独矩阵
def img2matrix(self, shuduImg):
# 单个数字的宽、高
sigSize = (int((self.shuduRect[2] - self.shuduRect[0]) / 9),
int((self.shuduRect[3] - self.shuduRect[1]) / 9))
numH = 0 # 行数
numL = 0 # 列数
for i in range(0, shuduImg.size[0], sigSize[0]+1):
for j in range(0, shuduImg.size[1], sigSize[1]+1):
tempRect = (j, i, j+sigSize[0], i+sigSize[1])
tempImg = shuduImg.crop(tempRect)
# 将蓝边截掉
tempImg = tempImg.crop( (10, 10, tempImg.size[0]-5, tempImg.size[1]-5) )
score = self.canny2score(tempImg)
self.matrix[numH][numL] = self.cannydict[score]
numL += 1
numH += 1
numL = 0
# 保留原来数据,之后解数独的答案就在matrix
for i in range(9):
for j in range(9):
self.matrixOrigin[i][j] = self.matrix[i][j]
def get_next(self, x: "空白格行数", y: "空白格列数"):
""" 功能:获得下一个空白格在数独中的坐标。
"""
for next_y in range(y + 1, 9): # 下一个空白格和当前格在一行的情况
if self.matrix[x][next_y] == 0:
return x, next_y
for next_x in range(x + 1, 9): # 下一个空白格和当前格不在一行的情况
for next_y in range(0, 9):
if self.matrix[next_x][next_y] == 0:
return next_x, next_y
return -1, -1 # 若不存在下一个空白格,则返回 -1,-1
def value(self, x: "空白格行数", y: "空白格列数"):
""" 功能:返回符合"每个横排和竖列以及
九宫格内无相同数字"这个条件的有效值。
"""
i, j = x // 3, y // 3
grid = [self.matrix[i * 3 + r][j * 3 + c] for r in range(3) for c in range(3)]
v = set([x for x in range(1, 10)]) - set(grid) - set(self.matrix[x]) - \
set(list(zip(*self.matrix))[y])
return v
def start_pos(self):
""" 功能:返回第一个空白格的位置坐标"""
for x in range(9):
for y in range(9):
if self.matrix[x][y] == 0:
return x, y
return -1, -1 # 若数独已完成,则返回 -1, -1
def try_sudoku(self, x: "空白格行数", y: "空白格列数"):
""" 功能:试着填写数独 """
self.runTime += 1
for v in self.value(x, y):
if self.matrix[x][y] == 0: # 判断是否为空格
self.matrix[x][y] = v
next_x, next_y = self.get_next(x, y)
if next_y == -1: # 如果无下一个空白格
#print(m)
return True
else:
end = self.try_sudoku(next_x, next_y) # 递归
if end:
return True
self.matrix[x][y] = 0 # 在递归的过程中,如果数独没有解开,
# 则回溯到上一个空白格
def sudoku(self):
x, y = self.start_pos()
self.try_sudoku(x, y)
'''
# 解数独的所有结果
for v in value(m, x, y):
m[x][y] = v
next_x, next_y = get_next(m, x, y)
try_sudoku(m, next_x, next_y)
'''
# 根据数独结果在屏幕上填入
def fillans(self):
# 单个数字的宽、高
sigSize = (int((self.shuduRect[2] - self.shuduRect[0]) / 9),
int((self.shuduRect[3] - self.shuduRect[1]) / 9))
numH = 0 # 行数
numL = 0 # 列数
for i in range(self.shuduRect[1], self.shuduRect[3], sigSize[0]+1): # 高
for j in range(self.shuduRect[0], self.shuduRect[2], sigSize[1]+1): # 宽
# 需要填充的地方
if self.matrixOrigin[numH][numL] == 0:
# 鼠标点击位置
clickPos = (int(j + sigSize[0]/2 + 15), int(i + sigSize[1]/2) + 5)
print(clickPos, self.matrix[numH][numL])
pyautogui.click(clickPos)
pyautogui.typewrite(message=str(self.matrix[numH][numL]))
#time.sleep(0.3)
numL += 1
numH += 1
numL = 0
pass
# 主控制程序
def master(self):
# 将鼠标移到一旁,防止干扰
pyautogui.moveTo(20, 100)
# 屏幕裁剪数独图片
shuduImg = ImageGrab.grab(self.shuduRect)
shuduImg.save("shuduimg.png")
# 将数独转为矩阵
self.img2matrix(shuduImg)
# 解数独矩阵
self.sudoku()
# 鼠标键盘录入
self.fillans()
if __name__ == "__main__":
winName = u"九宫格数独小游戏,在线玩,4399小游戏 - Google Chrome"
demo = GameAssist(winName)
demo.master()
print(demo.matrix)
print(demo.matrixOrigin)
print(demo.runTime)