improve search, prevent duplicate names, validate effect operations
This commit is contained in:
parent
71d2be85f1
commit
2bb842a559
|
@ -15,6 +15,7 @@ from adventure.utils.search import (
|
|||
find_item_in_room,
|
||||
find_room,
|
||||
)
|
||||
from adventure.utils.string import normalize_name
|
||||
from adventure.utils.world import describe_entity
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
@ -31,7 +32,7 @@ def action_look(target: str) -> str:
|
|||
with action_context() as (action_room, action_actor):
|
||||
broadcast(f"{action_actor.name} looks at {target}")
|
||||
|
||||
if target.lower() == action_room.name.lower():
|
||||
if normalize_name(target) == normalize_name(action_room.name):
|
||||
broadcast(f"{action_actor.name} saw the {action_room.name} room")
|
||||
return describe_entity(action_room)
|
||||
|
||||
|
@ -69,7 +70,11 @@ def action_move(direction: str) -> str:
|
|||
|
||||
with world_context() as (action_world, action_room, action_actor):
|
||||
portal = next(
|
||||
(p for p in action_room.portals if p.name.lower() == direction.lower()),
|
||||
(
|
||||
p
|
||||
for p in action_room.portals
|
||||
if normalize_name(p.name) == normalize_name(direction)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not portal:
|
||||
|
|
|
@ -19,6 +19,7 @@ from pyee.base import EventEmitter
|
|||
from adventure.game_system import GameSystem
|
||||
from adventure.models.entity import Actor, Room, World
|
||||
from adventure.models.event import GameEvent
|
||||
from adventure.utils.string import normalize_name
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -223,7 +224,7 @@ def get_actor_agent_for_name(name):
|
|||
(
|
||||
(actor, agent)
|
||||
for actor, agent in actor_agents.values()
|
||||
if actor.name.lower() == name.lower()
|
||||
if normalize_name(actor.name) == normalize_name(name)
|
||||
),
|
||||
(None, None),
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from enum import Enum
|
||||
from typing import Callable, Protocol
|
||||
from typing import Protocol
|
||||
|
||||
from packit.agent import Agent
|
||||
|
||||
from adventure.models.entity import World, WorldEntity
|
||||
from adventure.utils import format_callable
|
||||
|
||||
|
||||
class FormatPerspective(Enum):
|
||||
|
@ -57,10 +58,5 @@ class GameSystem:
|
|||
def __str__(self):
|
||||
return f"GameSystem(format={format_callable(self.format)}, generate={format_callable(self.generate)}, simulate={format_callable(self.simulate)})"
|
||||
|
||||
|
||||
# TODO: move to utils
|
||||
def format_callable(fn: Callable | None) -> str:
|
||||
if fn:
|
||||
return f"{fn.__module__}:{fn.__name__}"
|
||||
|
||||
return "None"
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
|
|
@ -6,7 +6,7 @@ from packit.agent import Agent
|
|||
from packit.loops import loop_retry
|
||||
from packit.utils import could_be_json
|
||||
|
||||
from adventure.context import broadcast
|
||||
from adventure.context import broadcast, set_current_world
|
||||
from adventure.game_system import GameSystem
|
||||
from adventure.models.config import DEFAULT_CONFIG, WorldConfig
|
||||
from adventure.models.entity import (
|
||||
|
@ -21,11 +21,31 @@ from adventure.models.entity import (
|
|||
WorldEntity,
|
||||
)
|
||||
from adventure.models.event import GenerateEvent
|
||||
from adventure.utils import try_parse_float, try_parse_int
|
||||
from adventure.utils.search import list_actors, list_items, list_rooms
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
world_config: WorldConfig = DEFAULT_CONFIG.world
|
||||
|
||||
PROMPT_TYPE_FRAGMENTS = {
|
||||
"both": "Enter a positive or negative number, or a string value",
|
||||
"number": "Enter a positive or negative number",
|
||||
"string": "Enter a string value",
|
||||
}
|
||||
|
||||
PROMPT_OPERATION_TYPES = {
|
||||
"set": "both",
|
||||
"add": "number",
|
||||
"subtract": "number",
|
||||
"multiply": "number",
|
||||
"divide": "number",
|
||||
"append": "string",
|
||||
"prepend": "string",
|
||||
}
|
||||
|
||||
OPERATIONS = list(PROMPT_OPERATION_TYPES.keys())
|
||||
|
||||
|
||||
def duplicate_name_parser(existing_names: List[str]):
|
||||
def name_parser(value: str, **kwargs):
|
||||
|
@ -62,19 +82,28 @@ def broadcast_generated(
|
|||
broadcast(event)
|
||||
|
||||
|
||||
def generate_system_attributes(
|
||||
agent: Agent, world: World, entity: WorldEntity, systems: List[GameSystem]
|
||||
) -> None:
|
||||
for system in systems:
|
||||
if system.generate:
|
||||
system.generate(agent, world.theme, entity)
|
||||
|
||||
|
||||
def generate_room(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
existing_rooms: List[str] = [],
|
||||
systems: List[GameSystem] = [],
|
||||
world: World,
|
||||
systems: List[GameSystem],
|
||||
) -> Room:
|
||||
existing_rooms = [room.name for room in list_rooms(world)]
|
||||
|
||||
name = loop_retry(
|
||||
agent,
|
||||
"Generate one room, area, or location that would make sense in the world of {world_theme}. "
|
||||
"Only respond with the room name in title case, do not include the description or any other text. "
|
||||
'Do not prefix the name with "the", do not wrap it in quotes. The existing rooms are: {existing_rooms}',
|
||||
context={
|
||||
"world_theme": world_theme,
|
||||
"world_theme": world.theme,
|
||||
"existing_rooms": existing_rooms,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_rooms),
|
||||
|
@ -88,27 +117,24 @@ def generate_room(
|
|||
)
|
||||
|
||||
actions = {}
|
||||
room = Room(name=name, description=desc, items=[], actors=[], actions=actions)
|
||||
|
||||
item_count = randint(
|
||||
world_config.size.room_items.min, world_config.size.room_items.max
|
||||
)
|
||||
broadcast_generated(f"Generating {item_count} items for room: {name}")
|
||||
|
||||
items = []
|
||||
for j in range(item_count):
|
||||
existing_items = [item.name for item in items]
|
||||
|
||||
for _ in range(item_count):
|
||||
try:
|
||||
item = generate_item(
|
||||
agent,
|
||||
world_theme,
|
||||
dest_room=name,
|
||||
existing_items=existing_items,
|
||||
world,
|
||||
systems=systems,
|
||||
dest_room=room,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
items.append(item)
|
||||
room.items.append(item)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
|
@ -117,35 +143,30 @@ def generate_room(
|
|||
)
|
||||
broadcast_generated(message=f"Generating {actor_count} actors for room: {name}")
|
||||
|
||||
actors = []
|
||||
for j in range(actor_count):
|
||||
existing_actors = [actor.name for actor in actors]
|
||||
|
||||
for _ in range(actor_count):
|
||||
try:
|
||||
actor = generate_actor(
|
||||
agent,
|
||||
world_theme,
|
||||
dest_room=name,
|
||||
existing_actors=existing_actors,
|
||||
world,
|
||||
systems=systems,
|
||||
dest_room=room,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, actor, systems)
|
||||
broadcast_generated(entity=actor)
|
||||
|
||||
actors.append(actor)
|
||||
room.actors.append(actor)
|
||||
except Exception:
|
||||
logger.exception("error generating actor")
|
||||
continue
|
||||
|
||||
return Room(
|
||||
name=name, description=desc, items=items, actors=actors, actions=actions
|
||||
)
|
||||
return room
|
||||
|
||||
|
||||
def generate_portals(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
world: World,
|
||||
source_room: Room,
|
||||
dest_room: Room,
|
||||
systems: List[GameSystem],
|
||||
) -> Tuple[Portal, Portal]:
|
||||
existing_source_portals = [portal.name for portal in source_room.portals]
|
||||
existing_dest_portals = [portal.name for portal in dest_room.portals]
|
||||
|
@ -161,7 +182,7 @@ def generate_portals(
|
|||
"source_room": source_room.name,
|
||||
"dest_room": dest_room.name,
|
||||
"existing_portals": existing_source_portals,
|
||||
"world_theme": world_theme,
|
||||
"world_theme": world.theme,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_source_portals),
|
||||
)
|
||||
|
@ -179,7 +200,7 @@ def generate_portals(
|
|||
"source_room": source_room.name,
|
||||
"dest_room": dest_room.name,
|
||||
"existing_portals": existing_dest_portals,
|
||||
"world_theme": world_theme,
|
||||
"world_theme": world.theme,
|
||||
"outgoing_name": outgoing_name,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_dest_portals),
|
||||
|
@ -192,26 +213,35 @@ def generate_portals(
|
|||
description=f"A {outgoing_name} leads to the {dest_room.name} room.",
|
||||
destination=dest_room.name,
|
||||
)
|
||||
generate_system_attributes(agent, world, outgoing_portal, systems)
|
||||
|
||||
incoming_portal = Portal(
|
||||
name=incoming_name,
|
||||
description=f"A {incoming_name} leads to the {source_room.name} room.",
|
||||
destination=source_room.name,
|
||||
)
|
||||
generate_system_attributes(agent, world, incoming_portal, systems)
|
||||
|
||||
return (outgoing_portal, incoming_portal)
|
||||
|
||||
|
||||
def generate_item(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
dest_room: str | None = None,
|
||||
dest_actor: str | None = None,
|
||||
existing_items: List[str] = [],
|
||||
world: World,
|
||||
systems: List[GameSystem],
|
||||
dest_room: Room | None = None,
|
||||
dest_actor: Actor | None = None,
|
||||
) -> Item:
|
||||
existing_items = [
|
||||
item.name
|
||||
for item in list_items(
|
||||
world, include_actor_inventory=True, include_item_inventory=True
|
||||
)
|
||||
]
|
||||
if dest_actor:
|
||||
dest_note = f"The item will be held by the {dest_actor} character"
|
||||
dest_note = f"The item will be held by the {dest_actor.name} character"
|
||||
elif dest_room:
|
||||
dest_note = f"The item will be placed in the {dest_room} room"
|
||||
dest_note = f"The item will be placed in the {dest_room.name} room"
|
||||
else:
|
||||
dest_note = "The item will be placed in the world"
|
||||
|
||||
|
@ -225,7 +255,7 @@ def generate_item(
|
|||
context={
|
||||
"dest_note": dest_note,
|
||||
"existing_items": existing_items,
|
||||
"world_theme": world_theme,
|
||||
"world_theme": world.theme,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_items),
|
||||
)
|
||||
|
@ -238,35 +268,30 @@ def generate_item(
|
|||
|
||||
actions = {}
|
||||
item = Item(name=name, description=desc, actions=actions)
|
||||
generate_system_attributes(agent, world, item, systems)
|
||||
|
||||
effect_count = randint(
|
||||
world_config.size.item_effects.min, world_config.size.item_effects.max
|
||||
)
|
||||
broadcast_generated(message=f"Generating {effect_count} effects for item: {name}")
|
||||
|
||||
effects = []
|
||||
for i in range(effect_count):
|
||||
existing_effects = [effect.name for effect in effects]
|
||||
|
||||
for _ in range(effect_count):
|
||||
try:
|
||||
effect = generate_effect(
|
||||
agent, world_theme, entity=item, existing_effects=existing_effects
|
||||
)
|
||||
effects.append(effect)
|
||||
effect = generate_effect(agent, world, entity=item)
|
||||
item.effects.append(effect)
|
||||
except Exception:
|
||||
logger.exception("error generating effect")
|
||||
|
||||
item.effects = effects
|
||||
return item
|
||||
|
||||
|
||||
def generate_actor(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
dest_room: str,
|
||||
existing_actors: List[str] = [],
|
||||
systems: List[GameSystem] = [],
|
||||
world: World,
|
||||
systems: List[GameSystem],
|
||||
dest_room: Room,
|
||||
) -> Actor:
|
||||
existing_actors = [actor.name for actor in list_actors(world)]
|
||||
name = loop_retry(
|
||||
agent,
|
||||
"Generate one person or creature that would make sense in the world of {world_theme}. "
|
||||
|
@ -276,9 +301,9 @@ def generate_actor(
|
|||
"Do not include the name of the room. Do not give characters any duplicate names."
|
||||
"Do not create any duplicate characters. The existing characters are: {existing_actors}",
|
||||
context={
|
||||
"dest_room": dest_room,
|
||||
"dest_room": dest_room.name,
|
||||
"existing_actors": existing_actors,
|
||||
"world_theme": world_theme,
|
||||
"world_theme": world.theme,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_actors),
|
||||
)
|
||||
|
@ -296,59 +321,38 @@ def generate_actor(
|
|||
name=name,
|
||||
)
|
||||
|
||||
actor = Actor(
|
||||
name=name, backstory=backstory, description=description, actions={}, items=[]
|
||||
)
|
||||
generate_system_attributes(agent, world, actor, systems)
|
||||
|
||||
# generate the actor's inventory
|
||||
item_count = randint(
|
||||
world_config.size.actor_items.min, world_config.size.actor_items.max
|
||||
)
|
||||
broadcast_generated(f"Generating {item_count} items for actor {name}")
|
||||
|
||||
items = []
|
||||
for k in range(item_count):
|
||||
existing_items = [item.name for item in items]
|
||||
|
||||
try:
|
||||
item = generate_item(
|
||||
agent,
|
||||
world_theme,
|
||||
dest_actor=name,
|
||||
existing_items=existing_items,
|
||||
world,
|
||||
systems,
|
||||
dest_actor=actor,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, item, systems)
|
||||
generate_system_attributes(agent, world, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
items.append(item)
|
||||
actor.items.append(item)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
return Actor(
|
||||
name=name,
|
||||
backstory=backstory,
|
||||
description=description,
|
||||
actions={},
|
||||
items=items,
|
||||
)
|
||||
return actor
|
||||
|
||||
|
||||
# TODO: move to utils
|
||||
def try_parse_int(value: str) -> int | None:
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
# TODO: move to utils
|
||||
def try_parse_float(value: str) -> float | None:
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def generate_effect(
|
||||
agent: Agent, theme: str, entity: Item, existing_effects: List[str] = []
|
||||
) -> Effect:
|
||||
def generate_effect(agent: Agent, world: World, entity: Item) -> Effect:
|
||||
entity_type = entity.type
|
||||
existing_effects = [effect.name for effect in entity.effects]
|
||||
|
||||
name = loop_retry(
|
||||
agent,
|
||||
|
@ -361,7 +365,7 @@ def generate_effect(
|
|||
"entity_name": entity.name,
|
||||
"entity_type": entity_type,
|
||||
"existing_effects": existing_effects,
|
||||
"theme": theme,
|
||||
"theme": world.theme,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_effects),
|
||||
)
|
||||
|
@ -374,52 +378,40 @@ def generate_effect(
|
|||
)
|
||||
|
||||
attribute_names = agent(
|
||||
"Generate a list of attributes that the {name} effect modifies. "
|
||||
"Generate a short list of attributes that the {name} effect modifies. Include 1 to 3 attributes. "
|
||||
"For example, 'heal' increases the target's 'health' attribute, while 'poison' decreases it. "
|
||||
"Use a comma-separated list of attribute names, such as 'health, strength, speed'. "
|
||||
"Only include the attribute names, do not include the question or any JSON.",
|
||||
name=name,
|
||||
)
|
||||
|
||||
def operation_parser(value: str, **kwargs):
|
||||
if value not in OPERATIONS:
|
||||
raise ValueError(
|
||||
f'"{value}" is not a valid operation. Choose from: {OPERATIONS}'
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
attributes = []
|
||||
for attribute_name in attribute_names.split(","):
|
||||
attribute_name = attribute_name.strip()
|
||||
if attribute_name:
|
||||
operation = agent(
|
||||
operation = loop_retry(
|
||||
agent,
|
||||
f"How does the {name} effect modify the {attribute_name} attribute? "
|
||||
"For example, 'heal' might 'add' to the 'health' attribute, while 'poison' might 'subtract' from it."
|
||||
"Another example is 'writing' might 'set' the 'text' attribute, while 'break' might 'set' the 'condition' attribute."
|
||||
"Reply with the operation only, without any other text. Give a single word."
|
||||
"Reply with the operation only, without any other text. Respond with a single word for the list of operations."
|
||||
"Choose from the following operations: {operations}",
|
||||
name=name,
|
||||
attribute_name=attribute_name,
|
||||
operations=[
|
||||
"set",
|
||||
"add",
|
||||
"subtract",
|
||||
"multiply",
|
||||
"divide",
|
||||
"append",
|
||||
"prepend",
|
||||
],
|
||||
context={
|
||||
"name": name,
|
||||
"attribute_name": attribute_name,
|
||||
"operations": OPERATIONS,
|
||||
},
|
||||
result_parser=operation_parser,
|
||||
)
|
||||
|
||||
PROMPT_TYPE_FRAGMENTS = {
|
||||
"both": "Enter a positive or negative number, or a string value",
|
||||
"number": "Enter a positive or negative number",
|
||||
"string": "Enter a string value",
|
||||
}
|
||||
|
||||
PROMPT_OPERATION_TYPES = {
|
||||
"set": "both",
|
||||
"add": "number",
|
||||
"subtract": "number",
|
||||
"multiply": "number",
|
||||
"divide": "number",
|
||||
"append": "string",
|
||||
"prepend": "string",
|
||||
}
|
||||
|
||||
operation_type = PROMPT_OPERATION_TYPES[operation]
|
||||
operation_prompt = PROMPT_TYPE_FRAGMENTS[operation_type]
|
||||
|
||||
|
@ -453,43 +445,34 @@ def generate_effect(
|
|||
return Effect(name=name, description=description, attributes=attributes)
|
||||
|
||||
|
||||
def generate_system_attributes(
|
||||
agent: Agent, theme: str, entity: WorldEntity, systems: List[GameSystem] = []
|
||||
) -> None:
|
||||
for system in systems:
|
||||
if system.generate:
|
||||
system.generate(agent, theme, entity)
|
||||
|
||||
|
||||
def generate_world(
|
||||
agent: Agent,
|
||||
name: str,
|
||||
theme: str,
|
||||
systems: List[GameSystem],
|
||||
room_count: int | None = None,
|
||||
systems: List[GameSystem] = [],
|
||||
) -> World:
|
||||
room_count = room_count or randint(
|
||||
world_config.size.rooms.min, world_config.size.rooms.max
|
||||
)
|
||||
|
||||
broadcast_generated(message=f"Generating a {theme} with {room_count} rooms")
|
||||
world = World(name=name, rooms=[], theme=theme, order=[])
|
||||
set_current_world(world)
|
||||
|
||||
# generate the rooms
|
||||
rooms = []
|
||||
for i in range(room_count):
|
||||
existing_rooms = [room.name for room in rooms]
|
||||
|
||||
for _ in range(room_count):
|
||||
try:
|
||||
room = generate_room(agent, theme, existing_rooms=existing_rooms)
|
||||
generate_system_attributes(agent, theme, room, systems)
|
||||
room = generate_room(agent, world, systems)
|
||||
generate_system_attributes(agent, world, room, systems)
|
||||
broadcast_generated(entity=room)
|
||||
rooms.append(room)
|
||||
world.rooms.append(room)
|
||||
except Exception:
|
||||
logger.exception("error generating room")
|
||||
continue
|
||||
|
||||
# generate portals to link the rooms together
|
||||
for room in rooms:
|
||||
for room in world.rooms:
|
||||
num_portals = randint(
|
||||
world_config.size.portals.min, world_config.size.portals.max
|
||||
)
|
||||
|
@ -502,23 +485,25 @@ def generate_world(
|
|||
message=f"Generating {num_portals} portals for room: {room.name}"
|
||||
)
|
||||
|
||||
for i in range(num_portals):
|
||||
for _ in range(num_portals):
|
||||
previous_destinations = [portal.destination for portal in room.portals] + [
|
||||
room.name
|
||||
]
|
||||
remaining_rooms = [r for r in rooms if r.name not in previous_destinations]
|
||||
remaining_rooms = [
|
||||
r for r in world.rooms if r.name not in previous_destinations
|
||||
]
|
||||
if len(remaining_rooms) == 0:
|
||||
logger.info(f"no more rooms to link to from {room.name}")
|
||||
break
|
||||
|
||||
# TODO: prompt the DM to choose a destination room
|
||||
dest_room = choice(
|
||||
[r for r in rooms if r.name not in previous_destinations]
|
||||
[r for r in world.rooms if r.name not in previous_destinations]
|
||||
)
|
||||
|
||||
try:
|
||||
outgoing_portal, incoming_portal = generate_portals(
|
||||
agent, theme, room, dest_room
|
||||
agent, world, room, dest_room, systems
|
||||
)
|
||||
|
||||
room.portals.append(outgoing_portal)
|
||||
|
@ -528,5 +513,5 @@ def generate_world(
|
|||
continue
|
||||
|
||||
# ensure actors act in a stable order
|
||||
order = [actor.name for room in rooms for actor in room.actors]
|
||||
return World(name=name, rooms=rooms, theme=theme, order=order)
|
||||
world.order = [actor.name for room in world.rooms for actor in room.actors]
|
||||
return world
|
||||
|
|
|
@ -209,6 +209,7 @@ def load_or_generate_world(args, players, systems, world_prompt: WorldPrompt):
|
|||
world_builder,
|
||||
args.world,
|
||||
world_prompt.theme,
|
||||
systems,
|
||||
room_count=args.rooms,
|
||||
)
|
||||
save_world(world, world_file)
|
||||
|
|
|
@ -88,8 +88,8 @@ DEFAULT_CONFIG = Config(
|
|||
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8001)),
|
||||
world=WorldConfig(
|
||||
size=WorldSizeConfig(
|
||||
actor_items=Range(min=1, max=3),
|
||||
item_effects=Range(min=1, max=3),
|
||||
actor_items=Range(min=0, max=2),
|
||||
item_effects=Range(min=1, max=2),
|
||||
portals=Range(min=1, max=3),
|
||||
rooms=Range(min=3, max=6),
|
||||
room_actors=Range(min=1, max=3),
|
||||
|
|
|
@ -110,4 +110,4 @@ class WorldState(BaseModel):
|
|||
type: Literal["world_state"] = "world_state"
|
||||
|
||||
|
||||
WorldEntity = Room | Actor | Item
|
||||
WorldEntity = Room | Actor | Item | Portal
|
||||
|
|
|
@ -38,7 +38,8 @@ def graph_world(world: World, step: int):
|
|||
graph_name = f"{path.basename(world.name)}-{step}"
|
||||
graph = graphviz.Digraph(graph_name, format="png")
|
||||
for room in world.rooms:
|
||||
room_label = "\n".join([room.name, *[actor.name for actor in room.actors]])
|
||||
actors = [actor.name for actor in room.actors]
|
||||
room_label = "\n".join([room.name, *actors])
|
||||
graph.node(room.name, room_label)
|
||||
for portal in room.portals:
|
||||
graph.edge(room.name, portal.destination, label=portal.name)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
from adventure.models.entity import Actor, Item, Room, World
|
||||
from typing import Any, Generator
|
||||
|
||||
from adventure.models.entity import Actor, Item, Portal, Room, World
|
||||
|
||||
from .string import normalize_name
|
||||
|
||||
|
||||
def find_room(world: World, room_name: str) -> Room | None:
|
||||
for room in world.rooms:
|
||||
if room.name.lower() == room_name.lower():
|
||||
if normalize_name(room.name) == normalize_name(room_name):
|
||||
return room
|
||||
|
||||
return None
|
||||
|
@ -20,7 +24,7 @@ def find_actor(world: World, actor_name: str) -> Actor | None:
|
|||
|
||||
def find_actor_in_room(room: Room, actor_name: str) -> Actor | None:
|
||||
for actor in room.actors:
|
||||
if actor.name.lower() == actor_name.lower():
|
||||
if normalize_name(actor.name) == normalize_name(actor_name):
|
||||
return actor
|
||||
|
||||
return None
|
||||
|
@ -46,7 +50,7 @@ def find_item_in_actor(
|
|||
actor: Actor, item_name: str, include_item_inventory=False
|
||||
) -> Item | None:
|
||||
for item in actor.items:
|
||||
if item.name.lower() == item_name.lower():
|
||||
if normalize_name(item.name) == normalize_name(item_name):
|
||||
return item
|
||||
|
||||
if include_item_inventory:
|
||||
|
@ -61,7 +65,7 @@ def find_item_in_container(
|
|||
container: Item, item_name: str, include_item_inventory=False
|
||||
) -> Item | None:
|
||||
for item in container.items:
|
||||
if item.name.lower() == item_name.lower():
|
||||
if normalize_name(item.name) == normalize_name(item_name):
|
||||
return item
|
||||
|
||||
if include_item_inventory:
|
||||
|
@ -79,7 +83,7 @@ def find_item_in_room(
|
|||
include_item_inventory=False,
|
||||
) -> Item | None:
|
||||
for item in room.items:
|
||||
if item.name.lower() == item_name.lower():
|
||||
if normalize_name(item.name) == normalize_name(item_name):
|
||||
return item
|
||||
|
||||
if include_item_inventory:
|
||||
|
@ -94,3 +98,43 @@ def find_item_in_room(
|
|||
return item
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def list_rooms(world: World) -> Generator[Room, Any, None]:
|
||||
for room in world.rooms:
|
||||
yield room
|
||||
|
||||
|
||||
def list_portals(world: World) -> Generator[Portal, Any, None]:
|
||||
for room in world.rooms:
|
||||
for portal in room.portals:
|
||||
yield portal
|
||||
|
||||
|
||||
def list_actors(world: World) -> Generator[Actor, Any, None]:
|
||||
for room in world.rooms:
|
||||
for actor in room.actors:
|
||||
yield actor
|
||||
|
||||
|
||||
def list_items(
|
||||
world: World, include_actor_inventory=False, include_item_inventory=False
|
||||
) -> Generator[Item, Any, None]:
|
||||
def list_items_in_container(container: Item) -> Generator[Item, Any, None]:
|
||||
for item in container.items:
|
||||
yield item
|
||||
|
||||
if include_item_inventory:
|
||||
yield from list_items_in_container(item)
|
||||
|
||||
for room in world.rooms:
|
||||
for item in room.items:
|
||||
yield item
|
||||
|
||||
if include_item_inventory:
|
||||
yield from list_items_in_container(item)
|
||||
|
||||
if include_actor_inventory:
|
||||
for actor in room.actors:
|
||||
for item in actor.items:
|
||||
yield item
|
||||
|
|
24
config.yml
24
config.yml
|
@ -22,3 +22,27 @@ render:
|
|||
steps:
|
||||
min: 30
|
||||
max: 50
|
||||
server:
|
||||
websocket:
|
||||
host: 0.0.0.0
|
||||
port: 8001
|
||||
world:
|
||||
size:
|
||||
actor_items:
|
||||
min: 0
|
||||
max: 3
|
||||
item_effects:
|
||||
min: 0
|
||||
max: 1
|
||||
portals:
|
||||
min: 1
|
||||
max: 3
|
||||
rooms:
|
||||
min: 3
|
||||
max: 6
|
||||
room_actors:
|
||||
min: 1
|
||||
max: 3
|
||||
room_items:
|
||||
min: 0
|
||||
max: 3
|
||||
|
|
Loading…
Reference in New Issue