Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various QOL updates #203

Merged
merged 3 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions clashroyalebuildabot/actions/archers_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ class ArchersAction(Action):

def calculate_score(self, state):
score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0]
for v in state.enemies.values():
for position in v["positions"]:
lhs = position.tile_x <= 8 and self.tile_x == 7
rhs = position.tile_x > 8 and self.tile_x == 10
if self.tile_y < position.tile_y <= 14 and (lhs or rhs):
score = [1, self.tile_y - position.tile_y]
for det in state.enemies:
lhs = det.position.tile_x <= 8 and self.tile_x == 7
rhs = det.position.tile_x > 8 and self.tile_x == 10
if self.tile_y < det.position.tile_y <= 14 and (lhs or rhs):
score = [1, self.tile_y - det.position.tile_y]
return score
1 change: 0 additions & 1 deletion clashroyalebuildabot/actions/arrows_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@
class ArrowsAction(SpellAction):
CARD = Cards.ARROWS
RADIUS = 4
MIN_TO_HIT = 5
6 changes: 6 additions & 0 deletions clashroyalebuildabot/actions/bats_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from clashroyalebuildabot import Cards
from clashroyalebuildabot.actions.overhead_action import OverheadAction


class BatsAction(OverheadAction):
CARD = Cards.BATS
1 change: 0 additions & 1 deletion clashroyalebuildabot/actions/fireball_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@
class FireballAction(SpellAction):
CARD = Cards.FIREBALL
RADIUS = 2.5
MIN_TO_HIT = 3
11 changes: 5 additions & 6 deletions clashroyalebuildabot/actions/knight_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ class KnightAction(Action):

def calculate_score(self, state):
score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0]
for v in state.enemies.values():
for position in v["positions"]:
lhs = position.tile_x <= 8 and self.tile_x == 8
rhs = position.tile_x > 8 and self.tile_x == 9
for det in state.enemies:
lhs = det.position.tile_x <= 8 and self.tile_x == 8
rhs = det.position.tile_x > 8 and self.tile_x == 9

if self.tile_y < position.tile_y <= 14 and (lhs or rhs):
score = [1, self.tile_y - position.tile_y]
if self.tile_y < det.position.tile_y <= 14 and (lhs or rhs):
score = [1, self.tile_y - det.position.tile_y]
return score
18 changes: 2 additions & 16 deletions clashroyalebuildabot/actions/minions_action.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
import math

from clashroyalebuildabot import Cards
from clashroyalebuildabot.actions.action import Action
from clashroyalebuildabot.actions.overhead_action import OverheadAction


class MinionsAction(Action):
class MinionsAction(OverheadAction):
CARD = Cards.MINIONS

def calculate_score(self, state):
score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0]
for v in state.enemies.values():
for position in v["positions"]:
distance = math.hypot(
position.tile_x - self.tile_x,
position.tile_y - self.tile_y,
)
if distance < 1:
score = [1, -distance]
return score
19 changes: 9 additions & 10 deletions clashroyalebuildabot/actions/musketeer_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ class MusketeerAction(Action):
CARD = Cards.MUSKETEER

def calculate_score(self, state):
for v in state.enemies.values():
for position in v["positions"]:
distance = math.hypot(
position.tile_x - self.tile_x,
position.tile_y - self.tile_y,
)
if 5 < distance < 6:
return [1]
if distance < 5:
return [0]
for det in state.enemies:
distance = math.hypot(
det.position.tile_x - self.tile_x,
det.position.tile_y - self.tile_y,
)
if 5 < distance < 6:
return [1]
if distance < 5:
return [0]
return [0]
20 changes: 20 additions & 0 deletions clashroyalebuildabot/actions/overhead_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import math

from clashroyalebuildabot.actions.action import Action


class OverheadAction(Action):
"""
Play the card directly on top of enemy units
"""

def calculate_score(self, state):
score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0]
for det in state.enemies:
distance = math.hypot(
det.position.tile_x - self.tile_x,
det.position.tile_y - self.tile_y,
)
if distance < 1:
score = [1, -distance]
return score
27 changes: 14 additions & 13 deletions clashroyalebuildabot/actions/spell_action.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import math

from clashroyalebuildabot.actions.action import Action
from clashroyalebuildabot.namespaces.units import Units


class SpellAction(Action):
RADIUS = None
MIN_TO_HIT = None
MIN_SCORE = 5
UNIT_TO_SCORE = {Units.SKELETON: 1}

def calculate_score(self, state):
hit_units = 0
hit_score = 0
max_distance = float("inf")
for v in state.enemies.values():
for position in v["positions"]:
distance = math.hypot(
self.tile_x - position.tile_x,
self.tile_y - position.tile_y + 2,
)
if distance <= self.RADIUS - 1:
hit_units += 1
max_distance = min(max_distance, -distance)
for det in state.enemies:
distance = math.hypot(
self.tile_x - det.position.tile_x,
self.tile_y - det.position.tile_y + 2,
)
if distance <= self.RADIUS - 1:
hit_score += self.UNIT_TO_SCORE.get(det.unit, 2)
max_distance = min(max_distance, -distance)

return [
1 if hit_units >= self.MIN_TO_HIT else 0,
hit_units,
1 if hit_score >= self.MIN_SCORE else 0,
hit_score,
max_distance,
]
1 change: 0 additions & 1 deletion clashroyalebuildabot/actions/zap_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@
class ZapAction(SpellAction):
CARD = Cards.ZAP
RADIUS = 2.5
MIN_TO_HIT = 3
4 changes: 2 additions & 2 deletions clashroyalebuildabot/bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,6 @@ def run(self):
try:
while True:
self.step()
except (KeyboardInterrupt, Exception):
self.emulator.quit()
except KeyboardInterrupt:
logger.info("Thanks for using CRBAB, see you next time!")
self.emulator.quit()
37 changes: 15 additions & 22 deletions clashroyalebuildabot/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,19 @@ def __init__(self):
os.makedirs(SCREENSHOTS_DIR, exist_ok=True)
os.makedirs(LABELS_DIR, exist_ok=True)
self.font = ImageFont.load_default()
self.unit_names = [unit[0] for unit in list(NAME2UNIT.values())]
self.unit_names = [unit["name"] for unit in list(NAME2UNIT.values())]

@staticmethod
def _write_label(image, state, basename):
labels = []
unit_names_and_positions = [
[unit_name, v["positions"]]
for unit_name, v in list(state.allies.items())
+ list(state.enemies.items())
]
for unit_name, positions in unit_names_and_positions:
for position in positions:
bbox = position.bbox
xc = (bbox[0] + bbox[2]) / (2 * image.width)
yc = (bbox[1] + bbox[3]) / (2 * image.height)
w = (bbox[2] - bbox[0]) / image.width
h = (bbox[3] - bbox[1]) / image.height
label = f"{unit_name} {xc} {yc} {w} {h}"
labels.append(label)
for det in state.allies + state.enemies:
bbox = det.position.bbox
xc = (bbox[0] + bbox[2]) / (2 * image.width)
yc = (bbox[1] + bbox[3]) / (2 * image.height)
w = (bbox[2] - bbox[0]) / image.width
h = (bbox[3] - bbox[1]) / image.height
label = f"{det.unit.name} {xc} {yc} {w} {h}"
labels.append(label)

with open(
os.path.join(LABELS_DIR, f"{basename}.txt"), "w", encoding="utf-8"
Expand All @@ -69,16 +63,15 @@ def _draw_text(self, d, bbox, text, rgba=(0, 0, 0, 255)):
d.rectangle(tuple(bbox), outline=rgba)
d.text(tuple(text_bbox[:2]), text=text, fill="white")

def _draw_unit_bboxes(self, d, units, prefix):
for unit_name, v in units.items():
colour_idx = self.unit_names.index(unit_name) % len(
def _draw_unit_bboxes(self, d, dets, prefix):
for det in dets:
colour_idx = self.unit_names.index(det.unit.name) % len(
self._COLOUR_AND_RGBA
)
rgba = self._COLOUR_AND_RGBA[colour_idx][1]
for position in v["positions"]:
self._draw_text(
d, position.bbox, f"{prefix}_{unit_name}", rgba
)
self._draw_text(
d, det.position.bbox, f"{prefix}_{det.unit.name}", rgba
)

def _write_image(self, image, state, basename):
d = ImageDraw.Draw(image, "RGBA")
Expand Down
9 changes: 5 additions & 4 deletions clashroyalebuildabot/detectors/detector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

from loguru import logger

from clashroyalebuildabot.constants import MODELS_DIR
from clashroyalebuildabot.debugger import Debugger
from clashroyalebuildabot.detectors.card_detector import CardDetector
Expand Down Expand Up @@ -35,14 +37,13 @@ def __init__(self, cards, debug=False):
self.debugger = Debugger()

def run(self, image):
logger.debug("Setting state...")
cards, ready = self.card_detector.run(image)
units = self.unit_detector.run(image)
allies, enemies = self.unit_detector.run(image)
numbers = self.number_detector.run(image)
screen = self.screen_detector.run(image)

state = State(
units["enemy"], units["ally"], numbers, cards, ready, screen
)
state = State(allies, enemies, numbers, cards, ready, screen)
if self.debugger is not None:
self.debugger.run(image, state)

Expand Down
35 changes: 17 additions & 18 deletions clashroyalebuildabot/detectors/unit_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from clashroyalebuildabot.constants import TILE_WIDTH
from clashroyalebuildabot.detectors.onnx_detector import OnnxDetector
from clashroyalebuildabot.detectors.side_detector import SideDetector
from clashroyalebuildabot.namespaces.state import Position
from clashroyalebuildabot.namespaces.units import Unit
from clashroyalebuildabot.namespaces.units import Position
from clashroyalebuildabot.namespaces.units import UnitDetection


class UnitDetector(OnnxDetector):
Expand Down Expand Up @@ -47,8 +47,7 @@ def _get_possible_ally_names(self):
for card in self.cards:
if card.units is None:
continue
for unit_ in card.units:
unit = Unit(*unit_)
for unit in card.units:
possible_ally_names.add(unit.name)
return possible_ally_names

Expand Down Expand Up @@ -76,30 +75,30 @@ def _preprocess(self, image):
def _post_process(self, pred, height, image):
pred[:, [1, 3]] *= self.UNIT_Y_END - self.UNIT_Y_START
pred[:, [1, 3]] += self.UNIT_Y_START * height
clean_pred = {"ally": {}, "enemy": {}}

allies = []
enemies = []
for p in pred:
l, t, r, b, conf, cls = p
bbox = (round(l), round(t), round(r), round(b))
tile_x, tile_y = self._get_tile_xy(bbox)
position = Position(bbox, conf, tile_x, tile_y)
name, category, target, transport = DETECTOR_UNITS[int(cls)]
side = self._calculate_side(image, bbox, name)
if name not in clean_pred[side]:
clean_pred[side][name] = {
"type": category,
"target": target,
"transport": transport,
"positions": [],
}
unit = DETECTOR_UNITS[int(cls)]
unit_detection = UnitDetection(unit, position)

side = self._calculate_side(image, bbox, unit.name)
if side == "ally":
allies.append(unit_detection)
else:
enemies.append(unit_detection)

clean_pred[side][name]["positions"].append(position)
return clean_pred
return allies, enemies

def run(self, image):
height, width = image.height, image.width
np_image, padding = self._preprocess(image)
pred = self._infer(np_image)[0]
pred = pred[pred[:, 4] > self.MIN_CONF]
pred = self.fix_bboxes(pred, width, height, padding)
pred = self._post_process(pred, height, image)
return pred
allies, enemies = self._post_process(pred, height, image)
return allies, enemies
13 changes: 3 additions & 10 deletions clashroyalebuildabot/namespaces/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@

from clashroyalebuildabot.namespaces.cards import Card
from clashroyalebuildabot.namespaces.screens import Screen
from clashroyalebuildabot.namespaces.units import UnitDetection


@dataclass
class State:
enemies: Any
allies: Any
allies: List[UnitDetection]
enemies: List[UnitDetection]
numbers: Any
cards: Tuple[Card, Card, Card, Card]
ready: List[int]
screen: Screen


@dataclass
class Position:
bbox: Tuple[int, int, int, int]
conf: float
tile_x: int
tile_y: int
Loading