1 | #!/usr/bin/env python2 |
---|
2 | # This file is part of PARPG. |
---|
3 | # |
---|
4 | # PARPG is free software: you can redistribute it and/or modify |
---|
5 | # it under the terms of the GNU General Public License as published by |
---|
6 | # the Free Software Foundation, either version 3 of the License, or |
---|
7 | # (at your option) any later version. |
---|
8 | # |
---|
9 | # PARPG is distributed in the hope that it will be useful, |
---|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
12 | # GNU General Public License for more details. |
---|
13 | # |
---|
14 | # You should have received a copy of the GNU General Public License |
---|
15 | # along with PARPG. If not, see <http://www.gnu.org/licenses/>. |
---|
16 | """ |
---|
17 | A very simple demonstration of the dialogue engine used for testing dialogue |
---|
18 | files. |
---|
19 | """ |
---|
20 | import logging |
---|
21 | import os |
---|
22 | import sys |
---|
23 | |
---|
24 | from parpg.common.optionparser import OptionParser, OptionError |
---|
25 | from parpg.dialogueparsers import YamlDialogueParser |
---|
26 | from parpg.dialogueprocessor import DialogueProcessor |
---|
27 | from parpg.quest_engine import QuestEngine |
---|
28 | |
---|
29 | def setupLogging(): |
---|
30 | """Set various logging parameters for this module.""" |
---|
31 | logging.basicConfig(filename='dialogue_demo.log') |
---|
32 | setupLogging() |
---|
33 | |
---|
34 | PARPG_ROOT_DIR = os.path.dirname(__file__) |
---|
35 | """Absolute path to the root of the PARPG installation.""" |
---|
36 | DIALOGUE_DIR = os.path.join(PARPG_ROOT_DIR, 'dialogue') |
---|
37 | """Absolute path to the dialogue directory of the PARPG installation.""" |
---|
38 | USAGE_MESSAGE = '''\ |
---|
39 | usage: dialogue_demo.py [-h] [dialogue_file] |
---|
40 | Script for testing dialogue files. |
---|
41 | |
---|
42 | -h, --help Show this help message. |
---|
43 | dialogue_file YAML file containing a dialogue; if not specified, |
---|
44 | the user will be prompted to choose a dialogue |
---|
45 | file from the dialogue directory. |
---|
46 | ''' |
---|
47 | |
---|
48 | class MockPlayerCharacter(object): |
---|
49 | """ |
---|
50 | Mock object representing the player character. |
---|
51 | """ |
---|
52 | def __init__(self): |
---|
53 | """ |
---|
54 | Initialize a new L{MockPlayerCharacter} instance. |
---|
55 | |
---|
56 | @ivar inventory: set of items carried by the L{MockPlayerCharacter}. |
---|
57 | @ivar known_npcs: set of IDs for the NPCs the player has met. |
---|
58 | """ |
---|
59 | self.inventory = set(['beer']) |
---|
60 | self.known_npcs = set() |
---|
61 | |
---|
62 | def meet(self, npc_id): |
---|
63 | """ |
---|
64 | Add an NPC to the list of NPCs known by the player. |
---|
65 | |
---|
66 | @param npc: ID of the NPC to add. |
---|
67 | @type npc: str |
---|
68 | """ |
---|
69 | if npc_id in self.known_npcs: |
---|
70 | raise RuntimeError("I already know {0}".format(npc_id)) |
---|
71 | self.known_npcs.add(npc_id) |
---|
72 | |
---|
73 | def met(self, npc): |
---|
74 | return npc in self.known_npcs |
---|
75 | |
---|
76 | |
---|
77 | class MockBeer(object): |
---|
78 | """Mock object representing a 'beer' item.""" |
---|
79 | quality = 3 |
---|
80 | |
---|
81 | |
---|
82 | class MockBox(object): |
---|
83 | """Mock box object than can be opened or closed.""" |
---|
84 | def __init__(self): |
---|
85 | """ |
---|
86 | Initialize a new {MockBox} instance. |
---|
87 | |
---|
88 | @ivar opened: whether the L{MockBox} has been "opened". |
---|
89 | """ |
---|
90 | self.opened = False |
---|
91 | |
---|
92 | def open(self): |
---|
93 | """Set the opened state of the L{MockBox} to True.""" |
---|
94 | self.opened = True |
---|
95 | |
---|
96 | def close(self): |
---|
97 | """Set the opened state of the L{MockBox} to False.""" |
---|
98 | self.opened = False |
---|
99 | |
---|
100 | |
---|
101 | def selectDialogueFile(): |
---|
102 | """ |
---|
103 | List all YAML dialogue files in the dialogue directory and prompt the user |
---|
104 | to select one for testing. |
---|
105 | """ |
---|
106 | dialogue_files = [file_name for file_name in os.listdir(DIALOGUE_DIR) |
---|
107 | if file_name.endswith('.yaml')] |
---|
108 | for index, file_name in enumerate(dialogue_files): |
---|
109 | print('{0} - {1}'.format(index, file_name)) |
---|
110 | while True: |
---|
111 | str_input = raw_input("> ") |
---|
112 | try: |
---|
113 | choice_n = int(str_input) |
---|
114 | selected_file_name = dialogue_files[choice_n] |
---|
115 | except (ValueError, IndexError): |
---|
116 | print(('"{0}" is an invalid selection, please choose a number ' |
---|
117 | 'between 0 and {1}').format(str_input, |
---|
118 | len(dialogue_files) - 1)) |
---|
119 | continue |
---|
120 | else: |
---|
121 | break |
---|
122 | |
---|
123 | selected_file_path = os.path.join(DIALOGUE_DIR, selected_file_name) |
---|
124 | return selected_file_path |
---|
125 | |
---|
126 | def chooseReply(dialogue_responses): |
---|
127 | """ |
---|
128 | Prompt the user to choose a L{DialogueResponse} from a list of valid |
---|
129 | responses. |
---|
130 | |
---|
131 | @param dialogue_responses: valid responses to choose from |
---|
132 | @type dialogue_responses: list of L{DialogueResponses} |
---|
133 | """ |
---|
134 | while (True): |
---|
135 | print('Available responses:') |
---|
136 | for index, response in enumerate(dialogue_responses): |
---|
137 | print('{0} - {1}'.format(index, response.text)) |
---|
138 | try: |
---|
139 | chosen_response_n = int(raw_input('Choose a response number> ')) |
---|
140 | chosen_response = dialogue_responses[chosen_response_n] |
---|
141 | except (ValueError, IndexError): |
---|
142 | print(('\ninvalid response, please enter an integer between 0 ' |
---|
143 | 'and {0}').format(len(dialogue_responses) - 1)) |
---|
144 | else: |
---|
145 | break |
---|
146 | |
---|
147 | return chosen_response |
---|
148 | |
---|
149 | def processDialogue(dialogue, game_state): |
---|
150 | """ |
---|
151 | Process a L{Dialogue} until the user selects a response that ends it. |
---|
152 | |
---|
153 | @param dialogue: dialogue data to process. |
---|
154 | @type dialogue: L{Dialogue} |
---|
155 | @param game_state: objects that should be made available for response |
---|
156 | conditional testing. |
---|
157 | @type game_state: dict of objects |
---|
158 | """ |
---|
159 | npc_name = dialogue.npc_name |
---|
160 | dialogue_processor = DialogueProcessor(dialogue, game_state) |
---|
161 | dialogue_processor.initiateDialogue() |
---|
162 | while dialogue_processor.in_dialogue: |
---|
163 | responses = dialogue_processor.continueDialogue() |
---|
164 | current_dialogue_section = \ |
---|
165 | dialogue_processor.getCurrentDialogueSection() |
---|
166 | dialogue_text = current_dialogue_section.text |
---|
167 | # Indent dialogue text after the first line. |
---|
168 | dialogue_text = dialogue_text.replace('\n', '\n ') |
---|
169 | print('\n{0}: {1}'.format(npc_name, dialogue_text)) |
---|
170 | chosen_reply = chooseReply(responses) |
---|
171 | dialogue_processor.reply(chosen_reply) |
---|
172 | |
---|
173 | def main(argv=sys.argv): |
---|
174 | option_parser = OptionParser(usage=USAGE_MESSAGE) |
---|
175 | for option in option_parser: |
---|
176 | if (option in ['-h', '--help']): |
---|
177 | print(option_parser.usage) |
---|
178 | sys.exit(0) |
---|
179 | else: |
---|
180 | option_parser.error('unrecognized option "{0}"'.format(option)) |
---|
181 | try: |
---|
182 | dialogue_file_path = option_parser.get_next_prog_arg() |
---|
183 | except OptionError: |
---|
184 | dialogue_file_path = selectDialogueFile() |
---|
185 | game_state = { |
---|
186 | 'quest': QuestEngine('quests'), |
---|
187 | 'pc': MockPlayerCharacter(), |
---|
188 | 'box': MockBox(), |
---|
189 | 'beer': MockBeer() |
---|
190 | } |
---|
191 | dialogue_parser = YamlDialogueParser() |
---|
192 | with file(dialogue_file_path, 'r') as dialogue_file: |
---|
193 | dialogue = dialogue_parser.load(dialogue_file) |
---|
194 | processDialogue(dialogue, game_state) |
---|
195 | |
---|
196 | if __name__ == "__main__": |
---|
197 | main() |
---|