Changeset 706


Ignore:
Timestamp:
02/04/11 00:53:44 (8 years ago)
Author:
technomage
Message:

Patch by Technomage

  • Fixed a few minor kludges in various dialogue-related modules;
  • Added a dedent_chomp function to the scripts.common.utils module which removes common whitespace from a multiline string and removes newlines as appropriate to join sentences and paragraphs (essentially the same formatting as docstrings);
  • Cleaned up unittests in the test_dialogueprocessor.py module;
Location:
trunk/game
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/game/scripts/common/utils.py

    r627 r706  
    1515 
    1616import os, sys, fnmatch 
     17from textwrap import dedent 
    1718 
    1819def addPaths (*paths): 
     
    4647    return Setting(app_name = "PARPG", 
    4748                   settings_file = settings_file,  
    48                    settings_gui_xml = "")   
     49                   settings_gui_xml = "") 
     50 
     51def dedent_chomp(string): 
     52    """Remove common leading whitespace and chomp each non-blank line.""" 
     53    dedented_string = dedent(string).strip() 
     54    lines = [] 
     55    for line in dedented_string.splitlines(): 
     56        if not line or line.isspace(): 
     57            line = '\n\n' 
     58        else: 
     59            line = ''.join([line, ' ']) 
     60        lines.append(line) 
     61    result = ''.join(lines) 
     62    return result 
  • trunk/game/scripts/dialogue.py

    r699 r706  
    5959        self.avatar_path = avatar_path 
    6060        self.default_greeting = default_greeting 
    61         self.greetings = greetings 
     61        self.greetings = greetings if greetings is not None else [] 
    6262        self.sections = OrderedDict() 
    63         all_sections = sections + [default_greeting] + greetings 
     63        all_sections = [default_greeting] 
     64        if (greetings is not None): 
     65            all_sections += greetings 
     66        if (sections is not None): 
     67            all_sections += sections 
    6468        if (__debug__): 
    6569            section_ids = [section.id for section in all_sections] 
  • trunk/game/scripts/dialogueprocessor.py

    r699 r706  
    2121import logging 
    2222 
     23from scripts.common.utils import dedent_chomp 
     24 
    2325if (__debug__): 
     26    from collections import Sequence, MutableMapping 
    2427    from scripts.dialogue import Dialogue 
    2528 
     
    97100            self._dialogue = dialogue 
    98101         
    99         def fdel(self): 
    100             self._dialogue = None 
    101          
    102102        return locals() 
    103103    dialogue = property(**dialogue()) 
     
    107107            return self._dialogue_section_stack 
    108108         
    109         def fdel(self): 
    110             self._dialogue_section_stack = [] 
     109        def fset(self, new_value): 
     110            assert isinstance(new_value, Sequence) and not \ 
     111                   isinstance(new_value, basestring), \ 
     112                   'dialogue_section_stack must be a Sequence, not {0}'\ 
     113                   .format(new_value) 
     114            self._dialogue_section_stack = new_value 
    111115         
    112116        return locals() 
     
    117121            return self._game_state 
    118122         
    119         def fdel(self): 
    120             self._game_state = {} 
     123        def fset(self, new_value): 
     124            assert isinstance(new_value, MutableMapping),\ 
     125                   'game_state must be a MutableMapping, not {0}'\ 
     126                   .format(new_value) 
     127            self._game_state = new_value 
    121128         
    122129        return locals() 
     
    149156        self._in_dialogue = False 
    150157     
    151     def getRootDialogueSection(self): 
     158    def getDialogueGreeting(self): 
    152159        """ 
    153160        Evaluate the L{RootDialogueSections<RootDialogueSection>} conditions 
     
    157164        @return: Valid root dialogue section. 
    158165        @rtype: L{DialogueSection} 
     166         
     167        @raise: RuntimeError - evaluation of a DialogueGreeting condition fails 
     168            by raising an exception (e.g. due to a syntax error). 
    159169        """ 
    160170        dialogue = self.dialogue 
    161         root_dialogue_section = None 
     171        dialogue_greeting = None 
    162172        for greeting in dialogue.greetings: 
    163             if (eval(greeting.condition, self.game_state)): 
    164                 root_dialogue_section = greeting 
    165         if (root_dialogue_section is None): 
    166             root_dialogue_section = dialogue.default_greeting 
    167          
    168         return root_dialogue_section 
     173            try: 
     174                condition_met = eval(greeting.condition, self.game_state) 
     175            except Exception as exception: 
     176                error_message = dedent_chomp(''' 
     177                    exception raised in DialogueGreeting {id} condition: 
     178                    {exception} 
     179                ''').format(id=greeting.id, exception=exception) 
     180                self._logger.error(error_message) 
     181            if (condition_met): 
     182                dialogue_greeting = greeting 
     183        if (dialogue_greeting is None): 
     184            dialogue_greeting = dialogue.default_greeting 
     185         
     186        return dialogue_greeting 
    169187     
    170188    def initiateDialogue(self): 
     
    173191        the starting L{DialogueSection} onto the L{dialogue_section_stack}. 
    174192         
    175         @raise TypeError: Unable to determine the root L{DialogueSection} 
     193        @raise RuntimeError: Unable to determine the root L{DialogueSection} 
    176194            defined by the L{Dialogue}. 
    177195        """ 
    178196        if (self.in_dialogue): 
    179197            self.endDialogue() 
    180         dialogue = self.dialogue 
    181         try: 
    182             root_dialogue_section = self.getRootDialogueSection() 
    183         except (RuntimeError,) as error: 
    184             self._logger.error(str(error)) 
    185             raise TypeError(('unable to determine root DialogueSection for ' 
    186                              '{0}').format(dialogue)) 
    187         else: 
    188             self.dialogue_section_stack.append(root_dialogue_section) 
    189             self.in_dialogue = True 
    190             self._logger.info('initiated dialogue {0}'.format(dialogue)) 
     198        dialogue_greeting = self.getDialogueGreeting() 
     199        self.dialogue_section_stack.append(dialogue_greeting) 
     200        self.in_dialogue = True 
     201        self._logger.info('initiated dialogue {0}'.format(self.dialogue)) 
    191202     
    192203    def continueDialogue(self): 
     
    206217        """ 
    207218        if (not self.in_dialogue): 
    208             raise RuntimeError('dialogue has not be initiated via ' 
    209                                'initiateDialogue yet') 
     219            error_message = dedent_chomp(''' 
     220                dialogue has not be initiated via initiateDialogue yet 
     221            ''') 
     222            raise RuntimeError(error_message) 
    210223        current_dialogue_section = self.getCurrentDialogueSection() 
    211224        self.runDialogueActions(current_dialogue_section) 
     
    228241        """ 
    229242        if (not self.in_dialogue): 
    230             raise RuntimeError('getCurrentDialogueSection called but the ' 
    231                                'dialogue has not been initiated yet') 
     243            error_message = dedent_chomp(''' 
     244                getCurrentDialogueSection called but the dialogue has not been 
     245                initiated yet 
     246            ''') 
     247            raise RuntimeError(error_message) 
    232248        try: 
    233249            current_dialogue_section = self.dialogue_section_stack[-1] 
    234250        except IndexError: 
    235             raise RuntimeError('getCurrentDialogueSection called but no ' 
    236                                'DialogueSections are in the stack') 
     251            error_message = dedent_chomp(''' 
     252                getCurrentDialogueSection called but no DialogueSections are in 
     253                the stack 
     254            ''') 
     255            raise RuntimeError(error_message) 
    237256         
    238257        return current_dialogue_section 
     
    279298                condition_met = condition is None or \ 
    280299                                eval(condition, self.game_state) 
    281             except (Exception,) as error: 
    282                 self._logger.error( 
    283                     ('evaluation of condition "{0}" for {1} failed with ' 
    284                      'error: {2}').format(dialogue_response.condition, 
    285                                    dialogue_response, error) 
    286                 ) 
     300            except (Exception,) as exception: 
     301                error_message = dedent_chomp(''' 
     302                    evaluation of condition {condition} for {response} failed 
     303                    with error: {exception} 
     304                ''').format(condition=dialogue_response.condition, 
     305                            response=dialogue_response, exception=exception) 
     306                self._logger.error(error_message) 
    287307            else: 
    288308                self._logger.debug( 
     
    311331        """ 
    312332        if (not self.in_dialogue): 
    313             raise RuntimeError('reply cannot be called until the dialogue has ' 
    314                                'been initiated via initiateDialogue') 
     333            error_message = dedent_chomp(''' 
     334                reply cannot be called until the dialogue has been initiated 
     335                via initiateDialogue 
     336            ''') 
     337            raise RuntimeError(error_message) 
    315338        self._logger.info('replied with {0}'.format(dialogue_response)) 
    316339        # FIXME: Technomage 2010-12-11: What happens if runDialogueActions 
     
    320343        if (next_section_id == 'back'): 
    321344            if (len(self.dialogue_section_stack) == 1): 
    322                 raise RuntimeError('attempted to run goto: back action but ' 
    323                                    'stack does not contain a previous ' 
    324                                    'DialogueSection') 
     345                error_message = dedent_chomp(''' 
     346                    attempted to run goto: back action but stack does not 
     347                    contain a previous DialogueSection 
     348                ''') 
     349                raise RuntimeError(error_message) 
    325350            else: 
    326351                try: 
    327352                    self.dialogue_section_stack.pop() 
    328353                except (IndexError,): 
    329                     raise RuntimeError('attempted to run goto: back action ' 
    330                                        'but the stack was empty') 
     354                    error_message = dedent_chomp(''' 
     355                        attempted to run goto: back action but the stack was 
     356                        empty 
     357                    ''') 
     358                    raise RuntimeError(error_message) 
    331359                else: 
    332360                    self._logger.debug( 
     
    341369                    self.dialogue.sections[next_section_id] 
    342370            except KeyError: 
    343                 raise RuntimeError( 
    344                     ('"{0}" is not a recognized goto: action or ' 
    345                      'DialogueSection identifier').format(next_section_id) 
    346                 ) 
     371                error_message = dedent_chomp(''' 
     372                    {0} is not a recognized goto: action or DialogueSection 
     373                    identifier 
     374                ''').format(next_section_id) 
     375                raise RuntimeError(error_message) 
    347376            else: 
    348377                self.dialogue_section_stack.append(next_dialogue_section) 
     
    353382        L{DialogueProcessor}. 
    354383        """ 
    355         del self.dialogue_section_stack 
     384        self.dialogue_section_stack = [] 
    356385        self.in_dialogue = False 
  • trunk/game/tests/test_dialogueprocessor.py

    r699 r706  
    2222    import unittest 
    2323 
     24from scripts.common.utils import dedent_chomp 
    2425from scripts.dialogueprocessor import DialogueProcessor 
    2526# NOTE Technomage 2010-12-08: Using the dialogue data structures might be a 
     
    126127    def setUp(self): 
    127128        TestDialogueProcessor.setUp(self) 
    128         self.dialogue_processor = DialogueProcessor(self.dialogue, 
    129                                                     self.game_state) 
    130      
    131     def testInitiateDialogue_setsState(self): 
    132         """Test initiateDialogue correctly sets DialogueProcessor state""" 
     129        self.dialogue = Dialogue( 
     130            npc_name='Mr. NPC', 
     131            avatar_path='/some/path', 
     132            default_greeting=DialogueSection( 
     133                id_='greeting', 
     134                text='This is the one (and only) dialogue section.', 
     135                responses=[ 
     136                    DialogueResponse( 
     137                        text=dedent_chomp(''' 
     138                            A response that moves the dialogue to 
     139                            another_section. 
     140                        '''), 
     141                        next_section_id='another_section' 
     142                    ), 
     143                    DialogueResponse( 
     144                        text='A response that ends the dialogue.', 
     145                        next_section_id='end', 
     146                    ), 
     147                ], 
     148            ), 
     149            sections=[ 
     150                DialogueSection( 
     151                    id_='another_section', 
     152                    text='This is another section.', 
     153                    responses=[ 
     154                        DialogueResponse( 
     155                            text='A response that ends the dialogue', 
     156                            next_section_id='end', 
     157                        ) 
     158                    ], 
     159                ), 
     160            ] 
     161        ) 
     162        self.dialogue_processor = DialogueProcessor(self.dialogue, {}) 
     163     
     164    def testSetsState(self): 
     165        """initiateDialogue correctly sets DialogueProcessor state""" 
    133166        dialogue_processor = self.dialogue_processor 
    134167        dialogue_processor.initiateDialogue() 
    135168         
    136169        # Default root dialogue section should have been pushed onto the stack. 
    137         root_dialogue_section = self.dialogue.default_greeting 
     170        default_greeting = self.dialogue.default_greeting 
    138171        self.assertStateEqual(dialogue_processor, in_dialogue=True, 
    139172                              dialogue=self.dialogue, 
    140                               dialogue_section_stack=[root_dialogue_section]) 
    141  
     173                              dialogue_section_stack=[default_greeting]) 
     174     
     175    def testEndsExistingDialogue(self): 
     176        """initiateDialogue ends a previously initiated dialogue""" 
     177        dialogue_processor = self.dialogue_processor 
     178        dialogue_processor.initiateDialogue() 
     179        valid_responses = dialogue_processor.continueDialogue() 
     180        dialogue_processor.reply(valid_responses[0]) 
     181         
     182        # Sanity check. 
     183        assert dialogue_processor.in_dialogue 
     184        dialogue_processor.initiateDialogue() 
     185        default_greeting = self.dialogue.default_greeting 
     186        self.assertStateEqual(dialogue_processor, in_dialogue=True, 
     187                              dialogue=self.dialogue, 
     188                              dialogue_section_stack=[default_greeting]) 
    142189 
    143190class TestEndDialogue(TestDialogueProcessor): 
     
    148195                                                    self.game_state) 
    149196     
    150     def testEndDialogue_resetsState(self): 
    151         """Test endDialogue correctly resets DialogueProcessor state""" 
     197    def testResetsState(self): 
     198        """endDialogue correctly resets DialogueProcessor state""" 
    152199        dialogue_processor = self.dialogue_processor 
    153200        # Case: No dialogue initiated. 
     
    179226     
    180227    def testRunsDialogueActions(self): 
    181         """Test continueDialogue executes all DialogueActions""" 
     228        """continueDialogue executes all DialogueActions""" 
    182229        dialogue_processor = self.dialogue_processor 
    183230        dialogue_processor.continueDialogue() 
     
    188235     
    189236    def testReturnsValidResponses(self): 
    190         """Test continueDialogue returns list of valid DialogueResponses""" 
     237        """continueDialogue returns list of valid DialogueResponses""" 
    191238        dialogue_processor = self.dialogue_processor 
    192239        valid_responses = \ 
     
    204251 
    205252class TestGetRootDialogueSection(TestDialogueProcessor): 
    206     """Tests of the L{DialogueProcessor.getRootDialogueSection} method.""" 
     253    """Tests of the L{DialogueProcessor.getDialogueGreeting} method.""" 
    207254    def setUp(self): 
    208255        TestDialogueProcessor.setUp(self) 
     
    214261     
    215262    def testReturnsCorrectDialogueSection(self): 
    216         """getRootDialogueSection returns first section with true condition""" 
     263        """getDialogueGreeting returns first section with true condition""" 
    217264        dialogue_processor = self.dialogue_processor 
    218265        dialogue = self.dialogue 
    219         root_dialogue_section = dialogue_processor.getRootDialogueSection() 
     266        root_dialogue_section = dialogue_processor.getDialogueGreeting() 
    220267        expected_dialogue_section = dialogue.greetings[0] 
    221268        self.assertEqual(root_dialogue_section, expected_dialogue_section) 
     
    231278     
    232279    def testReturnsCorrectDialogueSection(self): 
    233         """Test getCurrentDialogueSection returns section at top of stack""" 
     280        """getCurrentDialogueSection returns section at top of stack""" 
    234281        dialogue_processor = self.dialogue_processor 
    235282        expected_dialogue_section = self.dialogue.default_greeting 
     
    262309     
    263310    def testExecutesDialogueActions(self): 
    264         """Test runDialogueActions correctly executes DialogueActions""" 
     311        """runDialogueActions correctly executes DialogueActions""" 
    265312        dialogue_processor = self.dialogue_processor 
    266313        # Case: DialogueSection 
     
    288335     
    289336    def testReturnsValidResponses(self): 
    290         """Test getValidResponses returns list of valid DialogueResponses""" 
     337        """getValidResponses returns list of valid DialogueResponses""" 
    291338        dialogue_processor = self.dialogue_processor 
    292339        valid_responses = \ 
     
    314361     
    315362    def testRaisesExceptionWhenNotInitiated(self): 
    316         """Test reply raises exception when called before initiateDialogue""" 
     363        """reply raises exception when called before initiateDialogue""" 
    317364        dialogue_processor = self.dialogue_processor 
    318365        # Sanity check: A dialogue must not have been initiated beforehand. 
     
    351398     
    352399    def testCorrectlyEndsDialogue(self): 
    353         """Test reply ends dialogue when DialogueResponse specifies 'end'""" 
     400        """reply ends dialogue when DialogueResponse specifies 'end'""" 
    354401        dialogue_processor = self.dialogue_processor 
    355402        dialogue_processor.initiateDialogue() 
Note: See TracChangeset for help on using the changeset viewer.