数独を自動再生するので、すぐに数独プレーヤーになれる
プログラムオートフィルWeb数独ゲーム
数独ゲームのサイトがあります。 https://www.sudoku.name/index-cn.php
もちろん、この種の数独ゲームのサイトはたくさんあるので、とりあえずデモのためにこのサイトを例にとって説明します。
遊んだことのある人は、数独の基本的なルールをよく知っていますね。
- 1〜9の数字は各列に1回しか出現しない。
- 1~9の数字は、各列に1回のみ表示されます。
- 太い実線で区切られた3×3のボックスには、1~9の数字が一度だけ表示されます。
この数独ゲームのプレイを支援するプログラムを入手するにはどうしたらよいでしょうか?
アイデア
- Webオートメーションテストツール(seleniumなど)でページを開くことができます
- ページを解析してテーブルデータを取得する
- 着信ハンドラでのテーブルの自動解析
- 計算された数独の結果を自動的に書き込むプログラムを使用する
ステップバイステップで問題を解いてみましょう。
Selenium経由でターゲットURLにアクセスする
seleniumのインストールについては、https://blog.csdn.net/as604049322/article/details/114157526 を参照してください。
まず、以下のサイトでselenium経由でツアーを開きます。
from selenium import webdriver
browser = webdriver.Chrome()
selenium が正しくインストールされていれば、上記のコードを実行すると、Google Chrome のエクスプローラが
この時点で、数独ゲームのURLにアクセスするには、コントロールされたツアーに直接URLを入力するか、以下のコードでツアーをコントロールする必要があります。
url = "https://www.sudoku.name/index.php?ln=cn&puzzle_num=&play=1&difficulty=4&timer=&time_limit=0"
browser.get(url)
<イグ
社内PS:やはり今後はGoogle Chromeの広告削除プラグインを導入する必要がありますね
数独データ抽出
ノード解析
テーブルノードのidは
のvalue属性にノード値が存在する。
Seleniumを使ってツアーを制御するのはこの利点で、プログラムによって必要なデータをいつでも抽出できるようになります。
まず対象のテーブルタグを取得します。
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']]
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)
<ブロッククオート
場所を示すには、.を使用します。
数独計算機
上の数独の結果を計算するプログラムはどのようにすればいいのでしょうか?これには論理的なアルゴリズム思考が必要です。
この種の問題を解く最も基本的な考え方は、再帰的+バックトラックのアルゴリズムを使って、可能な塗りつぶしを一つずつ調べていき、矛盾がない状況を見つけるまで妥当性を検証していくことです。再帰の間、現在の空白セルがどの数字でも埋められない場合、バックトラックが実行されます。
その上で、最適化のためにビット演算を使うことができる。従来の方法では、各数字が発生したかどうかを示すために長さ99の配列を使用していましたが、ビット演算を使用すると、1つの整数だけで各数字が発生したかどうかを示すことができます。例えば、2進数の表(011000100)は、3,7,8が発生したことを示す。
具体的には、最終的なプログラムはかなり複雑なので、コードのロジックが理解できなければ、コピー&ペーストでOKです。
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', '.' , '.' , '.']]
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
上記のアルゴリズムは実際には17秒かかっており、最適化が必要である。
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])
もう一度実行してください。
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&difficulty=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])
<イグ
所要時間はわずか3.2秒と、大幅な性能向上を実現しました。
<ブロッククオート最適化です。空白のセルに、埋めるべき固有の数値、つまり対応するb値とb-1があれば、0が得られる(つまり、bのバイナリビットが1つだけ1である)。その時点で、再帰処理まで待たずに、空白のセルに埋めるべき数を決定することができる。
あとは結果を適切な場所に配置するだけです。手で叩き込むのは大変ですからね。
結果をページに書き戻す
Seleniumの場合、手動でのボタンクリックをシミュレートし、キーボードアクションを送信することができます。
ここでは、table タグを再トラバースし、click と send_keys メソッドを使用しています。
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])
実行時の効果です。
骨太の数独プレーヤーが証明すること
他の人は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&difficulty=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
上記のサイトが一通りできたので、このサイトのnode要素は比較的シンプルになり、同じ考えで、以下のようなコードを書きます。
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])
も遊んでみてください。
関連
-
[解決済み】TypeErrorを取得しました。エントリを持つ子テーブルの後に親テーブルを追加しようとすると、 __init__() missing 1 required positional argument: 'on_delete'
-
[解決済み】TypeError: 'encoding'はこの関数の無効なキーワード引数です。
-
pycharmでtensorflowを使う方法を教えます。
-
Python 人工知能 古い写真 修復 アルゴリズム 学習
-
[解決済み] numpy.random.multivariate_normal(mean, cov[, size]) を用いて複数サンプルを描画する。
-
[解決済み] ソケットを接続しようとするとECONNABORTEDが表示されますが、どういう意味ですか?
-
[解決済み] Django の QuerySet を dicts のリストに変換するにはどうしたらいいですか?
-
[解決済み] 動画(ディスク)をrtspストリームに変換する方法
-
[解決済み] return, return None, and no return at all?
-
AttributeError: module 'tensorflow' has no attribute 'ConfigProto'
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] '_pywrap_tensorflow_internal' という名前のモジュールはありません。
-
[解決済み】range使用時の「TypeError: 'float' object cannot be interpreted as an integer」の意味とは?
-
[解決済み] ValueError : 閉じたファイルへのI/O操作
-
[解決済み】 re.search().TypeError: bytes-like オブジェクトで文字列パターンを使用できない。
-
[解決済み] エラー "gcc: error: x86_64-linux-gnu-gcc: No such file or directory" をデバッグする。
-
[解決済み] UnicodeDecodeError: 'ascii' コーデックは、位置 13 のバイト 0xe2 をデコードできません: 序数が range(128) にありません。
-
[解決済み] TypeError: 'unicode'オブジェクトは呼び出し可能ではありません。
-
[解決済み] AtributeError: 'module' オブジェクトに 'plt' 属性がない - Seaborn
-
[解決済み] UnicodeEncodeError: 'ascii' codec can't encode character u'\xa0' in position 20: ordinal not in range(128)
-
[解決済み] cElementtreeとElementTreeの違いは何ですか?