Mercurial > skaapsteker
view skaapsteker/dialogue.py @ 596:a1ca84c797fb
Drop trailing colon
author | Stefano Rivera <stefano@rivera.za.net> |
---|---|
date | Sun, 10 Apr 2011 19:44:20 +0200 |
parents | e648501c2eea |
children | a91b2e4400a5 |
line wrap: on
line source
import json from . import data from .engine import OpenDialog, AddSpriteEvent, ChangeScene class DSM(object): """Dialogue State Machine! Parameters ---------- json_filename : str Path to file under data/ that contains JSON description of state machine. world : object Something to allow states to introspect the game state with. """ def __init__(self, name, world, json_filename, state): self.name = name self.state = state self.world = world self._me = getattr(self.world.npcs, name) self.states = AttrDict() src = json.loads(data.load(json_filename).read(), encoding='utf-8') for state, state_src in src.iteritems(): pseudo_path = [json_filename, state] self.states[state] = DsmState(state, state_src, pseudo_path) assert self.state in self.states, "DSM must have start state %r" % (self.state,) def get_state(self): return self.states[self.state] def has_text(self): self.poke() return bool(self.states[self.state].text) def _switch_dialogue_to(self, npc_name): """Switch dialogue to another npc.""" OpenDialog.post(npc_name) def _drop_item(self, item, shift=(1, 0)): """Create a tail of the given type.""" to_level = self._me.level to_pos = self._me.pos to_pos = to_pos[0] + shift[0], to_pos[1] + shift[1] gamestate = self.world.gamestate() sprite = gamestate.create_item_sprite(item, to_level=to_level, to_pos=to_pos) AddSpriteEvent.post(sprite) def _end_game(self): """End the game""" from .cutscene import VictoryCutScene ChangeScene.post(VictoryCutScene(None, None)) def _make_locals(self): my_locals = { "state": self.states, "world": self.world, "npcs": self.world.npcs, "switch_to": self._switch_dialogue_to, "drop_item": self._drop_item, "end_game": self._end_game, } return my_locals def choices(self): my_locals = self._make_locals() state = self.states[self.state] return state.choices(my_locals) def event(self, ev): my_locals = self._make_locals() my_locals.update(ev.items) state = self.states[self.state] next_state = state.event(my_locals) if next_state is not None and next_state.name in self.states: self.states[self.state].leave(my_locals) self.state = next_state.name self._me.state = self.state self.states[self.state].enter(my_locals) def choice(self, i): self.event(DsmEvent(choice=i)) def auto_next(self): self.event(DsmEvent(auto_next=True)) def poke(self): # poke the current state to see if it feels like making # a transition. self.event(DsmEvent(poke=True)) class AttrDict(dict): def __getattr__(self, name): if name not in self: raise AttributeError("No attribute %r" % (name,)) return self[name] class DsmEvent(object): def __init__(self, choice=None, auto_next=False, poke=False): self.items = { "choice": choice, "auto_next": auto_next, "poke": poke, } class DsmState(object): """State within a DSM. """ def __init__(self, name, state_src, base_path): self.name = name self.text = state_src.get("text", None) self._choices = [] self.triggers = [] choices = state_src.get("choices", []) for i, choice in enumerate(choices): pseudo_path = base_path + ["choice-%d" % i] if "if" in choice: choice_if = compile(choice["if"], "<%s>" % ":".join(pseudo_path + ["if"]), "eval") else: choice_if = None self._choices.append((i, choice["text"], choice_if)) next_state_code = choice.get("next", None) if next_state_code is not None: self.triggers.append( Trigger("choice == %d" % i, next_state_code, pseudo_path)) events = state_src.get("events", []) for i, event in enumerate(events): pseudo_path = base_path + ["event-%d" % i] self.triggers.append(Trigger(event["matches"], event["next"], pseudo_path)) auto_next = state_src.get("auto_next", None) self.auto_next_text = state_src.get("auto_next_text", None) self.auto_next = False if auto_next is not None: self.auto_next = True assert not self._choices, "%s: auto_next and choices are not compatible" % ":".join(base_path) pseudo_path = base_path + ["auto_next"] self.triggers.append(Trigger("""auto_next""", auto_next, pseudo_path)) on_entry = state_src.get("on_entry", None) if on_entry is not None: self.on_entry = compile(on_entry, "<%s>" % ":".join(base_path + ["on_entry"]), "exec") else: self.on_entry = None on_exit = state_src.get("on_exit", None) if on_exit is not None: self.on_exit = compile(on_exit, "<%s>" % ":".join(base_path + ["on_exit"]), "exec") else: self.on_exit = None def __repr__(self): return "<%r name=%r>" % (self.__class__.__name__, self.name) def choices(self, my_locals): for i, text, choice_if in self._choices: if choice_if is None: yield i, text elif eval(choice_if, {}, my_locals.copy()): yield i, text def event(self, my_locals): for trigger in self.triggers: next_state = trigger.fire(my_locals) if next_state is not None: return next_state def enter(self, my_locals): if self.on_entry is not None: exec(self.on_entry, {}, my_locals.copy()) def leave(self, my_locals): if self.on_exit is not None: exec(self.on_exit, {}, my_locals.copy()) class Trigger(object): """Matches DSM events and triggers state transitions. """ def __init__(self, matches_code, next_state_code, pseudo_path): self._matches = compile(matches_code, "<%s>" % ":".join(pseudo_path + ["match"]), "eval") self._next_state = compile(next_state_code, "<%s>" % ":".join(pseudo_path + ["next"]), "eval") def fire(self, my_locals): if eval(self._matches, {}, my_locals.copy()): return eval(self._next_state, {}, my_locals.copy()) return None