feat(battle): calibrate exact 5 player and 5 enemy card vertical slots
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,6 +19,7 @@ logs/
|
||||
*.log
|
||||
temp_*.png
|
||||
debug_*.png
|
||||
debug_cards/
|
||||
dataset/raw/*.png
|
||||
dataset/annotated/*.jpg
|
||||
dataset/images/
|
||||
|
||||
55
bot_core.py
55
bot_core.py
@ -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
110
inspect_battle_cards.py
Normal 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()
|
||||
Reference in New Issue
Block a user