feat(battle): calibrate exact 5 player and 5 enemy card vertical slots

This commit is contained in:
VolandSZ
2026-09-13 15:43:42 +03:00
parent 3a40c2003f
commit aa4d866d7d
3 changed files with 158 additions and 8 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ logs/
*.log
temp_*.png
debug_*.png
debug_cards/
dataset/raw/*.png
dataset/annotated/*.jpg
dataset/images/

View File

@ -49,6 +49,23 @@ class DirtyLeagueBot:
REL_ROI_OFFER_CLOSE = (0.8854, 0.0, 1.0, 0.1463)
REL_CROWN_CHEST_POS = (0.2279, 0.6146)
# 5 Player card slots (left vertical column) & 5 Opponent card slots (right vertical column)
PLAYER_CARD_SLOTS = [
(0.226, 0.150, 0.294, 0.278),
(0.226, 0.295, 0.294, 0.423),
(0.226, 0.440, 0.294, 0.568),
(0.226, 0.585, 0.294, 0.713),
(0.226, 0.730, 0.294, 0.858),
]
OPPONENT_CARD_SLOTS = [
(0.693, 0.150, 0.761, 0.278),
(0.693, 0.295, 0.761, 0.423),
(0.693, 0.440, 0.761, 0.568),
(0.693, 0.585, 0.761, 0.713),
(0.693, 0.730, 0.761, 0.858),
]
def __init__(self, config: dict):
self.config = config
self.bot_conf = config.get("bot", {})
@ -629,6 +646,23 @@ class DirtyLeagueBot:
else:
time.sleep(1.5)
def extract_battle_cards(self, frame: np.ndarray) -> Tuple[List[np.ndarray], List[np.ndarray]]:
"""Extracts exact 5 player and 5 opponent card portrait crops from battle screen."""
fh, fw = frame.shape[:2]
player_crops = []
for rx1, ry1, rx2, ry2 in self.PLAYER_CARD_SLOTS:
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
player_crops.append(frame[y1:y2, x1:x2])
opponent_crops = []
for rx1, ry1, rx2, ry2 in self.OPPONENT_CARD_SLOTS:
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
opponent_crops.append(frame[y1:y2, x1:x2])
return player_crops, opponent_crops
def handle_in_game(self, frame: np.ndarray, dets: Optional[Dict[str, List[Detection]]] = None):
should_surrender = self.derank_mode or (self.current_wins >= self.max_wins_limit)
reason = "Forced Derank" if self.derank_mode else f"Win Streak Limit ({self.current_wins}/{self.max_wins_limit})"
@ -638,16 +672,21 @@ class DirtyLeagueBot:
self.fast_surrender_pipeline(max_wait_sec=5.0)
return
else:
if dets and "card" in dets:
cards = sorted(dets["card"], key=lambda d: d.box[0])
recognized = []
for d in cards:
crop = frame[d.box[1]:d.box[3], d.box[0]:d.box[2]]
name, sim = self.embedder.identify_card(crop)
recognized.append(f"{name or 'Card'} ({sim:.2f})")
logging.info(f"[IN_GAME] Detected {len(cards)} hand cards: {', '.join(recognized)}")
player_crops, opponent_crops = self.extract_battle_cards(frame)
player_names = []
for i, crop in enumerate(player_crops, 1):
name, sim = self.embedder.identify_card(crop)
player_names.append(f"#{i}:{name or 'Unknown'}({sim:.2f})")
enemy_names = []
for i, crop in enumerate(opponent_crops, 1):
name, sim = self.embedder.identify_card(crop)
enemy_names.append(f"#{i}:{name or 'Unknown'}({sim:.2f})")
logging.info(f"[IN_GAME] Player Team: [{', '.join(player_names)}] vs Opponent: [{', '.join(enemy_names)}]")
logging.info("Match in progress (Normal Play, Auto-battle active). Waiting for match results...")
def handle_victory(self, frame: np.ndarray, dets: Optional[Dict[str, List[Detection]]] = None):
if not self._result_recorded_for_match:
self._result_recorded_for_match = True

110
inspect_battle_cards.py Normal file
View File

@ -0,0 +1,110 @@
"""
Inspection script to calibrate exact bounding boxes for:
- 5 Player cards (left vertical column)
- 5 Opponent cards (right vertical column)
on the DirtyLeague Battle Screen (3840x2050 design resolution).
"""
import os
import cv2
import numpy as np
from card_embedder import CardEmbedder
# Normalized relative coordinates (rx1, ry1, rx2, ry2) for 3840x2050
# Calibrated from actual 4K battle screenshot
PLAYER_CARD_SLOTS = [
# Slot 1 (top left)
(0.226, 0.150, 0.294, 0.278),
# Slot 2
(0.226, 0.295, 0.294, 0.423),
# Slot 3
(0.226, 0.440, 0.294, 0.568),
# Slot 4
(0.226, 0.585, 0.294, 0.713),
# Slot 5 (bottom left)
(0.226, 0.730, 0.294, 0.858),
]
OPPONENT_CARD_SLOTS = [
# Slot 1 (top right)
(0.693, 0.150, 0.761, 0.278),
# Slot 2
(0.693, 0.295, 0.761, 0.423),
# Slot 3
(0.693, 0.440, 0.761, 0.568),
# Slot 4
(0.693, 0.585, 0.761, 0.713),
# Slot 5 (bottom right)
(0.693, 0.730, 0.761, 0.858),
]
def extract_cards(frame: np.ndarray):
fh, fw = frame.shape[:2]
player_crops = []
opponent_crops = []
for rx1, ry1, rx2, ry2 in PLAYER_CARD_SLOTS:
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
player_crops.append(frame[y1:y2, x1:x2])
for rx1, ry1, rx2, ry2 in OPPONENT_CARD_SLOTS:
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
opponent_crops.append(frame[y1:y2, x1:x2])
return player_crops, opponent_crops
def main():
img_path = os.path.join("dataset", "raw", "dl_screen_20260910_233743_310.png")
if not os.path.exists(img_path):
print(f"[ERROR] Sample battle screenshot not found at: {img_path}")
return
frame = cv2.imread(img_path)
fh, fw = frame.shape[:2]
print(f"\nAnalyzing battle frame resolution: {fw}x{fh}")
player_crops, opponent_crops = extract_cards(frame)
os.makedirs("debug_cards", exist_ok=True)
embedder = CardEmbedder()
print("\n--- Player Cards (Left Column 1..5) ---")
for i, crop in enumerate(player_crops, 1):
crop_path = os.path.join("debug_cards", f"player_card_{i}.png")
cv2.imwrite(crop_path, crop)
vec = embedder.embed(crop)
print(f" Player Slot #{i}: {crop.shape[1]}x{crop.shape[0]}px | Vector norm: {np.linalg.norm(vec):.4f} | Saved to: {crop_path}")
print("\n--- Opponent Cards (Right Column 1..5) ---")
for i, crop in enumerate(opponent_crops, 1):
crop_path = os.path.join("debug_cards", f"opponent_card_{i}.png")
cv2.imwrite(crop_path, crop)
vec = embedder.embed(crop)
print(f" Opponent Slot #{i}: {crop.shape[1]}x{crop.shape[0]}px | Vector norm: {np.linalg.norm(vec):.4f} | Saved to: {crop_path}")
# Draw visual overlay on whole frame
annotated = frame.copy()
for i, (rx1, ry1, rx2, ry2) in enumerate(PLAYER_CARD_SLOTS, 1):
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
cv2.rectangle(annotated, (x1, y1), (x2, y2), (0, 255, 0), 3)
cv2.putText(annotated, f"Player #{i}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2)
for i, (rx1, ry1, rx2, ry2) in enumerate(OPPONENT_CARD_SLOTS, 1):
x1, y1 = int(rx1 * fw), int(ry1 * fh)
x2, y2 = int(rx2 * fw), int(ry2 * fh)
cv2.rectangle(annotated, (x1, y1), (x2, y2), (0, 0, 255), 3)
cv2.putText(annotated, f"Enemy #{i}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2)
preview_path = os.path.join("debug_cards", "annotated_battle_slots.png")
cv2.imwrite(preview_path, cv2.resize(annotated, (1920, 1025)))
print(f"\n[SUCCESS] Full annotated battle preview saved to: {preview_path}")
if __name__ == "__main__":
main()