├── .idea
├── .gitignore
├── Automatic-minesweeping.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.md
├── basic_operation.py
├── game.py
├── surround.py
├── use_keyboard_and_mouse.py
├── window.py
├── winmine.exe
├── 扫雷演示.mp4
└── 自动扫雷.exe
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/Automatic-minesweeping.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Zhang Yichuan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Automatic-minesweeping(自动扫雷)
2 | Automatic minesweeping for Windows XP classic version minesweeper by Python3.7
3 | 使用Python编写脚本,实现对经典扫雷游戏的程序自动完成游戏
4 |
5 | ## exe脚本使用方法:
6 | 1. 打开扫雷
7 | 2. 运行exe脚本程序
8 |
9 | ## 源码运行方式
10 | ```shell
11 | python game.py
12 | ```
13 |
14 | ## My Operating environment:
15 | Windows 10
16 | Python 3.7
17 | Windows经典的Minesweeper游戏
18 |
19 | ## Program requirement:
20 | Numpy:用于矩阵运算
21 | pillow:用于截图,根据截图判断每个格子内容
22 | pywin32:用户获取窗口,使用鼠标键盘
23 | ```shell
24 | pip install numpy
25 | pip install pillow
26 | pip install pywin32
27 | ```
28 |
29 | ## 主要思路:
30 | 1. 使用pywin32获取扫雷游戏的窗口,并模拟键盘鼠标操作进行点击和插旗
31 | 2. 利用截图中,各个块像素点的和来区分各个块,比如块“1”的像素点和为128664,块“红旗”的像素点和为129165,从而将图片信息转化为矩阵信息,矩阵对应着游戏中整个棋盘
32 | 3. 遍历矩阵每个元素,根据周边信息去判断什么时候插旗,什么时候点击,什么时候左右键同时
33 | 4. 循环遍历直到游戏失败或者胜利
34 |
35 | ## 代码结构:
36 | 1. window.py主要定义基础窗口类,用于获得基本的窗口信息
37 | 2. use_keyboard_and_mouse.py顾名思义定义了鼠标和键盘操作类
38 | 3. basic_operation.py封装了之前的类,实现了扫雷的基本操作,比如点击,插旗,重开等
39 | 4. game.py是脚本运行的主逻辑,进行循环遍历判断决策等。
40 | 5. surround.py定义了格子及其周边信息类,用于判断当前局势,方便决策
41 |
42 | ## 功能局限:
43 | 1. 目前只支持高级、中级、低级三种默认棋盘大小,不知道自定义模式
44 |
45 | ## 遇到的问题:
46 | 1. IndexError: list index out of range
47 | 原因:似乎是屏幕分辨率设置了缩进导致,设置为100%即可(win10-右键-显示设置-缩放与布局-100%)
48 |
--------------------------------------------------------------------------------
/basic_operation.py:
--------------------------------------------------------------------------------
1 | """
2 | 封装每局游戏的基本操作
3 | """
4 | from use_keyboard_and_mouse import OperateWindow
5 | from numpy import sum
6 |
7 |
8 | class BasicOperation(object):
9 | def __init__(self):
10 | self.op = OperateWindow()
11 | self.length = self.op.chessboard.shape[1]
12 | self.height = self.op.chessboard.shape[0]
13 | self.find_mines = sum(self.op.chessboard == self.op.info_to_int['flag'])
14 | self.unknown_boxes = sum(self.op.chessboard == self.op.info_to_int['unknown'])
15 |
16 | # 更新棋盘chessboard
17 | def refresh(self):
18 | self.op.update_chessboard()
19 | self.find_mines = sum(self.op.chessboard == self.op.info_to_int['flag'])
20 | self.unknown_boxes = sum(self.op.chessboard == self.op.info_to_int['unknown'])
21 |
22 | # 标红旗
23 | def make_flag(self, left_offset, top_offset):
24 | assert self.op.chessboard[top_offset][left_offset] == self.op.info_to_int['unknown']
25 | self.op.right_click(left_offset, top_offset)
26 | self.op.chessboard[top_offset][left_offset] = self.op.info_to_int['flag']
27 |
28 | # 点击区域
29 | def go(self, left_offset, top_offset):
30 | assert self.op.chessboard[top_offset][left_offset] == self.op.info_to_int['unknown']
31 | self.op.left_click(left_offset, top_offset)
32 | self.refresh()
33 |
34 | # 点击中央
35 | def center(self):
36 | # print("center:({},{})".format(self.length // 2, self.height // 2))
37 | self.go(left_offset=self.length // 2, top_offset=self.height // 2)
38 |
39 | # 重开
40 | def remake(self):
41 | self.op.f2()
42 | # 第一步点击屏幕中央
43 | self.refresh()
44 | self.center()
45 |
46 | # 同时按下左右键打开一片区域
47 | def open_surround(self, left_offset, top_offset):
48 | self.op.left_and_right_click(left_offset, top_offset)
49 | self.refresh()
50 |
51 |
52 | if __name__ == '__main__':
53 | basic_op = BasicOperation()
54 |
--------------------------------------------------------------------------------
/game.py:
--------------------------------------------------------------------------------
1 | """
2 | 定义每局游戏
3 | """
4 | from basic_operation import BasicOperation
5 | from surround import BoxWithSurround
6 | from random import shuffle
7 |
8 |
9 | class Game(object):
10 | def __init__(self):
11 | self.basic_op = BasicOperation()
12 | self.basic_op.refresh()
13 | self.info_to_int = self.basic_op.op.info_to_int
14 | self.int_to_info = self.basic_op.op.int_to_info
15 | self.length = self.basic_op.op.window.length
16 | self.height = self.basic_op.op.window.height
17 | self.max_mines = self.basic_op.op.window.max_mines
18 |
19 | # 运行
20 | def run(self):
21 | # 如果程序运行时已经输了或者已经赢了就重开
22 | if self.game_over() or self.win():
23 | self.basic_op.remake()
24 |
25 | # 如果新开的一把,就从中间开始点
26 | if self.new():
27 | self.basic_op.center()
28 |
29 | # 否则就继续游戏
30 | while not self.game_over() and not self.win():
31 | self.choose_next_click()
32 |
33 | if self.game_over():
34 | print('Game Over')
35 | if self.win():
36 | print('Win')
37 |
38 | # 判断是否游戏失败
39 | def game_over(self):
40 | return self.info_to_int['mine'] in self.basic_op.op.chessboard
41 |
42 | # 判断是否胜利
43 | def win(self):
44 | find_all = self.basic_op.find_mines == self.max_mines
45 |
46 | # 解决旗子插完但是还有未点击格子的问题
47 | if find_all:
48 | for top_offset in range(self.height):
49 | for left_offset in range(self.length):
50 | if self.basic_op.op.chessboard[top_offset][left_offset] == self.info_to_int['unknown']:
51 | self.basic_op.go(left_offset, top_offset)
52 | return find_all
53 |
54 | # 判断是否是新的一盘
55 | def new(self):
56 | return self.basic_op.unknown_boxes == self.length * self.height
57 |
58 | # 判断不出有把握的,就点第一个unknown
59 | def random(self):
60 | unknown_list = []
61 | for top_offset in range(self.height):
62 | for left_offset in range(self.length):
63 | if self.basic_op.op.chessboard[top_offset][left_offset] == self.info_to_int['unknown']:
64 | unknown_list.append((left_offset, top_offset))
65 |
66 | shuffle(unknown_list)
67 | left_offset, top_offset = unknown_list[0]
68 | self.basic_op.go(left_offset, top_offset)
69 | return
70 |
71 |
72 | # 根据列表中的坐标,统计旗的数量和雷的数量
73 | def count_list(self, pos_list):
74 | map = {
75 | 'flag': 0,
76 | 'unknown': 0
77 | }
78 | for pos in pos_list:
79 | val = self.basic_op.op.chessboard[pos[0]][pos[1]]
80 | if val > 8:
81 | var = self.int_to_info[val]
82 | if var in map.keys():
83 | map[var] += 1
84 | return map
85 |
86 | # 选择下一次点击的位置,决策部分
87 | def choose_next_click(self):
88 | # 首先遍历所有格子
89 | for top_offset in range(self.height):
90 | for left_offset in range(self.length):
91 | # 定义每个格子
92 | box = BoxWithSurround(self.basic_op.op.chessboard,
93 | top_offset,
94 | left_offset,
95 | self.info_to_int,
96 | self.int_to_info)
97 |
98 | # 只处理数字格子,并且周围有unknown格子
99 | if box.val <= 8 and len(box.distribution['unknown']) != 0:
100 | # 如果已经插旗数目等于当前格子的数字,则周围其他格子都不是雷
101 | if len(box.distribution['flag']) == box.val:
102 | self.basic_op.open_surround(left_offset, top_offset)
103 | return
104 |
105 | # 如果插旗数+未检测数等于当前数字,则未检测全是雷
106 | elif len(box.distribution['flag']) + len(box.distribution['unknown']) == box.val:
107 | for pos in box.distribution['unknown']:
108 | t, l = box.pos_to_offset(pos)
109 | self.basic_op.make_flag(left_offset=l, top_offset=t)
110 | return
111 |
112 | # 如果还不行,就要根据相邻数字进行组合推导
113 | elif len(box.distribution['digit']) > 0:
114 | for pos in box.distribution['digit']:
115 | t, l = box.pos_to_offset(pos)
116 | if t == top_offset or l == left_offset:
117 | # 找到两个相邻的数字
118 | neighbor = BoxWithSurround(self.basic_op.op.chessboard,
119 | top_offset=t,
120 | left_offset=l,
121 | info_to_int=self.info_to_int,
122 | int_to_info=self.int_to_info)
123 |
124 | # 分别获得两个格子周围的有效格子
125 | # 旗或者未检测
126 | # flag 或者 unknown
127 | box_surround = box.get_surround()
128 | neighbor_surround = neighbor.get_surround()
129 |
130 | # 共享的格子share
131 | share = list(set(box_surround) & set(neighbor_surround))
132 | # 各自双方
133 | selves = [(t, l), (top_offset, left_offset)]
134 |
135 | # 删除掉共享和各自双方后,剩下的私有的格子
136 | box_surround = list(set(box_surround) - set(share) - set(selves))
137 | neighbor_surround = list(set(neighbor_surround) - set(share) - set(selves))
138 |
139 | # 统计
140 | # {'flag': 0, 'unknown': 2}
141 | box_map = self.count_list(box_surround)
142 | neighbor_map = self.count_list(neighbor_surround)
143 | share_map = self.count_list(share)
144 |
145 | # 如果box在公共最多的雷数 == neighbor在公共最少的雷数
146 | if min(box.val - box_map['flag'] - share_map['flag'], 2) == \
147 | neighbor.val - neighbor_map['flag'] - neighbor_map['unknown'] - share_map['flag'] and \
148 | share_map['unknown'] == 2:
149 |
150 | for pos in neighbor_surround:
151 | if self.basic_op.op.chessboard[pos[0]][pos[1]] == self.info_to_int['unknown']:
152 | self.basic_op.make_flag(left_offset=pos[1], top_offset=pos[0])
153 | return
154 |
155 | # 如果box在公共最少的雷 == neighbor所有的雷
156 | if share_map['unknown'] == 2 and \
157 | box.val - box_map['unknown'] - box_map['flag'] - share_map['flag'] == 1 and \
158 | neighbor.val - neighbor_map['flag'] - share_map['flag'] == 1:
159 |
160 | for pos in neighbor_surround:
161 | if self.basic_op.op.chessboard[pos[0]][pos[1]] == self.info_to_int['unknown']:
162 | self.basic_op.go(left_offset=pos[1], top_offset=pos[0])
163 | return
164 |
165 | # 如果没有找到百分百概率,则随机点
166 | self.random()
167 |
168 |
169 | if __name__ == '__main__':
170 | g = Game()
171 | g.run()
172 |
--------------------------------------------------------------------------------
/surround.py:
--------------------------------------------------------------------------------
1 | """
2 | 定义每个格子及其周围8个格子的类
3 | """
4 |
5 |
6 | class BoxWithSurround(object):
7 | def __init__(self, chessboard, top_offset, left_offset, info_to_int, int_to_info):
8 | self.val = chessboard[top_offset][left_offset]
9 | self.chessboard = chessboard
10 | self.top_offset = top_offset
11 | self.left_offset = left_offset
12 | self.length = chessboard.shape[1]
13 | self.height = chessboard.shape[0]
14 | self.info_to_int = info_to_int
15 | self.int_to_info = int_to_info
16 |
17 | # 假设元素在电脑小键盘5的位置
18 | # 其相邻值就是对应的12346789的位置
19 | self.nearby = {
20 | '_1': self.is_out(-1, 1),
21 | '_2': self.is_out(0, 1),
22 | '_3': self.is_out(1, 1),
23 | '_4': self.is_out(-1, 0),
24 | '_6': self.is_out(1, 0),
25 | '_7': self.is_out(-1, -1),
26 | '_8': self.is_out(0, -1),
27 | '_9': self.is_out(1, -1)
28 | }
29 |
30 | # 统计nearby的分布情况
31 | self.distribution = {
32 | 'flag': [],
33 | 'unknown': [],
34 | 'null': [],
35 | 'digit': [],
36 | 'mine': [],
37 | 'red_mine': []
38 | }
39 | self.set_distribution()
40 |
41 | # 判断是否出界
42 | def is_out(self, top_offset, left_offset):
43 | x = self.top_offset + top_offset
44 | y = self.left_offset + left_offset
45 | # 如果超出边界就是视为null
46 | if x < 0 or x >= self.height or y < 0 or y >= self.length:
47 | return self.info_to_int['null']
48 | else:
49 | return self.chessboard[x][y]
50 |
51 | # 统计每个格子四周的分布情况
52 | def set_distribution(self):
53 | for key_val in self.nearby.items():
54 | val_int = key_val[1]
55 | # 如果是flag null或者unknown
56 | if self.int_to_info.get(val_int, None):
57 | val_info = self.int_to_info[val_int]
58 | self.distribution[val_info].append(key_val[0])
59 | # 如果是数字
60 | else:
61 | self.distribution['digit'].append(key_val[0])
62 | # 统计总长度为8
63 | assert sum([len(key_val[1]) for key_val in self.distribution.items()]) == 8
64 | # print(self.distribution)
65 |
66 | # pos 2 offset
67 | def pos_to_offset(self, pos):
68 | dic = {
69 | # top_offset, left_offset
70 | '_1': (-1, 1),
71 | '_2': (0, 1),
72 | '_3': (1, 1),
73 | '_4': (-1, 0),
74 | '_6': (1, 0),
75 | '_7': (-1, -1),
76 | '_8': (0, -1),
77 | '_9': (1, -1)
78 | }
79 | assert pos in dic.keys()
80 | top_offset = self.top_offset + dic[pos][0]
81 | left_offset = self.left_offset + dic[pos][1]
82 | return top_offset, left_offset
83 |
84 | def get_surround(self):
85 | res = list()
86 | for pos in self.nearby.keys():
87 | if self.nearby[pos] in [self.info_to_int['flag'], self.info_to_int['unknown']]:
88 | top_offset, left_offset = self.pos_to_offset(pos)
89 | if 0 <= top_offset < self.height and 0 <= left_offset < self.length:
90 | res.append((top_offset, left_offset))
91 | # print("get_surround" + str(res))
92 | return res
93 |
--------------------------------------------------------------------------------
/use_keyboard_and_mouse.py:
--------------------------------------------------------------------------------
1 | """
2 | 对GameWindow进行二次封装,增加键盘和鼠标操作
3 | """
4 |
5 | from window import GameWindow
6 | from win32api import SetCursorPos, mouse_event, keybd_event
7 | from win32con import MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, KEYEVENTF_KEYUP
8 | from PIL import ImageGrab # pip install pillow, 如果下载速度慢可以去pypi.org下载whl文件
9 | from numpy import array # pip install numpy, 如果下载速度慢可以去pypi.org下载whl文件
10 | from numpy import zeros, sum, int
11 |
12 |
13 | class OperateWindow(object):
14 | def __init__(self):
15 | self.window = GameWindow()
16 | self.img = None
17 | self.info_to_int = {
18 | 'flag': 10,
19 | 'null': 11,
20 | 'unknown': 12,
21 | 'mine': 13,
22 | 'red_mine': 14
23 | }
24 | self.int_to_info = dict(zip(self.info_to_int.values(), self.info_to_int.keys()))
25 | self.chessboard = None
26 |
27 | self.update_chessboard()
28 |
29 | # 单击鼠标左键
30 | # left_offset和top_offset表示
31 | # 从左往右第left_offset个,从上往下第top_offset个格子
32 | # 索引从0开始
33 | def left_click(self, left_offset, top_offset):
34 | pos = self.window.get_box(left_offset, top_offset)
35 | SetCursorPos(pos)
36 | mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
37 | mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
38 |
39 | # 单击鼠标右键
40 | # left_offset和top_offset表示
41 | # 从左往右第left_offset个,从上往下第top_offset个格子
42 | # 索引从0开始
43 | def right_click(self, left_offset, top_offset):
44 | pos = self.window.get_box(left_offset, top_offset)
45 | SetCursorPos(pos)
46 | mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0)
47 | mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0)
48 |
49 | # 鼠标左右键同时按下
50 | # left_offset和top_offset表示
51 | # 从左往右第left_offset个,从上往下第top_offset个格子
52 | # 索引从0开始
53 | def left_and_right_click(self, left_offset, top_offset):
54 | pos = self.window.get_box(left_offset, top_offset)
55 | SetCursorPos(pos)
56 | mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
57 | mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0)
58 | mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0)
59 | mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
60 |
61 | # 按下键盘F2
62 | def f2(self):
63 | key_f2 = 113 # f2对应的键盘输入值
64 | keybd_event(key_f2, 0, 0, 0)
65 | keybd_event(key_f2, 0, KEYEVENTF_KEYUP, 0)
66 |
67 | # 截图
68 | def get_img(self):
69 | self.img = array(ImageGrab.grab((self.window.get_side())))
70 |
71 | # 棋盘
72 | def update_chessboard(self):
73 | self.get_img()
74 | self.chessboard = img_to_chessboard(self.img,
75 | self.window.length,
76 | self.window.height,
77 | self.window.pixel_size,
78 | self.info_to_int)
79 |
80 |
81 | def img_to_chessboard(img, length, height, pixel_size, info_to_int):
82 | """
83 | 根据像素信息,将图片转换为棋盘信息
84 | 主要对比的方法是对每个块内所有像素值求和
85 | 恰好该值是独一无二的
86 | 因此可以用来表示对应的块
87 | """
88 | assert len(img.shape) == 3
89 | assert img.shape[0] == height * pixel_size
90 | assert img.shape[1] == length * pixel_size
91 | assert img.shape[2] == 3
92 |
93 | map = {
94 | 141504: info_to_int['null'],
95 | 129165: info_to_int['flag'],
96 | 147294: info_to_int['unknown'],
97 | 97908: info_to_int['mine'],
98 | 51684: info_to_int['red_mine'],
99 | 128664: 1,
100 | 112384: 2,
101 | 121602: 3,
102 | 116416: 4,
103 | 110144: 5,
104 | 118464: 6,
105 | 116160: 7,
106 | 126912: 8
107 | }
108 |
109 | # d = set()
110 |
111 | chessboard = zeros((height, length), dtype=int) # 初始化棋盘
112 | for i in range(height):
113 | for j in range(length):
114 | # 遍历chessboard每个位置
115 | # 截取对应img位置上的小图片区域
116 | img_piece = img[i * pixel_size: (i + 1) * pixel_size,
117 | j * pixel_size: (j + 1) * pixel_size, :]
118 | assert img_piece.shape == (pixel_size, pixel_size, 3)
119 |
120 | sum_val = sum(img_piece)
121 | if sum_val in map.keys():
122 | chessboard[i][j] = map[sum_val]
123 | else:
124 | # print("颜色判断错误", sum_val)
125 | chessboard[i][j] = sum_val
126 | # d.add(sum_val)
127 |
128 | # print(chessboard)
129 | # print(len(d))
130 | return chessboard
131 |
132 |
133 | if __name__ == '__main__':
134 | op = OperateWindow()
135 |
136 |
--------------------------------------------------------------------------------
/window.py:
--------------------------------------------------------------------------------
1 | """
2 | 扫雷游戏窗口
3 | """
4 | # pip install pywin32,如果下载速度慢可以去pypi.org下载whl文件,务必安装223版本,其他版本不行
5 | from win32gui import FindWindow, GetWindowRect, SetForegroundWindow, SendMessage
6 | from win32con import WM_SYSCOMMAND, SC_RESTORE
7 |
8 |
9 | class GameWindow(object):
10 | def __init__(self):
11 | self.hwnd = FindWindow(None, "扫雷") # 获取扫雷游戏窗口的句柄
12 | assert self.hwnd, "请先打开扫雷,再运行该脚本程序"
13 | SendMessage(self.hwnd, WM_SYSCOMMAND, SC_RESTORE, 0) # 还原最小化
14 | SetForegroundWindow(self.hwnd) # 窗口置顶
15 |
16 | self.pixel_size = 16 # 每个格子的大小固定,为16个像素
17 | self.left, self.top, self.right, self.bottom = GetWindowRect(self.hwnd) # 获取窗口坐标
18 | self.rank = None # 扫雷游戏的等级,分为:高级、中级、初级,不包含自定义模式
19 | self.max_mines = 0 # 扫雷游戏的所有雷的数目
20 |
21 | # 去除窗口边框,只保留所有格子
22 | self.left = self.left + 15 # 左边框15个像素宽
23 | self.right = self.right - 11 # 右边框11个像素宽
24 | self.bottom = self.bottom - 11 # 下边框11个像素宽
25 | self.top = self.top + 101 # 尚边框101个像素宽
26 |
27 | # 获得游戏横向和纵向的格子数目
28 | self.height = int((self.bottom - self.top) / self.pixel_size) # 扫雷游戏的纵向格子数目
29 | assert self.height in [16, 16, 9]
30 | self.length = int((self.right - self.left) / self.pixel_size) # 扫雷游戏的横向格子数目
31 | assert self.length in [30, 16, 9]
32 |
33 | # 获取游戏难度级别
34 | self.get_rank()
35 | self.get_max_mines()
36 |
37 | def get_side(self):
38 | return self.left, self.top, self.right, self.bottom
39 |
40 | def get_rank(self):
41 | length_to_rank = {30: '高级', 16: '中级', 9: '初级'}
42 | self.rank = length_to_rank[self.length]
43 |
44 | def get_max_mines(self):
45 | rank_to_max_mines = {'高级': 99, '中级': 40, '初级': 10}
46 | self.max_mines = rank_to_max_mines[self.rank]
47 |
48 | def get_box(self, left_offset, top_offset):
49 | # 根据角标返回对应格子的屏幕像素坐标
50 | # left_offset和top_offset表示
51 | # 从左往右第left_offset个,从上往下第top_offset个格子
52 | # 索引从0开始
53 | assert 0 <= left_offset < self.length
54 | assert 0 <= top_offset < self.height
55 | left_pos = self.left + left_offset * self.pixel_size + self.pixel_size // 2
56 | top_pos = self.top + top_offset * self.pixel_size + self.pixel_size // 2
57 | return left_pos, top_pos
58 |
59 | def get_center(self):
60 | # 获取游戏正中心格子的屏幕像素坐标
61 | left_offset = self.length // 2
62 | top_offset = self.height // 2
63 | center = self.get_box(left_offset, top_offset)
64 | return center
65 |
66 |
67 | if __name__ == '__main__':
68 | win = GameWindow()
69 |
70 |
71 |
--------------------------------------------------------------------------------
/winmine.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buaazyc/Automatic-minesweeping/16add7de3d0386213b12b1456388723c3c772be8/winmine.exe
--------------------------------------------------------------------------------
/扫雷演示.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buaazyc/Automatic-minesweeping/16add7de3d0386213b12b1456388723c3c772be8/扫雷演示.mp4
--------------------------------------------------------------------------------
/自动扫雷.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/buaazyc/Automatic-minesweeping/16add7de3d0386213b12b1456388723c3c772be8/自动扫雷.exe
--------------------------------------------------------------------------------