程序自动填网页数独游戏

有个玩数独游戏的网站:https://www.sudoku.name/index-cn.php

当然这类玩数独游戏的网站很多,现在我们先以该网站为例进行演示。

玩过的都非常清楚数独的基本规则:

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

如何让程序辅助我们玩这个数独游戏呢?

思路:

  • 我们可以通过web自动化测试工具(例如selenium)打开该网页
  • 解析网页获取表格数据
  • 传入处理程序中自动解析表格
  • 使用程序自动写入计算好的数独结果

下面我们尝试一步步解决这个问题:

通过Selenium访问目标网址

关于selenium的安装请参考:https://blog.csdn.net/as604049322/article/details/114157526

首先通过selenium打开游览器:

from selenium import webdriver
browser = webdriver.Chrome()

如果你的selenium已经正确安装,运行上述代码会打开谷歌游览器:

此时我们可以通过直接在受控制的游览器输入url访问,也可以用代码控制游览器访问数独游戏的网址:

url = "https://www.sudoku.name/index.php?ln=cn&puzzle_num=&play=1&difficult=4&timer=&time_limit=0"
browser.get(url)

内心PS:以后还是得给谷歌游览器装个去广告的插件

数独数据提取

节点分析

table节点的id为:

节点值存在于value属性中:

使用Selenium控制游览器就是这个好处,可以随时让程序提取我们需要的数据。

首先获取目标table标签:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECwait = WebDriverWait(browser, 10)table = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'table#sudoku_main_board')))

下面我们根据对节点的分析提取出我们需要的数独数据:

board = []
for tr in table.find_elements_by_xpath(".//tr"):row = []for input_e in tr.find_elements_by_xpath(".//input[@class='i3']"):cell = input_e.get_attribute("value")row.append(cell if cell else ".")board.append(row)
board
[['7', '.', '.', '.', '.', '4', '.', '.', '.'],['.', '4', '.', '.', '.', '5', '9', '.', '.'],['8', '.', '.', '.', '.', '.', '.', '2', '.'],['.', '.', '6', '.', '9', '.', '.', '.', '4'],['.', '1', '.', '.', '.', '.', '.', '3', '.'],['2', '.', '.', '.', '8', '.', '5', '.', '.'],['.', '5', '.', '.', '.', '.', '.', '.', '1'],['.', '.', '3', '7', '.', '.', '.', '8', '.'],['.', '.', '.', '2', '.', '.', '.', '.', '6']]

将凡是需要填写的位置都用.表示。

数独计算程序

如何对上述数独让程序来计算结果呢?这就需要逻辑算法的思维了。

这类问题最基本的解题思维就是通过递归 + 回溯算法遍历所有可能的填法挨个验证有效性,直到找到没有冲突的情况。在递归的过程中,如果当前的空白格不能填下任何一个数字,那么就进行回溯。

在此基础上,我们可以使用位运算进行优化。常规方法我们需要使用长度为 99 的数组表示每个数字是否出现过,借助位运算,仅使用一个整数就可以表示每个数字是否出现过。例如二进制表 (011000100)表示数字 3,7,8 已经出现过。

具体而言最终的程序还算比较复杂的,无法理解代码逻辑的可以直接复制粘贴:

def solveSudoku(board):def flip(i: int, j: int, digit: int):line[i] ^= (1 << digit)column[j] ^= (1 << digit)block[i // 3][j // 3] ^= (1 << digit)def dfs(pos: int):nonlocal validif pos == len(spaces):valid = Truereturni, j = spaces[pos]mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ffwhile mask:digitMask = mask & (-mask)digit = bin(digitMask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)dfs(pos + 1)flip(i, j, digit)mask &= (mask - 1)if valid:returnline = [0] * 9column = [0] * 9block = [[0] * 3 for _ in range(3)]valid = Falsespaces = list()for i in range(9):for j in range(9):if board[i][j] == ".":spaces.append((i, j))else:digit = int(board[i][j]) - 1flip(i, j, digit)dfs(0)

然后我们运行一下:

solveSudoku(board)
board
[['7', '2', '9', '3', '6', '4', '1', '5', '8'],['3', '4', '1', '8', '2', '5', '9', '6', '7'],['8', '6', '5', '9', '7', '1', '4', '2', '3'],['5', '3', '6', '1', '9', '2', '8', '7', '4'],['9', '1', '8', '5', '4', '7', '6', '3', '2'],['2', '7', '4', '6', '8', '3', '5', '1', '9'],['6', '5', '2', '4', '3', '8', '7', '9', '1'],['4', '9', '3', '7', '1', '6', '2', '8', '5'],['1', '8', '7', '2', '5', '9', '3', '4', '6']]

可以看到,程序已经计算出了数独的结果。

不过对于数据:

[['.', '.', '.', '6', '.', '.', '.', '3', '.'],['5', '.', '.', '.', '.', '.', '6', '.', '.'],['.', '9', '.', '.', '.', '5', '.', '.', '.'],['.', '.', '4', '.', '1', '.', '.', '.', '6'],['.', '.', '.', '4', '.', '3', '.', '.', '.'],['8', '.', '.', '.', '9', '.', '5', '.', '.'],['.', '.', '.', '7', '.', '.', '.', '4', '.'],['.', '.', '5', '.', '.', '.', '.', '.', '8'],['.', '3', '.', '.', '.', '8', '.', '.', '.']]

上述算法耗时居然达到17秒,还需继续优化算法:

def solveSudoku(board: list) -> None:def flip(i: int, j: int, digit: int):line[i] ^= (1 << digit)column[j] ^= (1 << digit)block[i // 3][j // 3] ^= (1 << digit)def dfs(pos: int):nonlocal validif pos == len(spaces):valid = Truereturni, j = spaces[pos]mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ffwhile mask:digitMask = mask & (-mask)digit = bin(digitMask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)dfs(pos + 1)flip(i, j, digit)mask &= (mask - 1)if valid:returnline = [0] * 9column = [0] * 9block = [[0] * 3 for _ in range(3)]valid = Falsespaces = list()for i in range(9):for j in range(9):if board[i][j] != ".":digit = int(board[i][j]) - 1flip(i, j, digit)while True:modified = Falsefor i in range(9):for j in range(9):if board[i][j] == ".":mask = ~(line[i] | column[j] |block[i // 3][j // 3]) & 0x1ffif not (mask & (mask - 1)):digit = bin(mask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)modified = Trueif not modified:breakfor i in range(9):for j in range(9):if board[i][j] == ".":spaces.append((i, j))dfs(0)

再次运行:

solveSudoku(board)
board

耗时仅3.2秒,性能提升不少。

优化思路:如果一个空白格只有唯一的数可以填入,也就是其对应的 b 值和 b-1 进行按位与运算后得到 0(即 b 中只有一个二进制位为 1)。此时,我们就可以确定这个空白格填入的数,而不用等到递归时再去处理它。

下面我们需要做的就是将结果填入到相应的位置中,毕竟自己手敲也挺费劲的。

写结果回写到网页

对于Selenium,我们可以模拟人工点击按钮并发送键盘操作。

下面我们重新遍历table标签,并使用click和send_keys方法:

for i, tr in enumerate(table.find_elements_by_xpath(".//tr")):for j, input_e in enumerate(tr.find_elements_by_xpath(".//input[@class='i3']")):if input_e.get_attribute("readonly") == "true":continueinput_e.click()input_e.clear()input_e.send_keys(board[i][j])

运行过程中的效果:

骨灰级数独玩家证明:

别人9分钟,你用程序11秒填完。

完整代码

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium import webdriverbrowser = webdriver.Chrome()
url = "https://www.sudoku.name/index.php?ln=cn&puzzle_num=&play=1&difficult=4&timer=&time_limit=0"
browser.get(url)wait = WebDriverWait(browser, 10)table = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'table#sudoku_main_board')))board = []
for tr in table.find_elements_by_xpath(".//tr"):row = []for input_e in tr.find_elements_by_xpath(".//input[@class='i3']"):cell = input_e.get_attribute("value")row.append(cell if cell else ".")board.append(row)def solveSudoku(board: list) -> None:def flip(i: int, j: int, digit: int):line[i] ^= (1 << digit)column[j] ^= (1 << digit)block[i // 3][j // 3] ^= (1 << digit)def dfs(pos: int):nonlocal validif pos == len(spaces):valid = Truereturni, j = spaces[pos]mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ffwhile mask:digitMask = mask & (-mask)digit = bin(digitMask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)dfs(pos + 1)flip(i, j, digit)mask &= (mask - 1)if valid:returnline = [0] * 9column = [0] * 9block = [[0] * 3 for _ in range(3)]valid = Falsespaces = list()for i in range(9):for j in range(9):if board[i][j] != ".":digit = int(board[i][j]) - 1flip(i, j, digit)while True:modified = Falsefor i in range(9):for j in range(9):if board[i][j] == ".":mask = ~(line[i] | column[j] |block[i // 3][j // 3]) & 0x1ffif not (mask & (mask - 1)):digit = bin(mask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)modified = Trueif not modified:breakfor i in range(9):for j in range(9):if board[i][j] == ".":spaces.append((i, j))dfs(0)solveSudoku(board)for i, tr in enumerate(table.find_elements_by_xpath(".//tr")):for j, input_e in enumerate(tr.find_elements_by_xpath(".//input[@class='i3']")):if input_e.get_attribute("readonly") == "true":continueinput_e.click()input_e.clear()input_e.send_keys(board[i][j])

数独网站2

还有一个数独网站是:https://cn.sudokupuzzle.org/

实际数独游戏处于框架内部,我们可以直接只看内部:https://cn.sudokupuzzle.org/online2.php?nd=4

上面的网站都搞定了,这个网站的节点元素相对简单很多,按照同样的思想,我们编写出如下代码:

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium import webdriverbrowser = webdriver.Chrome()
url = "https://cn.sudokupuzzle.org/online2.php?nd=4"
browser.get(url)wait = WebDriverWait(browser, 10)
table = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'table.sd')))
board = []
for tr in table.find_elements_by_xpath('.//tr'):row = []for input_e in tr.find_elements_by_xpath('.//td/input'):cell = input_e.get_attribute("value")row.append(cell if cell else ".")board.append(row)def solveSudoku(board: list) -> None:def flip(i: int, j: int, digit: int):line[i] ^= (1 << digit)column[j] ^= (1 << digit)block[i // 3][j // 3] ^= (1 << digit)def dfs(pos: int):nonlocal validif pos == len(spaces):valid = Truereturni, j = spaces[pos]mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ffwhile mask:digitMask = mask & (-mask)digit = bin(digitMask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)dfs(pos + 1)flip(i, j, digit)mask &= (mask - 1)if valid:returnline = [0] * 9column = [0] * 9block = [[0] * 3 for _ in range(3)]valid = Falsespaces = list()for i in range(9):for j in range(9):if board[i][j] != ".":digit = int(board[i][j]) - 1flip(i, j, digit)while True:modified = Falsefor i in range(9):for j in range(9):if board[i][j] == ".":mask = ~(line[i] | column[j] |block[i // 3][j // 3]) & 0x1ffif not (mask & (mask - 1)):digit = bin(mask).count("0") - 1flip(i, j, digit)board[i][j] = str(digit + 1)modified = Trueif not modified:breakfor i in range(9):for j in range(9):if board[i][j] == ".":spaces.append((i, j))dfs(0)solveSudoku(board)for i, tr in enumerate(table.find_elements_by_xpath(".//tr")):for j, input_e in enumerate(tr.find_elements_by_xpath(".//td/input")):if input_e.get_attribute("readonly") == "true":continueinput_e.click()input_e.clear()input_e.send_keys(board[i][j])

也玩一把:

让程序自动玩数独游戏让你秒变骨灰级数独玩家相关推荐

  1. 微信小程序 最强连一连攻略 程序自动玩

    微信小程序 最强连一连攻略 程序自动玩 步骤 1.截屏 2.分析游戏状态 3.搜索 4.自动触摸滑动过关路径 代码 测试结果 注意 步骤 程序将手动玩游戏的过程分四步完成 1.截屏 将手机屏幕截屏保存 ...

  2. 微信小程序 最强连一连攻略 程序自动玩 续集

    背景 前段时间,闲暇时间玩了微信中的一款游戏叫 最强连一连,玩了一段时间发现手动去玩不知道要玩几个月,于是就开始各种找资料.找了几个代码,最终找到这个 大神的代码,他写了一篇文章叫 <微信小程序 ...

  3. 想作为程序员工作 需要什么_您不想作为程序员玩的游戏

    想作为程序员工作 需要什么 by Amy M Haddad 通过艾米·M·哈达德(Amy M Haddad) 您不想作为程序员玩的游戏 (The game you don't want to play ...

  4. 一些适合程序员玩的游戏

    一些适合程序员玩的游戏 文章目录 一些适合程序员玩的游戏 一.简介 二.screeps 三.codingame 四.codewars 五.gcores 六.https://minecraft.make ...

  5. steam有什么适合程序员玩的游戏

    Steam上适合程序员玩的游戏有: <编程大师>(Programming Master):一款模拟编程的游戏,通过编写代码来完成任务. <人工智能:未来之路>(AI: The ...

  6. Java黑皮书课后题第6章:**6.30(游戏:双骰子)掷双骰子游戏是某场景中非常流行的骰子游戏。编写程序,玩这个游戏的变种

    6.30(游戏:双骰子)掷双骰子游戏是某场景中非常流行的骰子游戏.编写程序,玩这个游戏的变种 题目 题目描述 破题 代码 题目 题目描述 6.30(游戏:双骰子)掷双骰子游戏是某场境中非常流行的骰子游 ...

  7. 六一新玩法!AI涂鸦秒变精美艺术画

    摘要:上华为云ModelArts体验AI涂鸦新玩法,赢漫威复仇者联盟乐高!祝大小朋友们六一儿童节快乐~ 本文分享自华为云社区<[云享热点]六一新玩法!AI 涂鸦秒变精美艺术画>,作者:华为 ...

  8. 让Python程序自动玩数独游戏,秒变最强大脑!

    游戏界面如下图所示 当然这类玩数独游戏的网站很多,现在我们先以该网站为例进行演示.希望能用Python实现自动计算并填好数独游戏! 大概效果能像下面这样就好啦

  9. python数独游戏源代码_使用Python编写数独游戏自动出题程序

    数独是一个很好玩的游戏,可以锻炼推理能力.下面的代码可以自动生成数独游戏题目. from random import shuffle, randrange def generate(): # 初始网格 ...

最新文章

  1. 初探Django2.1:Win10+Python3.6.6环境下安装Django
  2. CCF 202104 Python
  3. linux rm 删除所有文件,linux无需rm就可快速删除大量文件
  4. linux磁盘和文件系统管理
  5. oracle常用命令收集
  6. (转)Bootstrap 之 Metronic 模板的学习之路 - (3)源码分析之 body 部分
  7. libcareplus多补丁管理
  8. 从零基础转行到前端大牛,需要经过哪几个阶段?
  9. [图示]抢逼围:项目开发3字经
  10. 五分钟学会python编程_每天五分钟python编程:生成器技术是python语言最强大的技术之一...
  11. 利用CSS3的transform做的动态时钟
  12. C#判断对象是不是数组
  13. JAVA开发工程师面试题shiro
  14. graphpad两组t检验_Graphpad 分析教程 | 手把手教你玩转独立样本 t 检验
  15. 他是发明声呐的物理天才,被妻子家暴后出轨守寡的师母,爱因斯坦却公开支持.........
  16. 如何实现 Android 短视频跨页面的流畅续播?
  17. 老主板N卡点的亮A卡点不亮、只有VGA和DVI亮解决思路
  18. 仿SDWebImage
  19. 2020.05.29
  20. Guava 系列 - Guava基础

热门文章

  1. 《番茄工作法》让你的一天变成26小时
  2. 索尼g8441是什么版本_复兴之路!索尼新机G8341/G8441现身波兰
  3. 扎拉赞恩 服务器 微信群,《魔兽世界怀旧服》杜隆塔尔扎拉赞恩攻略 任务怎么玩...
  4. linux 网卡多队列设置,网卡多队列
  5. 欢迎使用CSDN-markdown编辑器;看着蛮好玩~
  6. wordpress实时在线聊天室
  7. level升级打怪是什么意思_【信好有你】LEVEL+1 升级打怪
  8. winmail.dat
  9. GitChat·管理 | 一篇文章读懂项目管理中的精髓
  10. mysql数据库有string_mysql数据库内STRING系列数据类型介绍