Files
ages/agame/action.py

432 lines
12 KiB
Python
Raw Normal View History

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",
"PrintRoomAction",
"PlayerInputAction",
"TeleportAction",
"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 PrintRoomAction(Action):
"""
Prints the current room.
"""
def act(self, game: "Game"):
game.say("", "." * 70, "")
game.print_room()
@dataclasses.dataclass
class PlayerInputAction(Action):
"""
Waits for a user to press a key, or press enter, or whatever.
"""
prompt: str = "Press enter to continue..."
store_var_id: Optional[str] = None
def act(self, game: "Game"):
line = input(self.prompt)
if self.store_var_id:
assert (
self.store_var_id in game.vars
), f"could not find var with id {self.store_var_id}"
game.vars[self.store_var_id] = 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.teleport(game.database.rooms[self.room_id])
if self.announce:
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