set up a proper dungeon master
This commit is contained in:
parent
469ae79a9c
commit
5117db7150
|
@ -1,4 +1,4 @@
|
||||||
from functools import partial
|
from functools import partial, wraps
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from random import random
|
from random import random
|
||||||
from typing import Callable, Dict, List, Optional
|
from typing import Callable, Dict, List, Optional
|
||||||
|
@ -45,7 +45,7 @@ class LogicTable:
|
||||||
|
|
||||||
|
|
||||||
LogicTrigger = Callable[[Room | Actor | Item, Attributes], Attributes]
|
LogicTrigger = Callable[[Room | Actor | Item, Attributes], Attributes]
|
||||||
TriggerTable = Dict[LogicRule, List[LogicTrigger]]
|
TriggerTable = Dict[str, LogicTrigger]
|
||||||
|
|
||||||
|
|
||||||
def update_attributes(
|
def update_attributes(
|
||||||
|
@ -99,9 +99,10 @@ def update_attributes(
|
||||||
attributes.update(rule.set)
|
attributes.update(rule.set)
|
||||||
logger.info("logic set state: %s", rule.set)
|
logger.info("logic set state: %s", rule.set)
|
||||||
|
|
||||||
if rule in triggers:
|
if rule.trigger:
|
||||||
for trigger in triggers[rule]:
|
for trigger in rule.trigger:
|
||||||
attributes = trigger(entity, attributes)
|
if trigger in triggers:
|
||||||
|
attributes = triggers[trigger](entity, attributes)
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ def format_logic(attributes: Attributes, rules: LogicTable, self=True) -> str:
|
||||||
logger.debug("label has no relevant description: %s", label)
|
logger.debug("label has no relevant description: %s", label)
|
||||||
|
|
||||||
if len(labels) > 0:
|
if len(labels) > 0:
|
||||||
logger.info("adding labels: %s", labels)
|
logger.info("adding attribute labels: %s", labels)
|
||||||
|
|
||||||
return " ".join(labels)
|
return " ".join(labels)
|
||||||
|
|
||||||
|
@ -152,14 +153,17 @@ def init_from_file(filename: str):
|
||||||
logger.info("loading logic from file: %s", filename)
|
logger.info("loading logic from file: %s", filename)
|
||||||
with open(filename) as file:
|
with open(filename) as file:
|
||||||
logic_rules = LogicTable(**load(file, Loader=Loader))
|
logic_rules = LogicTable(**load(file, Loader=Loader))
|
||||||
logic_triggers = {
|
logic_triggers = {}
|
||||||
rule: [get_plugin_function(trigger) for trigger in rule.trigger]
|
|
||||||
for rule in logic_rules.rules
|
for rule in logic_rules.rules:
|
||||||
if rule.trigger
|
if rule.trigger:
|
||||||
}
|
for trigger in rule.trigger:
|
||||||
|
logic_triggers[trigger] = get_plugin_function(trigger)
|
||||||
|
|
||||||
logger.info("initialized logic system")
|
logger.info("initialized logic system")
|
||||||
return (
|
return (
|
||||||
partial(update_logic, rules=logic_rules, triggers=logic_triggers),
|
wraps(update_logic)(
|
||||||
partial(format_logic, rules=logic_rules),
|
partial(update_logic, rules=logic_rules, triggers=logic_triggers)
|
||||||
|
),
|
||||||
|
wraps(format_logic)(partial(format_logic, rules=logic_rules)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from json import load
|
from json import load
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
from os import environ, path
|
from os import environ, path
|
||||||
from typing import Callable, Dict, Sequence, Tuple
|
from typing import Callable, Sequence, Tuple
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from packit.agent import Agent, agent_easy_connect
|
from packit.agent import Agent, agent_easy_connect
|
||||||
|
@ -10,7 +10,8 @@ from packit.results import multi_function_or_str_result
|
||||||
from packit.toolbox import Toolbox
|
from packit.toolbox import Toolbox
|
||||||
from packit.utils import logger_with_colors
|
from packit.utils import logger_with_colors
|
||||||
|
|
||||||
from adventure.context import set_current_broadcast
|
from adventure.context import set_current_broadcast, set_dungeon_master
|
||||||
|
from adventure.models import Attributes
|
||||||
from adventure.plugins import load_plugin
|
from adventure.plugins import load_plugin
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
|
@ -50,7 +51,7 @@ if True:
|
||||||
from adventure.models import Actor, Room, World, WorldState
|
from adventure.models import Actor, Room, World, WorldState
|
||||||
from adventure.state import create_agents, save_world, save_world_state
|
from adventure.state import create_agents, save_world, save_world_state
|
||||||
|
|
||||||
logger = logger_with_colors(__name__, level="INFO")
|
logger = logger_with_colors(__name__, level="DEBUG")
|
||||||
|
|
||||||
load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
|
load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ def simulate_world(
|
||||||
steps: int = 10,
|
steps: int = 10,
|
||||||
actions: Sequence[Callable[..., str]] = [],
|
actions: Sequence[Callable[..., str]] = [],
|
||||||
systems: Sequence[
|
systems: Sequence[
|
||||||
Tuple[Callable[[World, int], None], Callable[[Dict[str, str]], str] | None]
|
Tuple[Callable[[World, int], None], Callable[[Attributes], str] | None]
|
||||||
] = [],
|
] = [],
|
||||||
event_callbacks: Sequence[Callable[[str], None]] = [],
|
event_callbacks: Sequence[Callable[[str], None]] = [],
|
||||||
input_callbacks: Sequence[Callable[[Room, Actor, str], None]] = [],
|
input_callbacks: Sequence[Callable[[Room, Actor, str], None]] = [],
|
||||||
|
@ -140,6 +141,9 @@ def simulate_world(
|
||||||
|
|
||||||
def result_parser(value, agent, **kwargs):
|
def result_parser(value, agent, **kwargs):
|
||||||
for callback in input_callbacks:
|
for callback in input_callbacks:
|
||||||
|
logger.info(
|
||||||
|
f"calling input callback for {actor_name}: {callback.__name__}"
|
||||||
|
)
|
||||||
callback(room, actor, value)
|
callback(room, actor, value)
|
||||||
|
|
||||||
return world_result_parser(value, agent, **kwargs)
|
return world_result_parser(value, agent, **kwargs)
|
||||||
|
@ -198,7 +202,13 @@ def parse_args():
|
||||||
help="Extra actions to include in the simulation",
|
help="Extra actions to include in the simulation",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--flavor", type=str, help="Some additional flavor text for the generated world"
|
"--discord", type=bool, help="Whether to run the simulation in a Discord bot"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--flavor",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="Some additional flavor text for the generated world",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--player", type=str, help="The name of the character to play as"
|
"--player", type=str, help="The name of the character to play as"
|
||||||
|
@ -289,7 +299,7 @@ def main():
|
||||||
else:
|
else:
|
||||||
logger.info(f"Generating a new {args.theme} world")
|
logger.info(f"Generating a new {args.theme} world")
|
||||||
llm = agent_easy_connect()
|
llm = agent_easy_connect()
|
||||||
agent = Agent(
|
world_builder = Agent(
|
||||||
"World Builder",
|
"World Builder",
|
||||||
f"You are an experienced game master creating a visually detailed {args.theme} world for a new adventure. {args.flavor}",
|
f"You are an experienced game master creating a visually detailed {args.theme} world for a new adventure. {args.flavor}",
|
||||||
{},
|
{},
|
||||||
|
@ -306,7 +316,7 @@ def main():
|
||||||
server_system(world, 0)
|
server_system(world, 0)
|
||||||
|
|
||||||
world = generate_world(
|
world = generate_world(
|
||||||
agent,
|
world_builder,
|
||||||
args.world,
|
args.world,
|
||||||
args.theme,
|
args.theme,
|
||||||
room_count=args.rooms,
|
room_count=args.rooms,
|
||||||
|
@ -349,14 +359,28 @@ def main():
|
||||||
logger.info(f"Loading extra systems from {system_name}")
|
logger.info(f"Loading extra systems from {system_name}")
|
||||||
module_systems = load_plugin(system_name)
|
module_systems = load_plugin(system_name)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Loaded extra systems: {[system.__name__ for system in module_systems]}"
|
f"Loaded extra systems: {[component.__name__ for system in module_systems for component in system]}"
|
||||||
)
|
)
|
||||||
extra_systems.append(module_systems)
|
extra_systems.extend(module_systems)
|
||||||
|
|
||||||
# make sure the server system runs after any updates
|
# make sure the server system runs after any updates
|
||||||
if args.server:
|
if args.server:
|
||||||
extra_systems.append((server_system, None))
|
extra_systems.append((server_system, None))
|
||||||
|
|
||||||
|
# create the DM
|
||||||
|
llm = agent_easy_connect()
|
||||||
|
world_builder = Agent(
|
||||||
|
"dungeon master",
|
||||||
|
(
|
||||||
|
f"You are the dungeon master in charge of a {world.theme} world. Be creative and original, and come up with "
|
||||||
|
f"interesting events that will keep players interested. {args.flavor}"
|
||||||
|
"Do not to repeat yourself unless you are given the same prompt with the same characters and actions."
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
llm,
|
||||||
|
)
|
||||||
|
set_dungeon_master(world_builder)
|
||||||
|
|
||||||
# start the sim
|
# start the sim
|
||||||
logger.debug("Simulating world: %s", world)
|
logger.debug("Simulating world: %s", world)
|
||||||
simulate_world(
|
simulate_world(
|
||||||
|
|
|
@ -3,21 +3,29 @@ from typing import Callable, List
|
||||||
|
|
||||||
from packit.agent import Agent, agent_easy_connect
|
from packit.agent import Agent, agent_easy_connect
|
||||||
|
|
||||||
from adventure.context import broadcast, get_agent_for_actor, get_current_context
|
from adventure.context import (
|
||||||
|
broadcast,
|
||||||
|
get_agent_for_actor,
|
||||||
|
get_current_context,
|
||||||
|
get_dungeon_master,
|
||||||
|
has_dungeon_master,
|
||||||
|
set_dungeon_master,
|
||||||
|
)
|
||||||
from adventure.generate import OPPOSITE_DIRECTIONS, generate_item, generate_room
|
from adventure.generate import OPPOSITE_DIRECTIONS, generate_item, generate_room
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
# this is the fallback dungeon master if none is set
|
||||||
llm = agent_easy_connect()
|
if not has_dungeon_master():
|
||||||
|
llm = agent_easy_connect()
|
||||||
# TODO: provide dungeon master with the world theme
|
set_dungeon_master(
|
||||||
dungeon_master = Agent(
|
Agent(
|
||||||
"dungeon master",
|
"dungeon master",
|
||||||
"You are the dungeon master in charge of a fantasy world.",
|
"You are the dungeon master in charge of a fantasy world.",
|
||||||
{},
|
{},
|
||||||
llm,
|
llm,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def action_explore(direction: str) -> str:
|
def action_explore(direction: str) -> str:
|
||||||
|
@ -29,6 +37,7 @@ def action_explore(direction: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
current_world, current_room, current_actor = get_current_context()
|
current_world, current_room, current_actor = get_current_context()
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
if not current_world:
|
if not current_world:
|
||||||
raise ValueError("No world found")
|
raise ValueError("No world found")
|
||||||
|
@ -53,12 +62,13 @@ def action_explore(direction: str) -> str:
|
||||||
return f"You explore {direction} and find a new room: {new_room.name}"
|
return f"You explore {direction} and find a new room: {new_room.name}"
|
||||||
|
|
||||||
|
|
||||||
def action_search() -> str:
|
def action_search(unused: bool) -> str:
|
||||||
"""
|
"""
|
||||||
Search the room for hidden items.
|
Search the room for hidden items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
action_world, action_room, action_actor = get_current_context()
|
action_world, action_room, action_actor = get_current_context()
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
if len(action_room.items) > 2:
|
if len(action_room.items) > 2:
|
||||||
return "You find nothing hidden in the room."
|
return "You find nothing hidden in the room."
|
||||||
|
@ -88,12 +98,17 @@ def action_use(item: str, target: str) -> str:
|
||||||
target: The name of the character to use the item on, or "self" to use the item on yourself.
|
target: The name of the character to use the item on, or "self" to use the item on yourself.
|
||||||
"""
|
"""
|
||||||
_, action_room, action_actor = get_current_context()
|
_, action_room, action_actor = get_current_context()
|
||||||
|
dungeon_master = get_dungeon_master()
|
||||||
|
|
||||||
available_items = [item.name for item in action_actor.items] + [
|
action_item = next(
|
||||||
item.name for item in action_room.items
|
(
|
||||||
]
|
search_item
|
||||||
|
for search_item in (action_actor.items + action_room.items)
|
||||||
if item not in available_items:
|
if search_item.name == item
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if not action_item:
|
||||||
return f"The {item} item is not available to use."
|
return f"The {item} item is not available to use."
|
||||||
|
|
||||||
if target == "self":
|
if target == "self":
|
||||||
|
@ -108,7 +123,10 @@ def action_use(item: str, target: str) -> str:
|
||||||
|
|
||||||
broadcast(f"{action_actor.name} uses {item} on {target}")
|
broadcast(f"{action_actor.name} uses {item} on {target}")
|
||||||
outcome = dungeon_master(
|
outcome = dungeon_master(
|
||||||
f"{action_actor.name} uses {item} on {target}. {action_actor.description}. {target_actor.description}. What happens? How does {target} react? "
|
f"{action_actor.name} uses {item} on {target}. "
|
||||||
|
f"{action_actor.description}. {target_actor.description}. {action_item.description}. "
|
||||||
|
f"What happens? How does {target} react? Be creative with the results. The outcome can be good, bad, or neutral."
|
||||||
|
"Decide based on the characters involved and the item being used."
|
||||||
"Specify the outcome of the action. Do not include the question or any JSON. Only include the outcome of the action."
|
"Specify the outcome of the action. Do not include the question or any JSON. Only include the outcome of the action."
|
||||||
)
|
)
|
||||||
broadcast(f"The action resulted in: {outcome}")
|
broadcast(f"The action resulted in: {outcome}")
|
||||||
|
|
Loading…
Reference in New Issue