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.models.config import DEFAULT_CONFIG
from adventure.models.planning import CalendarEvent
@ -148,7 +148,7 @@ def check_calendar(count: int):
"""
count = min(count, character_config.event_limit)
current_turn = get_current_step()
current_turn = get_current_turn()
with action_context() as (_, action_character):
if len(action_character.planner.calendar.events) == 0:

View File

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

View File

@ -39,9 +39,9 @@ class SystemInitialize(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:
from adventure.context import (
get_system_data,
set_current_step,
set_current_turn,
set_dungeon_master,
set_system_data,
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",
)
parser.add_argument(
"--steps",
"--turns",
type=int_or_inf,
default=10,
help="The number of simulation steps to run",
help="The number of simulation turns to run",
)
parser.add_argument(
"--systems",
@ -223,7 +223,7 @@ def load_or_generate_world(
add_rooms = args.add_rooms
memory = {}
step = 0
turn = 0
# prepare an agent for the world builder
llm = agent_easy_connect()
@ -241,11 +241,11 @@ def load_or_generate_world(
with open(world_state_file, "r") as 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)
memory = state.memory
step = state.step
turn = state.turn
world = state.world
elif path.exists(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)
create_agents(world, memory=memory, players=players)
return (world, world_state_file, step)
return (world, world_state_file, turn)
def main():
@ -356,20 +356,20 @@ def main():
# load or generate the world
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
)
# 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")
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))
# hack: send a snapshot to the websocket server
if args.server:
server_system(world, world_step)
server_system(world, world_turn)
# create the DM
llm = agent_easy_connect()
@ -390,7 +390,7 @@ def main():
logger.debug("simulating world: %s", world)
simulate_world(
world,
steps=args.steps,
turns=args.turns,
actions=extra_actions,
systems=extra_systems,
)

View File

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

View File

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

View File

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

View File

@ -301,11 +301,11 @@ async def server_main():
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
id = uuid4().hex # TODO: should a server be allowed to generate event IDs?
json_state = {
**snapshot_world(world, step),
**snapshot_world(world, turn),
"id": id,
"type": "snapshot",
}

View File

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

View File

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

View File

@ -130,7 +130,7 @@ def update_attributes(
def update_logic(
world: World,
step: int,
turn: int,
data: Any | None = None,
*,
rules: LogicTable,
@ -190,7 +190,7 @@ def load_logic(filename: str):
logger.info("initialized logic system")
system_format = wraps(format_logic)(partial(format_logic, rules=logic_rules))
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)(
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
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.
2. Update any active quests.

View File

@ -80,7 +80,7 @@ export function App(props: AppProps) {
});
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)) {
const event = JSON.parse(lastMessage.data);
@ -107,6 +107,7 @@ export function App(props: AppProps) {
return;
case 'snapshot':
setWorld(event.world);
setTurn(event.turn);
break;
default:
// 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) {
const { event } = props;
const { step, world } = event;
const { turn, world } = event;
const { name, theme } = world;
return <ListItem alignItems="flex-start" ref={props.focusRef}>
<ListItemAvatar>
<Avatar>{step}</Avatar>
<Avatar>{turn}</Avatar>
</ListItemAvatar>
<ListItemText
primary={name}
@ -109,7 +109,7 @@ export function SnapshotEventItem(props: EventItemProps) {
variant="body2"
color="text.primary"
>
Step: {step}
Turn: {turn}
</Typography>
World Theme: {theme}
</Fragment>

View File

@ -22,7 +22,7 @@ export function formatResult(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> = {

View File

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

View File

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

View File

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