source: trunk/game/scripts/dialogue.py @ 504

Revision 479, 9.8 KB checked in by maximinus_parpg, 10 years ago (diff)

More code cleanup.
Some preperation for sorting out the beer quest.

  • Property svn:eol-style set to native
Line 
1#!/usr/bin/python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import logging
19import itertools
20
21class EndException(Exception):
22    """EndException is used to bail out from a deeply nested
23       runSection/continueWithResponse call stack and end the
24       conversation"""
25    pass
26
27class ResponseException(Exception):
28    """ResponseException is used to bail out from a deeply nested
29       runSection/continueWithResponse call stack and allow the user to
30       specify a response"""
31    pass
32
33class BackException(Exception):
34    """BackException is used to bail out from a deeply nested
35       runSection/continueWithResponse call stack and rewind the section
36       stack"""
37    pass
38
39class DialogueEngine(object):
40    def __init__(self, obj, callbacks={}, state={}):
41        """A very simple dialogue engine for a game.
42           d = DialogueEngine(tree, callbacks)
43           d = DialogueEngine('screenplay.yaml', callbacks)"""
44        if isinstance(obj, dict):
45            self.tree = obj
46        elif isinstance(obj, str):
47            import yaml
48            self.tree = yaml.load(file(obj))
49
50        logging.basicConfig(level=logging.INFO)
51
52        self.callbacks = callbacks
53        self.state = state
54        self.section_stack = []
55
56    def run(self):
57        """Start running the dialogue engine.
58           @returns: list of lists (if requesting a response)
59           @returns: None (if at the end of the script)"""
60        start_section = self.tree['START']
61
62        npc_name_cb = self.callbacks.get('npc_name')
63        if npc_name_cb:
64            npc_name_cb(self.tree['NPC'])
65           
66        npc_avatar_cb = self.callbacks.get('npc_avatar')
67        if npc_avatar_cb:
68            npc_avatar_cb(self.tree['AVATAR'])
69
70        try:
71            self.runSection(start_section)
72        except EndException:
73            # we stopped talking to the NPC
74            logging.debug("Reached the end")
75            end_cb = self.callbacks.get('end')
76            if end_cb:
77                end_cb()
78            return
79        except ResponseException, e:
80            return e.args[0]
81        except BackException, e:
82            self.section_stack.pop(-1)
83            try:
84                self.runSection(self.section_stack[-1])
85                return e
86            except ResponseException, e:
87                return e.args[0]
88
89    def getSection(self, section_name):
90        """Return a section object.
91           @type section_name: string
92           @param section_name: The section to get
93           @return: dict"""
94        return self.tree['SECTIONS'][section_name]
95
96    def reply(self, response):
97        """After being prompted to provide a response, reply is called to
98           submit a response.
99           @type choice: int
100           @param choice: the index of the response to submit
101           @return: list of lists (if requesting a response)
102           @return: None (if at the end of the script)"""
103        while True:
104            try:
105                if response is not None:
106                    self.continueWithResponse(self.section_stack[-1], \
107                                                response)
108                else:
109                    self.runSection(self.section_stack[-1])
110            except ResponseException, e:
111                logging.debug("Got response exception %s" % (e.args, ))
112                return e.args[0]
113            except BackException, e:
114                # e.args contains the section to jump back to
115                if e.args:
116                    stack = self.section_stack[:]
117                    stack.reverse()
118                    for i, s in enumerate(stack):
119                        if s == e.args[0]:
120                            # remove the end of the section stack up to desired
121                            # section
122                            del self.section_stack[-i:]
123                            break
124                else:
125                    self.section_stack.pop(-1)
126                response = None
127                continue
128            except EndException:
129                end_cb = self.callbacks.get('end')
130                if end_cb:
131                    end_cb()
132                logging.debug("Reached the end")
133                return
134
135    def continueWithResponse(self, section_name, response):
136        """Reply to a response in a section and continue executing dialogue
137           script
138           @type section_name: str
139           @param section_name: the section to continue
140           @type response: int
141           @param response: the index [0,n-1] of the desired response
142           @raises: EndException on end of script
143           @raises: BackException on "back" reply
144           @return: None"""
145        state = self.state
146        if len(self.section_stack) > 1:
147            if self.section_stack[-1] == self.section_stack[-2]:
148                self.section_stack.pop(-1)
149
150        for command in itertools.cycle(self.getSection(section_name)):
151            if not command.get('responses'):
152                continue
153
154            responses = []
155            for r in command.get('responses'):
156                cond = r[2:]
157                try:
158                    if not cond or eval(cond[0], state, {}):
159                        responses.append(r)
160                except Exception, e:
161                    print "Error in response conditional: %s" % (cond[0],)
162                    #raise e
163
164            section = responses[response][1]
165            logging.debug("User chose %s" % (section,))
166
167            if section == "back":
168                raise BackException()
169            elif section.startswith("back "):
170                raise BackException(section[5:])
171            elif section == "end":
172                raise EndException()
173
174            self.runSection(section)
175
176    def runSection(self, section_name):
177        """Run a section
178           @type section_name: string
179           @param section_name: The section to run
180           @return: None
181           @raises: EndException on end of script
182           @raises: BackException on "back" reply"""
183
184        state = self.state
185        self.section_stack.append(section_name)
186
187        if len(self.section_stack) > 1:
188            if self.section_stack[-1] == self.section_stack[-2]:
189                self.section_stack.pop(-1)
190
191        logging.debug("In runSection %s %s" % (section_name, \
192                                               self.section_stack,))
193        for command in itertools.cycle(self.getSection(section_name)):
194            logging.debug("command was %s" % (command,))
195            if command.get("say"):
196                if self.callbacks.get('say'):
197                    self.callbacks["say"](state, command["say"])
198
199            elif command.get("responses"):
200                responses = []
201                for response in command.get('responses'):
202                    cond = response[2:]
203                    if not cond or eval(cond[0], state, {}):
204                        responses.append(response)
205                if self.callbacks.get("responses"):
206                    self.callbacks["responses"](state, responses)
207
208                raise ResponseException(responses)
209
210            elif command.get("start_quest"):
211                self.callbacks["start_quest"](state,
212                        command.get("start_quest"))
213
214            elif command.get("complete_quest"):
215                self.callbacks["complete_quest"](state,
216                        command.get("complete_quest"))
217
218            elif command.get("delete_quest"):
219                self.callbacks["delete_quest"](state,
220                        command.get("delete_quest"))
221
222            elif command.get("increase_value"):
223                self.callbacks["increase_value"](state,
224                        command.get("increase_value")["quest"],
225                        command.get("increase_value")["variable"],
226                        command.get("increase_value")["value"])
227
228            elif command.get("decrease_value"):
229                self.callbacks["decrease_value"](state,
230                        command.get("decrease_value")["quest"],
231                        command.get("decrease_value")["variable"],
232                        command.get("decrease_value")["value"])
233
234            elif command.get("set_value"):
235                self.callbacks["set_value"](state, 
236                        command.get("set_value")["quest"],
237                        command.get("set_value")["variable"],
238                        command.get("set_value")["value"])
239
240            elif command.get("meet"):
241                self.callbacks["meet"](state, command.get("meet"))
242
243            elif command.get("get_stuff"):
244                self.callbacks["get_stuff"](state, command.get("get_stuff"))
245
246            elif command.get("give_stuff"):
247                self.callbacks["give_stuff"](state, command.get("give_stuff"))
248
249            elif command.get("dialogue"):
250                command = command.get("dialogue")
251                if command == "end":
252                    # indicate we"d like to stop talking
253                    raise EndException
254                elif command == "back":
255                    raise BackException()
256                elif command.startswith("back "):
257                    raise BackException(command[5:])
258                else:
259                    raise Exception("Unknown command %s" % (command,))
260
261            else:
262                raise Exception("Unknown command %s %s" % (command,))
Note: See TracBrowser for help on using the repository browser.