From 2029c454a675da2a998a943c6b639a26819dcbbf Mon Sep 17 00:00:00 2001 From: Owen-Griffin <99355709+Owen-Griffin@users.noreply.github.com> Date: Mon, 24 Jun 2024 09:23:08 -0400 Subject: [PATCH 1/3] began implementation, fixed error in player.py --- gui/frames/npc.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++- models/player.py | 23 +++++++++++----------- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/gui/frames/npc.py b/gui/frames/npc.py index be0243b..3319143 100644 --- a/gui/frames/npc.py +++ b/gui/frames/npc.py @@ -1,5 +1,6 @@ import tkinter -from ..components import labels +from ..components import labels, buttons, inputs +from models import UnitType class NpcFrame(tkinter.Frame): @@ -10,5 +11,51 @@ def __init__(self, parent): self.label = labels.FrameLabel(self, "NPC") + self.player = self.parent.backend.get_player() + self.npcLevelLabel = labels.InputLabel(self, f"Last NPC Level Defeated: {self.player.npc_level}") + + self.troopSelectorLabel = labels.InputLabel(self, 'Select Troops:') + self.infantryLabel = labels.InputLabel(self, 'Infantry:') + self.infantryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.INFANTRY]) + self.cavalryLabel = labels.InputLabel(self, 'Cavalry:') + self.cavalryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.CAVALRY]) + self.artilleryLabel = labels.InputLabel(self, 'Artillery:') + self.artilleryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.ARTILLERY]) + self.assassinLabel = labels.InputLabel(self, 'Assassins:') + self.assassinsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.ASSASSINS]) + self.bowmenLabel = labels.InputLabel(self, 'Bowmen:') + self.bowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.BOWMEN]) + self.bigBowmenLabel = labels.InputLabel(self, 'Big Bowmen:') + self.bigBowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.BIG_BOWMEN]) + self.heavyMenLabel = labels.InputLabel(self, 'Heavy Men:') + self.heavyMenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.HEAVY_MEN]) + self.kingGuardsLabel = labels.InputLabel(self, 'King Guards:') + self.kingGuardsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.KINGS_GUARDS]) + + self.npcAttackButton = buttons.SubmitButton(self, text='Attack', height=2, width=6) + + def render(self): self.label.grid(row=0, column=0) + + self.npcLevelLabel.place(x=300, y=125) + + self.troopSelectorLabel.place(x=300, y=150) + self.infantryLabel.place(x=200, y=175) + self.infantryQuantity.place(x=260, y=175) + self.cavalryLabel.place(x=200, y=200) + self.cavalryQuantity.place(x=260, y=200) + self.artilleryLabel.place(x=200, y=225) + self.artilleryQuantity.place(x=260, y=225) + self.assassinLabel.place(x=200, y=250) + self.assassinsQuantity.place(x=260, y=250) + self.bowmenLabel.place(x=200, y=275) + self.bowmenQuantity.place(x=260, y=275) + self.bigBowmenLabel.place(x=185, y=300) + self.bigBowmenQuantity.place(x=260, y=300) + self.heavyMenLabel.place(x=190, y=325) + self.heavyMenQuantity.place(x=260, y=325) + self.kingGuardsLabel.place(x=185, y=350) + self.kingGuardsQuantity.place(x=260, y=350) + + self.npcAttackButton.place(x=300, y=400) diff --git a/models/player.py b/models/player.py index c20837a..1133436 100644 --- a/models/player.py +++ b/models/player.py @@ -6,6 +6,15 @@ __all__ = ("Player", "EventPlayer") + # For handling the edge-case where the date returned by the api is null, most likely to occur with last_npc_win +def _parse_date(date) -> datetime | None: + if date is None: + return None + + try: + date = datetime.fromisoformat(date) + except TypeError: + return None class Player(NamedTuple): id: int @@ -32,7 +41,7 @@ def from_data(data: dict): return Player( id=data["user_id"], - registered_at=Player._parse_date(data["registered_at"]), # type: ignore + registered_at=_parse_date(data["registered_at"]), gold=data["gold"], ruby=data["ruby"], units={ @@ -46,7 +55,7 @@ def from_data(data: dict): UnitType.KINGS_GUARDS: data["units"]["kings_guards"], }, npc_level=data["npc_level"], - last_npc_win=Player._parse_date(data["last_npc_win"]), # type: ignore + last_npc_win=_parse_date(data["last_npc_win"]), # type: ignore votes=data["votes"], alliance=Alliance.from_data(data["alliance"]), # type: ignore queue_slots=data["queue_slots"], @@ -58,16 +67,6 @@ def from_data(data: dict): prestige=data["prestige"], ) - # For handling the edge-case where the date returned by the api is null, most likely to occur with last_npc_win - def _parse_date(self, date) -> datetime | None: - if date is None: - return None - - try: - date = datetime.fromisoformat(date) - except TypeError: - return None - class EventPlayer(NamedTuple): user_id: int From 03d088f69e12d5d8e327dae5baaf66906b7c6243 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Mon, 24 Jun 2024 21:27:08 +0530 Subject: [PATCH 2/3] feat: Add NPC backend & result frame --- backend/api.py | 10 ++++- gui/frames/npc.py | 107 +++++++++++++++++++++++++++++++++------------ models/__init__.py | 1 + models/results.py | 46 +++++++++++++++++++ 4 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 models/results.py diff --git a/backend/api.py b/backend/api.py index c8cf5ad..a76e372 100644 --- a/backend/api.py +++ b/backend/api.py @@ -2,7 +2,7 @@ import requests from .route import Route -from models import Player, Alliance, MarketOrder +from models import Player, Alliance, MarketOrder, UnitType, NPCResult from .exceptions import AccessForbidden, ValidationError __all__ = ("API",) @@ -32,6 +32,7 @@ def request( # handle error here however you want to # error 403 & 401 are Access Forbidden elif response.status_code == 422: + print(response.json()) raise ValidationError(response.status_code, json) return response @@ -108,3 +109,10 @@ def upgrade_building(self, building_type: str, levels_to_upgrade: str): response = self.request(route, query_params=query_params) return response.json() + + def attack_npc(self, troops: dict[UnitType, int]) -> NPCResult: + route = Route("/npc", "POST") + json = {"troops": troops} + print(json) + response = self.request(route, json=json) + return NPCResult.from_api(response.json()) \ No newline at end of file diff --git a/gui/frames/npc.py b/gui/frames/npc.py index 3319143..9c20c2f 100644 --- a/gui/frames/npc.py +++ b/gui/frames/npc.py @@ -1,39 +1,89 @@ import tkinter from ..components import labels, buttons, inputs -from models import UnitType +from models import UnitType, NPCResult + +class NPCResultFrame(tkinter.Frame): + def __init__(self, parent, result: NPCResult): + self.bg = "orange" + super().__init__(parent, bg=self.bg) + self.parent = parent + + self.label = labels.FrameLabel(self, "NPC Result") + + self.result = result + + self.resultLabel = labels.InputLabel(self, f"Won: {self.result.won}") + # display lost troops & gained resources etc + + def render(self): + self.label.grid(row=0, column=0) + + self.resultLabel.place(x=300, y=150) class NpcFrame(tkinter.Frame): def __init__(self, parent): - self.bg = "blue" + self.bg = "orange" super().__init__(parent, bg=self.bg) self.parent = parent self.label = labels.FrameLabel(self, "NPC") self.player = self.parent.backend.get_player() + infantry = self.player.units[UnitType.INFANTRY] + cavalry = self.player.units[UnitType.CAVALRY] + artillery = self.player.units[UnitType.ARTILLERY] + assassins = self.player.units[UnitType.ASSASSINS] + bowmen = self.player.units[UnitType.BOWMEN] + big_bowmen = self.player.units[UnitType.BIG_BOWMEN] + heavy_men = self.player.units[UnitType.HEAVY_MEN] + king_guards = self.player.units[UnitType.KINGS_GUARDS] self.npcLevelLabel = labels.InputLabel(self, f"Last NPC Level Defeated: {self.player.npc_level}") + self.troopSelectorLabel = labels.InputLabel(self, 'Select Troops:') - self.infantryLabel = labels.InputLabel(self, 'Infantry:') - self.infantryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.INFANTRY]) - self.cavalryLabel = labels.InputLabel(self, 'Cavalry:') - self.cavalryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.CAVALRY]) - self.artilleryLabel = labels.InputLabel(self, 'Artillery:') - self.artilleryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.ARTILLERY]) - self.assassinLabel = labels.InputLabel(self, 'Assassins:') - self.assassinsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.ASSASSINS]) - self.bowmenLabel = labels.InputLabel(self, 'Bowmen:') - self.bowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.BOWMEN]) - self.bigBowmenLabel = labels.InputLabel(self, 'Big Bowmen:') - self.bigBowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.BIG_BOWMEN]) - self.heavyMenLabel = labels.InputLabel(self, 'Heavy Men:') - self.heavyMenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.HEAVY_MEN]) - self.kingGuardsLabel = labels.InputLabel(self, 'King Guards:') - self.kingGuardsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=self.player.units[UnitType.KINGS_GUARDS]) - - self.npcAttackButton = buttons.SubmitButton(self, text='Attack', height=2, width=6) + self.infantryLabel = labels.InputLabel(self, f'Infantry ({infantry}):') + self.cavalryLabel = labels.InputLabel(self, f'Cavalry ({cavalry}):') + self.artilleryLabel = labels.InputLabel(self, f'Artillery ({artillery}):') + self.assassinLabel = labels.InputLabel(self, f'Assassins ({assassins}):') + self.bowmenLabel = labels.InputLabel(self, f'Bowmen ({bowmen}):') + self.bigBowmenLabel = labels.InputLabel(self, f'Big Bowmen ({big_bowmen}):') + self.heavyMenLabel = labels.InputLabel(self, f'Heavy Men ({heavy_men}):') + self.kingGuardsLabel = labels.InputLabel(self, f'King Guards ({king_guards}):') + + self.infantryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=infantry) + self.cavalryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=cavalry) + self.artilleryQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=artillery) + self.assassinsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=assassins) + self.bowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=bowmen) + self.bigBowmenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=big_bowmen) + self.heavyMenQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=heavy_men) + self.kingGuardsQuantity = inputs.IntergerOnlyEntry(self, minNumber=1, maxNumber=king_guards) + + self.npcAttackButton = buttons.SubmitButton(self, text='Attack', height=2, width=6, command=self.attack_npc) + def attack_npc(self): + infantry = self.infantryQuantity.get() + cavalry = self.cavalryQuantity.get() + artillery = self.artilleryQuantity.get() + assassins = self.assassinsQuantity.get() + bowmen = self.bowmenQuantity.get() + big_bowmen = self.bigBowmenQuantity.get() + heavy_men = self.heavyMenQuantity.get() + king_guards = self.kingGuardsQuantity.get() + troops = { + "infantry": int(infantry) if infantry else 0, + "cavalry": int(cavalry) if cavalry else 0, + "artillery": int(artillery) if artillery else 0, + "assassins": int(assassins) if assassins else 0, + "bowmen": int(bowmen) if bowmen else 0, + "big_bowmen": int(big_bowmen) if big_bowmen else 0, + "heavy_men": int(heavy_men) if heavy_men else 0, + "king_guards": int(king_guards) if king_guards else 0, + } + result = self.parent.backend.attack_npc(troops) + npc_result_frame = NPCResultFrame(self.parent, result) + self.parent.change_frame(npc_result_frame) def render(self): self.label.grid(row=0, column=0) @@ -41,21 +91,22 @@ def render(self): self.npcLevelLabel.place(x=300, y=125) self.troopSelectorLabel.place(x=300, y=150) - self.infantryLabel.place(x=200, y=175) + self.infantryLabel.place(x=193, y=175) + self.cavalryLabel.place(x=195, y=200) + self.artilleryLabel.place(x=193, y=225) + self.assassinLabel.place(x=185, y=250) + self.bowmenLabel.place(x=187, y=275) + self.bigBowmenLabel.place(x=167, y=300) + self.heavyMenLabel.place(x=174, y=325) + self.kingGuardsLabel.place(x=170, y=350) + self.infantryQuantity.place(x=260, y=175) - self.cavalryLabel.place(x=200, y=200) self.cavalryQuantity.place(x=260, y=200) - self.artilleryLabel.place(x=200, y=225) self.artilleryQuantity.place(x=260, y=225) - self.assassinLabel.place(x=200, y=250) self.assassinsQuantity.place(x=260, y=250) - self.bowmenLabel.place(x=200, y=275) self.bowmenQuantity.place(x=260, y=275) - self.bigBowmenLabel.place(x=185, y=300) self.bigBowmenQuantity.place(x=260, y=300) - self.heavyMenLabel.place(x=190, y=325) self.heavyMenQuantity.place(x=260, y=325) - self.kingGuardsLabel.place(x=185, y=350) self.kingGuardsQuantity.place(x=260, y=350) self.npcAttackButton.place(x=300, y=400) diff --git a/models/__init__.py b/models/__init__.py index 8878fd8..dcd5d71 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -4,3 +4,4 @@ from .units import UnitType from .characteristics import Characteristics from .order import MarketOrder +from .results import NPCResult \ No newline at end of file diff --git a/models/results.py b/models/results.py new file mode 100644 index 0000000..b110faa --- /dev/null +++ b/models/results.py @@ -0,0 +1,46 @@ +from .units import UnitType +from typing import NamedTuple + +class NPCResult(NamedTuple): + won: bool + attacker_lost_troops: dict[UnitType, int] + npc_lost_troops: dict[UnitType] + gold_loot: int + ruby_loot: int + food_loot: int + token_loot: str + iron_frames: int + + @staticmethod + def from_api(response: dict | None): + if response is None: + return None + print(response) + return NPCResult( + won=response["won"], + attacker_lost_troops={ + UnitType.INFANTRY: response["attacker_lost_units"]["infantry"], + UnitType.CAVALRY: response["attacker_lost_units"]["cavalry"], + UnitType.ARTILLERY: response["attacker_lost_units"]["artillery"], + UnitType.ASSASSINS: response["attacker_lost_units"]["assassins"], + UnitType.BOWMEN: response["attacker_lost_units"]["bowmen"], + UnitType.BIG_BOWMEN: response["attacker_lost_units"]["big_bowmen"], + UnitType.HEAVY_MEN: response["attacker_lost_units"]["heavy_men"], + UnitType.KINGS_GUARDS: response["attacker_lost_units"]["kings_guards"], + }, + npc_lost_troops={ + UnitType.INFANTRY: response["npc_lost_units"]["infantry"], + UnitType.CAVALRY: response["npc_lost_units"]["cavalry"], + UnitType.ARTILLERY: response["npc_lost_units"]["artillery"], + UnitType.ASSASSINS: response["npc_lost_units"]["assassins"], + UnitType.BOWMEN: response["npc_lost_units"]["bowmen"], + UnitType.BIG_BOWMEN: response["npc_lost_units"]["big_bowmen"], + UnitType.HEAVY_MEN: response["npc_lost_units"]["heavy_men"], + UnitType.KINGS_GUARDS: response["npc_lost_units"]["kings_guards"], + }, + gold_loot=response["gold_loot"], + ruby_loot=response["ruby_loot"], + food_loot=response["food_loot"], + token_loot=response["token_loot"], + iron_frames=response["iron_frames"], + ) \ No newline at end of file From f766807ee7aa2fc2aeb9de764b309a2a7942df50 Mon Sep 17 00:00:00 2001 From: ItsNeil17 Date: Mon, 24 Jun 2024 22:42:47 +0530 Subject: [PATCH 3/3] fix type checking --- backend/api.py | 2 +- models/player.py | 2 +- models/results.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/api.py b/backend/api.py index a76e372..90dc597 100644 --- a/backend/api.py +++ b/backend/api.py @@ -110,7 +110,7 @@ def upgrade_building(self, building_type: str, levels_to_upgrade: str): response = self.request(route, query_params=query_params) return response.json() - def attack_npc(self, troops: dict[UnitType, int]) -> NPCResult: + def attack_npc(self, troops: dict[UnitType, int]) -> NPCResult | None: route = Route("/npc", "POST") json = {"troops": troops} print(json) diff --git a/models/player.py b/models/player.py index 1133436..79838ba 100644 --- a/models/player.py +++ b/models/player.py @@ -41,7 +41,7 @@ def from_data(data: dict): return Player( id=data["user_id"], - registered_at=_parse_date(data["registered_at"]), + registered_at=_parse_date(data["registered_at"]), # type: ignore gold=data["gold"], ruby=data["ruby"], units={ diff --git a/models/results.py b/models/results.py index b110faa..c692f15 100644 --- a/models/results.py +++ b/models/results.py @@ -4,7 +4,7 @@ class NPCResult(NamedTuple): won: bool attacker_lost_troops: dict[UnitType, int] - npc_lost_troops: dict[UnitType] + npc_lost_troops: dict[UnitType, int] gold_loot: int ruby_loot: int food_loot: int