from __future__ import annotations """ Canonical UI geometry contract for the live Bluestacks pipeline. Everything here is expressed in *normalized coordinates inside the game viewport* (after black bars/padding are removed): x_n in [0,1], y_n in [0,1] This keeps the extractor/executor stable across resolutions and window borders. """ from dataclasses import dataclass from typing import Literal Zone = Literal["back_left", "back_right", "mid_left", "mid_right", "bridge_left", "bridge_right"] @dataclass(frozen=True) class RectN: x1: float y1: float x2: float y2: float @dataclass(frozen=True) class PointN: x: float y: float # --- ROIs (starting calibration; refine per-device if needed) --- ROI_TIME_LEFT = RectN(0.74, 0.00, 1.00, 0.10) ROI_MY_ELIXIR = RectN(0.23, 0.86, 0.40, 0.97) ROI_NEXT_CARD = RectN(0.02, 0.80, 0.22, 0.99) ROI_HAND_BAR = RectN(0.22, 0.76, 1.00, 1.00) def hand_slot_roi(slot: int) -> RectN: slot = max(0, min(3, int(slot))) w = (ROI_HAND_BAR.x2 - ROI_HAND_BAR.x1) / 4.0 x1 = ROI_HAND_BAR.x1 + slot * w return RectN(x1, ROI_HAND_BAR.y1, x1 + w, ROI_HAND_BAR.y2) def hand_slot_center(slot: int) -> PointN: r = hand_slot_roi(slot) return PointN((r.x1 + r.x2) / 2.0, 0.88) # --- Placement zone click points (normalized) --- ZONE_POINTS: dict[Zone, PointN] = { "back_left": PointN(0.30, 0.63), "back_right": PointN(0.70, 0.63), "mid_left": PointN(0.30, 0.52), "mid_right": PointN(0.70, 0.52), "bridge_left": PointN(0.30, 0.42), "bridge_right": PointN(0.70, 0.42), }