rename step to turn
This commit is contained in:
parent
dd0e4ed573
commit
87ed47324c
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
...
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> = {
|
||||
|
|
|
@ -50,6 +50,7 @@ export interface World {
|
|||
order: Array<string>;
|
||||
rooms: Array<Room>;
|
||||
theme: string;
|
||||
turn: number;
|
||||
}
|
||||
|
||||
// TODO: copy event types from server
|
||||
|
|
|
@ -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 }),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue