1
0
Fork 0

rename step to turn

This commit is contained in:
Sean Sube 2024-05-27 07:54:36 -05:00
parent dd0e4ed573
commit 87ed47324c
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
19 changed files with 95 additions and 77 deletions

View File

@ -1,4 +1,4 @@
from adventure.context import action_context, get_agent_for_character, get_current_step from adventure.context import action_context, get_agent_for_character, get_current_turn
from adventure.errors import ActionError from adventure.errors import ActionError
from adventure.models.config import DEFAULT_CONFIG from adventure.models.config import DEFAULT_CONFIG
from adventure.models.planning import CalendarEvent from adventure.models.planning import CalendarEvent
@ -148,7 +148,7 @@ def check_calendar(count: int):
""" """
count = min(count, character_config.event_limit) count = min(count, character_config.event_limit)
current_turn = get_current_step() current_turn = get_current_turn()
with action_context() as (_, action_character): with action_context() as (_, action_character):
if len(action_character.planner.calendar.events) == 0: if len(action_character.planner.calendar.events) == 0:

View File

@ -25,7 +25,7 @@ from adventure.utils.string import normalize_name
logger = getLogger(__name__) logger = getLogger(__name__)
# world context # world context
current_step = 0 current_turn = 0
current_world: World | None = None current_world: World | None = None
current_room: Room | None = None current_room: Room | None = None
current_character: Character | None = None current_character: Character | None = None
@ -140,8 +140,8 @@ def get_current_character() -> Character | None:
return current_character return current_character
def get_current_step() -> int: def get_current_turn() -> int:
return current_step return current_turn
def get_dungeon_master() -> Agent: def get_dungeon_master() -> Agent:
@ -180,9 +180,9 @@ def set_current_character(character: Character | None):
current_character = character current_character = character
def set_current_step(step: int): def set_current_turn(turn: int):
global current_step global current_turn
current_step = step current_turn = turn
def set_character_agent(name, character, agent): def set_character_agent(name, character, agent):

View File

@ -39,9 +39,9 @@ class SystemInitialize(Protocol):
class SystemSimulate(Protocol): class SystemSimulate(Protocol):
def __call__(self, world: World, step: int, data: Any | None = None) -> None: def __call__(self, world: World, turn: int, data: Any | None = None) -> None:
""" """
Simulate the world for the given step. Simulate the world for the given turn. If this system has stored data, it will be passed in.
""" """
... ...

View File

@ -32,7 +32,7 @@ load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
if True: if True:
from adventure.context import ( from adventure.context import (
get_system_data, get_system_data,
set_current_step, set_current_turn,
set_dungeon_master, set_dungeon_master,
set_system_data, set_system_data,
subscribe, subscribe,
@ -142,10 +142,10 @@ def parse_args():
help="The file to save the world state to. Defaults to $world.state.json, if not set", help="The file to save the world state to. Defaults to $world.state.json, if not set",
) )
parser.add_argument( parser.add_argument(
"--steps", "--turns",
type=int_or_inf, type=int_or_inf,
default=10, default=10,
help="The number of simulation steps to run", help="The number of simulation turns to run",
) )
parser.add_argument( parser.add_argument(
"--systems", "--systems",
@ -223,7 +223,7 @@ def load_or_generate_world(
add_rooms = args.add_rooms add_rooms = args.add_rooms
memory = {} memory = {}
step = 0 turn = 0
# prepare an agent for the world builder # prepare an agent for the world builder
llm = agent_easy_connect() llm = agent_easy_connect()
@ -241,11 +241,11 @@ def load_or_generate_world(
with open(world_state_file, "r") as f: with open(world_state_file, "r") as f:
state = WorldState(**load_yaml(f)) state = WorldState(**load_yaml(f))
set_current_step(state.step) set_current_turn(state.turn)
load_or_initialize_system_data(args, systems, state.world) load_or_initialize_system_data(args, systems, state.world)
memory = state.memory memory = state.memory
step = state.step turn = state.turn
world = state.world world = state.world
elif path.exists(world_file): elif path.exists(world_file):
logger.info(f"loading world from {world_file}") logger.info(f"loading world from {world_file}")
@ -276,7 +276,7 @@ def load_or_generate_world(
link_rooms(world_builder, world, systems, new_rooms) link_rooms(world_builder, world, systems, new_rooms)
create_agents(world, memory=memory, players=players) create_agents(world, memory=memory, players=players)
return (world, world_state_file, step) return (world, world_state_file, turn)
def main(): def main():
@ -356,20 +356,20 @@ def main():
# load or generate the world # load or generate the world
world_prompt = get_world_prompt(args) world_prompt = get_world_prompt(args)
world, world_state_file, world_step = load_or_generate_world( world, world_state_file, world_turn = load_or_generate_world(
args, players, extra_systems, world_prompt=world_prompt args, players, extra_systems, world_prompt=world_prompt
) )
# make sure the snapshot system runs last # make sure the snapshot system runs last
def snapshot_system(world: World, step: int, data: None = None) -> None: def snapshot_system(world: World, turn: int, data: None = None) -> None:
logger.info("taking snapshot of world state") logger.info("taking snapshot of world state")
save_world_state(world, step, world_state_file) save_world_state(world, turn, world_state_file)
extra_systems.append(GameSystem(name="snapshot", simulate=snapshot_system)) extra_systems.append(GameSystem(name="snapshot", simulate=snapshot_system))
# hack: send a snapshot to the websocket server # hack: send a snapshot to the websocket server
if args.server: if args.server:
server_system(world, world_step) server_system(world, world_turn)
# create the DM # create the DM
llm = agent_easy_connect() llm = agent_easy_connect()
@ -390,7 +390,7 @@ def main():
logger.debug("simulating world: %s", world) logger.debug("simulating world: %s", world)
simulate_world( simulate_world(
world, world,
steps=args.steps, turns=args.turns,
actions=extra_actions, actions=extra_actions,
systems=extra_systems, systems=extra_systems,
) )

View File

@ -58,7 +58,7 @@ class WorldSizeConfig:
@dataclass @dataclass
class WorldStepConfig: class WorldTurnConfig:
action_retries: int action_retries: int
planning_steps: int planning_steps: int
planning_retries: int planning_retries: int
@ -68,7 +68,7 @@ class WorldStepConfig:
class WorldConfig: class WorldConfig:
character: WorldCharacterConfig character: WorldCharacterConfig
size: WorldSizeConfig size: WorldSizeConfig
step: WorldStepConfig turn: WorldTurnConfig
@dataclass @dataclass
@ -109,7 +109,7 @@ DEFAULT_CONFIG = Config(
room_characters=IntRange(min=1, max=3), room_characters=IntRange(min=1, max=3),
room_items=IntRange(min=1, max=3), room_items=IntRange(min=1, max=3),
), ),
step=WorldStepConfig( turn=WorldTurnConfig(
action_retries=5, action_retries=5,
planning_steps=3, planning_steps=3,
planning_retries=3, planning_retries=3,

View File

@ -74,7 +74,7 @@ class World(BaseModel):
@dataclass @dataclass
class WorldState(BaseModel): class WorldState(BaseModel):
memory: Dict[str, List[str | Dict[str, str]]] memory: Dict[str, List[str | Dict[str, str]]]
step: int turn: int
world: World world: World
id: str = Field(default_factory=uuid) id: str = Field(default_factory=uuid)
type: Literal["world_state"] = "world_state" type: Literal["world_state"] = "world_state"

View File

@ -125,7 +125,7 @@ class SnapshotEvent(BaseModel):
world: Dict[str, Any] world: Dict[str, Any]
memory: Dict[str, List[Any]] memory: Dict[str, List[Any]]
step: int turn: int
id: str = Field(default_factory=uuid) id: str = Field(default_factory=uuid)
type: Literal["snapshot"] = "snapshot" type: Literal["snapshot"] = "snapshot"

View File

@ -301,11 +301,11 @@ async def server_main():
await asyncio.Future() # run forever await asyncio.Future() # run forever
def server_system(world: World, step: int, data: Any | None = None): def server_system(world: World, turn: int, data: Any | None = None):
global last_snapshot global last_snapshot
id = uuid4().hex # TODO: should a server be allowed to generate event IDs? id = uuid4().hex # TODO: should a server be allowed to generate event IDs?
json_state = { json_state = {
**snapshot_world(world, step), **snapshot_world(world, turn),
"id": id, "id": id,
"type": "snapshot", "type": "snapshot",
} }

View File

@ -34,11 +34,11 @@ from adventure.context import (
broadcast, broadcast,
get_character_agent_for_name, get_character_agent_for_name,
get_character_for_agent, get_character_for_agent,
get_current_step, get_current_turn,
get_current_world, get_current_world,
set_current_character, set_current_character,
set_current_room, set_current_room,
set_current_step, set_current_turn,
set_current_world, set_current_world,
set_game_systems, set_game_systems,
) )
@ -55,7 +55,7 @@ from adventure.utils.world import describe_entity, format_attributes
logger = getLogger(__name__) logger = getLogger(__name__)
step_config = DEFAULT_CONFIG.world.step turn_config = DEFAULT_CONFIG.world.turn
def world_result_parser(value, agent, **kwargs): def world_result_parser(value, agent, **kwargs):
@ -154,7 +154,7 @@ def prompt_character_action(
toolbox=action_toolbox, toolbox=action_toolbox,
) )
logger.debug(f"{character.name} step result: {result}") logger.debug(f"{character.name} action result: {result}")
if agent.memory: if agent.memory:
# TODO: make sure this is not duplicating memories and wasting space # TODO: make sure this is not duplicating memories and wasting space
agent.memory.append(result) agent.memory.append(result)
@ -173,9 +173,9 @@ def get_notes_events(character: Character, current_turn: int):
notes_prompt = "You have no recent notes.\n" notes_prompt = "You have no recent notes.\n"
if len(upcoming_events) > 0: if len(upcoming_events) > 0:
current_step = get_current_step() current_turn = get_current_turn()
events = [ events = [
f"{event.name} in {event.turn - current_step} turns" f"{event.name} in {event.turn - current_turn} turns"
for event in upcoming_events for event in upcoming_events
] ]
events = "\n".join(events) events = "\n".join(events)
@ -194,7 +194,7 @@ def prompt_character_think(
current_turn: int, current_turn: int,
max_steps: int | None = None, max_steps: int | None = None,
) -> str: ) -> str:
max_steps = max_steps or step_config.planning_steps max_steps = max_steps or turn_config.planning_steps
notes_prompt, events_prompt = get_notes_events(character, current_turn) notes_prompt, events_prompt = get_notes_events(character, current_turn)
@ -242,7 +242,7 @@ def prompt_character_think(
def simulate_world( def simulate_world(
world: World, world: World,
steps: float | int = inf, turns: float | int = inf,
actions: Sequence[Callable[..., str]] = [], actions: Sequence[Callable[..., str]] = [],
systems: Sequence[GameSystem] = [], systems: Sequence[GameSystem] = [],
): ):
@ -279,8 +279,8 @@ def simulate_world(
# simulate each character # simulate each character
for i in count(): for i in count():
current_step = get_current_step() current_turn = get_current_turn()
logger.info(f"simulating step {i} of {steps} (world step {current_step})") logger.info(f"simulating turn {i} of {turns} (world turn {current_turn})")
for character_name in world.order: for character_name in world.order:
character, agent = get_character_agent_for_name(character_name) character, agent = get_character_agent_for_name(character_name)
@ -299,13 +299,13 @@ def simulate_world(
# decrement effects on the character and remove any that have expired # decrement effects on the character and remove any that have expired
expire_effects(character) expire_effects(character)
expire_events(character, current_step) expire_events(character, current_turn)
# give the character a chance to think and check their planner # give the character a chance to think and check their planner
if agent.memory and len(agent.memory) > 0: if agent.memory and len(agent.memory) > 0:
try: try:
thoughts = prompt_character_think( thoughts = prompt_character_think(
room, character, agent, planner_toolbox, current_step room, character, agent, planner_toolbox, current_turn
) )
logger.debug(f"{character.name} thinks: {thoughts}") logger.debug(f"{character.name} thinks: {thoughts}")
except Exception: except Exception:
@ -313,17 +313,22 @@ def simulate_world(
f"error during planning for character {character.name}" f"error during planning for character {character.name}"
) )
try:
result = prompt_character_action( result = prompt_character_action(
room, character, agent, action_names, action_tools, current_step room, character, agent, action_names, action_tools, current_turn
)
result_event = ResultEvent(
result=result, room=room, character=character
) )
result_event = ResultEvent(result=result, room=room, character=character)
broadcast(result_event) broadcast(result_event)
except Exception:
logger.exception(f"error during action for character {character.name}")
for system in systems: for system in systems:
if system.simulate: if system.simulate:
system.simulate(world, current_step) system.simulate(world, current_turn)
set_current_step(current_step + 1) set_current_turn(current_turn + 1)
if i >= steps: if i >= turns:
logger.info("reached step limit at world step %s", current_step + 1) logger.info("reached turn limit at world turn %s", current_turn + 1)
break break

View File

@ -34,10 +34,10 @@ def create_agents(
set_character_agent(character.name, character, agent) set_character_agent(character.name, character, agent)
def graph_world(world: World, step: int): def graph_world(world: World, turn: int):
import graphviz import graphviz
graph_name = f"{path.basename(world.name)}-{step}" graph_name = f"{path.basename(world.name)}-{turn}"
graph = graphviz.Digraph(graph_name, format="png") graph = graphviz.Digraph(graph_name, format="png")
for room in world.rooms: for room in world.rooms:
characters = [character.name for character in room.characters] characters = [character.name for character in room.characters]
@ -50,8 +50,8 @@ def graph_world(world: World, step: int):
graph.render(directory=graph_path) graph.render(directory=graph_path)
def snapshot_world(world: World, step: int): def snapshot_world(world: World, turn: int):
# save the world itself, along with the step number of the memory of each agent # save the world itself, along with the turn number and the memory of each agent
json_world = RootModel[World](world).model_dump() json_world = RootModel[World](world).model_dump()
json_memory = {} json_memory = {}
@ -62,7 +62,7 @@ def snapshot_world(world: World, step: int):
return { return {
"world": json_world, "world": json_world,
"memory": json_memory, "memory": json_memory,
"step": step, "turn": turn,
} }
@ -94,9 +94,9 @@ def save_world(world, filename):
f.write(json_world) f.write(json_world)
def save_world_state(world, step, filename): def save_world_state(world, turn, filename):
graph_world(world, step) graph_world(world, turn)
json_state = snapshot_world(world, step) json_state = snapshot_world(world, turn)
with open(filename, "w") as f: with open(filename, "w") as f:
dump(json_state, f, default=world_json, indent=2) dump(json_state, f, default=world_json, indent=2)

View File

@ -130,7 +130,7 @@ def update_attributes(
def update_logic( def update_logic(
world: World, world: World,
step: int, turn: int,
data: Any | None = None, data: Any | None = None,
*, *,
rules: LogicTable, rules: LogicTable,
@ -190,7 +190,7 @@ def load_logic(filename: str):
logger.info("initialized logic system") logger.info("initialized logic system")
system_format = wraps(format_logic)(partial(format_logic, rules=logic_rules)) system_format = wraps(format_logic)(partial(format_logic, rules=logic_rules))
system_initialize = wraps(update_logic)( system_initialize = wraps(update_logic)(
partial(update_logic, step=0, rules=logic_rules, triggers=logic_triggers) partial(update_logic, turn=0, rules=logic_rules, triggers=logic_triggers)
) )
system_simulate = wraps(update_logic)( system_simulate = wraps(update_logic)(
partial(update_logic, rules=logic_rules, triggers=logic_triggers) partial(update_logic, rules=logic_rules, triggers=logic_triggers)

View File

@ -187,7 +187,7 @@ def generate_quests(agent: Agent, theme: str, entity: WorldEntity) -> None:
# TODO: generate one new quest # TODO: generate one new quest
def simulate_quests(world: World, step: int, data: QuestData | None = None) -> None: def simulate_quests(world: World, turn: int, data: QuestData | None = None) -> None:
""" """
1. Check for any completed quests. 1. Check for any completed quests.
2. Update any active quests. 2. Update any active quests.

View File

@ -80,7 +80,7 @@ export function App(props: AppProps) {
}); });
useEffect(() => { useEffect(() => {
const { setClientId, setActiveTurn, setPlayers, appendEvent, setWorld, world, clientId, setPlayerCharacter: setCharacter } = store.getState(); const { setClientId, setActiveTurn, setPlayers, appendEvent, setTurn, setWorld, world, clientId, setPlayerCharacter: setCharacter } = store.getState();
if (doesExist(lastMessage)) { if (doesExist(lastMessage)) {
const event = JSON.parse(lastMessage.data); const event = JSON.parse(lastMessage.data);
@ -107,6 +107,7 @@ export function App(props: AppProps) {
return; return;
case 'snapshot': case 'snapshot':
setWorld(event.world); setWorld(event.world);
setTurn(event.turn);
break; break;
default: default:
// this is not concerning, other events are kept in history and displayed // this is not concerning, other events are kept in history and displayed

View File

@ -92,12 +92,12 @@ export function ActionEventItem(props: EventItemProps) {
export function SnapshotEventItem(props: EventItemProps) { export function SnapshotEventItem(props: EventItemProps) {
const { event } = props; const { event } = props;
const { step, world } = event; const { turn, world } = event;
const { name, theme } = world; const { name, theme } = world;
return <ListItem alignItems="flex-start" ref={props.focusRef}> return <ListItem alignItems="flex-start" ref={props.focusRef}>
<ListItemAvatar> <ListItemAvatar>
<Avatar>{step}</Avatar> <Avatar>{turn}</Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={name} primary={name}
@ -109,7 +109,7 @@ export function SnapshotEventItem(props: EventItemProps) {
variant="body2" variant="body2"
color="text.primary" color="text.primary"
> >
Step: {step} Turn: {turn}
</Typography> </Typography>
World Theme: {theme} World Theme: {theme}
</Fragment> </Fragment>

View File

@ -22,7 +22,7 @@ export function formatResult(data: any) {
} }
export function formatWorld(data: any) { export function formatWorld(data: any) {
return `${data.world.theme} - ${data.step}`; return `${data.world.theme} - ${data.turn}`;
} }
export const formatters: Record<string, any> = { export const formatters: Record<string, any> = {

View File

@ -50,6 +50,7 @@ export interface World {
order: Array<string>; order: Array<string>;
rooms: Array<Room>; rooms: Array<Room>;
theme: string; theme: string;
turn: number;
} }
// TODO: copy event types from server // TODO: copy event types from server

View File

@ -35,10 +35,12 @@ export interface ClientState {
export interface WorldState { export interface WorldState {
players: Record<string, string>; players: Record<string, string>;
turn: Maybe<number>;
world: Maybe<World>; world: Maybe<World>;
// setters // setters
setPlayers: (players: Record<string, string>) => void; setPlayers: (players: Record<string, string>) => void;
setTurn: (turn: Maybe<number>) => void;
setWorld: (world: Maybe<World>) => void; setWorld: (world: Maybe<World>) => void;
} }
@ -105,8 +107,10 @@ export function createClientStore(): StateCreator<ClientState> {
export function createWorldStore(): StateCreator<WorldState> { export function createWorldStore(): StateCreator<WorldState> {
return (set) => ({ return (set) => ({
players: {}, players: {},
turn: undefined,
world: undefined, world: undefined,
setPlayers: (players) => set({ players }), setPlayers: (players) => set({ players }),
setTurn: (turn) => set({ turn }),
setWorld: (world) => set({ world }), setWorld: (world) => set({ world }),
}); });
} }

View File

@ -1,5 +1,5 @@
import { Maybe, doesExist } from '@apextoaster/js-utils'; import { Maybe, doesExist } from '@apextoaster/js-utils';
import { Card, CardContent, Typography } from '@mui/material'; import { Card, CardContent, Divider, Stack, Typography } from '@mui/material';
import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { TreeItem } from '@mui/x-tree-view/TreeItem';
import React from 'react'; import React from 'react';
@ -40,6 +40,7 @@ export function characterStateSelector(s: StoreState) {
export function worldStateSelector(s: StoreState) { export function worldStateSelector(s: StoreState) {
return { return {
turn: s.turn,
world: s.world, world: s.world,
setDetailEntity: s.setDetailEntity, setDetailEntity: s.setDetailEntity,
}; };
@ -121,7 +122,7 @@ export function RoomItem(props: { room: Room } & BaseEntityItemProps) {
export function WorldPanel(props: BaseEntityItemProps) { export function WorldPanel(props: BaseEntityItemProps) {
const { setPlayer } = props; const { setPlayer } = props;
const state = useStore(store, worldStateSelector); const state = useStore(store, worldStateSelector);
const { world, setDetailEntity } = state; const { turn, world, setDetailEntity } = state;
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
if (!doesExist(world)) { if (!doesExist(world)) {
@ -136,14 +137,20 @@ export function WorldPanel(props: BaseEntityItemProps) {
return <Card style={{ minHeight: 200, overflow: 'auto' }}> return <Card style={{ minHeight: 200, overflow: 'auto' }}>
<CardContent> <CardContent>
<Stack direction="column" spacing={2}>
<Typography gutterBottom variant="h5" component="div">{world.name}</Typography> <Typography gutterBottom variant="h5" component="div">{world.name}</Typography>
<Typography variant="body1"> <Typography variant="body1">
Theme: {world.theme} Theme: {world.theme}
</Typography> </Typography>
<Typography variant="body2">
Turn: {turn}
</Typography>
<Divider />
<SimpleTreeView> <SimpleTreeView>
<TreeItem itemId="world-graph" label="Graph" onClick={() => setDetailEntity(world)} /> <TreeItem itemId="world-graph" label="Graph" onClick={() => setDetailEntity(world)} />
{world.rooms.map((room) => <RoomItem key={room.name} room={room} setPlayer={setPlayer} />)} {world.rooms.map((room) => <RoomItem key={room.name} room={room} setPlayer={setPlayer} />)}
</SimpleTreeView> </SimpleTreeView>
</Stack>
</CardContent> </CardContent>
</Card>; </Card>;
} }

View File

@ -154,7 +154,7 @@ The snapshot event is fired at the end of each turn and contains a complete snap
type: "snapshot" type: "snapshot"
world: Dict world: Dict
memory: Dict memory: Dict
step: int turn: int
``` ```
This is primarily used to save the world state, but can also be used to sync clients and populate the world menu. This is primarily used to save the world state, but can also be used to sync clients and populate the world menu.