Python
python3

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

Python3でマインスイーパーを解いてみた

先月とある事情でPythonを勉強したので,以前大学の講演会で外部講師の方のが暇つぶしにやったと言っていたのを真似して,自動でマインスイーパーを解いてみました.
終わってからから,ただのチート法作ってるだけだったかなとも思ったのですが,一人で楽しむだけなので問題ないかなと思ってます.
動画↓
https://www.youtube.com/watch?v=8JslBSjS8bI&feature=youtu.be

改良しました↓
https://qiita.com/RRRF/items/e0c36d1a8efd2a38e6fb

はじめに

本当は,昔のWindowsに初めから入っているやつを想定していたのですが,残念ながらWindows10には入っていないようなので,アプリストアで見つけたMicrosoft Minesweeperでやりました.
image.png
画面はこんな感じのやつです.

環境

  • Python 3.6.4
  • Windows10
  • Microsoft Minesweeper

画像から数字を読み取る

一マスずつスクリーンショットをとる

1マスずつスクリーンショットをとりました.これが結構時間がかかる要因だと思います.
本当は一枚のスクリーンショットを処理していけばよいと思ったのですが,ピクセル数の関係でできなかったので他の方法を考えんのもめんどくさくてこれにしました.
image.png

main.py
import pyautogui
X = 582   #右端
Y = 213   #上端
def imageread():   #スクリーンショットをとる関数
    key = 84
    for i in range(9):
        for j in range(9):
            img = pyautogui.screenshot(
                imageFilename = "imagefile/image" + str(i) + str(j) + ".png",
                region = (X + i * key, Y + j * key, key, key)
            )

スクリーンショットから数字を読み取る

本来ならpyocr等を使って数字を認識させるべきなのだと思いますが,なぜかpyocrをインストールしても使えるようにならないので(2,3時間くらい粘った)諦めました.
そこで,やむを得ず色から数字を判断することにしました.これも実行に時間がかかる要因だと思います.
また,色に対応する数字を自分でコードに書かなければならなく,またRGBの細かい値での条件分岐を考えるのがとてもめんどくさかったので1~3までの場合しか想定していません.ですから,4以上が出たときはそのゲームはエラーが出て終わります.

main.py
from PIL import Image
import numpy as np
def imagescan(i, j):   #指定したマスの数字を色から読み取る関数
    img = Image.open('imagefile/image' + str(i) + str(j) + '.png')
    width, height = img.size
    img_pixels = np.array([[img.getpixel((35, j)) for j in range(27, 54)]])
    image = img_pixels[0]
    count = 0
    count_b = 0
    count_one = 0
    count_three = 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が多いことより青っぽい色であることを記録する
            count_b += 1
        if r[0] > r[1] and r[0] > r[2]:   #RGBのRが一番多いと3だと判断する
            count_three += 1
    if count >= 27:
        return 0
    elif count_b >= 27:
        return 100
    elif count_three > 2:
        return 3
    elif count_one > 1:
        return 1
    else:
        return 2

マインスイーパーを解く

アルゴリズム的な話ですが,データ構造とアルゴリズムは今勉強中(今のところ習得できていない)のため,かなり計算量が多い方法をとってる気がします.

1.周辺のマスの状況を調べる

始めに,座標からマスを指定してその周辺8マスの状況を調べる関数を作りました.
次のような流れで動かします.
1.周辺8マスで未開封のマスが何個あるか数える
2.数えると同時に既に爆弾があることがわかっている場合はそれを記録する
3.周囲8マスに含まれる爆弾の数を周囲の未開封マスで割った数をそれぞれのマスに対応したリストSの値に足す.なお,2より既に爆弾があることがわかっている場合はその数分引いた爆弾の数を割る.
4.周囲の未開封マスの数 > 爆弾の数 = 爆弾があることが確定しているマスの数,となった時は,爆弾があることが確定していないマスには爆弾がないことが確実なので―50を代入する.
5.確実に爆弾があるマスには100を代入する

main.py
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 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を維持

2.開封していく

1.確実に爆弾がないマス(S=-50)のマスをすべて開ける
2.もしも上記に該当するマスがない場合は,Sの値が一番小さいマスを開ける

これらをひたすら繰り返しました.

感想

もっと人にはできないスピードでどんどん開封していくのを想像していたら,機械が解いていると思えないくらいゆっくりでとても残念です.
プログラミングスキルを上げてもっと素早く解く方法を考えてみたいものです.
次は寿司打を解いてみたいですが,そのためにはさすがにOCRのライブラリを使わなければできないと思うので,何とか使えるようにしたいところです.

コード全文も貼り付けておきます.
貼り付けようとしましたがなぜかできませんでした.

おわり.