¿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 .
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!
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!
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.
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.
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 +
.
Basado en los dos últimos, he creado un algoritmo que supera el puntaje de juego humano anterior de 170 con un puntaje de 445.
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)
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!
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.