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", "PrintRoomAction", "GetAction", "CheckInvItemsAction", "CheckRoomItemsAction", "Compare", "Var", "CheckVarAction", "SetVarAction", "RevealItemAction", "UnrevealItemAction", ) 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. """ # The room ID to teleport to. room_id: str # Whether to "announce" the teleportation; i.e., print out the room # description after teleporting to it. announce: bool = True def act(self, game: "Game"): assert ( self.room_id in game.database.rooms ), f"could not find room with id {self.room_id}" game.room = game.database.rooms[self.room_id] if self.announce: game.say("", "." * 70, "") game.print_room() class PrintRoomAction(Action): """ Prints the current room. """ def act(self, game: "Game"): game.say("", "." * 70, "") game.print_room() @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 __init__( self, item_ids: Union[str, Sequence[str]], yes: Union[Action, Sequence[Action]] = [], no: Union[Action, Sequence[Action]] = [], ): self.item_ids = item_ids # Put the "yes" action into an array if necessary if isinstance(yes, Action): self.yes = [yes] else: self.yes = yes # Put the "no" action into an array if necessary if isinstance(no, Action): self.no = [no] else: self.no = no 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: Union[str, Sequence[str]] yes: Sequence[Action] no: Sequence[Action] def __init__( self, item_ids: Union[str, Sequence[str]], yes: Union[Action, Sequence[Action]] = [], no: Union[Action, Sequence[Action]] = [], ): self.item_ids = item_ids # Put the "yes" action into an array if necessary if isinstance(yes, Action): self.yes = [yes] else: self.yes = yes # Put the "no" action into an array if necessary if isinstance(no, Action): self.no = [no] else: self.no = no 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 __init__( self, var_id: str, compare: Compare = Compare.EQUALS, against: Any = True, yes: Union[Action, Sequence[Action]] = [], no: Union[Action, Sequence[Action]] = [], ): self.var_id = var_id self.compare = compare self.against = against # Put the "yes" action into an array if necessary if isinstance(yes, Action): self.yes = [yes] else: self.yes = yes # Put the "no" action into an array if necessary if isinstance(no, Action): self.no = [no] else: self.no = no def act(self, game: "Game"): assert self.var_id in game.vars, f"variable '{self.var_id}' does not exist" val = game.vars.get(self.var_id, None) compare_val = None if isinstance(self.against, Var): assert ( self.against.id in game.vars ), f"variable '{self.against.id}' does not exist" 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"): assert self.var_id in game.vars, f"variable '{self.var_id}' does not exist" value = ( game.vars.get(self.value.id) if isinstance(self.value, Var) else self.value ) game.vars[self.var_id] = value @dataclasses.dataclass class RevealItemAction(Action): """ Reveal an item in the current room. """ item_id: str def act(self, game: "Game"): assert ( self.item_id in game.room.items ), f"item id {self.item_id} does not exist in the current room {game.room.id}" game.room.items[self.item_id].revealed = True @dataclasses.dataclass class UnrevealItemAction(Action): """ Unreveal an item in the current room. """ item_id: str def act(self, game: "Game"): assert ( self.item_id in game.room.items ), f"item id {self.item_id} does not exist in the current room {game.room.id}" game.room.items[self.item_id].revealed = False