#!/usr/bin/env python3 """Screenshot the game window, find a cafe affection sparkle, click it. Runs entirely on nik-gpu (screenshot -> detect -> click all local) so the whole cycle finishes in well under a second - roaming students move fast enough that a multi-hop SSH/scp pipeline misses the click. Prints "MATCH x y score" and exits 0 if a sparkle was found and clicked, or prints "NO_MATCH" and exits 1 otherwise. """ import subprocess import sys import cv2 import numpy as np TEMPLATE_PATH = "/home/nik/ba_assets/cafe_sparkle.png" SHOT_PATH = "/tmp/ba_live.png" OFFSET_X = 75 OFFSET_Y = 47 THRESHOLD = 0.97 ENV = {"DISPLAY": ":0", "XAUTHORITY": "/run/user/1000/gdm/Xauthority"} def screenshot(): subprocess.run( ["scrot", "-a", "0,0,1920,1200", "-o", SHOT_PATH], env=ENV, check=True, ) def find_sparkles(): template = cv2.imread(TEMPLATE_PATH) b, g, r = cv2.split(template.astype(np.int16)) yellow_white = ((r > 180) & (g > 140) & (r - b > 60)) | ((r > 200) & (g > 200) & (b > 200)) mask_plane = (yellow_white.astype(np.uint8)) * 255 mask = cv2.merge([mask_plane, mask_plane, mask_plane]) th, tw = template.shape[:2] img = cv2.imread(SHOT_PATH) result = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED, mask=mask) locs = np.where(result >= THRESHOLD) points = sorted(zip(*locs[::-1]), key=lambda p: -result[p[1], p[0]]) merged = [] for x, y in points: if all(abs(x - mx) > tw // 2 or abs(y - my) > th // 2 for mx, my, _ in merged): merged.append((x, y, result[y, x])) return [(x + tw // 2, y + th // 2, score) for x, y, score in merged] def click(x, y): subprocess.run( ["xdotool", "mousemove", str(x), str(y), "click", "1"], env=ENV, check=True, ) if __name__ == "__main__": screenshot() sparkles = find_sparkles() if not sparkles: print("NO_MATCH") sys.exit(1) x, y, score = sparkles[0] click(x + OFFSET_X, y + OFFSET_Y) print(f"MATCH {x} {y} {score:.4f}") sys.exit(0)