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

让程序自动玩数独游戏让你秒变骨灰级数独玩家程序自动填网页数独游戏有个玩数独游戏的网站 https www sudoku name index cn php 当然这类玩数独游戏的网站很多 现在我们先以该网站为例进行演示 玩过的都非常清楚数独的基本规则 数字 1 9 在每一行只能出现一次 数字 1 9 在每一列只能出现一次 数字 1 9 在每一个以粗实线分隔的 3×3 宫内只能出现一次 如何让程序辅助我们玩这个数独游戏呢 思路 我们可以通过 web 自动化测试工具 例如 selenium 打开该网页解析网页获取表格数据传入处理

程序自动填网页数独游戏

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

image-20210707014138477

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

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

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

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

思路:

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

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

通过Selenium访问目标网址

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

首先通过selenium打开游览器:

from selenium import webdriver browser = webdriver.Chrome() 

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

image-20210708175406793

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

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

image-20210708180832959

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

数独数据提取

节点分析

table节点的id为:

image-20210708184010599

节点值存在于value属性中:

image-20210708184159928

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

首先获取目标table标签:

from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC 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) board 
[['7', '.', '.', '.', '.', '4', '.', '.', '.'], ['.', '4', '.', '.', '.', '5', '9', '.', '.'], ['8', '.', '.', '.', '.', '.', '.', '2', '.'], ['.', '.', '6', '.', '9', '.', '.', '.', '4'], ['.', '1', '.', '.', '.', '.', '.', '3', '.'], ['2', '.', '.', '.', '8', '.', '5', '.', '.'], ['.', '5', '.', '.', '.', '.', '.', '.', '1'], ['.', '.', '3', '7', '.', '.', '.', '8', '.'], ['.', '.', '.', '2', '.', '.', '.', '.', '6']] 

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

数独计算程序

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

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

在此基础上,我们可以使用位运算进行优化。常规方法我们需要使用长度为 99 的数组表示每个数字是否出现过,借助位运算,仅使用一个整数就可以表示每个数字是否出现过。例如二进制表 (0)表示数字 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 valid if pos == len(spaces): valid = True return i, j = spaces[pos] mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff while mask: digitMask = mask & (-mask) digit = bin(digitMask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) dfs(pos + 1) flip(i, j, digit) mask &= (mask - 1) if valid: return line = [0] * 9 column = [0] * 9 block = [[0] * 3 for _ in range(3)] valid = False spaces = 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]) - 1 flip(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 valid if pos == len(spaces): valid = True return i, j = spaces[pos] mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff while mask: digitMask = mask & (-mask) digit = bin(digitMask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) dfs(pos + 1) flip(i, j, digit) mask &= (mask - 1) if valid: return line = [0] * 9 column = [0] * 9 block = [[0] * 3 for _ in range(3)] valid = False spaces = list() for i in range(9): for j in range(9): if board[i][j] != ".": digit = int(board[i][j]) - 1 flip(i, j, digit) while True: modified = False for i in range(9): for j in range(9): if board[i][j] == ".": mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff if not (mask & (mask - 1)): digit = bin(mask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) modified = True if not modified: break for i in range(9): for j in range(9): if board[i][j] == ".": spaces.append((i, j)) dfs(0) 

再次运行:

solveSudoku(board) board 

image-20210708195631062

耗时仅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": continue input_e.click() input_e.clear() input_e.send_keys(board[i][j]) 

运行过程中的效果:

录制_2021_07_08_20_45_24_372

骨灰级数独玩家证明:

image-20210708205314338

别人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 webdriver browser = 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 valid if pos == len(spaces): valid = True return i, j = spaces[pos] mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff while mask: digitMask = mask & (-mask) digit = bin(digitMask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) dfs(pos + 1) flip(i, j, digit) mask &= (mask - 1) if valid: return line = [0] * 9 column = [0] * 9 block = [[0] * 3 for _ in range(3)] valid = False spaces = list() for i in range(9): for j in range(9): if board[i][j] != ".": digit = int(board[i][j]) - 1 flip(i, j, digit) while True: modified = False for i in range(9): for j in range(9): if board[i][j] == ".": mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff if not (mask & (mask - 1)): digit = bin(mask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) modified = True if not modified: break for 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": continue input_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 webdriver browser = 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 valid if pos == len(spaces): valid = True return i, j = spaces[pos] mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff while mask: digitMask = mask & (-mask) digit = bin(digitMask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) dfs(pos + 1) flip(i, j, digit) mask &= (mask - 1) if valid: return line = [0] * 9 column = [0] * 9 block = [[0] * 3 for _ in range(3)] valid = False spaces = list() for i in range(9): for j in range(9): if board[i][j] != ".": digit = int(board[i][j]) - 1 flip(i, j, digit) while True: modified = False for i in range(9): for j in range(9): if board[i][j] == ".": mask = ~(line[i] | column[j] | block[i // 3][j // 3]) & 0x1ff if not (mask & (mask - 1)): digit = bin(mask).count("0") - 1 flip(i, j, digit) board[i][j] = str(digit + 1) modified = True if not modified: break for 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": continue input_e.click() input_e.clear() input_e.send_keys(board[i][j]) 

也玩一把:

录制_2021_07_08_21_50_37_630

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/217996.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午8:17
下一篇 2026年3月18日 上午8:18


相关推荐

  • Deepin安装wxpython教程

    Deepin安装wxpython教程环境:安装报错:解决:1.sudoapt-getinstalllibgtk-3-dev-y    2.sudoapt-getinstallfreeglut3-devlibgstreamer-plugins-base1.0-dev-y3.sudopip3install-U-fhttps://extras….

    2022年5月21日
    37
  • Javascript如何修改数组长度?

    Javascript如何修改数组长度?修改数组长度使用 数组名 length 可以获取或修改数组的长度 数组长度的计算方式为数组中元素的最大索引值加 1 示例代码如下 vararr a b c console log arr length 输出结果 3 在上述代码中 数组中最后一个元素是 c 该元素的索引为 2 因此数组长度为 3 使用 arr length 不仅可以获取数组长度 还可以修改数组长度 示例代码如下 vararr1 1 2 arr1 length 4 大于原有长度

    2026年3月16日
    2
  • APP性能测试工具——GT 使用方法

    APP性能测试工具——GT 使用方法参考链接:https://www.cnblogs.com/syw20170419/p/7228145.html?utm_source=itdadao&utm_medium=referralGT官方使用介绍文档地址:https://gt.qq.comGT(随身调)是APP的随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE,IntegratedDebugEnvi…

    2022年6月28日
    58
  • torch.meshgrid()函数解析

    torch.meshgrid()函数解析torch.meshgrid()函数解析torch.meshgrid()的功能是生成网格,可以用于生成坐标。函数输入两个数据类型相同的一维张量,两个输出张量的行数为第一个输入张量的元素个数,列数为第二个输入张量的元素个数,当两个输入张量数据类型不同或维度不是一维时会报错。其中第一个输出张量填充第一个输入张量中的元素,各行元素相同;第二个输出张量填充第二个输入张量中的元素各列元素相同。#【1】importtorcha=torch.tensor([1,…

    2022年6月5日
    136
  • 矩阵的行列式、秩的意义

    矩阵的行列式、秩的意义线性代数真是一个很抽象的东西,即使我们很多人都学过,但是我相信绝大部分的都不知道这是干嘛用的,找了不少资料,终于发现了这么一篇好文章,于是强烈希望可以和大家分享,帮助大伙进一步理解矩阵的行列式和秩的本质意义。1关于面积:    一种映射 大家会说,面积,不就是长乘以宽么,其实不然。我们首先明确,这里所讨论的面积,是欧几里得空间几何面积的基本单位:平行四边形的面积。平行四边形面积

    2022年5月8日
    53
  • 月之暗面(Kimi)近期完成5亿美元C轮融资

    月之暗面(Kimi)近期完成5亿美元C轮融资

    2026年3月12日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号