use DM to generate portals between rooms
This commit is contained in:
parent
ea5ac0cd10
commit
71d2be85f1
|
@ -68,19 +68,22 @@ def action_move(direction: str) -> str:
|
|||
"""
|
||||
|
||||
with world_context() as (action_world, action_room, action_actor):
|
||||
destination_name = action_room.portals.get(direction.lower())
|
||||
if not destination_name:
|
||||
portal = next(
|
||||
(p for p in action_room.portals if p.name.lower() == direction.lower()),
|
||||
None,
|
||||
)
|
||||
if not portal:
|
||||
return f"You cannot move {direction} from here."
|
||||
|
||||
destination_room = find_room(action_world, destination_name)
|
||||
destination_room = find_room(action_world, portal.destination)
|
||||
if not destination_room:
|
||||
return f"The {destination_name} room does not exist."
|
||||
return f"The {portal.destination} room does not exist."
|
||||
|
||||
broadcast(f"{action_actor.name} moves {direction} to {destination_name}")
|
||||
broadcast(f"{action_actor.name} moves {direction} to {destination_room.name}")
|
||||
action_room.actors.remove(action_actor)
|
||||
destination_room.actors.append(action_actor)
|
||||
|
||||
return f"You move {direction} and arrive at {destination_name}."
|
||||
return f"You move {direction} and arrive at {destination_room.name}."
|
||||
|
||||
|
||||
def action_take(item_name: str) -> str:
|
||||
|
|
|
@ -12,7 +12,7 @@ from adventure.context import (
|
|||
set_dungeon_master,
|
||||
world_context,
|
||||
)
|
||||
from adventure.generate import OPPOSITE_DIRECTIONS, generate_item, generate_room
|
||||
from adventure.generate import generate_item, generate_room
|
||||
from adventure.utils.effect import apply_effect
|
||||
from adventure.utils.search import find_actor_in_room
|
||||
from adventure.utils.world import describe_actor, describe_entity
|
||||
|
@ -55,8 +55,7 @@ def action_explore(direction: str) -> str:
|
|||
action_world.rooms.append(new_room)
|
||||
|
||||
# link the rooms together
|
||||
action_room.portals[direction] = new_room.name
|
||||
new_room.portals[OPPOSITE_DIRECTIONS[direction]] = action_room.name
|
||||
# TODO: generate portals
|
||||
|
||||
broadcast(
|
||||
f"{action_actor.name} explores {direction} of {action_room.name} and finds a new room: {new_room.name}"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from logging import getLogger
|
||||
from random import choice, randint
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
from packit.agent import Agent
|
||||
from packit.loops import loop_retry
|
||||
|
@ -8,11 +8,13 @@ from packit.utils import could_be_json
|
|||
|
||||
from adventure.context import broadcast
|
||||
from adventure.game_system import GameSystem
|
||||
from adventure.models.config import DEFAULT_CONFIG, WorldConfig
|
||||
from adventure.models.entity import (
|
||||
Actor,
|
||||
Effect,
|
||||
Item,
|
||||
NumberAttributeEffect,
|
||||
Portal,
|
||||
Room,
|
||||
StringAttributeEffect,
|
||||
World,
|
||||
|
@ -22,12 +24,7 @@ from adventure.models.event import GenerateEvent
|
|||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
OPPOSITE_DIRECTIONS = {
|
||||
"north": "south",
|
||||
"south": "north",
|
||||
"east": "west",
|
||||
"west": "east",
|
||||
}
|
||||
world_config: WorldConfig = DEFAULT_CONFIG.world
|
||||
|
||||
|
||||
def duplicate_name_parser(existing_names: List[str]):
|
||||
|
@ -69,6 +66,7 @@ def generate_room(
|
|||
agent: Agent,
|
||||
world_theme: str,
|
||||
existing_rooms: List[str] = [],
|
||||
systems: List[GameSystem] = [],
|
||||
) -> Room:
|
||||
name = loop_retry(
|
||||
agent,
|
||||
|
@ -89,15 +87,120 @@ def generate_room(
|
|||
name=name,
|
||||
)
|
||||
|
||||
items = []
|
||||
actors = []
|
||||
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]
|
||||
|
||||
try:
|
||||
item = generate_item(
|
||||
agent,
|
||||
world_theme,
|
||||
dest_room=name,
|
||||
existing_items=existing_items,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
items.append(item)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
actor_count = randint(
|
||||
world_config.size.room_actors.min, world_config.size.room_actors.max
|
||||
)
|
||||
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]
|
||||
|
||||
try:
|
||||
actor = generate_actor(
|
||||
agent,
|
||||
world_theme,
|
||||
dest_room=name,
|
||||
existing_actors=existing_actors,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, actor, systems)
|
||||
broadcast_generated(entity=actor)
|
||||
|
||||
actors.append(actor)
|
||||
except Exception:
|
||||
logger.exception("error generating actor")
|
||||
continue
|
||||
|
||||
return Room(
|
||||
name=name, description=desc, items=items, actors=actors, actions=actions
|
||||
)
|
||||
|
||||
|
||||
def generate_portals(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
source_room: Room,
|
||||
dest_room: Room,
|
||||
) -> 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]
|
||||
|
||||
outgoing_name = loop_retry(
|
||||
agent,
|
||||
"Generate the name of a portal that leads from the {source_room} room to the {dest_room} room and fits the world theme of {world_theme}. "
|
||||
"Some example portal names are: 'door', 'gate', 'archway', 'staircase', 'trapdoor', 'mirror', and 'magic circle'. "
|
||||
"Only respond with the portal name in title case, do not include a description or any other text. "
|
||||
'Do not prefix the name with "the", do not wrap it in quotes. Use a unique name. '
|
||||
"Do not create any duplicate portals in the same room. The existing portals are: {existing_portals}",
|
||||
context={
|
||||
"source_room": source_room.name,
|
||||
"dest_room": dest_room.name,
|
||||
"existing_portals": existing_source_portals,
|
||||
"world_theme": world_theme,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_source_portals),
|
||||
)
|
||||
broadcast_generated(message=f"Generating portal: {outgoing_name}")
|
||||
|
||||
incoming_name = loop_retry(
|
||||
agent,
|
||||
"Generate the opposite name of the portal that leads from the {dest_room} room to the {source_room} room. "
|
||||
"The name should be the opposite of the {outgoing_name} portal and should fit the world theme of {world_theme}. "
|
||||
"Some example portal names are: 'door', 'gate', 'archway', 'staircase', 'trapdoor', 'mirror', and 'magic circle'. "
|
||||
"Only respond with the portal name in title case, do not include a description or any other text. "
|
||||
'Do not prefix the name with "the", do not wrap it in quotes. Use a unique name. '
|
||||
"Do not create any duplicate portals in the same room. The existing portals are: {existing_portals}",
|
||||
context={
|
||||
"source_room": source_room.name,
|
||||
"dest_room": dest_room.name,
|
||||
"existing_portals": existing_dest_portals,
|
||||
"world_theme": world_theme,
|
||||
"outgoing_name": outgoing_name,
|
||||
},
|
||||
result_parser=duplicate_name_parser(existing_dest_portals),
|
||||
)
|
||||
|
||||
broadcast_generated(message=f"Linking {outgoing_name} to {incoming_name}")
|
||||
|
||||
outgoing_portal = Portal(
|
||||
name=outgoing_name,
|
||||
description=f"A {outgoing_name} leads to the {dest_room.name} room.",
|
||||
destination=dest_room.name,
|
||||
)
|
||||
incoming_portal = Portal(
|
||||
name=incoming_name,
|
||||
description=f"A {incoming_name} leads to the {source_room.name} room.",
|
||||
destination=source_room.name,
|
||||
)
|
||||
|
||||
return (outgoing_portal, incoming_portal)
|
||||
|
||||
|
||||
def generate_item(
|
||||
agent: Agent,
|
||||
world_theme: str,
|
||||
|
@ -136,13 +239,19 @@ def generate_item(
|
|||
actions = {}
|
||||
item = Item(name=name, description=desc, actions=actions)
|
||||
|
||||
effect_count = randint(1, 2)
|
||||
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]
|
||||
|
||||
try:
|
||||
effect = generate_effect(agent, world_theme, entity=item)
|
||||
effect = generate_effect(
|
||||
agent, world_theme, entity=item, existing_effects=existing_effects
|
||||
)
|
||||
effects.append(effect)
|
||||
except Exception:
|
||||
logger.exception("error generating effect")
|
||||
|
@ -156,6 +265,7 @@ def generate_actor(
|
|||
world_theme: str,
|
||||
dest_room: str,
|
||||
existing_actors: List[str] = [],
|
||||
systems: List[GameSystem] = [],
|
||||
) -> Actor:
|
||||
name = loop_retry(
|
||||
agent,
|
||||
|
@ -186,18 +296,59 @@ def generate_actor(
|
|||
name=name,
|
||||
)
|
||||
|
||||
# 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,
|
||||
)
|
||||
generate_system_attributes(agent, world_theme, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
items.append(item)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
return Actor(
|
||||
name=name,
|
||||
backstory=backstory,
|
||||
description=description,
|
||||
actions={},
|
||||
items=items,
|
||||
)
|
||||
|
||||
|
||||
def generate_effect(agent: Agent, theme: str, entity: Item) -> Effect:
|
||||
entity_type = entity.type
|
||||
# TODO: move to utils
|
||||
def try_parse_int(value: str) -> int | None:
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
existing_effects = [effect.name for effect in entity.effects]
|
||||
|
||||
# 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:
|
||||
entity_type = entity.type
|
||||
|
||||
name = loop_retry(
|
||||
agent,
|
||||
|
@ -252,23 +403,45 @@ def generate_effect(agent: Agent, theme: str, entity: Item) -> Effect:
|
|||
"prepend",
|
||||
],
|
||||
)
|
||||
|
||||
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]
|
||||
|
||||
value = agent(
|
||||
f"How much does the {name} effect modify the {attribute_name} attribute? "
|
||||
"For example, heal might add '10' to the health attribute, while poison might subtract '5' from it."
|
||||
"Enter a positive or negative number, or a string value. Do not include any other text. Do not use JSON.",
|
||||
f"{operation_prompt}. Do not include any other text. Do not use JSON.",
|
||||
name=name,
|
||||
attribute_name=attribute_name,
|
||||
)
|
||||
value = value.strip()
|
||||
if value.isdigit():
|
||||
value = int(value)
|
||||
|
||||
int_value = try_parse_int(value)
|
||||
if int_value is not None:
|
||||
attribute_effect = NumberAttributeEffect(
|
||||
name=attribute_name, operation=operation, value=value
|
||||
name=attribute_name, operation=operation, value=int_value
|
||||
)
|
||||
elif value.isdecimal():
|
||||
value = float(value)
|
||||
else:
|
||||
float_value = try_parse_float(value)
|
||||
if float_value is not None:
|
||||
attribute_effect = NumberAttributeEffect(
|
||||
name=attribute_name, operation=operation, value=value
|
||||
name=attribute_name, operation=operation, value=float_value
|
||||
)
|
||||
else:
|
||||
attribute_effect = StringAttributeEffect(
|
||||
|
@ -293,120 +466,66 @@ def generate_world(
|
|||
name: str,
|
||||
theme: str,
|
||||
room_count: int | None = None,
|
||||
max_rooms: int = 5,
|
||||
systems: List[GameSystem] = [],
|
||||
) -> World:
|
||||
room_count = room_count or randint(3, max_rooms)
|
||||
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")
|
||||
|
||||
existing_actors: List[str] = []
|
||||
existing_items: List[str] = []
|
||||
existing_rooms: List[str] = []
|
||||
|
||||
# generate the rooms
|
||||
rooms = []
|
||||
for i in range(room_count):
|
||||
existing_rooms = [room.name for room in rooms]
|
||||
|
||||
try:
|
||||
room = generate_room(agent, theme, existing_rooms=existing_rooms)
|
||||
generate_system_attributes(agent, theme, room, systems)
|
||||
broadcast_generated(entity=room)
|
||||
rooms.append(room)
|
||||
existing_rooms.append(room.name)
|
||||
except Exception:
|
||||
logger.exception("error generating room")
|
||||
continue
|
||||
|
||||
item_count = randint(1, 3)
|
||||
broadcast_generated(f"Generating {item_count} items for room: {room.name}")
|
||||
|
||||
for j in range(item_count):
|
||||
try:
|
||||
item = generate_item(
|
||||
agent,
|
||||
theme,
|
||||
dest_room=room.name,
|
||||
existing_items=existing_items,
|
||||
)
|
||||
generate_system_attributes(agent, theme, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
room.items.append(item)
|
||||
existing_items.append(item.name)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
actor_count = randint(1, 3)
|
||||
broadcast_generated(
|
||||
message=f"Generating {actor_count} actors for room: {room.name}"
|
||||
)
|
||||
|
||||
for j in range(actor_count):
|
||||
try:
|
||||
actor = generate_actor(
|
||||
agent,
|
||||
theme,
|
||||
dest_room=room.name,
|
||||
existing_actors=existing_actors,
|
||||
)
|
||||
generate_system_attributes(agent, theme, actor, systems)
|
||||
broadcast_generated(entity=actor)
|
||||
|
||||
room.actors.append(actor)
|
||||
existing_actors.append(actor.name)
|
||||
except Exception:
|
||||
logger.exception("error generating actor")
|
||||
continue
|
||||
|
||||
# generate the actor's inventory
|
||||
item_count = randint(0, 2)
|
||||
broadcast_generated(f"Generating {item_count} items for actor {actor.name}")
|
||||
|
||||
for k in range(item_count):
|
||||
try:
|
||||
item = generate_item(
|
||||
agent,
|
||||
theme,
|
||||
dest_room=room.name,
|
||||
existing_items=existing_items,
|
||||
)
|
||||
generate_system_attributes(agent, theme, item, systems)
|
||||
broadcast_generated(entity=item)
|
||||
|
||||
actor.items.append(item)
|
||||
existing_items.append(item.name)
|
||||
except Exception:
|
||||
logger.exception("error generating item")
|
||||
|
||||
# generate portals to link the rooms together
|
||||
for room in rooms:
|
||||
directions = ["north", "south", "east", "west"]
|
||||
for direction in directions:
|
||||
if direction in room.portals:
|
||||
logger.debug(f"Room {room.name} already has a {direction} portal")
|
||||
continue
|
||||
|
||||
opposite_direction = OPPOSITE_DIRECTIONS[direction]
|
||||
|
||||
if randint(0, 1):
|
||||
dest_room = choice([r for r in rooms if r.name != room.name])
|
||||
|
||||
# make sure not to create duplicate links
|
||||
if room.name in dest_room.portals.values():
|
||||
logger.debug(
|
||||
f"Room {dest_room.name} already has a portal to {room.name}"
|
||||
num_portals = randint(
|
||||
world_config.size.portals.min, world_config.size.portals.max
|
||||
)
|
||||
|
||||
if len(room.portals) >= num_portals:
|
||||
logger.info(f"room {room.name} already has enough portals")
|
||||
continue
|
||||
|
||||
if opposite_direction in dest_room.portals:
|
||||
logger.debug(
|
||||
f"Room {dest_room.name} already has a {opposite_direction} portal"
|
||||
broadcast_generated(
|
||||
message=f"Generating {num_portals} portals for room: {room.name}"
|
||||
)
|
||||
continue
|
||||
|
||||
# create bidirectional links
|
||||
room.portals[direction] = dest_room.name
|
||||
dest_room.portals[OPPOSITE_DIRECTIONS[direction]] = room.name
|
||||
for i 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]
|
||||
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]
|
||||
)
|
||||
|
||||
try:
|
||||
outgoing_portal, incoming_portal = generate_portals(
|
||||
agent, theme, room, dest_room
|
||||
)
|
||||
|
||||
room.portals.append(outgoing_portal)
|
||||
dest_room.portals.append(incoming_portal)
|
||||
except Exception:
|
||||
logger.exception("error generating portal")
|
||||
continue
|
||||
|
||||
# ensure actors act in a stable order
|
||||
order = [actor.name for room in rooms for actor in room.actors]
|
||||
|
|
|
@ -91,12 +91,6 @@ def parse_args():
|
|||
default="",
|
||||
help="Some additional flavor text for the generated world",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-rooms",
|
||||
default=6,
|
||||
type=int,
|
||||
help="The maximum number of rooms to generate",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--optional-actions",
|
||||
action="store_true",
|
||||
|
@ -216,7 +210,6 @@ def load_or_generate_world(args, players, systems, world_prompt: WorldPrompt):
|
|||
args.world,
|
||||
world_prompt.theme,
|
||||
room_count=args.rooms,
|
||||
max_rooms=args.max_rooms,
|
||||
)
|
||||
save_world(world, world_file)
|
||||
|
||||
|
|
|
@ -47,11 +47,27 @@ class ServerConfig:
|
|||
websocket: WebsocketServerConfig
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorldSizeConfig:
|
||||
actor_items: Range
|
||||
item_effects: Range
|
||||
portals: Range
|
||||
room_actors: Range
|
||||
room_items: Range
|
||||
rooms: Range
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorldConfig:
|
||||
size: WorldSizeConfig
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
bot: BotConfig
|
||||
render: RenderConfig
|
||||
server: ServerConfig
|
||||
world: WorldConfig
|
||||
|
||||
|
||||
DEFAULT_CONFIG = Config(
|
||||
|
@ -69,5 +85,15 @@ DEFAULT_CONFIG = Config(
|
|||
},
|
||||
steps=Range(min=30, max=30),
|
||||
),
|
||||
server=ServerConfig(websocket=WebsocketServerConfig(host="localhost", port=8000)),
|
||||
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),
|
||||
portals=Range(min=1, max=3),
|
||||
rooms=Range(min=3, max=6),
|
||||
room_actors=Range(min=1, max=3),
|
||||
room_items=Range(min=1, max=3),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
|
|
@ -67,6 +67,17 @@ class Actor(BaseModel):
|
|||
type: Literal["actor"] = "actor"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Portal(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
destination: str
|
||||
actions: Actions = Field(default_factory=dict)
|
||||
attributes: Attributes = Field(default_factory=dict)
|
||||
id: str = Field(default_factory=uuid)
|
||||
type: Literal["portal"] = "portal"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Room(BaseModel):
|
||||
name: str
|
||||
|
@ -75,7 +86,7 @@ class Room(BaseModel):
|
|||
actions: Actions = Field(default_factory=dict)
|
||||
attributes: Attributes = Field(default_factory=dict)
|
||||
items: List[Item] = Field(default_factory=list)
|
||||
portals: Dict[str, str] = Field(default_factory=dict)
|
||||
portals: List[Portal] = Field(default_factory=list)
|
||||
id: str = Field(default_factory=uuid)
|
||||
type: Literal["room"] = "room"
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ def launch_server(config: WebsocketServerConfig):
|
|||
|
||||
|
||||
async def server_main():
|
||||
async with websockets.serve(handler, "", 8001):
|
||||
async with websockets.serve(handler, server_config.host, server_config.port):
|
||||
logger.info("websocket server started")
|
||||
await asyncio.Future() # run forever
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ def simulate_world(
|
|||
|
||||
room_actors = [actor.name for actor in room.actors]
|
||||
room_items = [item.name for item in room.items]
|
||||
room_directions = list(room.portals.keys())
|
||||
room_directions = [portal.name for portal in room.portals]
|
||||
|
||||
actor_attributes = format_attributes(actor)
|
||||
actor_items = [item.name for item in actor.items]
|
||||
|
|
|
@ -40,8 +40,8 @@ def graph_world(world: World, step: int):
|
|||
for room in world.rooms:
|
||||
room_label = "\n".join([room.name, *[actor.name for actor in room.actors]])
|
||||
graph.node(room.name, room_label)
|
||||
for direction, destination in room.portals.items():
|
||||
graph.edge(room.name, destination, label=direction)
|
||||
for portal in room.portals:
|
||||
graph.edge(room.name, portal.destination, label=portal.name)
|
||||
|
||||
graph_path = path.dirname(world.name)
|
||||
graph.render(directory=graph_path)
|
||||
|
|
Loading…
Reference in New Issue