diff --git a/clashroyalebuildabot/actions/archers_action.py b/clashroyalebuildabot/actions/archers_action.py index 357a09c..9177a30 100644 --- a/clashroyalebuildabot/actions/archers_action.py +++ b/clashroyalebuildabot/actions/archers_action.py @@ -6,7 +6,7 @@ class ArchersAction(Action): CARD = Cards.ARCHERS def calculate_score(self, state): - score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0] + score = [0.5] if state.numbers.elixir.number == 10 else [0] 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 diff --git a/clashroyalebuildabot/actions/giant_action.py b/clashroyalebuildabot/actions/giant_action.py index b886783..d5b1ee2 100644 --- a/clashroyalebuildabot/actions/giant_action.py +++ b/clashroyalebuildabot/actions/giant_action.py @@ -6,14 +6,16 @@ class GiantAction(Action): CARD = Cards.GIANT def calculate_score(self, state): - score = [0] - left_hp, right_hp = ( - state.numbers[f"{direction}_enemy_princess_hp"]["number"] - for direction in ["left", "right"] - ) - if state.numbers["elixir"]["number"] == 10: - if self.tile_x == 3: - score = [1, self.tile_y, left_hp != -1, left_hp <= right_hp] - elif self.tile_x == 14: - score = [1, self.tile_y, right_hp != -1, right_hp <= left_hp] - return score + if state.numbers.elixir.number != 10: + return [0] + + left_hp = state.numbers.left_enemy_princess_hp.number + right_hp = state.numbers.right_enemy_princess_hp.number + + if (self.tile_x, self.tile_y) == (3, 15): + return [1, left_hp > 0, left_hp <= right_hp] + + if (self.tile_x, self.tile_y) == (14, 15): + return [1, right_hp > 0, right_hp <= left_hp] + + return [0] diff --git a/clashroyalebuildabot/actions/goblin_barrel_action.py b/clashroyalebuildabot/actions/goblin_barrel_action.py index d712b43..5cf37d7 100644 --- a/clashroyalebuildabot/actions/goblin_barrel_action.py +++ b/clashroyalebuildabot/actions/goblin_barrel_action.py @@ -6,15 +6,16 @@ class GoblinBarrelAction(Action): CARD = Cards.GOBLIN_BARREL def calculate_score(self, state): - left_hp, right_hp = ( - state.numbers[f"{direction}_enemy_princess_hp"]["number"] - for direction in ["left", "right"] - ) - if (self.tile_x, self.tile_y) == (14, 25) and right_hp <= left_hp: - score = [1] - elif (self.tile_x, self.tile_y) == (3, 25) and left_hp <= right_hp: - score = [1] - else: - score = [0] + left_hp = state.numbers.left_enemy_princess_hp.number + right_hp = state.numbers.right_enemy_princess_hp.number - return score + if (self.tile_x, self.tile_y) == (3, 25) and left_hp > 0: + return [1, left_hp <= right_hp] + + if (self.tile_x, self.tile_y) == (14, 25) and right_hp > 0: + return [1, right_hp <= left_hp] + + if (self.tile_x, self.tile_y) in {(8, 27), (9, 27), (8, 28), (9, 28)}: + return [0.5] + + return [0] diff --git a/clashroyalebuildabot/actions/knight_action.py b/clashroyalebuildabot/actions/knight_action.py index cab6840..8159563 100644 --- a/clashroyalebuildabot/actions/knight_action.py +++ b/clashroyalebuildabot/actions/knight_action.py @@ -6,7 +6,7 @@ class KnightAction(Action): CARD = Cards.KNIGHT def calculate_score(self, state): - score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0] + score = [0.5] if state.numbers.elixir.number == 10 else [0] 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 diff --git a/clashroyalebuildabot/actions/minipekka_action.py b/clashroyalebuildabot/actions/minipekka_action.py index 0e9179d..3eb9fc5 100644 --- a/clashroyalebuildabot/actions/minipekka_action.py +++ b/clashroyalebuildabot/actions/minipekka_action.py @@ -6,14 +6,16 @@ class MinipekkaAction(Action): CARD = Cards.MINIPEKKA def calculate_score(self, state): - left_hp, right_hp = ( - state.numbers[f"{direction}_enemy_princess_hp"]["number"] - for direction in ["left", "right"] - ) - if self.tile_x in [3, 14]: - return ( - [1, self.tile_y, left_hp != -1, left_hp <= right_hp] - if self.tile_x == 3 - else [1, self.tile_y, right_hp != -1, right_hp <= left_hp] - ) + if state.numbers.elixir.number != 10: + return [0] + + left_hp = state.numbers.left_enemy_princess_hp.number + right_hp = state.numbers.right_enemy_princess_hp.number + + if (self.tile_x, self.tile_y) == (3, 15): + return [1, left_hp > 0, left_hp <= right_hp] + + if (self.tile_x, self.tile_y) == (14, 15): + return [1, right_hp > 0, right_hp <= left_hp] + return [0] diff --git a/clashroyalebuildabot/actions/overhead_action.py b/clashroyalebuildabot/actions/overhead_action.py index 37f00e4..0313ec7 100644 --- a/clashroyalebuildabot/actions/overhead_action.py +++ b/clashroyalebuildabot/actions/overhead_action.py @@ -9,7 +9,7 @@ class OverheadAction(Action): """ def calculate_score(self, state): - score = [0.5] if state.numbers["elixir"]["number"] == 10 else [0] + 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, diff --git a/clashroyalebuildabot/bot/bot.py b/clashroyalebuildabot/bot/bot.py index 727e195..f55ae04 100644 --- a/clashroyalebuildabot/bot/bot.py +++ b/clashroyalebuildabot/bot/bot.py @@ -84,9 +84,9 @@ def _get_card_centre(card_n): def _get_valid_tiles(self): tiles = ALLY_TILES - if self.state.numbers["left_enemy_princess_hp"]["number"] == 0: + if self.state.numbers.left_enemy_princess_hp.number == 0: tiles += LEFT_PRINCESS_TILES - if self.state.numbers["right_enemy_princess_hp"]["number"] == 0: + if self.state.numbers.right_enemy_princess_hp.number == 0: tiles += RIGHT_PRINCESS_TILES return tiles @@ -97,7 +97,7 @@ def get_actions(self): actions = [] for i in self.state.ready: card = self.state.cards[i + 1] - if int(self.state.numbers["elixir"]["number"]) < card.cost: + if self.state.numbers.elixir.number < card.cost: continue tiles = ALL_TILES if card.target_anywhere else valid_tiles diff --git a/clashroyalebuildabot/debugger.py b/clashroyalebuildabot/debugger.py index 172d318..f35482d 100644 --- a/clashroyalebuildabot/debugger.py +++ b/clashroyalebuildabot/debugger.py @@ -1,3 +1,4 @@ +from dataclasses import asdict import os from PIL import ImageDraw @@ -6,6 +7,7 @@ from clashroyalebuildabot.constants import CARD_CONFIG from clashroyalebuildabot.constants import LABELS_DIR from clashroyalebuildabot.constants import SCREENSHOTS_DIR +from clashroyalebuildabot.namespaces.numbers import NumberDetection from clashroyalebuildabot.namespaces.units import NAME2UNIT @@ -75,9 +77,10 @@ def _draw_unit_bboxes(self, d, dets, prefix): def _write_image(self, image, state, basename): d = ImageDraw.Draw(image, "RGBA") - for v in state.numbers.values(): - d.rectangle(tuple(v["bounding_box"])) - self._draw_text(d, v["bounding_box"], str(v["number"])) + for det in asdict(state.numbers).values(): + det = NumberDetection(**det) + d.rectangle(det.bbox) + self._draw_text(d, det.bbox, str(det.number)) self._draw_unit_bboxes(d, state.allies, "ally") self._draw_unit_bboxes(d, state.enemies, "enemy") diff --git a/clashroyalebuildabot/detectors/number_detector.py b/clashroyalebuildabot/detectors/number_detector.py index 5770a7d..eafa8de 100644 --- a/clashroyalebuildabot/detectors/number_detector.py +++ b/clashroyalebuildabot/detectors/number_detector.py @@ -7,6 +7,8 @@ from clashroyalebuildabot.constants import NUMBER_HEIGHT from clashroyalebuildabot.constants import NUMBER_WIDTH from clashroyalebuildabot.detectors.onnx_detector import OnnxDetector +from clashroyalebuildabot.namespaces.numbers import NumberDetection +from clashroyalebuildabot.namespaces.numbers import Numbers class NumberDetector(OnnxDetector): @@ -18,7 +20,11 @@ def _calculate_elixir(image): std = np.array(crop).std(axis=(0, 2)) rolling_std = np.convolve(std, np.ones(10) / 10, mode="valid") change_points = np.nonzero(rolling_std < 50)[0] - return (change_points[0] + 10) // 25 if len(change_points) > 0 else 10 + if len(change_points) == 0: + elixir = 10 + else: + elixir = (change_points[0] + 10) // 25 + return elixir @staticmethod def _clean_king_levels(pred): @@ -49,11 +55,15 @@ def _clean_king_hp(pred): def _calculate_confidence_and_number(self, pred): pred = [p for p in pred.tolist() if p[4] > self.MIN_CONF][:4] pred.sort(key=lambda x: x[0]) + confidence = [p[4] for p in pred] + if len(confidence) == 0: + confidence = -1 + number = "".join([str(int(p[5])) for p in pred]) - return confidence if confidence else [-1], ( - int(number) if number else -1 - ) + number = int(number) if len(number) > 0 else 0 + + return confidence, number def _post_process(self, pred): clean_pred = {} @@ -90,9 +100,14 @@ def run(self, image): ) pred = self._post_process(preds) - pred["elixir"] = { - "bounding_box": ELIXIR_BOUNDING_BOX, - "confidence": 1.0, - "number": self._calculate_elixir(image), + pred = { + k: NumberDetection( + tuple(v["bounding_box"]), v["confidence"], v["number"] + ) + for k, v in pred.items() } - return pred + pred["elixir"] = NumberDetection( + tuple(ELIXIR_BOUNDING_BOX), [1.0], self._calculate_elixir(image) + ) + numbers = Numbers(**pred) + return numbers diff --git a/clashroyalebuildabot/namespaces/numbers.py b/clashroyalebuildabot/namespaces/numbers.py new file mode 100644 index 0000000..5e03de6 --- /dev/null +++ b/clashroyalebuildabot/namespaces/numbers.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from typing import List, Tuple + + +@dataclass(frozen=True) +class NumberDetection: + bbox: Tuple[int, int, int, int] + confidence: List[float] + number: int + + +@dataclass(frozen=True) +class Numbers: + enemy_king_level: NumberDetection + enemy_king_hp: NumberDetection + left_enemy_princess_hp: NumberDetection + right_enemy_princess_hp: NumberDetection + ally_king_level: NumberDetection + ally_king_hp: NumberDetection + left_ally_princess_hp: NumberDetection + right_ally_princess_hp: NumberDetection + elixir: NumberDetection diff --git a/clashroyalebuildabot/namespaces/state.py b/clashroyalebuildabot/namespaces/state.py index 4be83a0..1827c87 100644 --- a/clashroyalebuildabot/namespaces/state.py +++ b/clashroyalebuildabot/namespaces/state.py @@ -1,7 +1,8 @@ from dataclasses import dataclass -from typing import Any, List, Tuple +from typing import List, Tuple from clashroyalebuildabot.namespaces.cards import Card +from clashroyalebuildabot.namespaces.numbers import Numbers from clashroyalebuildabot.namespaces.screens import Screen from clashroyalebuildabot.namespaces.units import UnitDetection @@ -10,7 +11,7 @@ class State: allies: List[UnitDetection] enemies: List[UnitDetection] - numbers: Any + numbers: Numbers cards: Tuple[Card, Card, Card, Card] ready: List[int] screen: Screen diff --git a/main.py b/main.py index 335df51..d9d7c2e 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,6 @@ import time from clashroyalebuildabot.actions.archers_action import ArchersAction -from clashroyalebuildabot.actions.fireball_action import FireballAction from clashroyalebuildabot.actions.giant_action import GiantAction from clashroyalebuildabot.actions.goblin_barrel_action import ( GoblinBarrelAction,