Skip to content

Commit

Permalink
Various QOL updates (#203)
Browse files Browse the repository at this point in the history
* Various QOL fixes

* Various QOL fixes

* Various QOL fixes
  • Loading branch information
Pbatch committed Jul 6, 2024
1 parent f5a8ecd commit fdeee1a
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 144 deletions.
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

0 comments on commit fdeee1a

Please sign in to comment.