From 850ee2e8f6b017036fc3dce22a2ea16a85ab3b4b Mon Sep 17 00:00:00 2001 From: niuyazhe Date: Wed, 8 Mar 2023 16:46:51 +0800 Subject: [PATCH] feature(nyz): add py3.8 support and get started guide --- README.md | 16 +++++ gisim/agent.py | 7 +- gisim/cards/__init__.py | 3 +- gisim/cards/base.py | 8 +-- gisim/cards/characters/Cryo/KamisatoAyaka.py | 22 +++--- gisim/cards/characters/base.py | 13 ++-- gisim/cards/equipments/weapons.py | 44 ++++++------ gisim/classes/action.py | 20 +++--- gisim/classes/character.py | 12 ++-- gisim/classes/entity.py | 3 +- gisim/classes/enums.py | 2 +- gisim/classes/equipment.py | 2 +- gisim/classes/message.py | 70 ++++++++++---------- gisim/classes/status.py | 4 +- gisim/classes/summon.py | 2 +- gisim/env.py | 5 +- gisim/game.py | 6 +- gisim/player_area.py | 66 +++++++++--------- pyproject.toml | 2 +- 19 files changed, 163 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 229a556..bee9991 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,22 @@ Package name `gisim` stands for both `Genshin Impact` and `Genius Invokation` 希望可以实现类似openai-gym的API,用于训练ai&评估卡组强度 +# Get Started + +Prerequisites: +* Python >=3.9 +* [Poetry 1.1.14+](https://python-poetry.org) + +Installation: enter the project directory and execute the following command: +```bash +poetry install +``` + +Runnable basic demo locally: give the following a try: +```bash +poetry run python3 -u tests/test_framework.py +``` + # Roadmap - [x] Encode the game status into a dictionary diff --git a/gisim/agent.py b/gisim/agent.py index 9bed3c8..60184c5 100644 --- a/gisim/agent.py +++ b/gisim/agent.py @@ -1,9 +1,8 @@ """Player & agent APIs """ -import enum from abc import ABC, abstractmethod from collections import Counter -from typing import OrderedDict +from typing import OrderedDict, List, Dict from gisim.cards.characters import get_character_card from gisim.classes.action import ( @@ -42,8 +41,8 @@ def __init__(self, player_id: PlayerID): def get_dice_idx_greedy( self, - dice: list[ElementType], - cost: dict[ElementType, int], + dice: List[ElementType], + cost: Dict[ElementType, int], char_element: ElementType = ElementType.NONE, ): # First determine whether the current dice are enough diff --git a/gisim/cards/__init__.py b/gisim/cards/__init__.py index 1deecff..138d1cb 100644 --- a/gisim/cards/__init__.py +++ b/gisim/cards/__init__.py @@ -1,3 +1,4 @@ +from typing import Tuple from gisim.cards.base import Card from gisim.cards.characters import * from gisim.cards.equipments import * @@ -22,7 +23,7 @@ def get_card(card_name: str): return card -def get_equipment(equipment_name: str, target: tuple[PlayerID, CharPos]): +def get_equipment(equipment_name: str, target: Tuple[PlayerID, CharPos]): equipment_name = equipment_name.replace(" ", "").replace("'", "") equipment_class = globals()[equipment_name] equipment: EquipmentEntity = equipment_class( diff --git a/gisim/cards/base.py b/gisim/cards/base.py index 49f4c67..ef31d90 100644 --- a/gisim/cards/base.py +++ b/gisim/cards/base.py @@ -1,5 +1,5 @@ from queue import PriorityQueue -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Dict, cast from pydantic import BaseModel @@ -21,13 +21,13 @@ class Card(BaseModel): id: int name: str - costs: dict[ElementType, int] + costs: Dict[ElementType, int] text: str card_type: CardType def use_card( self, - msg_queue: PriorityQueue[Message], + msg_queue: PriorityQueue, # PriorityQueue[Message] game_info: "GameInfo", ): pass @@ -42,7 +42,7 @@ class WeaponCard(Card): weapon_type: WeaponType card_type: CardType = CardType.WEAPON - def use_card(self, msg_queue: PriorityQueue[Message], game_info: "GameInfo"): + def use_card(self, msg_queue: PriorityQueue, game_info: "GameInfo"): top_msg = msg_queue.queue[0] top_msg = cast(UseCardMsg, top_msg) player_id, entity_type, idx = top_msg.card_target[0] diff --git a/gisim/cards/characters/Cryo/KamisatoAyaka.py b/gisim/cards/characters/Cryo/KamisatoAyaka.py index fabd19d..f02d74f 100644 --- a/gisim/cards/characters/Cryo/KamisatoAyaka.py +++ b/gisim/cards/characters/Cryo/KamisatoAyaka.py @@ -1,7 +1,7 @@ """神里绫华""" from queue import PriorityQueue -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Dict, List, cast from xml.dom.minidom import Element from gisim.cards.base import TalentCard @@ -41,7 +41,7 @@ class KamisatoArtKabuki(GenericSkill): text: str = """ Deals 2 Physical DMG. """ - costs: dict[ElementType, int] = {ElementType.CRYO: 1, ElementType.ANY: 2} + costs: Dict[ElementType, int] = {ElementType.CRYO: 1, ElementType.ANY: 2} type: SkillType = SkillType.NORMAL_ATTACK damage_element: ElementType = ElementType.NONE damage_value: int = 2 @@ -53,7 +53,7 @@ class KamisatoArtHyouka(GenericSkill): text: str = """ Deals 3 Cryo DMG """ - costs: dict[ElementType, int] = {ElementType.CRYO: 3} + costs: Dict[ElementType, int] = {ElementType.CRYO: 3} type: SkillType = SkillType.ELEMENTAL_SKILL damage_element: ElementType = ElementType.CRYO damage_value: int = 3 @@ -65,7 +65,7 @@ class KamisatoArtSoumetsu(GenericSkill): text: str = """ Deals 4 Cryo DMG, summons 1 Frostflake Seki no To. """ - costs: dict[ElementType, int] = {ElementType.CRYO: 3, ElementType.POWER: 3} + costs: Dict[ElementType, int] = {ElementType.CRYO: 3, ElementType.POWER: 3} type: SkillType = SkillType.ELEMENTAL_BURST damage_element: ElementType = ElementType.CRYO damage_value: int = 4 @@ -79,10 +79,10 @@ class KamisatoArtSenho(CharacterSkill): text: str = """ (Passive) When switched to be the active character, this character gains Cryo Elemental Infusion. """ - costs: dict[ElementType, int] = {} + costs: Dict[ElementType, int] = {} type: SkillType = SkillType.PASSIVE_SKILL - def use_skill(self, msg_queue: PriorityQueue[Message], parent: "CharacterEntity"): + def use_skill(self, msg_queue: PriorityQueue, parent: "CharacterEntity"): top_msg = msg_queue.queue[0] updated = False if isinstance(top_msg, ChangeCharacterMsg): @@ -105,12 +105,12 @@ class KamisatoAyaka(CharacterCard): id: int = 1105 name: str = "Kamisato Ayaka" element_type: ElementType = ElementType.CRYO - nations: list[Nation] = [Nation.Inazuma] + nations: List[Nation] = [Nation.Inazuma] health_point: int = 10 power: int = 0 max_power: int = 3 weapon_type: WeaponType = WeaponType.SWORD - skills: list[CharacterSkill] = [ + skills: List[CharacterSkill] = [ KamisatoArtKabuki(), KamisatoArtHyouka(), KamisatoArtSoumetsu(), @@ -129,7 +129,7 @@ class KantenSenmyouBlessingCard(TalentCard): id = 211051 name = "Kanten Senmyou Blessing" character_name: str = "Kamisato Ayaka" - costs: dict[ElementType, int] = {ElementType.CRYO: 2} + costs: Dict[ElementType, int] = {ElementType.CRYO: 2} text: str = """ The Cryo Elemental Infusion created by your Kamisato Ayaka, who has this card equipped, allows the character to which it is attached to deal +1 Cryo DMG. When you switch to Kamisato Ayaka, who has this card equipped: Spend 1 less Elemental Die. (Once per Round) @@ -138,7 +138,7 @@ class KantenSenmyouBlessingCard(TalentCard): def use_card( self, - msg_queue: PriorityQueue[Message], + msg_queue: PriorityQueue, game_info: "GameInfo", ): top_msg = msg_queue.queue[0] @@ -157,7 +157,7 @@ def use_card( class KantenSenmyouBlessing(TalentEntity): name: str = "Kanten Senmyou Blessing" - def msg_handler(self, msg_queue: PriorityQueue["Message"]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: top_msg = msg_queue.queue[0] updated = False if self._uuid in top_msg.responded_entities: diff --git a/gisim/cards/characters/base.py b/gisim/cards/characters/base.py index 0671c37..504dc3e 100644 --- a/gisim/cards/characters/base.py +++ b/gisim/cards/characters/base.py @@ -1,9 +1,8 @@ """ Basic character card classes """ -from abc import abstractmethod from queue import PriorityQueue -from typing import TYPE_CHECKING, Optional, Type, cast +from typing import TYPE_CHECKING, Optional, Dict, List, cast from pydantic import BaseModel, Field, validator @@ -27,7 +26,7 @@ class CharacterSkill(BaseModel): id: int name: str text: str - costs: dict[ElementType, int] + costs: Dict[ElementType, int] type: SkillType resource: Optional[str] = None # 图片链接 accumulate_power: int = 1 @@ -35,7 +34,7 @@ class CharacterSkill(BaseModel): Elemental burst will accumulate no power by default.""" # @abstractmethod - def use_skill(self, msg_queue: PriorityQueue[Message], parent: "CharacterEntity"): + def use_skill(self, msg_queue: PriorityQueue, parent: "CharacterEntity"): """ Called when the skill is activated, by default, it parses the skill text and returns a list of messages to be sent to the game @@ -68,7 +67,7 @@ class GenericSkill(CharacterSkill): heal_all_value: int = 0 """Heal all your alive characters""" - def use_skill(self, msg_queue: PriorityQueue[Message], parent: "CharacterEntity"): + def use_skill(self, msg_queue: PriorityQueue, parent: "CharacterEntity"): msg = msg_queue.get() msg = cast(UseSkillMsg, msg) target_player_id, target_char_pos = msg.skill_targets[0] @@ -153,9 +152,9 @@ class CharacterCard(BaseModel): id: int name: str element_type: ElementType - nations: list[Nation] = Field(..., min_items=1, max_items=3) # 所属地区/阵营 + nations: List[Nation] = Field(..., min_items=1, max_items=3) # 所属地区/阵营 health_point: int = 10 - skills: list[CharacterSkill] + skills: List[CharacterSkill] resource: Optional[str] = None # 图片链接 power: int = 0 max_power: int diff --git a/gisim/cards/equipments/weapons.py b/gisim/cards/equipments/weapons.py index 97fcb42..53d6e60 100644 --- a/gisim/cards/equipments/weapons.py +++ b/gisim/cards/equipments/weapons.py @@ -1,11 +1,11 @@ from queue import PriorityQueue -from typing import cast +from typing import cast, Dict -from cards.base import WeaponCard -from cards.characters import get_skill_type -from classes.enums import ElementType, EquipmentType, WeaponType -from classes.equipment import WeaponEntity -from classes.message import AfterUsingSkillMsg, DealDamageMsg, Message +from gisim.cards.base import WeaponCard +from gisim.cards.characters import get_skill_type +from gisim.classes.enums import ElementType, EquipmentType, WeaponType +from gisim.classes.equipment import WeaponEntity +from gisim.classes.message import AfterUsingSkillMsg, DealDamageMsg, Message from gisim.classes.enums import SkillType from gisim.classes.message import ChangeDiceMsg @@ -15,7 +15,7 @@ class MagicGuideCard(WeaponCard): id: int = 311101 name: str = "Magic Guide" text: str = """The character deals +1 DMG.(Only Catalyst Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 2} + costs: Dict[ElementType, int] = {ElementType.SAME: 2} weapon_type: WeaponType = WeaponType.CATALYST @@ -28,7 +28,7 @@ class SacrificialFragmentsCard(WeaponCard): id: int = 311102 name: str = "Sacrificial Fragments" text: str = """The character deals +1 DMG.After the character uses an Elemental Skill: Create 1 Elemental Die of the same Elemental Type as this character. (Once per Round)(Only Catalyst Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.CATALYST @@ -41,7 +41,7 @@ class SkywardAtlasCard(WeaponCard): id: int = 311103 name: str = "Skyward Atlas" text: str = """The character deals +1 DMG.Once per Round: This character's Normal Attacks deal +1 additional DMG.(Only Catalyst Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.CATALYST @@ -54,7 +54,7 @@ class RavenBowCard(WeaponCard): id: int = 311201 name: str = "Raven Bow" text: str = """The character deals +1 DMG.(Only Bow Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 2} + costs: Dict[ElementType, int] = {ElementType.SAME: 2} weapon_type: WeaponType = WeaponType.BOW @@ -67,7 +67,7 @@ class SacrificialBowCard(WeaponCard): id: int = 311202 name: str = "Sacrificial Bow" text: str = """The character deals +1 DMG.After the character uses an Elemental Skill: Create 1 Elemental Die of the same Elemental Type as this character. (Once per Round)(Only Bow Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.BOW @@ -80,7 +80,7 @@ class SkywardHarpCard(WeaponCard): id: int = 311203 name: str = "Skyward Harp" text: str = """The character deals +1 DMG.Once per Round: This character's Normal Attacks deal +1 additional DMG.(Only Bow Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.BOW @@ -93,7 +93,7 @@ class WhiteIronGreatswordCard(WeaponCard): id: int = 311301 name: str = "White Iron Greatsword" text: str = """The character deals +1 DMG.(Only Claymore Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 2} + costs: Dict[ElementType, int] = {ElementType.SAME: 2} weapon_type: WeaponType = WeaponType.CLAYMORE @@ -106,7 +106,7 @@ class SacrificialGreatswordCard(WeaponCard): id: int = 311302 name: str = "Sacrificial Greatsword" text: str = """The character deals +1 DMG.After the character uses an Elemental Skill: Create 1 Elemental Die of the same Elemental Type as this character. (Once per Round)(Only Claymore Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.CLAYMORE @@ -119,7 +119,7 @@ class WolfsGravestoneCard(WeaponCard): id: int = 311303 name: str = "Wolf's Gravestone" text: str = """The character deals +1 DMG.Deal +2 additional DMG if the target's remaining HP is equal to or less than 6.(Only Claymore Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.CLAYMORE @@ -132,7 +132,7 @@ class WhiteTasselCard(WeaponCard): id: int = 311401 name: str = "White Tassel" text: str = """The character deals +1 DMG.(Only Polearm Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 2} + costs: Dict[ElementType, int] = {ElementType.SAME: 2} weapon_type: WeaponType = WeaponType.POLEARM @@ -145,7 +145,7 @@ class LithicSpearCard(WeaponCard): id: int = 311402 name: str = "Lithic Spear" text: str = """The character deals +1 DMG.When played: For each party member from Liyue, grant 1 Shield point to the character to which this is attached. (Max 3 points)(Only Polearm Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.POLEARM @@ -158,7 +158,7 @@ class SkywardSpineCard(WeaponCard): id: int = 311403 name: str = "Skyward Spine" text: str = """The character deals +1 DMG.Once per Round: This character's Normal Attacks deal +1 additional DMG.(Only Polearm Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.POLEARM @@ -171,7 +171,7 @@ class TravelersHandySwordCard(WeaponCard): id: int = 311501 name: str = "Traveler's Handy Sword" text: str = """The character deals +1 DMG.(Only Sword Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 2} + costs: Dict[ElementType, int] = {ElementType.SAME: 2} weapon_type: WeaponType = WeaponType.SWORD @@ -184,7 +184,7 @@ class SacrificialSwordCard(WeaponCard): id: int = 311502 name: str = "Sacrificial Sword" text: str = """The character deals +1 DMG.After the character uses an Elemental Skill: Create 1 Elemental Die of the same Elemental Type as this character. (Once per Round)(Only Sword Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.SWORD @@ -192,7 +192,7 @@ class SacrificialSword(WeaponEntity): name: str = "Sacrificial Sword" weapon_type: WeaponType = WeaponType.SWORD - def msg_handler(self, msg_queue: PriorityQueue[Message]): + def msg_handler(self, msg_queue: PriorityQueue): # Increase 1 dmg by default without any advanced effects top_msg = msg_queue.queue[0] updated = False @@ -237,7 +237,7 @@ class AquilaFavoniaCard(WeaponCard): id: int = 311503 name: str = "Aquila Favonia" text: str = """The character deals +1 DMG.After the opposing character uses a Skill: If the character with this attached is the active character, heal this character for 1 HP. (Max twice per Round)(Only Sword Characters can equip this. A character can equip a maximum of 1 Weapon)""" - costs: dict[ElementType, int] = {ElementType.SAME: 3} + costs: Dict[ElementType, int] = {ElementType.SAME: 3} weapon_type: WeaponType = WeaponType.SWORD diff --git a/gisim/classes/action.py b/gisim/classes/action.py index b4d156b..5ec429f 100644 --- a/gisim/classes/action.py +++ b/gisim/classes/action.py @@ -8,8 +8,8 @@ """ -from abc import ABC, abstractmethod - +from abc import ABC +from typing import List, Tuple from pydantic import BaseModel from gisim.classes.entity import Entity @@ -19,7 +19,7 @@ class Action(Entity, ABC): """Action includes cost information.""" - def _check_cards_index(self, cards_idx: list[int]): + def _check_cards_index(self, cards_idx: List[int]): assert type(cards_idx) == list for card in cards_idx: assert type(card) == int and card >= 0 @@ -32,22 +32,22 @@ def _check_dice_index(self, dice_idx): class ChangeCharacterAction(Action): position: CharPos - dice_idx: list[int] + dice_idx: List[int] class ChangeCardsAction(Action): - cards_idx: list[int] + cards_idx: List[int] class RollDiceAction(Action): - dice_idx: list[int] + dice_idx: List[int] class UseSkillAction(Action): user_position: CharPos skill_name: str - dice_idx: list[int] - skill_targets: list[tuple[PlayerID, CharPos]] + dice_idx: List[int] + skill_targets: List[Tuple[PlayerID, CharPos]] class DeclareEndAction(Action): @@ -56,8 +56,8 @@ class DeclareEndAction(Action): class UseCardAction(Action): card_idx: int - dice_idx: list[int] - card_target: list[tuple[PlayerID, EntityType, int]] + dice_idx: List[int] + card_target: List[Tuple[PlayerID, EntityType, int]] card_user_pos: CharPos diff --git a/gisim/classes/character.py b/gisim/classes/character.py index 023e1c4..58441ba 100644 --- a/gisim/classes/character.py +++ b/gisim/classes/character.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from collections import OrderedDict from queue import PriorityQueue -from typing import Optional, cast +from typing import Optional, List, cast from gisim.cards.characters import get_character_card from gisim.cards.characters.base import CharacterCard, CharacterSkill @@ -33,11 +33,11 @@ class CharacterEntity(Entity): character_card: CharacterCard id: int element_type: ElementType - nationalities: list[Nation] + nationalities: List[Nation] weapon_type: WeaponType - skills: list[CharacterSkill] + skills: List[CharacterSkill] skill_num: int - skill_names: list[str] + skill_names: List[str] health_point: int power: int max_power: int @@ -106,14 +106,14 @@ def get_skill( ), f"Skill type {skill_type} is not unique." return self.skills[skill_types.index(skill_type)] - def passive_skill_handler(self, msg_queue: PriorityQueue[Message]): + def passive_skill_handler(self, msg_queue: PriorityQueue): updated = False for skill in self.skills: if skill.type == SkillType.PASSIVE_SKILL: updated = skill.use_skill(msg_queue=msg_queue, parent=self) return updated - def msg_handler(self, msg_queue: PriorityQueue[Message]): + def msg_handler(self, msg_queue: PriorityQueue): """Will respond to `UseSkillMsg` etc.""" msg = msg_queue.queue[0] if self._uuid in msg.responded_entities: diff --git a/gisim/classes/entity.py b/gisim/classes/entity.py index d19378d..3ad9aee 100644 --- a/gisim/classes/entity.py +++ b/gisim/classes/entity.py @@ -20,6 +20,7 @@ def encode(self) -> dict: ... # @abstractmethod - def msg_handler(self, msg_queue: PriorityQueue["Message"]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: + # PriorityQueue["Message"] """Return value should be a boolean to indicate whether the message queue is updated""" ... diff --git a/gisim/classes/enums.py b/gisim/classes/enums.py index 6d45f35..28fabd6 100644 --- a/gisim/classes/enums.py +++ b/gisim/classes/enums.py @@ -61,7 +61,7 @@ class ElementType(IntEnum): """穿透伤害""" @staticmethod - def get_basic_elements() -> set["ElementType"]: + def get_basic_elements(): #-> set["ElementType"]: """七种基础元素""" return { ElementType.CRYO, diff --git a/gisim/classes/equipment.py b/gisim/classes/equipment.py index edac695..ebd63d4 100644 --- a/gisim/classes/equipment.py +++ b/gisim/classes/equipment.py @@ -36,7 +36,7 @@ class WeaponEntity(EquipmentEntity): equipment_type: EquipmentType = EquipmentType.WEAPON weapon_type: WeaponType - def msg_handler(self, msg_queue: PriorityQueue[Message]): + def msg_handler(self, msg_queue: PriorityQueue): # Increase 1 dmg by default without any advanced effects top_msg = msg_queue.queue[0] updated = False diff --git a/gisim/classes/message.py b/gisim/classes/message.py index 7db3b46..8946266 100644 --- a/gisim/classes/message.py +++ b/gisim/classes/message.py @@ -4,7 +4,7 @@ import itertools from abc import ABC, abstractmethod from ast import Param -from typing import Optional, ParamSpec +from typing import Optional, List, Tuple, Dict from uuid import UUID from pydantic import BaseModel, root_validator @@ -34,9 +34,9 @@ class Message(Entity, ABC): _msg_id: int = -1 sender_id: PlayerID priority: MsgPriority - respondent_zones: list[tuple[PlayerID, RegionType]] = [] + respondent_zones: List[Tuple[PlayerID, RegionType]] = [] """The message will travel all listed zones for respond. It will travel all zones by default as in the root validator""" - responded_entities: list[UUID] = [] + responded_entities: List[UUID] = [] """The UUID of all responded entities""" change_active_player: bool = False @@ -107,7 +107,7 @@ def init_respondent_zones(cls, values): class GenerateCharacterStatusMsg(Message): priority: MsgPriority = MsgPriority.IMMEDIATE_OPERATION - target: tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] status_name: str remaining_round: int remaining_usage: int @@ -140,7 +140,7 @@ def init_respondent_zones(cls, values): class GenerateEquipmentMsg(Message): "Usually generated from Cards" priority: MsgPriority = MsgPriority.IMMEDIATE_OPERATION - target: tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] equipment_name: str equipment_type: EquipmentType @@ -158,8 +158,8 @@ class ChangeCardsMsg(Message): Include both discard cards and drawing cards.""" priority: MsgPriority = MsgPriority.IMMEDIATE_OPERATION - discard_cards_idx: list[int] - draw_cards_type: list[CardType] + discard_cards_idx: List[int] + draw_cards_type: List[CardType] """If no type specified, use `CardType.ANY`""" @root_validator @@ -174,11 +174,11 @@ def init_respondent_zones(cls, values): # Changing Dice related class ChangeDiceMsg(Message): priority: MsgPriority = MsgPriority.IMMEDIATE_OPERATION - remove_dice_idx: list[int] + remove_dice_idx: List[int] """Index of dice to be removed""" - new_target_element: list[ElementType] + new_target_element: List[ElementType] """Number of elements should be the same as number of dice to be generated.\n - Target element: + Target element: ElementType.BASIC represents a random dice among 7 element types (e.g. dice generated from 元素质变仪)\n ElementType.ANY represents a random dice among 8 kinds of dice (including the OMNI element)""" consume_reroll_chance: bool = False @@ -199,10 +199,10 @@ def init_respondent_zones(cls, values): class PayCostMsg(Message, ABC): priority: MsgPriority = MsgPriority.PAY_COST simulate: bool = False - required_cost: dict[ElementType, int] = {} + required_cost: Dict[ElementType, int] = {} """Required cost of this action. Will be affected by equipment/character status/ combat status/support""" - paid_dice_idx: list[int] = [] + paid_dice_idx: List[int] = [] """What the user actual paid.""" @@ -211,7 +211,7 @@ class PayCardCostMsg(PayCostMsg): """Will calculate and remove the cost before processing `UseCardMsg`""" card_idx: int - card_user_pos: tuple[PlayerID, CharPos] + card_user_pos: Tuple[PlayerID, CharPos] """The user of the card. e.g. talent card""" @root_validator @@ -233,7 +233,7 @@ class PaySkillCostMsg(PayCostMsg): priority: MsgPriority = MsgPriority.PAY_COST user_pos: CharPos skill_name: str - skill_targets: list[tuple[PlayerID, CharPos]] + skill_targets: List[Tuple[PlayerID, CharPos]] """Will not trigger the reduce cost status in the simulate mode, for validity check""" @root_validator @@ -273,8 +273,8 @@ def init_respondent_zones(cls, values): class ChangeCharacterMsg(Message): priority: MsgPriority = MsgPriority.PLAYER_ACTION - current_active: tuple[PlayerID, CharPos] - target: tuple[PlayerID, CharPos] + current_active: Tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] @root_validator def init_respondent_zones(cls, values): @@ -290,16 +290,16 @@ def init_respondent_zones(cls, values): class UseCardMsg(Message): priority: MsgPriority = MsgPriority.PLAYER_ACTION card_idx: int - card_target: list[tuple[PlayerID, EntityType, int]] + card_target: List[Tuple[PlayerID, EntityType, int]] """The last element in the tuple is the index of the target starting from 0 (e.g. character, equipment, summon)""" - card_user_pos: tuple[PlayerID, CharPos] + card_user_pos: Tuple[PlayerID, CharPos] class UseSkillMsg(Message): priority: MsgPriority = MsgPriority.PLAYER_ACTION user_pos: CharPos skill_name: str - skill_targets: list[tuple[PlayerID, CharPos]] + skill_targets: List[Tuple[PlayerID, CharPos]] """In case one character can assign multiple targets in the future""" @@ -307,7 +307,7 @@ class AfterUsingSkillMsg(Message): priority: MsgPriority = MsgPriority.ACTION_DONE user_pos: CharPos skill_name: str - skill_targets: list[tuple[PlayerID, CharPos]] + skill_targets: List[Tuple[PlayerID, CharPos]] elemental_reaction_triggered: ElementalReactionType change_active_player: bool = True @@ -316,21 +316,21 @@ class AfterUsingCardMsg(Message): priority: MsgPriority = MsgPriority.ACTION_DONE card_name: str card_user_pos: CharPos - card_target: list[tuple[PlayerID, EntityType, int]] + card_target: List[Tuple[PlayerID, EntityType, int]] card_type: CardType # For 便携营养袋 card_idx: int class AfterChangingCharacterMsg(Message): priority: MsgPriority = MsgPriority.ACTION_DONE - target: tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] change_active_player: bool = True class DeclareEndMsg(Message): priority: MsgPriority = MsgPriority.ACTION_DONE change_active_player: bool = True - respondent_zones: list[tuple[PlayerID, RegionType]] = [] + respondent_zones: List[Tuple[PlayerID, RegionType]] = [] # Changing hp/power/ related @@ -341,10 +341,10 @@ class DealDamageMsg(Message): """Send from Character(Skill)/Character Status/Summon/Combat Status""" priority: MsgPriority = MsgPriority.GENERAL_EFFECT - attacker: tuple[PlayerID, CharPos] + attacker: Tuple[PlayerID, CharPos] """If the damage is generated by summon, CharPos should be set to CharPos.NONE""" attack_type: AttackType - targets: list[tuple[PlayerID, CharPos, ElementType, int]] + targets: List[Tuple[PlayerID, CharPos, ElementType, int]] elemental_reaction_triggered: ElementalReactionType = ElementalReactionType.NONE """Will be modified if elemental reaction is triggered""" all_buffs_included = False @@ -355,26 +355,26 @@ class AttachElementMsg(Message): """Send from Character/Summon who is being attacked and all other effects are already calculated""" priority: MsgPriority = MsgPriority.GENERAL_EFFECT - targets: list[tuple[PlayerID, CharPos]] - element_types: list[ElementType] + targets: List[Tuple[PlayerID, CharPos]] + element_types: List[ElementType] class HealHpMsg(Message): """Send from Card/Character(Skill)/Equipment/Support/Summon/...""" priority: MsgPriority = MsgPriority.GENERAL_EFFECT - targets: list[tuple[PlayerID, CharPos, int]] + targets: List[Tuple[PlayerID, CharPos, int]] class ChangePowerMsg(Message): priority: MsgPriority = MsgPriority.GENERAL_EFFECT - change_targets: list[tuple[PlayerID, CharPos]] - change_vals: list[int] + change_targets: List[Tuple[PlayerID, CharPos]] + change_vals: List[int] @root_validator def init_respondent_zones(cls, values): - change_vals: list[int] = values["change_vals"] - change_targets: list[tuple[PlayerID, CharPos]] = values["change_targets"] + change_vals: List[int] = values["change_vals"] + change_targets: List[Tuple[PlayerID, CharPos]] = values["change_targets"] if not values["respondent_zones"]: values["respondent_zones"] = [ @@ -394,19 +394,19 @@ class ElementalReactionTriggeredMsg(Message): priority: MsgPriority = MsgPriority.ELEMENTAL_REACTION_EFFECT elemental_reaction_type: ElementalReactionType - target: tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] class CharacterDiedMsg(Message): """Send from Character(under attack)""" priority: MsgPriority = MsgPriority.HP_CHANGED - target: tuple[PlayerID, CharPos] + target: Tuple[PlayerID, CharPos] @root_validator def init_respondent_zones(cls, values): if not values["respondent_zones"]: - target: tuple[PlayerID, CharPos] = values["target"] + target: Tuple[PlayerID, CharPos] = values["target"] values["respondent_zones"] = [ (target[0], RegionType(target[1].value)), # 本大爷 (~target[0], RegionType.CHARACTER_ACTIVE), # 赌徒 diff --git a/gisim/classes/status.py b/gisim/classes/status.py index b3d1f85..8ba8669 100644 --- a/gisim/classes/status.py +++ b/gisim/classes/status.py @@ -23,7 +23,7 @@ class CharacterStatusEntity(Entity, ABC): remaining_usage: int value: int - def msg_handler(self, msg_queue: PriorityQueue["Message"]): + def msg_handler(self, msg_queue: PriorityQueue): ... def encode(self): @@ -54,7 +54,7 @@ class ElementalInfusion(CharacterStatusEntity): active: bool = True remaining_usage: int = INF_INT - def msg_handler(self, msg_queue: PriorityQueue["Message"]): + def msg_handler(self, msg_queue: PriorityQueue): top_msg = msg_queue.queue[0] if self._uuid in top_msg.responded_entities: return False diff --git a/gisim/classes/summon.py b/gisim/classes/summon.py index 0557228..9c85fc0 100644 --- a/gisim/classes/summon.py +++ b/gisim/classes/summon.py @@ -28,7 +28,7 @@ def encode(self): return self.dict(exclude={"_uuid", "_logger"}) @abstractmethod - def msg_handler(self, msg_queue: PriorityQueue["Message"]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: ... diff --git a/gisim/env.py b/gisim/env.py index 591a4e6..6ae8ff8 100644 --- a/gisim/env.py +++ b/gisim/env.py @@ -8,7 +8,10 @@ import os logger = logging.getLogger(__name__) -DISPLAY_LANGUAGE = locale.getdefaultlocale(("LANG",))[0].replace("_", "-").lower() +try: + DISPLAY_LANGUAGE = locale.getdefaultlocale(("LANG",))[0].replace("_", "-").lower() +except AttributeError: + DISPLAY_LANGUAGE = "en-us" path = os.path.join(os.path.dirname(__file__), "resources", "cards_20221205_i18n.json") diff --git a/gisim/game.py b/gisim/game.py index cd328be..61fd892 100644 --- a/gisim/game.py +++ b/gisim/game.py @@ -4,7 +4,7 @@ from collections import OrderedDict from queue import PriorityQueue from random import Random -from typing import Optional, cast +from typing import Optional, List, Dict, cast from .classes.action import * from .classes.action import Action @@ -249,8 +249,8 @@ def process_msg_queue(self): return False - def get_zones(self, zones: list[tuple[PlayerID, RegionType]]): - zone_pointers: list[BaseZone] = [] + def get_zones(self, zones: List[Tuple[PlayerID, RegionType]]): + zone_pointers: List[BaseZone] = [] for player_id, zone_type in zones: zone_pointers += self.player_area[player_id].get_zones(zone_type) # Remove empty zones (e.g. no active character) diff --git a/gisim/player_area.py b/gisim/player_area.py index bd97d29..70f7826 100644 --- a/gisim/player_area.py +++ b/gisim/player_area.py @@ -7,7 +7,7 @@ from logging import getLogger from queue import PriorityQueue from random import Random -from typing import TYPE_CHECKING, Generic, Optional, TypeVar, cast +from typing import TYPE_CHECKING, Generic, Optional, TypeVar, List, cast from uuid import uuid4 from gisim.cards import get_card, get_equipment, get_summon_entity @@ -67,7 +67,7 @@ def __init__( self.card_zone = CardZone(self, random_state, deck["cards"]) self.card_zone.shuffle() self.dice_zone = DiceZone(self, random_state) - self.character_zones: list["CharacterZone"] = [ + self.character_zones: List["CharacterZone"] = [ CharacterZone(self, name, CharPos(i)) for i, name in enumerate(deck["characters"]) ] @@ -118,7 +118,7 @@ def encode(self, viewer_id: PlayerID): } ) - def get_zones(self, zone_type: RegionType) -> list[BaseZone]: + def get_zones(self, zone_type: RegionType) -> List[BaseZone]: assert isinstance(zone_type, RegionType), "zone_type should be RegionType" if zone_type == RegionType.CHARACTER_ACTIVE: @@ -169,20 +169,20 @@ def get_zones(self, zone_type: RegionType) -> list[BaseZone]: else: raise ValueError("Current `zone_type` is not in the player area.") - return cast(list[BaseZone], zones) + return cast(List[BaseZone], zones) - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: ... class CardZone(BaseZone): - def __init__(self, parent: "PlayerArea", random_state: Random, cards: list[str]): + def __init__(self, parent: "PlayerArea", random_state: Random, cards: List[str]): super().__init__() self._parent = parent self._random_state = random_state self.deck_original_cards = cards self.deck_cards = cards - self.hand_cards: list[Card] = [] + self.hand_cards: List[Card] = [] def shuffle(self): self._random_state.shuffle(self.deck_cards) @@ -191,7 +191,7 @@ def shuffle(self): def card_names(self): return [card.name for card in self.hand_cards] - def draw_cards_from_deck(self, cards_type: list[CardType]): + def draw_cards_from_deck(self, cards_type: List[CardType]): drawn_cards = [] for card_type in cards_type: if len(self.deck_cards) == 0: @@ -210,8 +210,8 @@ def draw_cards_from_deck(self, cards_type: list[CardType]): for card_name in drawn_cards: self.hand_cards.append(get_card(card_name)) - def remove_hand_cards(self, cards_idx: list[int]): - removed_names: list[str] = [] + def remove_hand_cards(self, cards_idx: List[int]): + removed_names: List[str] = [] for i in sorted(cards_idx, reverse=True): removed_names.append(self.hand_cards[i].name) del self.hand_cards[i] @@ -230,7 +230,7 @@ def encode(self, viewer_id): } ) - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: top_msg = msg_queue.queue[0] if self._uuid in top_msg.responded_entities: return False @@ -273,7 +273,7 @@ class SummonZone(BaseZone): def __init__(self, parent: "PlayerArea"): super().__init__() self._parent = parent - self.summons: list["Summon"] = [] + self.summons: List["Summon"] = [] self.summon_limit: int = 4 """4 Summons at most in each player's summon zone""" @@ -289,7 +289,7 @@ def remove_summon(self, idx: int): # Reset positions summon.position = idx - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: msg = msg_queue.queue[0] if self._uuid in msg.responded_entities: return False @@ -325,12 +325,12 @@ class SupportZone(BaseZone): def __init__(self, parent: "PlayerArea"): super().__init__() self._parent = parent - self.supports: list["Support"] = [] + self.supports: List["Support"] = [] def encode(self): return [support.encode() for support in self.supports] - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: ... @@ -339,9 +339,9 @@ def __init__(self, parent: "PlayerArea", random_state: Random): super().__init__() self._parent = parent self._random_state = random_state - self._dice: list[ElementType] = [] + self._dice: List[ElementType] = [] self.init_dice_num = 8 - self.fixed_dice: list[ElementType] = [] + self.fixed_dice: List[ElementType] = [] self.max_reroll_chance = 1 def init_dice(self): @@ -350,13 +350,13 @@ def init_dice(self): self.remaining_reroll_chance = self.max_reroll_chance # TODO: fixed dice from artifact/support - def reroll_dice(self, dice_idx: list[int]): + def reroll_dice(self, dice_idx: List[int]): self.remove_dice(dice_idx) self.add_dice([ElementType.ANY for _ in dice_idx]) self.remaining_reroll_chance -= 1 return self.remaining_reroll_chance - def add_dice(self, element_types: list[ElementType]): + def add_dice(self, element_types: List[ElementType]): for element_type in element_types: if element_type == ElementType.ANY: self._dice.append( @@ -369,7 +369,7 @@ def add_dice(self, element_types: list[ElementType]): else: self._dice.append(element_type) - def remove_dice(self, dice_idx: list[int]): + def remove_dice(self, dice_idx: List[int]): for i in sorted(dice_idx, reverse=True): del self._dice[i] @@ -381,7 +381,7 @@ def encode(self, viewer_id): else None, } - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: msg = msg_queue.queue[0] if self._uuid in msg.responded_entities: return False @@ -415,12 +415,12 @@ class CombatStatusZone(BaseZone): def __init__(self, parent: "PlayerArea"): super().__init__() self._parent = parent - self.status_entities: list["CombatStatusEntity"] = [] + self.status_entities: List["CombatStatusEntity"] = [] def encode(self): return [status_entity.encode() for status_entity in self.status_entities] - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: ... @@ -435,7 +435,7 @@ def __init__(self, parent: "PlayerArea", name: str, char_pos: CharPos): self.talent: Optional[TalentEntity] = None self.weapon: Optional[WeaponEntity] = None self.artifact: Optional[ArtifactEntity] = None - self.status: list[CharacterStatusEntity] = [] + self.status: List[CharacterStatusEntity] = [] def encode(self): return { @@ -446,7 +446,7 @@ def encode(self): "status": [status.encode() for status in self.status], } - def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: + def msg_handler(self, msg_queue: PriorityQueue) -> bool: if not self.character.alive and not self.character.active: # Return immediately if the character has already died. return False @@ -496,7 +496,7 @@ def msg_handler(self, msg_queue: PriorityQueue[Message]) -> bool: self.artifact, *self.status, ] - entities = cast(list[Entity], entities) + entities = cast(List[Entity], entities) for entity in entities: if entity is None: continue @@ -525,17 +525,17 @@ def __init__(self, player_info_dict: OrderedDict): self.player_id: PlayerID = player_info_dict["player_id"] self.declared_end: bool = player_info_dict["declared_end"] self.hand_length: int = player_info_dict["card_zone"]["hand_length"] - self.hand_cards: list[str] = player_info_dict["card_zone"]["hand_cards"] + self.hand_cards: List[str] = player_info_dict["card_zone"]["hand_cards"] self.deck_length: int = player_info_dict["card_zone"]["deck_length"] - self.deck: list[str] = player_info_dict["card_zone"]["deck_cards"] + self.deck: List[str] = player_info_dict["card_zone"]["deck_cards"] self.dice_zone_len: int = player_info_dict["dice_zone"]["length"] - self.dice_zone: list[ElementType] = player_info_dict["dice_zone"]["items"] - self.summon_zone: list[dict] = player_info_dict["summon_zone"] - self.support_zone: list[Support] = player_info_dict["support_zone"] - self.combat_status_zone: list[CombatStatusEntity] = player_info_dict[ + self.dice_zone: List[ElementType] = player_info_dict["dice_zone"]["items"] + self.summon_zone: List[dict] = player_info_dict["summon_zone"] + self.support_zone: List[Support] = player_info_dict["support_zone"] + self.combat_status_zone: List[CombatStatusEntity] = player_info_dict[ "combat_status_zone" ] - self.characters: list[CharacterInfo] = [ + self.characters: List[CharacterInfo] = [ CharacterInfo(player_info_dict["character_zones"][k]) for k in range(3) ] self.active_character_position: CharPos = player_info_dict[ diff --git a/pyproject.toml b/pyproject.toml index 818fe97..f3d2899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ readme = "README.md" packages = [{ include = "gisim" }] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.8" pydantic = "^1.10.2" devtools = "^0.10.0"