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 | from optparse import OptionParser |
---|
24 | |
---|
25 | from parpg.dialogueparsers import YamlDialogueParser |
---|
26 | from parpg.dialogueprocessor import DialogueProcessor |
---|
27 | from parpg.quest_engine import QuestEngine |
---|
28 | from parpg.settings import Settings |
---|
29 | |
---|
30 | class MockPlayerCharacter(object): |
---|
31 | """ |
---|
32 | Mock object representing the player character. |
---|
33 | """ |
---|
34 | def __init__(self): |
---|
35 | """ |
---|
36 | Initialize a new L{MockPlayerCharacter} instance. |
---|
37 | |
---|
38 | @ivar inventory: set of items carried by the L{MockPlayerCharacter}. |
---|
39 | @ivar known_npcs: set of IDs for the NPCs the player has met. |
---|
40 | """ |
---|
41 | self.inventory = set(['beer']) |
---|
42 | self.known_npcs = set() |
---|
43 | |
---|
44 | def meet(self, npc_id): |
---|
45 | """ |
---|
46 | Add an NPC to the list of NPCs known by the player. |
---|
47 | |
---|
48 | @param npc: ID of the NPC to add. |
---|
49 | @type npc: str |
---|
50 | """ |
---|
51 | if npc_id in self.known_npcs: |
---|
52 | raise RuntimeError("I already know {0}".format(npc_id)) |
---|
53 | self.known_npcs.add(npc_id) |
---|
54 | |
---|
55 | def met(self, npc): |
---|
56 | return npc in self.known_npcs |
---|
57 | |
---|
58 | |
---|
59 | class MockBeer(object): |
---|
60 | """Mock object representing a 'beer' item.""" |
---|
61 | quality = 3 |
---|
62 | |
---|
63 | |
---|
64 | class MockBox(object): |
---|
65 | """Mock box object than can be opened or closed.""" |
---|
66 | def __init__(self): |
---|
67 | """ |
---|
68 | Initialize a new {MockBox} instance. |
---|
69 | |
---|
70 | @ivar opened: whether the L{MockBox} has been "opened". |
---|
71 | """ |
---|
72 | self.opened = False |
---|
73 | |
---|
74 | def open(self): |
---|
75 | """Set the opened state of the L{MockBox} to True.""" |
---|
76 | self.opened = True |
---|
77 | |
---|
78 | def close(self): |
---|
79 | """Set the opened state of the L{MockBox} to False.""" |
---|
80 | self.opened = False |
---|
81 | |
---|
82 | |
---|
83 | def selectDialogueFile(settings): |
---|
84 | """ |
---|
85 | List all YAML dialogue files in the dialogue directory and prompt the user |
---|
86 | to select one for testing. |
---|
87 | """ |
---|
88 | dialogue_dir = os.path.join(settings.system_path, |
---|
89 | settings.parpg.DialoguesDirectory) |
---|
90 | dialogue_files = [file_name for file_name in os.listdir(dialogue_dir) |
---|
91 | if file_name.endswith('.yaml')] |
---|
92 | for index, file_name in enumerate(dialogue_files): |
---|
93 | print('{0} - {1}'.format(index, file_name)) |
---|
94 | while True: |
---|
95 | str_input = raw_input("> ") |
---|
96 | try: |
---|
97 | choice_n = int(str_input) |
---|
98 | selected_file_name = dialogue_files[choice_n] |
---|
99 | except (ValueError, IndexError): |
---|
100 | print(('"{0}" is an invalid selection, please choose a number ' |
---|
101 | 'between 0 and {1}').format(str_input, |
---|
102 | len(dialogue_files) - 1)) |
---|
103 | continue |
---|
104 | else: |
---|
105 | break |
---|
106 | |
---|
107 | selected_file_path = os.path.join(dialogue_dir, selected_file_name) |
---|
108 | return selected_file_path |
---|
109 | |
---|
110 | def chooseReply(dialogue_responses): |
---|
111 | """ |
---|
112 | Prompt the user to choose a L{DialogueResponse} from a list of valid |
---|
113 | responses. |
---|
114 | |
---|
115 | @param dialogue_responses: valid responses to choose from |
---|
116 | @type dialogue_responses: list of L{DialogueResponses} |
---|
117 | """ |
---|
118 | while (True): |
---|
119 | print('Available responses:') |
---|
120 | for index, response in enumerate(dialogue_responses): |
---|
121 | print('{0} - {1}'.format(index, response.text)) |
---|
122 | try: |
---|
123 | chosen_response_n = int(raw_input('Choose a response number> ')) |
---|
124 | chosen_response = dialogue_responses[chosen_response_n] |
---|
125 | except (ValueError, IndexError): |
---|
126 | print(('\ninvalid response, please enter an integer between 0 ' |
---|
127 | 'and {0}').format(len(dialogue_responses) - 1)) |
---|
128 | else: |
---|
129 | break |
---|
130 | |
---|
131 | return chosen_response |
---|
132 | |
---|
133 | def processDialogue(dialogue, game_state): |
---|
134 | """ |
---|
135 | Process a L{Dialogue} until the user selects a response that ends it. |
---|
136 | |
---|
137 | @param dialogue: dialogue data to process. |
---|
138 | @type dialogue: L{Dialogue} |
---|
139 | @param game_state: objects that should be made available for response |
---|
140 | conditional testing. |
---|
141 | @type game_state: dict of objects |
---|
142 | """ |
---|
143 | npc_name = dialogue.npc_name |
---|
144 | dialogue_processor = DialogueProcessor(dialogue, game_state) |
---|
145 | dialogue_processor.initiateDialogue() |
---|
146 | while dialogue_processor.in_dialogue: |
---|
147 | responses = dialogue_processor.continueDialogue() |
---|
148 | current_dialogue_section = \ |
---|
149 | dialogue_processor.getCurrentDialogueSection() |
---|
150 | dialogue_text = current_dialogue_section.text |
---|
151 | # Indent dialogue text after the first line. |
---|
152 | dialogue_text = dialogue_text.replace('\n', '\n ') |
---|
153 | print('\n{0}: {1}'.format(npc_name, dialogue_text)) |
---|
154 | chosen_reply = chooseReply(responses) |
---|
155 | dialogue_processor.reply(chosen_reply) |
---|
156 | |
---|
157 | def main(): |
---|
158 | parser = OptionParser(description='Script for testing dialogue files') |
---|
159 | parser.add_option('-s', '--settings', nargs=2, |
---|
160 | dest='paths', default=['.'], |
---|
161 | help='One or more paths to load settings from') |
---|
162 | parser.add_option('-f', '--logfile', |
---|
163 | help='Name of log file to save to') |
---|
164 | parser.add_option('-l', '--loglevel', default='critical', |
---|
165 | help='desired output level for log file') |
---|
166 | |
---|
167 | opts, args = parser.parse_args() |
---|
168 | |
---|
169 | settings = Settings(*opts.paths) |
---|
170 | |
---|
171 | levels = {'debug': logging.DEBUG, |
---|
172 | 'info': logging.INFO, |
---|
173 | 'warning': logging.WARNING, |
---|
174 | 'error': logging.ERROR, |
---|
175 | 'critical': logging.CRITICAL} |
---|
176 | |
---|
177 | #TODO: setup formating |
---|
178 | logging.basicConfig(filename=opts.logfile, level=levels[opts.loglevel]) |
---|
179 | logger = logging.getLogger('dialogue_demo') |
---|
180 | |
---|
181 | try: |
---|
182 | dialogue_file_path = args[0] |
---|
183 | except IndexError: |
---|
184 | logger.info('No dialogue file provided, generating a list..') |
---|
185 | dialogue_file_path = selectDialogueFile(settings) |
---|
186 | |
---|
187 | game_state = { |
---|
188 | 'quest': QuestEngine('quests'), |
---|
189 | 'pc': MockPlayerCharacter(), |
---|
190 | 'box': MockBox(), |
---|
191 | 'beer': MockBeer() |
---|
192 | } |
---|
193 | dialogue_parser = YamlDialogueParser() |
---|
194 | with file(dialogue_file_path, 'r') as dialogue_file: |
---|
195 | dialogue = dialogue_parser.load(dialogue_file) |
---|
196 | processDialogue(dialogue, game_state) |
---|
197 | |
---|
198 | if __name__ == "__main__": |
---|
199 | main() |
---|