Files
ages/agame/trigger.py

347 lines
9.7 KiB
Python

import abc
import re
from typing import Match, Pattern, TYPE_CHECKING
from agame.util import search_item_name, trigger_help_builder
if TYPE_CHECKING:
from agame.game import Game
# Triggers:
# get/take/grab/pick up [a[n]/the] x
# use [a[n]/the] x [with/on [a[n]/the] y]
# put [a[n]/the] x on/in [a[n]/the] y
# look [[at] [a[n]/the] x]
# read [a[n]/the] x
# open x
# close x
# go/(go to)/leave/exit x
# give [a[n]/the] x to [a[n]/the] y - TODO
# push [a[n]/the] x - TODO
# pull [a[n]/the] x - TODO
# (put down)/drop [a[n]/the] x
__all__ = (
"Trigger",
"HelpTrigger",
"GetTrigger",
"UseTrigger",
"PutTrigger",
"LookTrigger",
"ReadTrigger",
"OpenTrigger",
"CloseTrigger",
"GoTrigger",
"HELP",
"GET",
"USE",
"PUT",
"LOOK",
"READ",
"OPEN",
"CLOSE",
"GO",
)
HELP = "help"
GET = "get"
USE = "use"
PUT = "put"
LOOK = "look"
READ = "read"
OPEN = "open"
CLOSE = "close"
GO = "go"
class Trigger(metaclass=abc.ABCMeta):
@staticmethod
@abc.abstractmethod
def pattern() -> Pattern:
pass
@staticmethod
@abc.abstractmethod
def help() -> str:
pass
@abc.abstractmethod
def trigger(self, game: "Game", match: Match):
pass
class HelpTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile("help", re.IGNORECASE)
@staticmethod
def help() -> str:
return trigger_help_builder("help")
def trigger(self, game: "Game", _match: Match):
game.say("In this game, short commands are usually the best.")
game.say()
game.say("These are the available actions that are recognized:")
game.say()
for trigger in game.database.triggers:
game.say(trigger.help())
game.say("To quit the game, type ((quit)).")
class GetTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>get|take|grab|pick[ ]*up)
(([ ]+(an?|the))?[ ]+(?P<item>.+))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder(
("get", "take", "grab", "pick up", "pickup"), "item"
)
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
otrigger = match["trigger"].lower().capitalize()
game.say(f"{otrigger} what?")
return
item = game.room.search_item_name(item_name)
if item and item.revealed and GET in item.triggers:
actions = item.triggers[GET]
# if there are any actions, do them. else do the default action
game.do_actions(actions)
else:
game.say("Can't get that.")
class UseTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
# TODO(low) - wouldn't it be cool to specify "use" actions?
# e.g. you have a gun item and you want to be allowed to use "shoot" in order to
# use the gun.
return re.compile(
r"""
(?P<trigger>use)
(([ ]+(an?|the))?[ ]+(?P<item>.+?)
([ ]+(with|on)([ ]+(an?|the))?[ ]+(?P<target>.+))?)?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("use", ("item", "[with/on target]"))
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Use what?")
return
target_name = match["target"]
# Get the item from inventory or room
item = game.room.search_item_name(item_name) or search_item_name(
game.inventory.values(), item_name
)
if not item or not item.revealed:
game.say(f"I'm not sure what you mean by {item_name}.")
return
target = None
if target_name:
# Get the target from inventory or room
target = game.room.search_item_name(target_name) or search_item_name(
game.inventory.values(), target_name
)
if not target or not target.revealed:
game.say(f"I'm not sure what you mean by {target_name}.")
# Check if the target can be used on something
elif target.id in item.use_actions:
game.do_actions(item.use_actions[target.id])
else:
game.say("I'm not sure how to do that.")
elif USE in item.triggers:
# Check if the item can be used by itself
game.do_actions(item.triggers[USE])
elif item.use_actions:
# This item can be used with *something*, but we don't know what.
game.say(f"Use (({item_name})) with what?")
else:
# This can't be used.
game.say("I can't really use that.")
class PutTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>put)
([ ]+((an?|the)[ ]+)?(?P<item>.+?)
((on|in)[ ]+)?((an?|the)[ ]+)?[ ]+(?P<target>.+))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("put", ("item", "[on/in target]"))
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
class LookTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>look)
([ ]+(at[ ]+)?((an?|the)[ ]+)?(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("look", args=["[at]", "item"])
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.print_room()
return
item = game.room.search_item_name(item_name) or search_item_name(
game.inventory.values(), item_name
)
if item and LOOK in item.triggers and item.revealed:
actions = item.triggers[LOOK]
game.do_actions(actions)
else:
game.say("Can't see that.")
class ReadTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>read)
([ ]+((an?|the)[ ]+)?(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("read", "item")
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.print_room()
return
item = game.room.search_item_name(item_name) or search_item_name(
game.inventory.values(), item_name
)
if item and READ in item.triggers and item.revealed:
actions = item.triggers[READ]
game.do_actions(actions)
else:
game.say("Can't read that.")
class OpenTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>open)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("open", "item")
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Open what?")
return
item = game.room.search_item_name(item_name)
if item and OPEN in item.triggers and item.revealed:
actions = item.triggers[OPEN]
game.do_actions(actions)
else:
game.say("Can't open that.")
class CloseTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>close)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder("close", "item")
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
game.say("Close what?")
return
item = game.room.search_item_name(item_name)
if item and CLOSE in item.triggers and item.revealed:
actions = item.triggers[CLOSE]
game.do_actions(actions)
else:
game.say("Can't close that.")
class GoTrigger(Trigger):
@staticmethod
def pattern() -> Pattern:
return re.compile(
r"""
(?P<trigger>go([ ]*to)?|leave|exit)
(((an?|the)[ ]+)?[ ]+(?P<item>.+?))?
""",
re.IGNORECASE | re.VERBOSE,
)
@staticmethod
def help() -> str:
return trigger_help_builder(["go", "go to", "goto", "leave", "exit"], "target")
def trigger(self, game: "Game", match: Match):
item_name = match["item"]
if not item_name:
otrigger = match["trigger"].lower().capitalize()
game.say(f"{otrigger} where?")
if otrigger.lower() == "exit":
# I really really really hate this hack, but regex can only go so far
# perhaps a real parser is in order :I
game.say("{{Perhaps you meant ((quit))}}{{ to leave the game?}}")
return
item = game.room.search_item_name(item_name)
if item and GO in item.triggers and item.revealed:
actions = item.triggers[GO]
game.do_actions(actions)
else:
game.say("Can't go there.")