From 90fe18ee1fd2a7524cc6e61852e88800d5d1c6c3 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 18:27:28 -0400 Subject: [PATCH 01/51] deleted changes.md, fixed super() call --- CHANGES.md | 41 ------------------------- clash_royale/envs/game_engine/struct.py | 12 +++----- 2 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 CHANGES.md diff --git a/CHANGES.md b/CHANGES.md deleted file mode 100644 index 4cc7044..0000000 --- a/CHANGES.md +++ /dev/null @@ -1,41 +0,0 @@ -# Controversial Changes - -This document outlines some very opinionated changes that are done to the backend. -These changes SHOULD be discussed and SHOULD be debated if we want to move forward. - -This branch contains a decent redesign of the backend architecture, -which aims to make the backend engine -(let's come up with a cute name for this, maybe Clash royal Simulation Engine (CSE)) -generic and modular, so new cards (and maybe game features) -can be quickly developed and integrated. - -All the changes live under `ngame_engine`, and can be merged/deleted as we see fit. - -## Arena - -We utilize a new implementation of the CSE arena. -This component is the highest level component in our engine, -and it will be what most people will primarily interact with. - -This component has the following goals: - -- Rendering - Renders frames of the simulation for display/further processing -- Entity Management - Adding, removal, and entity simulation operations -- Time Handling - An attempt to make timekeeping independent of framerate, i.e accurate simulation with arbitrary framerate -- Maybe some more... - -Most of these are implemented by other components, such as pygame, and we will go into detail as to how this is done. - -## Entity - -An 'entity' is something that is displayed on screen, -and something that (probably) has logic and interacts with other entities. -EVERYTHING is an entity in CSE. -There are specializations, such as buildings and troops, -but everything is an entity and inherits entity features. - -Entity's have the following goals: - -- Rendering - Each entity MUST render themselves -- Logic - Each entity gets a function that is executed each time the entity is asked to simulate -- State - Entities keep track of their state, and can hook custom actions into each state diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index 81a2782..b04d177 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -13,7 +13,7 @@ class Scheduler: def __init__(self, fps: int =30): self.fps: int = fps self.frame_num: int = 0 - + def reset(self): self.frame_num = 0 @@ -23,21 +23,19 @@ def step(self, frames: int=1): def frame(self) -> int: return self.frame_num - + class GameScheduler: """ Template class for game scheduling """ def __init__(self, scheduler: Scheduler, fps: int=30) -> None: - self.scheduler = scheduler + self.scheduler: Scheduler = scheduler + self.fps: int = fps class DefaultScheduler(GameScheduler): """ Class for default 1v1 game scheduling """ - def __init__(self, scheduler: Scheduler, fps: int=30) -> None: - super.__init__(scheduler, fps) - def elixir_rate(self) -> float: pass @@ -47,7 +45,7 @@ def game_state(self) -> int: ex: Game is over, double elixir, overtime, etc. """ pass - + @dataclasses.dataclass(slots=True) From 14c315c1d71d42252e78aff250a99953eea5aba5 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 18:37:26 -0400 Subject: [PATCH 02/51] fixing types and bugs from previous prs --- clash_royale/envs/game_engine/player.py | 65 ++++++------------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index fccd653..ce31888 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -8,82 +8,49 @@ class Player(): This class represents the current state of players' cards, and the logic of playing cards. Handle elixir and legal cards. - """ + def __init__(self, - deck: list[type[Card]]) -> None: - + deck: list[Card]) -> None: """ - - Player component is initialized with deck of string, specifiying the cards' names in the deck. - + Player component is initialized with deck of string, + specifying the cards' names in the deck. """ - - self.elixir: int = 5 + + self.elixir: int = 0 random.shuffle(deck) self.deck: Queue = Queue(maxsize = 8) for card in deck: self.deck.put(card, block = False) - - self.hand: list[type[Card]] = [self.deck.get(block = False) for i in range(4)] - self.next: Card = self.get(block = False) - def reset(self, elixir: int = 5) -> None: + self.hand: list[Card] = [self.deck.get(block = False) for i in range(4)] + self.next: Card = self.deck.get(block = False) + def reset(self, elixir: int = 5) -> None: """ - - This method is used to delete information of Player class in previous matches. - + This method is used to reset Player class. """ self.elixir: int = elixir + def get_pseudo_legal_cards(self) -> list[Card]: """ - - One way of reseting the player class with the same deck and without calling __init__ again when a new match starts. - - for card in self.hand: - self.hand.put(card, block = False) - self.hand.put(self.next, block = False) - random.shuffle(self.deck) - - self.hand: list[type[Card]] = [self.deck.get(block = False) for i in range(4)] - self.next: Card = self.get(block = False) - - """ - - """ - - Reseting all variables to None to reduce the unexpected side effects - - """ - - self.deck: None = None - self.hand: None = None - self.next: None = None - - def get_pseudo_legal_cards(self, current_elixir: float) -> list[type[Card]]: - - """ - - This method is used to get all cards that can be played given the current amount of elixir. - + This method is used to get all cards that can be + played given the current amount of elixir. """ - legal_cards: list[type[Card]] = [] + legal_cards: list[Card] = [] for card in self.hand: if card.elixir <= self.elixir: legal_cards.append(card) return legal_cards def step(self, - elixir_rate: float, - fps: int, + elixir_rate: float, + fps: int, frame: int) -> None: - """ - Called with the value of elixir_rate and frame to update the elixir of player after 'frame' number of frames to better customize the elixir_rate that can vary depends on game modes. From 54132c6448ea22317a57c8b72a573a8de8579e90 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 18:54:24 -0400 Subject: [PATCH 03/51] fixing entity documentation and using fstrings, added more type annotations --- .../envs/game_engine/entities/__init__.py | 2 +- .../envs/game_engine/entities/entity.py | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/__init__.py b/clash_royale/envs/game_engine/entities/__init__.py index 5e8da5c..0d28077 100644 --- a/clash_royale/envs/game_engine/entities/__init__.py +++ b/clash_royale/envs/game_engine/entities/__init__.py @@ -3,4 +3,4 @@ These range from the base entity class, to highly specialized entity classes that implement a custom troop. -""" \ No newline at end of file +""" diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 2c23acd..6a0020b 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -9,6 +9,7 @@ from clash_royale.envs.game_engine.struct import Stats if TYPE_CHECKING: + # Only import for typechecking to prevent circular dependency from clash_royale.envs.game_engine.arena import Arena @@ -83,7 +84,7 @@ def arena(self) -> EntityCollection: return self.collection - def load(self): + def load(self) -> None: """ Method called when this entity is loaded. @@ -100,21 +101,21 @@ def load(self): self.state = Entity.LOADED - def unload(self): + def unload(self) -> None: """ Method called when this entity is unloaded. This method is invoked when the entity is unloaded from a high level component. When this method is called, it is reasonable to assume that this entity is not going to be used again. - entitys can use this a a sign that their work is done. + entities can use this a a sign that their work is done. It is recommended to make any final, permanent changes once this method is called. """ self.state = Entity.UNLOADED - def start(self): + def start(self) -> None: """ Method called when this entity is started. @@ -130,7 +131,7 @@ def start(self): self.state = Entity.STARTED - def stop(self): + def stop(self) -> None: """ Method called when this entity is stopped. @@ -151,7 +152,7 @@ def stop(self): self.state = Entity.STOPPED - def render(self): + def render(self) -> None: """ Asks this entity to render itself! @@ -164,7 +165,7 @@ def render(self): pass - def simulate(self): + def simulate(self) -> None: """ Preforms entity simulation for this frame. @@ -208,11 +209,11 @@ class EntityCollection(object): def __init__(self) -> None: # entity storage component - self.entities = [] + self.entities: list[Entity] = [] - self.running = False # Value determining if we are running - self.num_loaded = 0 # Number of entity's currently loaded - self.max_loaded = 0 # Max number of entity's loaded + self.running: bool = False # Value determining if we are running + self.num_loaded: int = 0 # Number of entity's currently loaded + self.max_loaded: int = 0 # Max number of entity's loaded def load_entity(self, entity: Entity) -> Entity: """ @@ -241,7 +242,7 @@ def load_entity(self, entity: Entity) -> Entity: # Load error occurred! Raise an exception! - raise Exception("entity load() method failed! Not loading: {}".format(entity), e) + raise Exception(f"entity load() method failed! Not loading: {entity}", e) # Add the entity to our collection: @@ -292,7 +293,7 @@ def unload_entity(self, entity: Entity) -> Entity: # Raise an exception of our own: - raise Exception("entity failed to unload! Unloading: {}".format(entity), e) + raise Exception(f"entity failed to unload! Unloading: {entity}", e) # Unload the entity: @@ -328,7 +329,7 @@ def stop_entity(self, entity: Entity) -> Entity: self._unload_entity(entity) - raise Exception("entity stop() method failed! Unloading: {}".format(entity), e) + raise Exception(f"entity stop() method failed! Unloading: {entity}", e) # Return the entity: @@ -364,7 +365,7 @@ def start_entity(self, entity: Entity) -> Entity: # Raise an exception: - raise Exception("entity start() method failed! Unloading: {}".format(entity), e) + raise Exception(f"entity start() method failed! Unloading: {entity}", e) # Return the entity: From 7d7c090d77321cf10210f4079563664c70a67133 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 18:58:44 -0400 Subject: [PATCH 04/51] fixed spelling errors --- clash_royale/envs/game_engine/entities/entity.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 6a0020b..21f60ac 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List from clash_royale.envs.game_engine.struct import Stats @@ -209,7 +209,7 @@ class EntityCollection(object): def __init__(self) -> None: # entity storage component - self.entities: list[Entity] = [] + self.entities: List[Entity] = [] self.running: bool = False # Value determining if we are running self.num_loaded: int = 0 # Number of entity's currently loaded @@ -384,9 +384,9 @@ def restart_entity(self, entity: Entity) -> Entity: then the entity will be forcefully unloaded! :param entity: entity to restart - :type entity: Baseentity + :type entity: Entity :return: The entity we restarted - :rtype: Baseentity + :rtype: Entity """ # Stop the entity @@ -401,11 +401,11 @@ def restart_entity(self, entity: Entity) -> Entity: return entity - def start(self): + def start(self) -> None: """ Method used to start this entityCollection. - We set our running status and start all loaded entitys. + We set our running status and start all loaded entities. """ # Set our running status: @@ -414,7 +414,7 @@ def start(self): # Start all connected entity's: - for mod in self.entitys: + for mod in self.entities: # Determine if this entity needs starting: From 7fa3871ccc8acfbe5c83350e0bd01ffb7eb6147b Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 18:59:35 -0400 Subject: [PATCH 05/51] Entitiy collection stop method corrected spelling error --- clash_royale/envs/game_engine/entities/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 21f60ac..56750ff 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -429,7 +429,7 @@ def stop(self): Method used to stop this entityCollection. We set our running status, - and stop all started entitys. + and stop all started entities. Sub-classes should put stop code here to end their relevant components. """ From d12e8b5872de358efbc02489f2c3996f8ad1bed8 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:03:26 -0400 Subject: [PATCH 06/51] fixed _load_entity entity loading method --- clash_royale/envs/game_engine/entities/entity.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 56750ff..6d5b123 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -438,7 +438,7 @@ def stop(self): self.running = False - for mod in self.entitys: + for mod in self.entities: # Determine if this entity needs stopping: @@ -450,7 +450,7 @@ def stop(self): return - def _load_entity(self, mod: Entity): + def _load_entity(self, mod: Entity) -> None: """ Adds the entity to our collection. @@ -458,16 +458,11 @@ def _load_entity(self, mod: Entity): be worked with by end users! :param mod: entity to add - :type mod: Baseentity + :type mod: Entity """ # Create the data to be stored: - - temp = (mod,) - - # Add the entity to the collection: - - self.entitys = self.entitys + temp + self.entities.append(mod) # Update our stats: From 4a4982bae6f26a9c47ef58b9434b8d6f04a65739 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:05:48 -0400 Subject: [PATCH 07/51] fixed mod->entity artifacts, fixed entities spelling error --- .../envs/game_engine/entities/entity.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 6d5b123..3c032b1 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -450,19 +450,19 @@ def stop(self): return - def _load_entity(self, mod: Entity) -> None: + def _load_entity(self, entity: Entity) -> None: """ Adds the entity to our collection. This low-level method is not intended to be worked with by end users! - :param mod: entity to add + :param entity: entity to add :type mod: Entity """ # Create the data to be stored: - self.entities.append(mod) + self.entities.append(entity) # Update our stats: @@ -473,30 +473,21 @@ def _load_entity(self, mod: Entity) -> None: mod.collection = self - def _unload_entity(self, mod: Entity): + def _unload_entity(self, entity: Entity): """ - Low-level method for unloading entitys from the list. + Low-level method for unloading entities from the list. We do not call any methods or work with the entity in any way other than removing it from the data structure. - :param mod: The entity in question to remove - :type mod: Baseentity + :param entity: The entity in question to remove + :type entity: Entity :param key: Key of the entity to remove :type key: str """ - # Convert the tuple into a list: - - temp = list(self.entitys) - # Remove the offending entity: - - temp.remove(mod) - - # Set our list: - - self.entitys = tuple(temp) + self.entities.remove(entity) # Update our stats: From 5912e13e5f56f3ee62370aa036d80ccb38c49d93 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:06:15 -0400 Subject: [PATCH 08/51] entity.py linting finished --- clash_royale/envs/game_engine/entities/entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 3c032b1..43600c9 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -492,3 +492,4 @@ def _unload_entity(self, entity: Entity): # Update our stats: self.num_loaded -= 1 + From b9037ceabf56c6bc14e1f23e99fcecdc2ef5e66e Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:09:41 -0400 Subject: [PATCH 09/51] entity.py is actually done now --- clash_royale/envs/game_engine/entities/entity.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 43600c9..03172d2 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -37,11 +37,11 @@ class Entity: Created -> Loaded +> Started -> Running -> Stopped +> Unloaded * Created - Entity is instantiated - * Loaded - Entity is loaded into a collection, relevant load code is ran - * Started - Entity is started, relevant start code is ran + * Loaded - Entity is loaded into a collection, load code is ran + * Started - Entity is started, start code is ran * Running - Entity is running and working in some way - * Stopped - Entity is stopped, relevant stop code is ran and entity is no longer working with data - * Unloaded - Entity is unloaded, relevant unload code is ran + * Stopped - Entity is stopped, stop code is ran and entity is no longer working with data + * Unloaded - Entity is unloaded, unload code is ran """ CREATED: int = 0 @@ -458,7 +458,7 @@ def _load_entity(self, entity: Entity) -> None: be worked with by end users! :param entity: entity to add - :type mod: Entity + :type entity: Entity """ # Create the data to be stored: @@ -471,7 +471,7 @@ def _load_entity(self, entity: Entity) -> None: # Attach the collection to the entity: - mod.collection = self + entity.collection = self def _unload_entity(self, entity: Entity): """ @@ -492,4 +492,3 @@ def _unload_entity(self, entity: Entity): # Update our stats: self.num_loaded -= 1 - From 14585b4c84a232bfde64cce57d16659d9c3f3459 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:10:39 -0400 Subject: [PATCH 10/51] added none return type to entity.py --- clash_royale/envs/game_engine/entities/entity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/entity.py b/clash_royale/envs/game_engine/entities/entity.py index 03172d2..6eb6954 100644 --- a/clash_royale/envs/game_engine/entities/entity.py +++ b/clash_royale/envs/game_engine/entities/entity.py @@ -424,7 +424,7 @@ def start(self) -> None: self.start_entity(mod) - def stop(self): + def stop(self) -> None: """ Method used to stop this entityCollection. @@ -473,7 +473,7 @@ def _load_entity(self, entity: Entity) -> None: entity.collection = self - def _unload_entity(self, entity: Entity): + def _unload_entity(self, entity: Entity) -> None: """ Low-level method for unloading entities from the list. From 5640449d043c23d01f8389cea07f18b1179695a0 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:12:03 -0400 Subject: [PATCH 11/51] shorten line length, rename __init__.py --- clash_royale/envs/game_engine/logic/__init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/logic/__init.py b/clash_royale/envs/game_engine/logic/__init.py index 4e9207e..b360933 100644 --- a/clash_royale/envs/game_engine/logic/__init.py +++ b/clash_royale/envs/game_engine/logic/__init.py @@ -5,7 +5,7 @@ These classes can be utilized in entities that support them. The components can be broken down into these pieces: -- Movement - How does this entity move? Should take into consideration movement speed and flying/ground +- Movement - How does this entity move? Consider movement speed and flying/ground - Targeting - How are entity targets selected? Should consider sight range and entity targets - Attack - How does this entity attack? Damage? Single/splash? """ From 36f254a738bb7ec833cb7459759db18164e365da Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:13:02 -0400 Subject: [PATCH 12/51] rename __init__.py --- clash_royale/envs/game_engine/logic/{__init.py => __init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clash_royale/envs/game_engine/logic/{__init.py => __init__.py} (100%) diff --git a/clash_royale/envs/game_engine/logic/__init.py b/clash_royale/envs/game_engine/logic/__init__.py similarity index 100% rename from clash_royale/envs/game_engine/logic/__init.py rename to clash_royale/envs/game_engine/logic/__init__.py From 8b86e1cceedf3c35efadeca387117726ff548c2e Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:14:54 -0400 Subject: [PATCH 13/51] rename tentity->target_entity in entity_distance in attack.py --- clash_royale/envs/game_engine/logic/attack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clash_royale/envs/game_engine/logic/attack.py b/clash_royale/envs/game_engine/logic/attack.py index 47359c5..33e833a 100644 --- a/clash_royale/envs/game_engine/logic/attack.py +++ b/clash_royale/envs/game_engine/logic/attack.py @@ -34,17 +34,17 @@ def __init__(self) -> None: self.last_attack: int = 0 # Frame of last attack - def entity_distance(self, tentity: Entity) -> float: + def entity_distance(self, target_entity: Entity) -> float: """ Determines the distance between an entity and ourselves. - :param tentity: Entity to determine distance - :type tentity: Entity + :param target_entity: Entity to determine distance + :type target_entity: Entity :return: Distance between entities :rtype: float """ - return distance(self.entity.x, self.entity.y, tentity.x, tentity.y) + return distance(self.entity.x, self.entity.y, target_entity.x, target_entity.y) def can_attack(self) -> bool: """ From 381b8c0143b5907245f78d42c79a8eee6276919d Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:19:21 -0400 Subject: [PATCH 14/51] rename target_ent to target_entity --- .../envs/game_engine/entities/logic_entity.py | 4 ++-- clash_royale/envs/game_engine/logic/attack.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clash_royale/envs/game_engine/entities/logic_entity.py b/clash_royale/envs/game_engine/entities/logic_entity.py index 1ede2f2..f65c4c8 100644 --- a/clash_royale/envs/game_engine/entities/logic_entity.py +++ b/clash_royale/envs/game_engine/entities/logic_entity.py @@ -31,7 +31,7 @@ def __init__(self) -> None: self.target: BaseTarget # Target component to use self.movement: BaseMovement # Movement component to use - self.target_ent: Entity # Current entity being considered + self.target_entity: Entity # Current entity being considered def simulate(self): """ @@ -40,7 +40,7 @@ def simulate(self): # First, ask for targeting: - self.target_ent = self.target.target() + self.target_entity = self.target.target() # Next, determine attack: diff --git a/clash_royale/envs/game_engine/logic/attack.py b/clash_royale/envs/game_engine/logic/attack.py index 33e833a..221037c 100644 --- a/clash_royale/envs/game_engine/logic/attack.py +++ b/clash_royale/envs/game_engine/logic/attack.py @@ -28,7 +28,7 @@ class BaseAttack: """ def __init__(self) -> None: - + self.entity: LogicEntity # Entity we are managing self.arena: Arena # Arena we are apart of @@ -59,7 +59,7 @@ def can_attack(self) -> bool: # Determine if we have a target: - if self.entity.target_ent is not None: + if self.entity.target_entity is not None: # Determine if our delay has passed: @@ -67,14 +67,14 @@ def can_attack(self) -> bool: # Determine if entity is within range: - if self.entity.stats.attack_range <= self.entity_distance(self.entity.target_ent): + if self.entity.stats.attack_range <= self.entity_distance(self.entity.target_entity): # Within range, return True: return True - + # Otherwise, return False: - + return False def attack(self): @@ -105,4 +105,4 @@ def attack(self): # Otherwise, preform an attack: - self.entity.target_ent.stats.health -= self.entity.stats.damage + self.entity.target_entity.stats.health -= self.entity.stats.damage From ca03d753fb077f3bbd0eff339cd62087200746e3 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:21:27 -0400 Subject: [PATCH 15/51] linted movement.py --- clash_royale/envs/game_engine/logic/movement.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/logic/movement.py b/clash_royale/envs/game_engine/logic/movement.py index 213db54..af0109c 100644 --- a/clash_royale/envs/game_engine/logic/movement.py +++ b/clash_royale/envs/game_engine/logic/movement.py @@ -10,11 +10,10 @@ from typing import TYPE_CHECKING -from clash_royale.envs.game_engine.entities.entity import Entity from clash_royale.envs.game_engine.arena import Arena -from clash_royale.envs.game_engine.utils import slope if TYPE_CHECKING: + # Only import for typechecking to prevent circular dependency from clash_royale.envs.game_engine.entities.logic_entity import LogicEntity @@ -24,11 +23,11 @@ class BaseMovement: """ def __init__(self) -> None: - + self.entity: LogicEntity # Entity we are attached to self.arena: Arena # Arena component to consider - def move(self): + def move(self) -> None: """ Moves the entity in some way. @@ -44,7 +43,7 @@ class SimpleMovement(BaseMovement): SimpleMovement - Simply move in a straight line to the target """ - def move(self): + def move(self) -> None: """ Moves in a straight line towards target. """ From 876fc0d29483be759cfdd8210e7d9c70a33f0a09 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:22:25 -0400 Subject: [PATCH 16/51] rename target_entity --- clash_royale/envs/game_engine/logic/target.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/logic/target.py b/clash_royale/envs/game_engine/logic/target.py index 46a7a6c..3b5c53d 100644 --- a/clash_royale/envs/game_engine/logic/target.py +++ b/clash_royale/envs/game_engine/logic/target.py @@ -13,6 +13,7 @@ from clash_royale.envs.game_engine.utils import distance if TYPE_CHECKING: + # Only import for typechecking to prevent circular dependency from clash_royale.envs.game_engine.entities.logic_entity import LogicEntity class BaseTarget: @@ -21,21 +22,21 @@ class BaseTarget: """ def __init__(self) -> None: - + self.arena: Arena # Arena component to consider self.entity: LogicEntity # Entity we are attached to - def entity_distance(self, tentity: Entity) -> float: + def entity_distance(self, target_entity: Entity) -> float: """ Determines the distance between an entity and ourselves. - :param tentity: Entity to determine distance - :type tentity: Entity + :param target_entity: Entity to determine distance + :type target_entity: Entity :return: Distance between entities :rtype: float """ - return distance(self.entity.x, self.entity.y, tentity.x, tentity.y) + return distance(self.entity.x, self.entity.y, target_entity.x, target_entity.y) def target(self): """ From 31403427bcf3498c87336125e8ead9bc4e05c517 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:23:10 -0400 Subject: [PATCH 17/51] linted file and spelling errors --- clash_royale/envs/game_engine/logic/target.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clash_royale/envs/game_engine/logic/target.py b/clash_royale/envs/game_engine/logic/target.py index 3b5c53d..681eb3f 100644 --- a/clash_royale/envs/game_engine/logic/target.py +++ b/clash_royale/envs/game_engine/logic/target.py @@ -38,7 +38,7 @@ def entity_distance(self, target_entity: Entity) -> float: return distance(self.entity.x, self.entity.y, target_entity.x, target_entity.y) - def target(self): + def target(self) -> None: """ Finds a target in the arena, and returns an entity. """ @@ -54,7 +54,7 @@ class RadiusTarget(BaseTarget): and will target the first entity within our radius. """ - def target(self): + def target(self) -> None: """ Finds the first target that is within our radius. """ @@ -65,8 +65,8 @@ def target(self): # Determine if entity is near us: - if self.entity.stats.sight_range >= self.entity_distance(self.entity.target_ent): + if self.entity.stats.sight_range >= self.entity_distance(self.entity.target_entity): # We found a target, set: - self.entity.target_ent = ent + self.entity.target_entity = ent From e77cd9df12a9c5c68a951dce013d44825ae19bf6 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:26:19 -0400 Subject: [PATCH 18/51] renamed arena.py --- clash_royale/envs/game_engine/arena.py | 2 +- clash_royale/envs/game_engine/logic/attack.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 2734425..390a463 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING -from clash_royale.envs.game_engine.entities.entity import Entity, EntityCollection +from clash_royale.envs.game_engine.entities.entity import EntityCollection if TYPE_CHECKING: from clash_royale.envs.game_engine.game_engine import GameEngine diff --git a/clash_royale/envs/game_engine/logic/attack.py b/clash_royale/envs/game_engine/logic/attack.py index 221037c..5065cbc 100644 --- a/clash_royale/envs/game_engine/logic/attack.py +++ b/clash_royale/envs/game_engine/logic/attack.py @@ -15,6 +15,7 @@ from clash_royale.envs.game_engine.utils import distance if TYPE_CHECKING: + # Only import for typechecking to prevent circular dependency from clash_royale.envs.game_engine.entities.logic_entity import LogicEntity From a13dbed697d5f5263a927fa23fa02b3f741e4b62 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:29:34 -0400 Subject: [PATCH 19/51] linted arena.py --- clash_royale/envs/game_engine/arena.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 390a463..4dc5d14 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -11,6 +11,7 @@ from clash_royale.envs.game_engine.entities.entity import EntityCollection if TYPE_CHECKING: + # Only import for typechecking to prevent circular dependency from clash_royale.envs.game_engine.game_engine import GameEngine class Arena(EntityCollection): @@ -29,14 +30,16 @@ class Arena(EntityCollection): """ def __init__(self, width: int =8, height: int=18) -> None: - - self.width = width # Width of arena - self.height = height # Height of arena + + super().__init__() + + self.width: int = width # Width of arena + self.height: int = height # Height of arena self.engine: GameEngine # Game engine that is managing this arena - def reset(self): + def reset(self) -> None: pass - def step(self): + def step(self) -> None: pass From 833f32e751ca3e37affddded355ec91f1d25509f Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:33:27 -0400 Subject: [PATCH 20/51] card added newline, missing module docstring --- clash_royale/envs/game_engine/card.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/card.py b/clash_royale/envs/game_engine/card.py index bb755a3..a6e273a 100644 --- a/clash_royale/envs/game_engine/card.py +++ b/clash_royale/envs/game_engine/card.py @@ -6,4 +6,5 @@ class Card(): ''' def __init__(self, elixir: int): - pass \ No newline at end of file + pass + \ No newline at end of file From 048f76175b500e85c02d077443deea5e2571a965 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:34:42 -0400 Subject: [PATCH 21/51] removed entity collection inheretance in game_engine --- clash_royale/envs/game_engine/game_engine.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 6aa1267..80df059 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -14,13 +14,13 @@ import numpy.typing as npt import pygame -from clash_royale.envs.game_engine.entities.entity import Entity, EntityCollection +from clash_royale.envs.game_engine.entities.entity import EntityCollection from clash_royale.envs.game_engine.arena import Arena from clash_royale.envs.game_engine.struct import Scheduler, DefaultScheduler from clash_royale.envs.game_engine.player import Player -class GameEngine(EntityCollection): +class GameEngine: """ Arena - High-level simulation component @@ -35,7 +35,7 @@ class GameEngine(EntityCollection): TODO: Need to figure out frame independent timekeeping """ - def __init__(self, + def __init__(self, width: int =18, height: int=32, resolution: Tuple[int, int] =(128, 128), @@ -46,10 +46,8 @@ def __init__(self, The game_engine should be initialized with settings such as resolution and framerate, this shouldn't be used to initialize any specific actual game, that will be handled in reset. - - Arena() Constructor missing - Player() Constructor missing """ + self.width = width # Width of arena self.height = height # Height of arena self.resolution = resolution From f3b7f0b87b8a32a5c63e09556f65c0c433ccb80a Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:37:41 -0400 Subject: [PATCH 22/51] fixed game engine dangerous default value --- clash_royale/envs/game_engine/game_engine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 80df059..199138d 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -36,11 +36,11 @@ class GameEngine: """ def __init__(self, - width: int =18, - height: int=32, + deck1: List[str], + deck2: List[str], + width: int=18, + height: int=32, resolution: Tuple[int, int] =(128, 128), - deck1: List[str] =['barbarian'] * 8, - deck2: List[str] =['barbarian'] * 8, ) -> None: """ The game_engine should be initialized with settings such as resolution From c4178934aa528ac2e744f9f4b31d9d04959960c4 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:38:56 -0400 Subject: [PATCH 23/51] fixed list typing in player.py --- clash_royale/envs/game_engine/game_engine.py | 2 +- clash_royale/envs/game_engine/player.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 199138d..f20c9e0 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -40,7 +40,7 @@ def __init__(self, deck2: List[str], width: int=18, height: int=32, - resolution: Tuple[int, int] =(128, 128), + resolution: Tuple[int, int]=(128, 128), ) -> None: """ The game_engine should be initialized with settings such as resolution diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index ce31888..07c9423 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -1,7 +1,10 @@ +from typing import List + from queue import Queue -from clash_royale.envs.game_engine.card import Card import random +from clash_royale.envs.game_engine.card import Card + class Player(): """ Player class @@ -11,12 +14,12 @@ class Player(): """ def __init__(self, - deck: list[Card]) -> None: + deck: List[Card]) -> None: """ Player component is initialized with deck of string, specifying the cards' names in the deck. """ - + self.elixir: int = 0 random.shuffle(deck) From f88e1c039b1c20ac40e52de73a31f22eb5f0f79c Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:41:40 -0400 Subject: [PATCH 24/51] linted init --- clash_royale/envs/game_engine/game_engine.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index f20c9e0..9e01118 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -4,11 +4,10 @@ This file contains components that are intended to manage and preform the simulation. The components defined here are probably what end users want to utilize, as they will greatly simplify the simulation procedure. - -How should we decide order? Length Width Height? -I think LxHxCard. """ +from __future__ import annotations + from typing import List, Tuple import numpy as np import numpy.typing as npt @@ -16,7 +15,7 @@ from clash_royale.envs.game_engine.entities.entity import EntityCollection from clash_royale.envs.game_engine.arena import Arena -from clash_royale.envs.game_engine.struct import Scheduler, DefaultScheduler +from clash_royale.envs.game_engine.struct import Scheduler, GameScheduler, DefaultScheduler from clash_royale.envs.game_engine.player import Player @@ -48,16 +47,16 @@ def __init__(self, any specific actual game, that will be handled in reset. """ - self.width = width # Width of arena - self.height = height # Height of arena - self.resolution = resolution + self.width: int = width # Width of arena + self.height: int = height # Height of arena + self.resolution: Tuple[int, int] = resolution - self.arena = Arena(width=self.width, height=self.height) - self.player1 = Player(deck1) - self.player2 = Player(deck2) + self.arena: Arena = Arena(width=self.width, height=self.height) + self.player1: Player = Player(deck1) + self.player2: Player = Player(deck2) - self.scheduler = Scheduler(fps=30) # counting frames - self.game_scheduler = DefaultScheduler(self.scheduler) # determining elixir etc. + self.scheduler: Scheduler = Scheduler(fps=30) # counting frames + self.game_scheduler: GameScheduler = DefaultScheduler(self.scheduler) # determining elixir etc. def reset(self) -> None: """ @@ -67,6 +66,7 @@ def reset(self) -> None: Arena.reset() method missing. Player.reset() method missing. """ + self.arena.reset() self.player1.reset(elixir=5) self.player2.reset(elixir=5) From eadca1b6020468a1c177485b84c5ede7d1dfbd7c Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:44:22 -0400 Subject: [PATCH 25/51] missing entity stuff --- clash_royale/envs/game_engine/arena.py | 5 ++++- clash_royale/envs/game_engine/game_engine.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 4dc5d14..4721303 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING -from clash_royale.envs.game_engine.entities.entity import EntityCollection +from clash_royale.envs.game_engine.entities.entity import Entity, EntityCollection if TYPE_CHECKING: # Only import for typechecking to prevent circular dependency @@ -43,3 +43,6 @@ def reset(self) -> None: def step(self) -> None: pass + + def get_entities(self) -> List[Entity]: + pass diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 9e01118..623253c 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -66,7 +66,7 @@ def reset(self) -> None: Arena.reset() method missing. Player.reset() method missing. """ - + self.arena.reset() self.player1.reset(elixir=5) self.player2.reset(elixir=5) @@ -84,7 +84,7 @@ def make_image(self, player_id: int) -> npt.NDArray[np.uint8]: arena.get_entities() missing """ - entities = self.arena.get_entities() + entities: List[Entity] = self.arena.get_entities() canvas = pygame.Surface(size=self.resolution) #rendering logic goes here... From 76794d69eaf8fe6dc9b9449334c247e557546d6f Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:48:19 -0400 Subject: [PATCH 26/51] added template playcard function --- clash_royale/envs/game_engine/arena.py | 4 ++++ clash_royale/envs/game_engine/card.py | 3 ++- clash_royale/envs/game_engine/game_engine.py | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 4721303..853a5b3 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING from clash_royale.envs.game_engine.entities.entity import Entity, EntityCollection +from clash_royale.envs.game_engine.card import Card if TYPE_CHECKING: # Only import for typechecking to prevent circular dependency @@ -46,3 +47,6 @@ def step(self) -> None: def get_entities(self) -> List[Entity]: pass + + def play_card(self, x: int, y: int, card: Card) -> None: + pass diff --git a/clash_royale/envs/game_engine/card.py b/clash_royale/envs/game_engine/card.py index a6e273a..65777dd 100644 --- a/clash_royale/envs/game_engine/card.py +++ b/clash_royale/envs/game_engine/card.py @@ -6,5 +6,6 @@ class Card(): ''' def __init__(self, elixir: int): + + self.elixir: int = elixir pass - \ No newline at end of file diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 623253c..ce48f0d 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -13,10 +13,10 @@ import numpy.typing as npt import pygame -from clash_royale.envs.game_engine.entities.entity import EntityCollection from clash_royale.envs.game_engine.arena import Arena from clash_royale.envs.game_engine.struct import Scheduler, GameScheduler, DefaultScheduler from clash_royale.envs.game_engine.player import Player +from clash_royale.envs.game_engine.card import Card class GameEngine: @@ -98,17 +98,17 @@ def apply(self, player_id: int, action: Tuple[int, int, int] | None) -> None: """ if action is None: return - + assert(action[0] >= 0 and action[0] < self.width) assert(action[1] >= 0 and action[1] < self.height) assert(action[2] >= 0 and action[2] < 4) - + if player_id == 0: curr_player = self.player1 else: curr_player = self.player2 - card = curr_player.hand[action[2]] + card: Card = curr_player.hand[action[2]] assert(card.elixir <= curr_player.elixir) self.arena.play_card(action[0], action[1], card) From 901638af9f876ddf737cd42433c591d50e5fb43f Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 19:49:52 -0400 Subject: [PATCH 27/51] removed assert space --- clash_royale/envs/game_engine/game_engine.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index ce48f0d..4b05f6e 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -99,9 +99,9 @@ def apply(self, player_id: int, action: Tuple[int, int, int] | None) -> None: if action is None: return - assert(action[0] >= 0 and action[0] < self.width) - assert(action[1] >= 0 and action[1] < self.height) - assert(action[2] >= 0 and action[2] < 4) + assert action[0] >= 0 and action[0] < self.width + assert action[1] >= 0 and action[1] < self.height + assert action[2] >= 0 and action[2] < 4 if player_id == 0: curr_player = self.player1 @@ -109,7 +109,7 @@ def apply(self, player_id: int, action: Tuple[int, int, int] | None) -> None: curr_player = self.player2 card: Card = curr_player.hand[action[2]] - assert(card.elixir <= curr_player.elixir) + assert card.elixir <= curr_player.elixir self.arena.play_card(action[0], action[1], card) curr_player.play_card(action[2]) @@ -121,7 +121,7 @@ def step(self, frames: int=1) -> None: """ # update elixir first, order TBD. - elixir_rate = self.game_scheduler.elixir_rate() + elixir_rate: float = self.game_scheduler.elixir_rate() self.player1.step(elixir_rate, frames) self.player2.step(elixir_rate, frames) From 34469dd17e641981bdc013f2e15f33b971399e8d Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:22:48 -0400 Subject: [PATCH 28/51] added fps to initializer player --- clash_royale/envs/game_engine/game_engine.py | 9 ++++++--- clash_royale/envs/game_engine/player.py | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 4b05f6e..76ec466 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -40,6 +40,7 @@ def __init__(self, width: int=18, height: int=32, resolution: Tuple[int, int]=(128, 128), + fps: int=30 ) -> None: """ The game_engine should be initialized with settings such as resolution @@ -50,12 +51,13 @@ def __init__(self, self.width: int = width # Width of arena self.height: int = height # Height of arena self.resolution: Tuple[int, int] = resolution + self.fps: int = fps self.arena: Arena = Arena(width=self.width, height=self.height) - self.player1: Player = Player(deck1) - self.player2: Player = Player(deck2) + self.player1: Player = Player(deck1, fps) + self.player2: Player = Player(deck2, fps) - self.scheduler: Scheduler = Scheduler(fps=30) # counting frames + self.scheduler: Scheduler = Scheduler(fps) # counting frames self.game_scheduler: GameScheduler = DefaultScheduler(self.scheduler) # determining elixir etc. def reset(self) -> None: @@ -103,6 +105,7 @@ def apply(self, player_id: int, action: Tuple[int, int, int] | None) -> None: assert action[1] >= 0 and action[1] < self.height assert action[2] >= 0 and action[2] < 4 + curr_player: Player if player_id == 0: curr_player = self.player1 else: diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index 07c9423..4bb8e85 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -14,13 +14,15 @@ class Player(): """ def __init__(self, - deck: List[Card]) -> None: + deck: List[Card], + fps: int) -> None: """ Player component is initialized with deck of string, specifying the cards' names in the deck. """ self.elixir: int = 0 + self.fps: int = fps random.shuffle(deck) self.deck: Queue = Queue(maxsize = 8) @@ -51,15 +53,14 @@ def get_pseudo_legal_cards(self) -> list[Card]: def step(self, elixir_rate: float, - fps: int, - frame: int) -> None: + frames: int=1) -> None: """ Called with the value of elixir_rate and frame to update the elixir of player after 'frame' number of frames to better customize the elixir_rate that can vary depends on game modes. """ - self.elixir += (elixir_rate / fps) * frame + self.elixir += (elixir_rate / self.fps) * frames def pop(self, card_index: int) -> None: @@ -89,4 +90,4 @@ def play_card(self, card_index: int) -> None: elixir_cost: float = self.hand[card_index].elixir_cost assert(elixir_cost <= self.elixir) self.pop(card_index) - self.elixir -= elixir_cost \ No newline at end of file + self.elixir -= elixir_cost From f9a3c18ea1ce59c899fd16af6e6cc6db881821af Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:24:48 -0400 Subject: [PATCH 29/51] adding pseudovalues for return value of template functions --- clash_royale/envs/game_engine/arena.py | 4 ++-- clash_royale/envs/game_engine/player.py | 6 +++--- clash_royale/envs/game_engine/struct.py | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 853a5b3..004f005 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -42,11 +42,11 @@ def __init__(self, width: int =8, height: int=18) -> None: def reset(self) -> None: pass - def step(self) -> None: + def step(self, frames: int=1) -> None: pass def get_entities(self) -> List[Entity]: - pass + return [] def play_card(self, x: int, y: int, card: Card) -> None: pass diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index 4bb8e85..d37a9ab 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -49,19 +49,19 @@ def get_pseudo_legal_cards(self) -> list[Card]: for card in self.hand: if card.elixir <= self.elixir: legal_cards.append(card) + return legal_cards - + def step(self, elixir_rate: float, frames: int=1) -> None: """ Called with the value of elixir_rate and frame to update the elixir of player after 'frame' number of frames to better customize the elixir_rate that can vary depends on game modes. - """ self.elixir += (elixir_rate / self.fps) * frames - + def pop(self, card_index: int) -> None: """ diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index b04d177..4707e13 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -36,15 +36,16 @@ class DefaultScheduler(GameScheduler): """ Class for default 1v1 game scheduling """ + def elixir_rate(self) -> float: - pass + return 0 def game_state(self) -> int: """ Function to get current game state: ex: Game is over, double elixir, overtime, etc. """ - pass + return 0 From 1fe63a4348a40e6161274e426575d7e69e8d6220 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:25:13 -0400 Subject: [PATCH 30/51] updated card.py --- clash_royale/envs/game_engine/card.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clash_royale/envs/game_engine/card.py b/clash_royale/envs/game_engine/card.py index 65777dd..82e744e 100644 --- a/clash_royale/envs/game_engine/card.py +++ b/clash_royale/envs/game_engine/card.py @@ -5,7 +5,6 @@ class Card(): This class is created for Player class to refer to statistics of cards. ''' - def __init__(self, elixir: int): - + def __init__(self, elixir: int) -> None: self.elixir: int = elixir pass From 099cca7af2846f2ba19568e39ecba590ab6f1603 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:26:49 -0400 Subject: [PATCH 31/51] updated player1 spelling error --- clash_royale/envs/game_engine/game_engine.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 76ec466..54bd48c 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -136,12 +136,13 @@ def legal_actions(self, player_id: int) -> npt.NDArray[np.float64]: """ Returns a list of legal actions. """ - actions = np.zeros(shape=(32, 18, 5), dtype=np.float64) - actions[:,:,4] = 1 # no card is always legal + actions = np.zeros(shape=(32, 18, 4), dtype=np.float64) + + hand: List[Card] if player_id == 0: - hand = self.player_1.get_pseudo_legal_cards() + hand = self.player1.get_pseudo_legal_cards() else: - hand = self.player_2.get_pseudo_legal_cards() + hand = self.player2.get_pseudo_legal_cards() placement_mask = self.arena.get_placement_mask() for card_index in hand: From 4569c3954f8c90455166fa277c20e468778c71b1 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:31:32 -0400 Subject: [PATCH 32/51] added is_game_over template function in struct --- clash_royale/envs/game_engine/arena.py | 6 ++++++ clash_royale/envs/game_engine/struct.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 004f005..1c43e20 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -8,6 +8,9 @@ from typing import TYPE_CHECKING +import numpy as np +import numpy.typing as npt + from clash_royale.envs.game_engine.entities.entity import Entity, EntityCollection from clash_royale.envs.game_engine.card import Card @@ -50,3 +53,6 @@ def get_entities(self) -> List[Entity]: def play_card(self, x: int, y: int, card: Card) -> None: pass + + def get_placement_mask(self) -> npt.NDArray[bool]: + return np.ones(shape=(32, 18), dtype=bool) diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index 4707e13..c59283a 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -46,6 +46,9 @@ def game_state(self) -> int: ex: Game is over, double elixir, overtime, etc. """ return 0 + + def is_game_over(self) -> bool: + return False From 4f88b04533f6e51439bdebd46ad4240fd1c71e7c Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:32:27 -0400 Subject: [PATCH 33/51] added tower_count function --- clash_royale/envs/game_engine/arena.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 1c43e20..cdb876b 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -56,3 +56,6 @@ def play_card(self, x: int, y: int, card: Card) -> None: def get_placement_mask(self) -> npt.NDArray[bool]: return np.ones(shape=(32, 18), dtype=bool) + + def tower_count(self, player_id: int) -> int: + return 0 From f9d0aea52ebf485c7e526293eed9dfde0ce34cfc Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:33:44 -0400 Subject: [PATCH 34/51] updated game_scheduler type annotation --- clash_royale/envs/game_engine/game_engine.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 54bd48c..3aff46f 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -58,7 +58,7 @@ def __init__(self, self.player2: Player = Player(deck2, fps) self.scheduler: Scheduler = Scheduler(fps) # counting frames - self.game_scheduler: GameScheduler = DefaultScheduler(self.scheduler) # determining elixir etc. + self.game_scheduler: DefaultScheduler = DefaultScheduler(self.scheduler) # determining elixir etc. def reset(self) -> None: """ @@ -156,13 +156,13 @@ def is_terminal(self) -> bool: """ if self.game_scheduler.is_game_over(): return True - + if self.game_scheduler.is_overtime(): - player1_val = self.arena.tower_count(0) - player2_val = self.arena.tower_count(1) + player1_val: int = self.arena.tower_count(0) + player2_val: int = self.arena.tower_count(1) if player1_val != player2_val: return True - + return False def terminal_value(self) -> int: From ac5f44127c0a81f066f72307523d6213d4b08c2a Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:35:39 -0400 Subject: [PATCH 35/51] finished game_engine linting --- clash_royale/envs/game_engine/arena.py | 3 +++ clash_royale/envs/game_engine/game_engine.py | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index cdb876b..51faeb3 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -59,3 +59,6 @@ def get_placement_mask(self) -> npt.NDArray[bool]: def tower_count(self, player_id: int) -> int: return 0 + + def lowest_tower_health(self, player_id: int) -> int: + return 0 diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 3aff46f..fba2054 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -169,20 +169,17 @@ def terminal_value(self) -> int: """ Returns side won, otherwise returns -1. """ - player1_val = self.arena.tower_count(0) - player2_val = self.arena.tower_count(1) + player1_val: int = self.arena.tower_count(0) + player2_val: int = self.arena.tower_count(1) if player1_val == player2_val: player1_val = self.arena.lowest_tower_health(0) player2_val = self.arena.lowest_tower_health(1) if player1_val > player2_val: return 1 - + if player2_val > player1_val: return 0 if player1_val == player2_val: return -1 - - - From 138a8e1044805cba4cecded51392c6e60ac04fd7 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:39:28 -0400 Subject: [PATCH 36/51] added L to list in pseudo legal cards return type --- clash_royale/envs/game_engine/player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index d37a9ab..bc45118 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -35,11 +35,12 @@ def __init__(self, def reset(self, elixir: int = 5) -> None: """ This method is used to reset Player class. + TODO: implement deck shuffling """ self.elixir: int = elixir - def get_pseudo_legal_cards(self) -> list[Card]: + def get_pseudo_legal_cards(self) -> List[Card]: """ This method is used to get all cards that can be played given the current amount of elixir. From 653d70b54da511dde79ed02e2dfadf75703beffb Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:41:46 -0400 Subject: [PATCH 37/51] changed elixir to type float --- clash_royale/envs/game_engine/player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index bc45118..9e669b8 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -38,7 +38,7 @@ def reset(self, elixir: int = 5) -> None: TODO: implement deck shuffling """ - self.elixir: int = elixir + self.elixir: float = elixir def get_pseudo_legal_cards(self) -> List[Card]: """ @@ -53,7 +53,7 @@ def get_pseudo_legal_cards(self) -> List[Card]: return legal_cards - def step(self, + def step(self, elixir_rate: float, frames: int=1) -> None: """ From d0892180521cadfcf786df20fb0033e1c5850505 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:41:59 -0400 Subject: [PATCH 38/51] fixed pseudolegalcards to be card_index and not card --- clash_royale/envs/game_engine/game_engine.py | 2 +- clash_royale/envs/game_engine/player.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index fba2054..9b8d0ea 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -138,7 +138,7 @@ def legal_actions(self, player_id: int) -> npt.NDArray[np.float64]: """ actions = np.zeros(shape=(32, 18, 4), dtype=np.float64) - hand: List[Card] + hand: List[int] if player_id == 0: hand = self.player1.get_pseudo_legal_cards() else: diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index 9e669b8..a897db7 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -21,7 +21,7 @@ def __init__(self, specifying the cards' names in the deck. """ - self.elixir: int = 0 + self.elixir: float = 0 self.fps: int = fps random.shuffle(deck) @@ -40,16 +40,16 @@ def reset(self, elixir: int = 5) -> None: self.elixir: float = elixir - def get_pseudo_legal_cards(self) -> List[Card]: + def get_pseudo_legal_cards(self) -> List[int]: """ This method is used to get all cards that can be played given the current amount of elixir. """ - legal_cards: list[Card] = [] - for card in self.hand: - if card.elixir <= self.elixir: - legal_cards.append(card) + legal_cards: list[int] = [] + for card_index in len(self.hand): + if self.hand[card_index].elixir <= self.elixir: + legal_cards.append(card_index) return legal_cards From 0e1344b6b9955db3db1fcebf98afc496cc61242d Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:42:54 -0400 Subject: [PATCH 39/51] added is_overtime template function --- clash_royale/envs/game_engine/struct.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index c59283a..2264ba2 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -46,11 +46,12 @@ def game_state(self) -> int: ex: Game is over, double elixir, overtime, etc. """ return 0 - + def is_game_over(self) -> bool: return False - + def is_overtime(self) -> bool: + return False @dataclasses.dataclass(slots=True) class Stats: From ea61435cf9875ab5b0bc20c763a95d85a38c1b1f Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:43:38 -0400 Subject: [PATCH 40/51] added line after docstring --- clash_royale/envs/game_engine/game_engine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 9b8d0ea..6bbdade 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -169,6 +169,7 @@ def terminal_value(self) -> int: """ Returns side won, otherwise returns -1. """ + player1_val: int = self.arena.tower_count(0) player2_val: int = self.arena.tower_count(1) if player1_val == player2_val: From dea107896f66b2175d9ee6ed3bd0d94bb86cf829 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:44:21 -0400 Subject: [PATCH 41/51] added extra check --- clash_royale/envs/game_engine/game_engine.py | 2 +- clash_royale/envs/game_engine/player.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/clash_royale/envs/game_engine/game_engine.py b/clash_royale/envs/game_engine/game_engine.py index 6bbdade..23367f1 100644 --- a/clash_royale/envs/game_engine/game_engine.py +++ b/clash_royale/envs/game_engine/game_engine.py @@ -169,7 +169,7 @@ def terminal_value(self) -> int: """ Returns side won, otherwise returns -1. """ - + player1_val: int = self.arena.tower_count(0) player2_val: int = self.arena.tower_count(1) if player1_val == player2_val: diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index a897db7..5bc54a0 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -57,7 +57,7 @@ def step(self, elixir_rate: float, frames: int=1) -> None: """ - Called with the value of elixir_rate and frame to update the elixir of player after 'frame' number of frames + Called with the value of elixir_rate and frame to update the elixir of player to better customize the elixir_rate that can vary depends on game modes. """ @@ -72,7 +72,8 @@ def pop(self, card_index: int) -> None: """ - assert(card_index < 4) + assert card_index >= 0 and card_index < 4 + self.deck.put(self.hand[card_index], block = False) self.hand[card_index] = self.next self.next: Card = self.deck.get(block = False) From 18e8a10088f8084b1fc2e0ac912458da26e132bf Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:46:28 -0400 Subject: [PATCH 42/51] added elixir cost for template --- clash_royale/envs/game_engine/card.py | 3 ++- clash_royale/envs/game_engine/player.py | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/clash_royale/envs/game_engine/card.py b/clash_royale/envs/game_engine/card.py index 82e744e..cc7d5d6 100644 --- a/clash_royale/envs/game_engine/card.py +++ b/clash_royale/envs/game_engine/card.py @@ -5,6 +5,7 @@ class Card(): This class is created for Player class to refer to statistics of cards. ''' - def __init__(self, elixir: int) -> None: + def __init__(self, elixir: int, elixir_cost: int) -> None: self.elixir: int = elixir + self.elixir_cost: int = elixir_cost pass diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index 5bc54a0..4e93872 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -76,19 +76,17 @@ def pop(self, card_index: int) -> None: self.deck.put(self.hand[card_index], block = False) self.hand[card_index] = self.next - self.next: Card = self.deck.get(block = False) - + self.next = self.deck.get(block = False) def play_card(self, card_index: int) -> None: """ - - Called with the index of cards in hand to update the state of available cards, next cards, and elixir. + Called with the index of cards in hand to update the state of + available cards, next cards, and elixir. Should be called with the initial rendering of entity. - """ - assert(card_index < 4) + assert card_index >= 0 and card_index < 4 elixir_cost: float = self.hand[card_index].elixir_cost assert(elixir_cost <= self.elixir) self.pop(card_index) From 09ea9baf9de329ed40278d635e0456cbd6597c79 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:46:56 -0400 Subject: [PATCH 43/51] finished linting player --- clash_royale/envs/game_engine/player.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/player.py b/clash_royale/envs/game_engine/player.py index 4e93872..9025c43 100644 --- a/clash_royale/envs/game_engine/player.py +++ b/clash_royale/envs/game_engine/player.py @@ -88,6 +88,7 @@ def play_card(self, card_index: int) -> None: assert card_index >= 0 and card_index < 4 elixir_cost: float = self.hand[card_index].elixir_cost - assert(elixir_cost <= self.elixir) + + assert elixir_cost <= self.elixir self.pop(card_index) self.elixir -= elixir_cost From 927cf9042333205517a23f0dd78a7137b01205fc Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:48:19 -0400 Subject: [PATCH 44/51] added newline after docstring --- clash_royale/envs/game_engine/struct.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index 2264ba2..ca89acf 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -2,6 +2,8 @@ Various structures to be utilized """ +from __future__ import annotations + import dataclasses class Scheduler: @@ -28,6 +30,7 @@ class GameScheduler: """ Template class for game scheduling """ + def __init__(self, scheduler: Scheduler, fps: int=30) -> None: self.scheduler: Scheduler = scheduler self.fps: int = fps From 7c225ee6a7ad61c5debb313e86b0ec83170d8461 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:48:48 -0400 Subject: [PATCH 45/51] missing stuff in struct --- clash_royale/envs/game_engine/struct.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/struct.py b/clash_royale/envs/game_engine/struct.py index ca89acf..c25e905 100644 --- a/clash_royale/envs/game_engine/struct.py +++ b/clash_royale/envs/game_engine/struct.py @@ -30,7 +30,7 @@ class GameScheduler: """ Template class for game scheduling """ - + def __init__(self, scheduler: Scheduler, fps: int=30) -> None: self.scheduler: Scheduler = scheduler self.fps: int = fps @@ -48,6 +48,7 @@ def game_state(self) -> int: Function to get current game state: ex: Game is over, double elixir, overtime, etc. """ + return 0 def is_game_over(self) -> bool: From a5065e9e26c3eed1e0bb3f4762cc7bfb23067656 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:54:27 -0400 Subject: [PATCH 46/51] renamed clash_royale_env class --- clash_royale/envs/clash_royale_env.py | 2 +- clash_royale/envs/game_engine/utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/clash_royale/envs/clash_royale_env.py b/clash_royale/envs/clash_royale_env.py index dd56bff..5777dbb 100644 --- a/clash_royale/envs/clash_royale_env.py +++ b/clash_royale/envs/clash_royale_env.py @@ -83,7 +83,7 @@ def draw_troop(canvas, troop, health_bar_color, pix_square_size): TROOP_HEALTH[troop_type], pix_square_size) -class ArenaEnv(gym.Env): +class ClashRoyaleEnv(gym.Env): metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 16} def __init__(self, render_mode=None, width=8, height=18): diff --git a/clash_royale/envs/game_engine/utils.py b/clash_royale/envs/game_engine/utils.py index 2d85009..63bb0fc 100644 --- a/clash_royale/envs/game_engine/utils.py +++ b/clash_royale/envs/game_engine/utils.py @@ -4,7 +4,6 @@ import math - def distance(x1: int, y1: int, x2: int, y2: int) -> float: """ Determines the distance between two points From c2bf69831b28af18392c5ec6cc22bc89e16b63e5 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 20:57:52 -0400 Subject: [PATCH 47/51] give up on clash_royale_env for now --- clash_royale/envs/clash_royale_env.py | 97 +++------------------------ 1 file changed, 8 insertions(+), 89 deletions(-) diff --git a/clash_royale/envs/clash_royale_env.py b/clash_royale/envs/clash_royale_env.py index 5777dbb..aa6b01c 100644 --- a/clash_royale/envs/clash_royale_env.py +++ b/clash_royale/envs/clash_royale_env.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from typing import Tuple, List + import numpy as np import pygame @@ -6,103 +9,19 @@ from clash_royale.envs.game_engine.game_engine import GameEngine -MAX_NUMBER_TROOPS = 32 -MAX_TROOP_TYPES = 32 -MAX_TROOP_HEALTH = 1000 - -KING_TOWER_RANGE = 7.0 -KING_TOWER_DAMAGE = 109 -KING_TOWER_HEALTH = 4824 - -TROOP_SPEED_MAP = {'slow': 0.75, 'medium': 1.0, 'fast': 1.5, 'very fast': 2.0} - -# barbarian, archer, giant, skeleton -TROOP_COLORS = [(153, 255, 51), (255, 102, 155), (139, 69, 19), (250, 250, 250)] -TROOP_SPEEDS = [1.0, 1.0, 0.75, 1.5] -TROOP_SIZES = [0.3, 0.3, 0.3, 0.3] -TROOP_ATTACK_RANGE = [0.8, 5, 0.8, 0.8] -TROOP_SIGHT_RANGE = [5.5, 5.5, 7.5, 5.5] -TROOP_HEALTH = [670, 304, 4091, 81] -TROOP_DAMAGE = [147, 118, 169, 81] -TROOP_BUILDING_TARGETING = [False, False, True, False] - - - -HEALTH_BAR_HEIGHT = 0.15 - - -def calculate_health_bar_length(max_health): - #return 0.5529703695314562 * np.log(max_health + 1) - return 1 - -def draw_rectangle_from_center(canvas, color, location, size): - pygame.draw.rect( - canvas, - color, - pygame.Rect( - location - size / 2, - size, - ), - ) - -def draw_health_bar(canvas, health_bar_color, center, health, max_health, pix_square_size): - draw_rectangle_from_center(canvas, health_bar_color, - center * pix_square_size, - [(health / max_health) * calculate_health_bar_length(max_health), - HEALTH_BAR_HEIGHT] * pix_square_size) - -def draw_king_tower(canvas, side, location, pix_square_size, health): - color = (190, 0, 0) if side == 'red' else (0,0,190) - health_color = (155, 0, 0) if side == 'red' else (0, 0, 155) - direction = [0, -0.7] if side == 'red' else [0, 0.7] - draw_rectangle_from_center(canvas, (0, 0, 0), location * pix_square_size, pix_square_size) - draw_rectangle_from_center(canvas, color, location * pix_square_size, pix_square_size*0.9) - draw_health_bar(canvas, health_color, - location + direction, - health, KING_TOWER_HEALTH, pix_square_size) - -def draw_troop(canvas, troop, health_bar_color, pix_square_size): - # draws using circles - troop_type = int(troop[2]) - radius = TROOP_SIZES[troop_type] - pygame.draw.circle( - canvas, - (0,0,0), - troop[:2] * pix_square_size, - radius * pix_square_size[1], - ) - pygame.draw.circle( - canvas, - TROOP_COLORS[troop_type], - troop[:2] * pix_square_size, - radius * pix_square_size[1]*0.95, - ) - draw_health_bar(canvas, health_bar_color, - [troop[0], troop[1] - radius - 0.15], - troop[3], - TROOP_HEALTH[troop_type], - pix_square_size) class ClashRoyaleEnv(gym.Env): metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 16} - def __init__(self, render_mode=None, width=8, height=18): - self.width = width # The size of the square grid - self.height = height - self.window_size_width = 360 # The size of the PyGame window width - self.window_size_height = 810 # The size of the PyGame window height + def __init__(self, render_mode: str | None=None, width: int=18, height: int=32): + self.width: int = width # The size of the square grid + self.height: int = height + resolution: Tuple[int, int] = (128, 128) - # Observations are dictionaries with the agent's and the target's location. - # Each location is encoded as an element of {0, ..., `size`}^2, i.e. MultiDiscrete([size, size]). self.observation_space = spaces.Dict( - { - "blue-troops": spaces.Box(0, np.tile(np.array([width, height, MAX_TROOP_TYPES, MAX_TROOP_HEALTH]), (MAX_NUMBER_TROOPS,1,)), shape=(MAX_NUMBER_TROOPS, 4,), dtype=np.float32), - "red-troops": spaces.Box(0, np.tile(np.array([width, height, MAX_TROOP_TYPES, MAX_TROOP_HEALTH]), (MAX_NUMBER_TROOPS,1,)), shape=(MAX_NUMBER_TROOPS, 4,), dtype=np.float32), - } ) - # We have 4 actions, corresponding to "right", "up", "left", "down" - self.action_space = spaces.Discrete(4) + self.action_space = spaces.Discrete(1) assert render_mode is None or render_mode in self.metadata["render_modes"] From 4822ed110197b9b6ca6b21531e755fa89661cd85 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 21:02:15 -0400 Subject: [PATCH 48/51] add module dependencies for pylint github actions --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index a536e7c..cb111e4 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pylint + pip install pylint, gymnasium, pygame - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') From 00c76344c1cb36bad6600696229c1f60db030f52 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 21:04:57 -0400 Subject: [PATCH 49/51] fixed pyling yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index cb111e4..f6059cf 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pylint, gymnasium, pygame + pip install pylint gymnasium pygame - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') From b7ecab87ef5ebf036c8f6c6fecadddc401693cac Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Mon, 11 Mar 2024 21:07:48 -0400 Subject: [PATCH 50/51] cleared clashRoyaleEnv code, we can review the commits if we need it back --- clash_royale/envs/clash_royale_env.py | 211 ++------------------------ 1 file changed, 10 insertions(+), 201 deletions(-) diff --git a/clash_royale/envs/clash_royale_env.py b/clash_royale/envs/clash_royale_env.py index aa6b01c..51d1656 100644 --- a/clash_royale/envs/clash_royale_env.py +++ b/clash_royale/envs/clash_royale_env.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Tuple, List +from typing import Tuple import numpy as np import pygame @@ -9,7 +9,6 @@ from clash_royale.envs.game_engine.game_engine import GameEngine - class ClashRoyaleEnv(gym.Env): metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 16} @@ -38,212 +37,22 @@ def __init__(self, render_mode: str | None=None, width: int=18, height: int=32): self.clock = None def _get_obs(self): - return {"blue-troops": self._blue_troops, "red-troops": self._red_troops} - - def _get_info(self): - return { - } - - def reset(self, seed=None, options=None): - # We need the following line to seed self.np_random - super().reset(seed=seed) - - self._king_blue_tower_center_location = np.array([self.width//2, self.height-1]) - self._king_red_tower_center_location = np.array([self.width//2, 1]) - - self._king_blue_tower_health = KING_TOWER_HEALTH - self._king_red_tower_health = KING_TOWER_HEALTH + pass - self._king_KING_TOWER_RANGE = KING_TOWER_RANGE - - self._blue_troops = np.zeros((MAX_NUMBER_TROOPS,4,), dtype=np.float32) - self._red_troops = np.zeros((MAX_NUMBER_TROOPS,4,), dtype=np.float32) - - #Test - self._blue_troops[0] = [3.2, 5.6, 0, TROOP_HEALTH[0]] - self._blue_troops[1] = [0.5, 3.2, 2, TROOP_HEALTH[2]] - self._red_troops[0] = [5.8, 3.76, 1, TROOP_HEALTH[1]] - self._red_troops[1] = [1.3, 10.75, 3, TROOP_HEALTH[3]] - self._red_troops[2] = [2.1, 3.7, 0, TROOP_HEALTH[0] - 100] - - observation = self._get_obs() - info = self._get_info() + def _get_info(self): + pass - if self.render_mode == "human": - self._render_frame() + def reset(self, seed=None, options=None): + pass - return observation, info - def step(self, action): - move_direction = [[[None, 10000] for j in range(MAX_NUMBER_TROOPS)] for i in range(2)] - - for i in range(MAX_NUMBER_TROOPS): - troop_type_i = int(self._blue_troops[i][2]) - if self._blue_troops[i][3] <= 0 or TROOP_BUILDING_TARGETING[troop_type_i]: - continue - - min_v = None - min_dist = 1000 - for j in range(MAX_NUMBER_TROOPS): - if self._red_troops[j][3] <= 0: - continue - v = (self._red_troops[j] - self._blue_troops[i])[:2] - dist = np.linalg.norm(v) - if min_dist > dist and dist <= TROOP_SIGHT_RANGE[troop_type_i]: - min_dist = dist - min_v = v / dist - - if not min_v is None: - move_direction[0][i] = min_v, min_dist - - for j in range(MAX_NUMBER_TROOPS): - troop_type_j = int(self._red_troops[j][2]) - if self._red_troops[j][3] <= 0 or TROOP_BUILDING_TARGETING[troop_type_j]: - continue - - min_v = None - min_dist = 1000 - for i in range(MAX_NUMBER_TROOPS): - if self._blue_troops[i][3] <= 0: - continue - v = (self._blue_troops[i] - self._red_troops[j])[:2] - dist = np.linalg.norm(v) - if min_dist > dist and dist <= TROOP_SIGHT_RANGE[troop_type_j]: - min_dist = dist - min_v = v / dist - - if not min_v is None: - move_direction[1][j] = min_v, min_dist - - for i in range(MAX_NUMBER_TROOPS): - troop = self._blue_troops[i] - if troop[3] > 0: - v = self._king_red_tower_center_location - troop[:2] - dist = np.linalg.norm(v) - if move_direction[0][i][1] > dist: - move_direction[0][i] = v / dist, dist - - v, dist = move_direction[0][i] - if dist > TROOP_ATTACK_RANGE[int(troop[2])]: - v = v * TROOP_SPEEDS[int(troop[2])] / self.metadata["render_fps"] - troop[0] += v[0] - troop[1] += v[1] - - troop = self._red_troops[i] - if troop[3] > 0: - v = self._king_blue_tower_center_location - troop[:2] - dist = np.linalg.norm(v) - if move_direction[1][i][1] > dist: - move_direction[1][i] = v/dist, dist - v, dist = move_direction[1][i] - if dist > TROOP_ATTACK_RANGE[int(troop[2])]: - v = v * TROOP_SPEEDS[int(troop[2])] / self.metadata["render_fps"] - troop[0] += v[0] - troop[1] += v[1] - - # An episode is done iff the agent has reached the target - terminated = False - reward = 1 if terminated else 0 # Binary sparse rewards - observation = self._get_obs() - info = self._get_info() - - if self.render_mode == "human": - self._render_frame() - - return observation, reward, terminated, False, info + pass def render(self): - if self.render_mode == "rgb_array": - return self._render_frame() + pass def _render_frame(self): - if self.window is None and self.render_mode == "human": - pygame.init() - pygame.display.init() - self.window = pygame.display.set_mode( - (self.window_size_width, self.window_size_height) - ) - if self.clock is None and self.render_mode == "human": - self.clock = pygame.time.Clock() - - canvas = pygame.Surface((self.window_size_width, self.window_size_height)) - canvas.fill((255, 255, 255)) - pix_square_size_height = ( - self.window_size_height / self.height - ) # The height of a single grid square in pixels - pix_square_size_width = ( - self.window_size_width / self.width - ) - - pix_square_size = np.array([pix_square_size_width, pix_square_size_height]) - - - # Draw tower ranges - pygame.draw.circle( - canvas, - (211, 211, 211), - self._king_blue_tower_center_location * pix_square_size, - self._king_KING_TOWER_RANGE * pix_square_size_height, - ) - pygame.draw.circle( - canvas, - (211, 211, 211), - self._king_red_tower_center_location * pix_square_size, - self._king_KING_TOWER_RANGE * pix_square_size_height, - ) + pass - # Add some gridlines - for x in range(self.height + 1): - pygame.draw.line( - canvas, - (0, 0, (x == self.height // 2 - 1 or x == self.height // 2 + 1) * 255), - (0, pix_square_size_height * x), - (self.window_size_width, pix_square_size_height * x), - width=(1 + (x == self.height // 2 - 1 or x == self.height // 2 + 1)), - ) - - for x in range(self.width + 1): - pygame.draw.line( - canvas, - 0, - (pix_square_size_width * x, 0), - (pix_square_size_width * x, self.window_size_height), - width=1, - ) - - draw_king_tower(canvas, "red", - self._king_red_tower_center_location, - pix_square_size, self._king_red_tower_health) - - - for troop in self._blue_troops: - if troop[3] > 0: - draw_troop(canvas, troop, (0,0,155), pix_square_size) - - for troop in self._red_troops: - if troop[3] > 0: - draw_troop(canvas, troop, (155,0, 0), pix_square_size) - - - draw_king_tower(canvas, "blue", - self._king_blue_tower_center_location, - pix_square_size, self._king_blue_tower_health) - - if self.render_mode == "human": - # The following line copies our drawings from `canvas` to the visible window - self.window.blit(canvas, canvas.get_rect()) - pygame.event.pump() - pygame.display.update() - - # We need to ensure that human-rendering occurs at the predefined framerate. - # The following line will automatically add a delay to keep the framerate stable. - self.clock.tick(self.metadata["render_fps"]) - else: # rgb_array - return np.transpose( - np.array(pygame.surfarray.pixels3d(canvas)), axes=(1, 0, 2) - ) - def close(self): - if self.window is not None: - pygame.display.quit() - pygame.quit() \ No newline at end of file + pass From 1db94e225faddf84f46bf5e7cd6a3341b8a9f5f8 Mon Sep 17 00:00:00 2001 From: Nathanael Lu Date: Tue, 12 Mar 2024 00:37:09 -0400 Subject: [PATCH 51/51] added List import for arena.py --- clash_royale/envs/game_engine/arena.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clash_royale/envs/game_engine/arena.py b/clash_royale/envs/game_engine/arena.py index 51faeb3..6a4fb25 100644 --- a/clash_royale/envs/game_engine/arena.py +++ b/clash_royale/envs/game_engine/arena.py @@ -6,7 +6,7 @@ as they will greatly simplify the simulation procedure. """ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List import numpy as np import numpy.typing as npt