import dataclasses import time from typing import Any, Optional, Sequence, Union, TYPE_CHECKING import enum if TYPE_CHECKING: from agame.game import Game __all__ = ( "Action", "SleepAction", "PrintAction", "TeleportAction", "GetAction", "CheckInvItemsAction", "CheckRoomItemsAction", "Compare", "Var", "CheckVarAction", "SetVarAction", ) class Action: """ An action that the game engine can take. This is the base class. It can be instantiated as a "dummy" action if needed. """ def act(self, game: "Game"): """ Complete this action. """ @dataclasses.dataclass class SleepAction(Action): """ An action that delays the amount of time, in seconds. Decimal values are allowed. """ secs: float def act(self, game: "Game"): time.sleep(self.secs) @dataclasses.dataclass class PrintAction(Action): """ Prints a message to the screen. """ lines: Sequence[str] def __init__(self, *lines: str): self.lines = lines def act(self, game: "Game"): if not self.lines: return line = self.lines[0] game.say(line) for line in self.lines[1:]: game.say() game.say(line) @dataclasses.dataclass class TeleportAction(Action): """ Moves the player to another room. """ room_id: str def act(self, game: "Game"): # TODO raise NotImplementedError("TODO - implement teleport action") @dataclasses.dataclass class GetAction(Action): """ Removes an item from the current room and puts it in the player's inventory. """ item_id: str pickup_text: Optional[str] = None # def __init__(self, item_id: str, pickup_text: ) def act(self, game: "Game"): # Find the first instance of the item in the room and remove it item = game.room.remove(self.item_id) assert ( item is not None ), f"attempted to remove an item (id: {self.item_id}) that does not exist in the current room; this is a game logic error/bug" if item.id in game.inventory: # Item in inventory already? Update count game.inventory[item.id].count += item.count else: # Otherwise just add it game.inventory[item.id] = item # Print text if self.pickup_text is None: game.say(f"You pick up (({item.name})).") else: game.say(self.pickup_text) @dataclasses.dataclass class CheckInvItemsAction(Action): """ Checks if all supplied items are present in the inventory, and executes the appropriate action sequence. """ item_ids: Union[str, Sequence[str]] yes: Sequence[Action] no: Sequence[Action] def act(self, game: "Game"): # If the item_ids are just a string, use that as a list. items = [self.item_ids] if isinstance(self.item_ids, str) else self.item_ids for item_id in items: if item_id not in game.inventory: game.do_actions(self.no) return game.do_actions(self.yes) @dataclasses.dataclass class CheckRoomItemsAction(Action): """ Checks if all supplied items are present in the current room, and executes the appropriate action sequence. """ item_ids: str yes: Sequence[Action] no: Sequence[Action] def act(self, game: "Game"): items = [self.item_ids] if isinstance(self.item_ids, str) else self.item_ids for item_id in items: if item_id not in game.room.items: game.do_actions(self.no) return game.do_actions(self.yes) class Compare(enum.Enum): """ A comparison for a value. """ # Does what it says on the tin. # # This is type-sensitive and literally just does `==` in Python. Seriously. EQUALS = enum.auto() # Also does what it says on the tin. # # This is type-sensitive and literally just does `!=` in Python. Seriously. NOT_EQUALS = enum.auto() # Checks if a value is less than to another value. # # This uses the `cmp` to check the values. LESS_THAN = enum.auto() # Checks if a value is less than or equal to another value. # # This uses the `cmp` to check the values. LESS_THAN_EQUALS = enum.auto() # Checks if a value is greater than to another value. # # This uses the `cmp` to check the values. GREATER_THAN = enum.auto() # Checks if a value is greater than or equal to another value. # # This uses the `cmp` to check the values. GREATER_THAN_EQUALS = enum.auto() # Checks if the string-ified version of the value matches the other value, # as a regex. MATCHES = enum.auto() @dataclasses.dataclass class Var: id: str @dataclasses.dataclass class CheckVarAction(Action): """ Check a variable's value using some kind of comparison. If you want to check one variable against another, use the `action.Var` type. """ # The variable to check the value of. var_id: str # The comparison operation to use. See `Compare` for comparisons. compare: Compare # The constant value to check against. against: Any # The action to execute when this is true. yes: Sequence[Action] # The action to execute when this is false. no: Sequence[Action] def act(self, game: "Game"): val = game.vars.get(self.var_id, None) compare_val = None if isinstance(self.against, Var): compare_val = game.vars.get(self.against.id, None) else: compare_val = self.against result = False if self.compare == Compare.EQUALS: result = val == compare_val elif self.compare == Compare.NOT_EQUALS: result = val != compare_val elif self.compare == Compare.LESS_THAN: if isinstance(val, str) or isinstance(compare_val, str): result = str(val) < str(compare_val) elif (isinstance(val, int) or isinstance(val, float)) and ( isinstance(compare_val, int) or isinstance(compare_val, float) ): result = val < compare_val elif self.compare == Compare.LESS_THAN_EQUALS: if isinstance(val, str) or isinstance(compare_val, str): result = str(val) <= str(compare_val) elif (isinstance(val, int) or isinstance(val, float)) and ( isinstance(compare_val, int) or isinstance(compare_val, float) ): result = val <= compare_val elif self.compare == Compare.GREATER_THAN: if isinstance(val, str) or isinstance(compare_val, str): result = str(val) > str(compare_val) elif (isinstance(val, int) or isinstance(val, float)) and ( isinstance(compare_val, int) or isinstance(compare_val, float) ): result = val > compare_val elif self.compare == Compare.GREATER_THAN_EQUALS: if isinstance(val, str) or isinstance(compare_val, str): result = str(val) >= str(compare_val) elif (isinstance(val, int) or isinstance(val, float)) and ( isinstance(compare_val, int) or isinstance(compare_val, float) ): result = val >= compare_val elif self.compare == Compare.MATCHES: pass else: assert False, f"{self.compare} isn't a Compare value, ya doink" # Check result, do action if result: game.do_actions(self.yes) else: game.do_actions(self.no) @dataclasses.dataclass class SetVarAction(Action): """ Set a variable to a specific value. """ var_id: str value: Any def act(self, game: "Game"): value = ( game.vars.get(self.value.id) if isinstance(self.value, Var) else self.value ) game.vars[self.var_id] = value