From 0459e5722ebf9da27e37bd817a5da1e6eefd235c Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Fri, 4 Apr 2025 08:39:02 +0100 Subject: [PATCH] Initial commit --- main.py | 396 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 397 insertions(+) create mode 100644 main.py create mode 100644 requirements.txt diff --git a/main.py b/main.py new file mode 100644 index 0000000..be7813b --- /dev/null +++ b/main.py @@ -0,0 +1,396 @@ +import os +from subprocess import Popen, PIPE +import numpy as np +import cv2 +import threading as th +import time as t +import hyprpy as hypr +from evdev import UInput, ecodes as e +import math + + +# Function to create and initialize a virtual keyboard +def create_virtual_keyboard(): + capabilities = { + e.EV_KEY: [e.KEY_W, e.KEY_A, e.KEY_D, e.KEY_S] + } + return UInput(capabilities) + + +virtual_keyboard = create_virtual_keyboard() +print(virtual_keyboard) + + +# Function to send a keystroke +def send_keystroke(virtual_keyboard, key): + virtual_keyboard.write(e.EV_KEY, key, 1) # Key down + t.sleep(0.01) + virtual_keyboard.write(e.EV_KEY, key, 0) # Key up + virtual_keyboard.syn() + + +instance = hypr.Hyprland() + +print("please select the game area") +stream = os.popen('slurp') +capture_area = stream.read() +size = list(map(int, capture_area.split(' ')[1].split('x'))) +boxPos = list(map(int, capture_area.split(' ')[0].split(','))) +print(capture_area, size) + +height = 0 +width = 0 + + +def colorCodeToColorArary(target: str): + return np.array([int(target[4:6], 16), int(target[2:4], 16), int(target[0:2], 16)]) + + +green_1 = colorCodeToColorArary("AAD751") +green_2 = colorCodeToColorArary("A2D149") +fruit_color_1 = colorCodeToColorArary("D8B81C") +fruit_color_2 = colorCodeToColorArary("CCAD1E") + + +def get_img(): + global height, width + stream = Popen(['grim', '-g', capture_area.strip(), '-'], stdout=PIPE) + img = cv2.imdecode(np.frombuffer(stream.stdout.read(), dtype='uint8'), cv2.IMREAD_COLOR) + height, width, _ = img.shape + return img + + +def get_start(img): + start = None + + for i in range(height): + for j in range(width): + if start is None: + if (img[i, j] == green_1).all(): + print("found target at start", i, j) + start = [i, j] + else: + if (img[i, j] == green_2).all(): + print("found end at", i, j) + return [*start, j - start[1]] + if start is not None: + print("Something is wrong found start but not end at the same line") + exit(1) + + +img = get_img() +cv2.imshow('image', img) +cv2.waitKey(1) + +start = get_start(img) + +targets = [] + +# 0 - empty +# 1 - fruit +# 2 - snake +# 3 - head +targets_v = {} +head = None +food = None +snake_part = [] + +for i in range(15): + for j in range(17): + i2 = i * start[2] + int(start[2] / 2) + start[0] + j2 = j * start[2] + int(start[2] / 2) + start[1] + targets.append([i2, j2, i, j]) + targets_v[f"{i}-{j}"] = None + + +def draw_square(img, pos, color): + i, j = pos + i2 = int(i * start[2] + int(start[2] / 2) + start[0]) + j2 = int(j * start[2] + int(start[2] / 2) + start[1]) + img[i2-5:i2+5, j2-5:j2+5] = color + + +def draw_dots_from_data(img): + global food, head + for target in targets: + i2, j2, i, j = target + v = targets_v[f"{i}-{j}"] + if v == 0: + # img[i2-5:i2+5, j2-5:j2+5] = [0, 255, 0] + pass + elif v == 1: + img[i2-5:i2+5, j2-5:j2+5] = [0, 0, 255] + elif v == 2: + img[i2-5:i2+5, j2-5:j2+5] = [255, 0, 0] + elif v == 3: + img[i2-5:i2+5, j2-5:j2+5] = [255, 255, 255] + else: + img[i2-5:i2+5, j2-5:j2+5] = [255, 0, 255] + +def draw_dots(img): + global food, head, snake_part, head_p + snake_part = [] + for target in targets: + i2, j2, i, j = target + color = img[i2, j2] + + # body samples + bs_1 = img[i2 - 7, j2] + bs_2 = img[i2 + 7, j2] + bs_3 = img[i2, j2 - 7] + bs_4 = img[i2, j2 + 7] + + all_the_same = (bs_1 == bs_2).all() and (bs_1 == bs_3).all() and (bs_1 == bs_4).all() + + bs_list = np.array([bs_1, bs_2, bs_3, bs_4]) + + if (color == green_1).all() or (color == green_2).all() and all_the_same: + # img[i2-5:i2+5, j2-5:j2+5] = [0, 255, 0] + targets_v[f"{i}-{j}"] = 0 + elif (color == fruit_color_1).all() or (color == fruit_color_2).all(): + img[i2-5:i2+5, j2-5:j2+5] = [0, 0, 255] + targets_v[f"{i}-{j}"] = 1 + food = [i, j] + elif color[0] > 150: + img[i2-5:i2+5, j2-5:j2+5] = [255, 0, 0] + targets_v[f"{i}-{j}"] = 2 + snake_part.append([i, j]) + else: + greens_1 = np.sum(bs_list == green_1, axis=1) + greens_2 = np.sum(bs_list == green_2, axis=1) + if np.sum(greens_1) == 9 and bs_list[np.argmin(greens_1)][0] > 150: + targets_v[f"{i}-{j}"] = 3 + img[i2-5:i2+5, j2-5:j2+5] = [255, 255, 255] + head = np.array([i, j]) + if np.sum(greens_2) == 9 and bs_list[np.argmin(greens_2)][0] > 150: + targets_v[f"{i}-{j}"] = 3 + img[i2-5:i2+5, j2-5:j2+5] = [255, 255, 255] + head = np.array([i, j]) + else: + img[i2, j2] = [255, 0, 255] + img[i2 - 7, j2] = [255, 0, 255] + img[i2 + 7, j2] = [255, 0, 255] + img[i2, j2 - 7] = [255, 0, 255] + img[i2, j2 + 7] = [255, 0, 255] + targets_v[f"{i}-{j}"] = None + + +def pt_dist(a, b): + return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2) + + +def get_all_adj(a): + return [ + [a[0] + 1, a[1]], + [a[0] - 1, a[1]], + [a[0], a[1] + 1], + [a[0], a[1] - 1], + ] + + +def draw_path(img, ps, final=False): + for p in ps: + i, j = p + i2 = i * start[2] + int(start[2] / 2) + start[0] + j2 = j * start[2] + int(start[2] / 2) + start[1] + if final: + img[i2-3:i2+3, j2-3:j2+3] = [255, 255, 0] + else: + img[i2-3:i2+3, j2-3:j2+3] = [0, 255, 255] + + +def calculate_path(pos, visited, img=None): +# if img is not None and visited is not None: +# draw_dots_from_data(img) +# draw_path(img, visited) +# cv2.imshow('image-thread', img) +# cv2.waitKey(1) + + n_visited = visited.copy() + n_visited.append(pos) + queue = get_all_adj(pos) + queue.sort(key=lambda p: pt_dist(food, p), reverse=False) + for i in queue: + if i in n_visited: + # print("already visisted", i) + continue + v = targets_v.get(f'{i[0]}-{i[1]}') + if v == 1: + # print("is food", i) + return [pos, i] + if v != 0: + # print("is ocupied", i) + continue + + maybe_path = calculate_path(i, n_visited, img=img) + if maybe_path is not None: + maybe_path.insert(0, pos) + return maybe_path + return None + + +draw_dots(img) +cv2.imshow('image', img) +cv2.waitKey(1) + +# Good for testing +# cv2.waitKey(0) +# exit(1) + +last = t.time_ns() + +g_img = img + + +def img_collection_and_path_analisis(data): + print("start img col") + + path = None + + while True: + img = get_img() + data['img'] = img + + draw_dots(img) + if path is not None: + draw_path(img, path, final=True) + + if data['path'] is None: + if head is None: + print("could not find head skiping") + t.sleep(0.001) + continue + if food is None: + print("could not find food skiping") + t.sleep(0.001) + continue + + # print("calculate path") + + maybe_path = calculate_path(list(head), [], img) + if maybe_path is None: + # print("No path found") + pass + + draw_dots_from_data(img) + if maybe_path is not None: + draw_path(img, maybe_path, True) + + # print("got_path") + + path = maybe_path + data['path'] = maybe_path + + +data = {'path': None, 'img': None} + +img_col = th.Thread(target=img_collection_and_path_analisis, args=(data, )) +img_col.start() + + +last = t.time_ns() + +head_pred = None +time_to_pred = None +start_time_to_pred = None +last_time_to_pred = 0 + +lp_avg = 0 +lp_count = 0 + +while True: + # sleep mode if the mouse is outside the area + pos = instance.get_cursor_pos() + if not ((pos[0] > boxPos[0]) and (pos[0] < boxPos[0] + size[0]) and (pos[1] > boxPos[1]) and (pos[1] < boxPos[1] + size[1])): + t.sleep(0.5) + cv2.waitKey(1) + continue + + now = t.time_ns() + lp_count += 1 + lp_time = (now - last)/1000000 + lp_avg += lp_time + time_since_last_move = (now - start_time_to_pred)/1000000 if start_time_to_pred is not None else 0 + lp_time_next = (last_time_to_pred - time_since_last_move) / (lp_avg/lp_count) + + print( + f'\r lp time {lp_time:.4f}ms lp avg {lp_avg/lp_count:.4f}ms', + "pred: ", last_time_to_pred, + f"loops til next: {lp_time_next:.2f}", + f"loops time since last move {time_since_last_move:.2f}", + " ms ", end="") + last = t.time_ns() + + path = data['path'] + img = data['img'].copy() + + if img is not None: + cv2.imshow('image', img) + cv2.waitKey(1) + + l_head = list(head) + + if l_head == head_pred and time_to_pred is None: + time_to_pred = t.time_ns() - start_time_to_pred + last_time_to_pred = (t.time_ns() - start_time_to_pred)/1000000 + print("pred hit in ", time_to_pred / 1000000, "ms!", end=" ") + + if head is not None and path is not None: + if l_head in path: + index = path.index(l_head) + if index < len(path) - 1: + diff = np.array(l_head) - np.array(path[index + 1]) + if diff[1] == 1: + # print('turn Left', end="") + send_keystroke(virtual_keyboard, e.KEY_A) + elif diff[1] == -1: + # print('turn Right', end="") + send_keystroke(virtual_keyboard, e.KEY_D) + elif diff[0] == 1: + # print('turn Up', end="") + send_keystroke(virtual_keyboard, e.KEY_W) + elif diff[0] == -1: + # print('turn Down', end="") + send_keystroke(virtual_keyboard, e.KEY_S) + else: + print("not sure what to do", diff, end="") + exit(1) + if head_pred != path[index + 1]: + head_pred = path[index + 1] + start_time_to_pred = t.time_ns() + time_to_pred = None + else: + print('head not in path', end="") + data['path'] = None + else: + # print("head", head, "path", path) + pass + + print(" ", end="") + +cv2.waitKey(0) + + +# import pygame +# scrn = pygame.display.set_mode(size) + +# running = True +# while (running): +# for i in pygame.event.get(): +# if i.type == pygame.QUIT: +# running = False +# +# stream = Popen(['grim', '-g', capture_area.strip(), '-'], stdout=PIPE) +# # print(stream.stdout.read()) +# +# # img = pygame.image.load(stream.stdout, "test.png") +# +# print(len(stream.stdout.read())) +# +# # img = pygame.image.frombuffer(stream.stdout.read(), size, "RGB") +# +# # scrn.blit(img, (0, 0)) +# +# # pygame.display.flip() +# +# pygame.quit() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..86dbb1d --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-pillow