paint-brush
Cómo hacer un bot de juegos que supere a los humanos usando Python y OpenCVpor@zetyquickly
24,002 lecturas
24,002 lecturas

Cómo hacer un bot de juegos que supere a los humanos usando Python y OpenCV

por Emil Bogomolov2021/09/26
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Un bot de Python se apoya en la biblioteca de visión por computadora OpenCV para vencer a Don't touch the red, un corredor sin fin de Addicting Games. El bot usa el algoritmo OpenCV para encontrar un parche coincidente en la imagen de destino. No es un enfoque de red neuronal, es mucho más simple y limitado. Las otras partes importantes del bot son cómo podemos obtener pantallas para analizar y tomar capturas de pantalla de una parte particular del juego. El bot es muy útil para configurar la "acción de interrupción" en algún teclado cuando el mouse está controlado por una tecla.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Cómo hacer un bot de juegos que supere a los humanos usando Python y OpenCV
Emil Bogomolov HackerNoon profile picture



¿Recuerdas la época en que Flappy Bird apareció en las pantallas de los smartphones? Este juego ha iniciado la era de los juegos casuales con muy pocas acciones en el juego, y cada movimiento en falso significa que tu juego ha terminado. El que dure más tiempo con vida encabezará la clasificación.


Hoy veremos cómo escribir un bot de Python que se aproveche de la biblioteca de visión por computadora OpenCV para vencer a Don't touch the red , un corredor sin fin de Addicting Games .


Pantalla de inicio

Reglas del juego

El juego es bastante simple: los botones verdes caen y el jugador debe presionarlos antes de salir de la pantalla. Y por supuesto, ¡no toques el rojo!


Captura de pantalla del juego

Hay una característica crucial; si juegas en modo arcade, los botones caen con velocidad creciente. Eso hace que el juego sea difícil para un jugador humano. ¡Pero no es un problema para nuestro bot!


Coincidencia de plantillas de OpenCV


La parte principal de nuestro bot de visión artificial es la coincidencia de plantillas disponible en la biblioteca de OpenCV. No es un enfoque de red neuronal. Es mucho más simple y limitado. Este algoritmo está diseñado para buscar un parche en la imagen de destino, por ejemplo, un "botón verde" en una "pantalla de juego". Funciona de la siguiente manera: el algoritmo toma la imagen de la plantilla y luego, utilizando una ventana deslizante, intenta encontrar un parche coincidente en la imagen de destino. Usando esto, podemos obtener las posiciones y las medidas de similitud para cada píxel.


Ejemplo de detección de monedas en Mario (fuente https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_template_matching/py_template_matching.html)

En la aplicación de código de la plantilla, la coincidencia se ve así

 import cv2 template = cv2.imread('template.png') target = cv2.imread('target.png') result = cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result)


Como resultado, tendremos max_val igual a la similitud máxima en la imagen de destino y max_loc es la esquina superior izquierda de la coincidencia encontrada.


Este algoritmo es más rápido cuando funciona con imágenes objetivo más pequeñas y patrones más pequeños. En primer lugar, he intentado trabajar con botones verdes enteros, pero luego he cambiado a unos más pequeños que funcionan más rápido, y con eso he conseguido puntuaciones más altas.

Dos tipos de patrones usados

Toma de capturas de pantalla e interacción


Las otras partes importantes del bot son obtener pantallas para analizar y cómo enviamos los clics del mouse al juego. Es necesario mencionar que Addicting Games proporciona juegos que puedes jugar usando tu navegador de Internet, por lo que no se debe instalar nada adicional.

Hay dos paquetes de Python que ayudan con las tareas anteriores: mss y pyautogui , los usamos para obtener las capturas de pantalla de una parte particular de la pantalla y enviar clics a la ventana del navegador correspondientemente. También uso la biblioteca del keyboard , ya que es muy útil para establecer la "acción de ruptura" en alguna tecla en el caso de que el mouse esté controlado por un bot. La biblioteca del keyboard (y probablemente pyautogui ) requiere derechos de sudo , así que ejecute su secuencia de comandos de Python como un ejecutable con un encabezado shebang adecuado.


Aquí proporciono fragmentos de código sobre cómo obtener capturas de pantalla y enviar clics:

 #!/hdd/anaconda2/envs/games_ai/bin/python # ^ change above to your python path ^ import keyboard import mss import pyautogui pyautogui.PAUSE = 0.0 print("Press 's' to start") print("Press 'q' to quit") keyboard.wait('s') # setup mss and get the full size of your monitor sct = mss.mss() mon = sct.monitors[0] while True: # decide on the part of the screen roi = { "left": 0, "top": int(mon["height"] * 0.2), "width": int(mon["width"] / 2), "height": int(mon["height"] * 0.23) } roi_crop = numpy.array(sct.grab(roi))[:,:,:3] # do something with `roi_crop` if keyboard.is_pressed('q'): break


Aquí también hay una cosa. Cuando usa pyautogui en Linux, es posible que se enfrente a Xlib.error.DisplayConnectionError que es posible superar con el comando xhost + .


mi algoritmo

Basado en los dos últimos, he creado un algoritmo que supera el puntaje de juego humano anterior de 170 con un puntaje de 445.


Robot en acción

Hay dos partes en un programa. Primero intenta hacer clic en los primeros tres botones disponibles en una pantalla cuando comienza el juego. El campo de juego no se mueve hasta que un jugador presiona el primer botón, por lo que podemos tratar un campo como estático cuando hacemos clic en los tres primeros. Para ello, inspeccionamos las tres líneas de la pantalla, buscando un pequeño patrón (ver la figura anterior), y luego pulsamos sobre ellas


La primera mitad del código:


 #!/hdd/anaconda2/envs/games_ai/bin/python # if "Xlib.error.DisplayConnectionError" use "xhost +" on linux import shutil import os import keyboard import mss import cv2 import numpy from time import time, sleep import pyautogui from random import randint import math pyautogui.PAUSE = 0.0 print("Press 's' to start") print("Press 'q' to quit") keyboard.wait('s') try: shutil.rmtree("./screenshots") except FileNotFoundError: pass os.mkdir("./screenshots") # setup mss and get the full size of your monitor sct = mss.mss() mon = sct.monitors[0] frame_id = 0 # decide where is the region of interest for idx in range(3,0,-1): roi = { "left": 0, "top": int(mon["height"] * (idx * 0.2)), "width": int(mon["width"] / 2), "height": int(mon["height"] * 0.23) } green_button = cv2.imread('green_button.png') offset_x = int(green_button.shape[0] / 2) offset_y = int(green_button.shape[1] / 2) roi_crop = numpy.array(sct.grab(roi))[:,:,:3] result = cv2.matchTemplate(roi_crop, green_button, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) print(max_val, max_loc) button_center = (max_loc[0] + offset_y, max_loc[1] + offset_x) roi_crop = cv2.circle(roi_crop.astype(float), button_center, 20, (255, 0, 0), 2) cv2.imwrite(f"./screenshots/{frame_id:03}.jpg", roi_crop) abs_x_roi = roi["left"] + button_center[0] abs_y_roi = roi["top"] + button_center[1] pyautogui.click(x=abs_x_roi, y=abs_y_roi) frame_id += 1



En la segunda parte, presionamos los siguientes 400 botones; se implementa como un ciclo while infinito que captura la pantalla y hace clic en el píxel donde se espera ver un botón con respecto a la velocidad actual. La función de velocidad se ha seleccionado como una función logarítmica del número de iteraciones. Esta función proporciona un desplazamiento de píxeles necesario para ajustar cuando ha pasado el tiempo desde que se encontró el patrón.


La segunda mitad:

 second_roi = { "left": 0, "top": int(mon["height"] * 0.18), "width": int(mon["width"] / 2), "height": int(mon["height"] * 0.06) } btn = cv2.imread('center.png') offset_y = int(btn.shape[0]) offset_x = int(btn.shape[1] / 2) thresh = 0.9 frame_list = [] btn_cnt = 1 while True: frame_id += 1 second_roi_crop = numpy.array(sct.grab(second_roi))[:,:,:3] result = cv2.matchTemplate(second_roi_crop, btn, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) # define the speed of the screen speed = math.floor(math.log(frame_id)**2.5) print(frame_id, max_val, max_loc, speed) frame_list.append(max_loc[0]) if max_val > thresh: button_center = (max_loc[0] + offset_x, max_loc[1] + offset_y) second_roi_crop = cv2.circle(second_roi_crop.astype(float), button_center, 20, (255, 0, 0), 2) cv2.imwrite(f"./screenshots/{frame_id:03}.jpg", second_roi_crop) abs_x_sec = second_roi["left"] + button_center[0] abs_y_sec = second_roi["top"] + button_center[1] + speed pyautogui.click(x=abs_x_sec, y=abs_y_sec) btn_cnt += 1 if keyboard.is_pressed('q'): break

Como puede ver, la velocidad está parametrizada y, según la configuración de su PC, puede encontrar mejores parámetros que superen mi puntuación más alta. ¡Te animo a que lo hagas! Esto se debe a que el código depende mucho de la velocidad de procesamiento de la imagen y puede variar de un sistema a otro.


Aquí está el vistazo de la carrera. Cómo se ve cuando el bot se está ejecutando realmente.




Para no ser infundado, aquí está la captura de pantalla de la tabla de clasificación. Debo mencionar que en este juego en particular, la puntuación en todos los niveles de dificultad va a la tabla de clasificación, por lo que no es necesario que juegues "difícilmente". El nivel "Fácil" está bien (por cierto, cuando llegas a 100 botones presionados, ya no puedes decir que es fácil)


La tabla de posiciones

El código del proyecto está disponible en Github https://github.com/zetyquickly/addicting-games-ai . Sería genial crear una biblioteca extensa de juegos adictivos pirateados y mantener todos estos algoritmos allí. ¡Así que está invitado a crear las solicitudes de extracción!


Expresiones de gratitud

Este video inspiró este proyecto:


https://www.youtube.com/watch?v=vXqKniVe6P8


Aquí el autor supera la clasificación del juego Kick Ya Chop, tiene similitudes con Don't Touch the Red, pero también hay una gran diferencia. En Kick Ya Chop, el jugador decide la velocidad del juego. Cuanto más rápido haga clic el humano/bot, más rápido caerá el árbol. En Don't Touch the Red, el juego decide la velocidad de los próximos botones.