Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Hebat, memecahkan rekod dunia menggunakan Python!

Hebat, memecahkan rekod dunia menggunakan Python!

王林
王林ke hadapan
2023-04-20 13:19:061223semak imbas

Hebat, memecahkan rekod dunia menggunakan Python!

Helo semua,

Gunakan Python+OpenCV untuk melaksanakan pembersihan lombong automatik dan memecahkan rekod dunia dahulu .

Hebat, memecahkan rekod dunia menggunakan Python!

Pertengahan - 0.74 saat 3BV/S=60.81

Saya percaya ramai orang telah mengetahui tentang Minesweeper, permainan klasik (ujian kad grafik) untuk sekian lama masa (Perisian), ramai orang telah mendengar tentang Orang Suci Petir Cina, Guo Weijia, yang juga penyapu ranjau nombor satu di China dan kedua di dunia. Sebagai permainan klasik yang dilahirkan pada era Windows 9x, Minesweeper masih mempunyai daya tarikan uniknya dari dahulu hingga sekarang: keperluan operasi tetikus yang pantas dan berketepatan tinggi, keupayaan tindak balas pantas dan keseronokan menetapkan rekod semua perkara yang dibawa oleh Penyapu ranjau Keseronokan unik yang dibawa oleh rakan-rakan penyapu ranjau adalah unik kepada Penyapu ranjau.

1. Persediaan

Sebelum anda bersedia untuk mencipta perisian automasi pelepasan lombong, anda perlu menyediakan alatan/perisian/persekitaran berikut

- Persekitaran pembangunan

  1. Persekitaran Python3 - 3.6 ke atas disyorkan [Anaconda3 lebih disyorkan, banyak perpustakaan bergantung berikut tidak perlu dipasang]
  2. pustaka bergantung numpy [tidak perlu memasang jika anda mempunyai Anaconda ]
  3. Pustaka bergantung PIL [seperti Jika anda mempunyai Anaconda, anda tidak perlu memasangnya]
  4. opencv-python
  5. win32gui, pustaka pergantungan win32api
  6. IDE yang menyokong Python [pilihan, jika anda boleh bertolak ansur menggunakan penyunting teks untuk menulis program Juga]

- Perisian Penyapu Ranjau

· Pengadil Penyapu Ranjau (MS-Arbiter mestilah digunakan untuk kapal penyapu ranjau!)

Baiklah, jadi persiapan kami selesai Selesai! Mari mulakan~

2. Idea Pelaksanaan

Apakah perkara yang paling penting sebelum melakukan sesuatu? Ia adalah untuk membina rangka kerja langkah demi langkah dalam fikiran anda untuk apa yang anda akan lakukan. Hanya dengan cara ini kita dapat memastikan bahawa proses melakukan perkara ini adalah seberfikir yang mungkin, supaya akan ada hasil yang baik pada akhirnya. Apabila kita menulis program, kita harus mencuba yang terbaik untuk mempunyai idea umum dalam fikiran sebelum memulakan pembangunan secara rasmi.

Untuk projek ini, proses pembangunan umum adalah seperti berikut:

  1. Lengkapkan bahagian pemintasan kandungan borang
  2. Lengkapkan bahagian pembahagian blok petir
  3. Lengkapkan bahagian pengenalan jenis blok lombong
  4. Lengkapkan algoritma pembersihan lombong

Baiklah, sekarang kita ada idea, mari kita singsingkan lengan dan bekerja keras!

1. Pemintasan bentuk

Malah, untuk projek ini, pemintasan bentuk adalah bahagian yang mudah tetapi agak menyusahkan untuk dilaksanakan, dan ia juga merupakan bahagian yang sangat diperlukan. Kami mendapat dua maklumat berikut melalui Spy++:

class_name = "TMain"
title_name = "Minesweeper Arbiter "
  • Kategori bentuk utama ms_arbiter.exe ialah "TMain"
  • ms_arbiter.exe Nama borang utama ialah "Pengarbit Ranjau"

Perasan tak? Terdapat ruang selepas nama borang utama. Ruang inilah yang menyusahkan pengarang buat seketika. Hanya dengan menambah ruang ini boleh win32gui mendapatkan pemegang borang secara normal.

Projek ini menggunakan win32gui untuk mendapatkan maklumat kedudukan borang Kod khusus adalah seperti berikut:

hwnd = win32gui.FindWindow(class_name, title_name)
if hwnd:
left, top, right, bottom = win32gui.GetWindowRect(hwnd)

Melalui kod di atas, kami mendapat relatif. kedudukan borang berbanding keseluruhan kedudukan skrin blok. Selepas itu, kita perlu menggunakan PIL untuk memintas papan catur antara muka penyapu ranjau.

Kami perlu mengimport perpustakaan PIL terlebih dahulu

from PIL import ImageGrab

dan kemudian melaksanakan operasi khusus.

left += 15
top += 101
right -= 15
bottom -= 43
rect = (left, top, right, bottom)
img = ImageGrab.grab().crop(rect)

Jika anda bijak, anda pasti telah menemui Nombor Ajaib itu sepintas lalu Ya, ini sememangnya Nombor Ajaib, dan ia adalah semua perkara yang kita perolehi sedikit pelarasan halus Kedudukan papan berbanding dengan bentuk.

Nota: Data ini hanya diuji di bawah Windows 10. Jika digunakan di bawah sistem Windows lain, ketepatan kedudukan relatif tidak dijamin, kerana versi lama sistem mungkin mempunyai lebar sempadan bentuk yang berbeza.

Hebat, memecahkan rekod dunia menggunakan Python!Kawasan oren adalah yang kami perlukan

Baiklah, kami mempunyai imej papan catur Langkah seterusnya ialah membahagikan imej setiap blok lombong~

2. Pembahagian blok lombong

Hebat, memecahkan rekod dunia menggunakan Python!

Sebelum meneruskan segmentasi blok lombong, kita perlu mengetahui saiz blok lombong dan saiz sempadannya terlebih dahulu. Mengikut ukuran pengarang, di bawah ms_arbiter, saiz setiap blok lombong ialah 16px*16px.

Mengetahui saiz blok petir, kita boleh memotong setiap blok petir. Mula-mula kita perlu mengetahui bilangan blok lombong dalam kedua-dua arah mendatar dan menegak.

block_width, block_height = 16, 16
blocks_x = int((right - left) / block_width)
blocks_y = int((bottom - top) / block_height)

Selepas itu, kami mencipta tatasusunan dua dimensi untuk menyimpan imej setiap blok lombong, membahagikan imej dan menyimpannya dalam tatasusunan yang dibuat sebelum ini.

def crop_block(hole_img, x, y):
x1, y1 = x * block_width, y * block_height
x2, y2 = x1 + block_width, y1 + block_height
return hole_img.crop((x1, y1, x2, y2))
blocks_img = [[0 for i in range(blocks_y)] for i in range(blocks_x)]
for y in range(blocks_y):
for x in range(blocks_x):
blocks_img[x][y] = crop_block(img, x, y)

Enkapsulasi keseluruhan bahagian pemerolehan dan pembahagian imej ke dalam perpustakaan, yang boleh dipanggil pada bila-bila masa~ Dalam pelaksanaan pengarang, kami merangkum bahagian ini ke dalam imageProcess.py, di mana fungsi get_frame() Digunakan untuk melengkapkan proses pemerolehan dan pembahagian imej di atas.

3. 雷块识别

这一部分可能是整个项目里除了扫雷算法本身之外最重要的部分了。笔者在进行雷块检测的时候采用了比较简单的特征,高效并且可以满足要求。

def analyze_block(self, block, location):
block = imageProcess.pil_to_cv(block)
block_color = block[8, 8]
x, y = location[0], location[1]
# -1:Not opened
# -2:Opened but blank
# -3:Un initialized
# Opened
if self.equal(block_color, self.rgb_to_bgr((192, 192, 192))):
if not self.equal(block[8, 1], self.rgb_to_bgr((255, 255, 255))):
self.blocks_num[x][y] = -2
self.is_started = True
else:
self.blocks_num[x][y] = -1
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 255))):
self.blocks_num[x][y] = 1
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 0))):
self.blocks_num[x][y] = 2
elif self.equal(block_color, self.rgb_to_bgr((255, 0, 0))):
self.blocks_num[x][y] = 3
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 128))):
self.blocks_num[x][y] = 4
elif self.equal(block_color, self.rgb_to_bgr((128, 0, 0))):
self.blocks_num[x][y] = 5
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 128))):
self.blocks_num[x][y] = 6
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 0))):
if self.equal(block[6, 6], self.rgb_to_bgr((255, 255, 255))):
# Is mine
self.blocks_num[x][y] = 9
elif self.equal(block[5, 8], self.rgb_to_bgr((255, 0, 0))):
# Is flag
self.blocks_num[x][y] = 0
else:
self.blocks_num[x][y] = 7
elif self.equal(block_color, self.rgb_to_bgr((128, 128, 128))):
self.blocks_num[x][y] = 8
else:
self.blocks_num[x][y] = -3
self.is_mine_form = False
if self.blocks_num[x][y] == -3 or not self.blocks_num[x][y] == -1:
self.is_new_start = False

可以看到,我们采用了读取每个雷块的中心点像素的方式来判断雷块的类别,并且针对插旗、未点开、已点开但是空白等情况进行了进一步判断。具体色值是笔者直接取色得到的,并且屏幕截图的色彩也没有经过压缩,所以通过中心像素结合其他特征点来判断类别已经足够了,并且做到了高效率。

在本项目中,我们实现的时候采用了如下标注方式:

  • 1-8:表示数字1到8
  • 9:表示是地雷
  • 0:表示插旗
  • -1:表示未打开
  • -2:表示打开但是空白
  • -3:表示不是扫雷游戏中的任何方块类型

通过这种简单快速又有效的方式,我们成功实现了高效率的图像识别。

4. 扫雷算法实现

这可能是本篇文章最激动人心的部分了。在这里我们需要先说明一下具体的扫雷算法思路:

  1. 遍历每一个已经有数字的雷块,判断在它周围的九宫格内未被打开的雷块数量是否和本身数字相同,如果相同则表明周围九宫格内全部都是地雷,进行标记。
  2. 再次遍历每一个有数字的雷块,取九宫格范围内所有未被打开的雷块,去除已经被上一次遍历标记为地雷的雷块,记录并且点开。
  3. 如果以上方式无法继续进行,那么说明遇到了死局,选择在当前所有未打开的雷块中随机点击。(当然这个方法不是最优的,有更加优秀的解决方案,但是实现相对麻烦)

基本的扫雷流程就是这样,那么让我们来亲手实现它吧~

首先我们需要一个能够找出一个雷块的九宫格范围的所有方块位置的方法。因为扫雷游戏的特殊性,在棋盘的四边是没有九宫格的边缘部分的,所以我们需要筛选来排除掉可能超过边界的访问。

def generate_kernel(k, k_width, k_height, block_location):
 ls = []
 loc_x, loc_y = block_location[0], block_location[1]
for now_y in range(k_height):
for now_x in range(k_width):
if k[now_y][now_x]:
 rel_x, rel_y = now_x - 1, now_y - 1
 ls.append((loc_y + rel_y, loc_x + rel_x))
return ls
 kernel_width, kernel_height = 3, 3
# Kernel mode:[Row][Col]
 kernel = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
# Left border
if x == 0:
for i in range(kernel_height):
 kernel[i][0] = 0
# Right border
if x == self.blocks_x - 1:
for i in range(kernel_height):
 kernel[i][kernel_width - 1] = 0
# Top border
if y == 0:
for i in range(kernel_width):
 kernel[0][i] = 0
# Bottom border
if y == self.blocks_y - 1:
for i in range(kernel_width):
 kernel[kernel_height - 1][i] = 0
# Generate the search map
 to_visit = generate_kernel(kernel, kernel_width, kernel_height, location)

我们在这一部分通过检测当前雷块是否在棋盘的各个边缘来进行核的删除(在核中,1为保留,0为舍弃),之后通过generate_kernel函数来进行最终坐标的生成。

def count_unopen_blocks(blocks):
count = 0
for single_block in blocks:
if self.blocks_num[single_block[1]][single_block[0]] == -1:
count += 1
return count
def mark_as_mine(blocks):
for single_block in blocks:
if self.blocks_num[single_block[1]][single_block[0]] == -1:
self.blocks_is_mine[single_block[1]][single_block[0]] = 1
unopen_blocks = count_unopen_blocks(to_visit)
if unopen_blocks == self.blocks_num[x][y]:
 mark_as_mine(to_visit)

在完成核的生成之后,我们有了一个需要去检测的雷块“地址簿”:to_visit。之后,我们通过count_unopen_blocks函数来统计周围九宫格范围的未打开数量,并且和当前雷块的数字进行比对,如果相等则将所有九宫格内雷块通过mark_as_mine函数来标注为地雷。

def mark_to_click_block(blocks):
for single_block in blocks:
# Not Mine
if not self.blocks_is_mine[single_block[1]][single_block[0]] == 1:
# Click-able
if self.blocks_num[single_block[1]][single_block[0]] == -1:
# Source Syntax: [y][x] - Converted
if not (single_block[1], single_block[0]) in self.next_steps:
self.next_steps.append((single_block[1], single_block[0]))
def count_mines(blocks):
count = 0
for single_block in blocks:
if self.blocks_is_mine[single_block[1]][single_block[0]] == 1:
count += 1
return count
mines_count = count_mines(to_visit)
if mines_count == block:
mark_to_click_block(to_visit)

扫雷流程中的第二步我们也采用了和第一步相近的方法来实现。先用和第一步完全一样的方法来生成需要访问的雷块的核,之后生成具体的雷块位置,通过count_mines函数来获取九宫格范围内所有雷块的数量,并且判断当前九宫格内所有雷块是否已经被检测出来。

如果是,则通过mark_to_click_block函数来排除九宫格内已经被标记为地雷的雷块,并且将剩余的安全雷块加入next_steps数组内。

# Analyze the number of blocks
self.iterate_blocks_image(BoomMine.analyze_block)
# Mark all mines
self.iterate_blocks_number(BoomMine.detect_mine)
# Calculate where to click
self.iterate_blocks_number(BoomMine.detect_to_click_block)
if self.is_in_form(mouseOperation.get_mouse_point()):
for to_click in self.next_steps:
 on_screen_location = self.rel_loc_to_real(to_click)
 mouseOperation.mouse_move(on_screen_location[0], on_screen_location[1])
 mouseOperation.mouse_click()

在最终的实现内,笔者将几个过程都封装成为了函数,并且可以通过iterate_blocks_number方法来对所有雷块都使用传入的函数来进行处理,这有点类似Python中Filter的作用。

之后笔者做的工作就是判断当前鼠标位置是否在棋盘之内,如果是,就会自动开始识别并且点击。具体的点击部分,笔者采用了作者为"wp"的一份代码(从互联网搜集而得),里面实现了基于win32api的窗体消息发送工作,进而完成了鼠标移动和点击的操作。具体实现封装在mouseOperation.py中,可以在查看完整代码:

https://www.php.cn/link/b8a6550662b363eb34145965d64d0cfb

Hebat, memecahkan rekod dunia menggunakan Python!

Atas ialah kandungan terperinci Hebat, memecahkan rekod dunia menggunakan Python!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:51cto.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam