Pythonでマインスイーパーを解いてみる改良版

Pythonでマインスイーパーを解くプログラムを書いてみたが絶望的に動きがノロかったので，いろいろと改良してみました。
改良前↓
https://qiita.com/RRRF/https://qiita.com/RRRF/items/0347944ba8975f4a3a38items/0347944ba8975f4a3a38

動画↓
https://youtu.be/7A1LcLinO_M
##環境##
- Python 3.6.4
- Windows10
- Microsoft Minesweeper

##主な変更点##
###スクリーンショットのサイズを変える###
スクリーンショットの取得を，１マスごとではなく1行ごとに，また数字の判定に必要な部分のみを抽出して取得するようにしました．

```python:main.py
X = 581.25   #マインスイーパーの右端
Y = 213   #マインスイーパーの上端
key = 47.25
def imageread():   #スクリーンショットをとる関数
    global key, X, Y
    for i in range(16):
        img = pyautogui.screenshot(
            imageFilename = "imagefile/image" + str(i) + ".png",
            region = (X, Y + i * key + 23, key * 16, 1)
            )
```
取得した画像たち
![image.png](https://qiita-image-store.s3.amazonaws.com/0/207917/96af2296-0a6a-562a-3296-89861edd8f43.png)
一つだけだとこんな感じ
![image0.png](https://qiita-image-store.s3.amazonaws.com/0/207917/72051c73-639b-772f-4637-eb9f67df6a93.png)
ピクセル数が1×752とかなので，ものすごく細長い．
しかしこうすることで，取得する画像の枚数が元の6％くらいになったので実行時間はかなり少なくなりました．

これだけです．
ちなみに昨日は9×9でしたが，16×16に対応するように変えました．9×9に比べて，マスのサイズや座標をかなり正確に撮らなければならず，調整にとても手間取りました．

前回の記事を書いて，改良のアドバイスをくださったのでいつかさらに書き直してみたいです．

##コード##

```python:main.py
import pyautogui
import imageread
import time
from PIL import Image
import numpy as np
S = [[0 for i in range(18)] for j in range(18)]
#各マスに爆弾があるかをリストSに数値で示す
for i in range(18):
    S[0][i] = -1
    S[17][i] = -1
    S[i][0] = -1
    S[i][17] = -1
#-1はすでにあけられているマス
#100は確実に爆弾があるマス
#0は不明のマス
#-50は未開封だが確実にないマス

no_bomb = []   #爆弾がないことが確定しているマス番地

def sarch(a, b, key):   #引数の周囲8マスのSを更新する関数
    a += 1
    b += 1
    count = 0   #未開封マスのカウンター
    for i in range(a - 1, a + 2):
        for j in range(b - 1, b + 2):
            if i != a or j != b:
                if S[i][j] == 100:
                    key -= 1
                    #周囲のマスに未開封爆弾ありマスがある場合keyから1を引く
                elif S[i][j] >= 0:
                    count += 1
    if a == 5 and b == 15:
        print(key, count)
    if count == key:   #周囲8マスの未開封マスと爆弾の数が一致した場合
        key = 100
    elif key != 0:
        key = key / count
    for i in range(a - 1, a + 2):
        for j in range(b - 1, b + 2):
            if key == 0 and S[i][j] != 100:
                if S[i][j] != -1:
                    S[i][j] = -50
                    click(i, j)
                    no_bomb.append([i, j])
                    #周囲8マスに未開封爆弾がありかつ，自身のマスではない場合-50を代入
                    #自身のマス番地をno_bombに追加
            elif i != a or j != b:
                if S[i][j] < 100 and S[i][j] != -1 and S[i][j] != -50:
                    S[i][j] += key
                    #周囲8マスについて未開封爆弾の位置がわからない場合，Sにkeyを足す
                if S[i][j] >= 100:
                    S[i][j] = 100
                    #未開封爆弾が確実にあるマスについては100を維持

X = 581.25   #マインスイーパーの右端
Y = 213   #マインスイーパーの上端
key = 47.25
   
def click(a, b):   #マウスをクリックする関数
    global X, Y, key
    xx = X + key / 2 + (a - 1) * key
    yy = Y + key / 2 + (b - 1) * key
    pyautogui.mouseDown(x = xx, y = yy, button = "left")
    pyautogui.mouseUp()

def imageread():   #スクリーンショットをとる関数
    global key, X, Y
    for i in range(16):
        img = pyautogui.screenshot(
            imageFilename = "imagefile/image" + str(i) + ".png",
            region = (X, Y + i * key + 23, key * 16, 1)
            )
    
def imagescan(i, j):   #指定したマスの数字を色から読み取る関数
    #i -= 1
    
#j -= 1
    img = Image.open('imagefile/image' + str(j) + '.png')
    width, height = img.size
    img_pixels = np.array([[img.getpixel((i, 0)) for i in range(i * int(key) + 10, i * int(key) + 40)]])
    image = img_pixels[0]
    count = 0
    count_b = 0
    count_one = 0
    count_three = 0
    count_four = 0
    count_five = 0
    for r in image:
        if sum(r) > 730:   #RGB値を足した数が730以上だと背景が白と判断する
            count += 1
        elif r[2] > 200:
            if r[2] > r[0] and r[2] > r[1]:   #RGBのBが一番多いと1だと判断する
                count_one += 1   #RGBのBが多いことより青っぽい色であることを記録する
            if r[0] < 100 and r[1] < 100:
                count_four += 1
            count_b += 1
        elif r[0] < 100 and r[1] < 100:
            count_four += 1
        if r[0] > r[1] and r[0] > r[2]:   #RGBのRが一番多いと3だと判断する
            count_three += 1
    if count >= 23:
        return 0
    elif count_b >= 30:
        return 100
    elif count_three > 2:
        return 3
    elif count_four > 1:
        return 4
    elif count_one > 1:
        return 1
    else:
        return 2

click(8, 8)

now_t = time.time()   #スクリーンショットをとる関係で時間をつぶす

while True:    
    A = [[100 for i in range(16)] for j in range(16)]
    if time.time() > now_t + 1.5:
         break

while True:
    if pyautogui.position() == [0, 0]:
         break   #エスケープ方法を指定(左上にカーソルを持ってくるとおわる)

    start = time.time()
    imageread()
    print('read' + str(time.time() - start))

    start = time.time()
    for i in range(16):
        for j in range(16):
            if A[i][j] == 100:
                A[i][j] = imagescan(i, j)   #i, j地点の数字を調べる
                if A[i][j] != 100:
                    S[i + 1][j + 1] = -1
    print('imagescan' + str(time.time() - start))

#    for i in range(16):
 #       for j in range(16):
  #          print(A[j][i], end =' ')
   #     print()
  
    start = time.time()
    for i in range(16):
        for j in range(16):
            if A[i][j] != 0 and A[i][j] != 100:
                sarch(i, j, A[i][j])   #i, j地点が数字であった時その周りのマスについて調べる
    print('sarch' + str(time.time() - start))


    if len(no_bomb) == 0:   #未開封マスで確実に爆弾があるマスがないときの操作を指示する
        min = 50
        a = 0
        b = 0
        for i in range(1, 17):
            for j in range(1, 17):
                if S[i][j] <= min and S[i][j] > 0:
                    a = i
                    b = j
        click(a, b)

    print("end")

    del no_bomb[0:len(no_bomb)]
```

おわり．