Commits

dr0id committed e83a104

update mosuefollower docs
added more tests
re-enabled forcing of -O mode in symplehfsm_demo

Comments (0)

Files changed (4)

trunk/symplehfsm/examples/TestHFSM/symplehfsm_demo.py

     
     if __debug__:
         import os
-        # print("use 'python -O "+os.path.split(__file__)[1]+"' to run the demo!")
-        # return
+        print("use 'python -O "+os.path.split(__file__)[1]+"' to run the demo!")
+        return
     
     print_help()
             

trunk/symplehfsm/examples/mousefollower/mousefollower.py

 
 """
 Example usage of the symplehfsm statemachine for a game entity using pygame. See inline comments!
+
+
+A statemachine has certain input it has to process. Depending on the current state and the kind of input, the statemachine can might perform some actions whether it changes state or not. There are three types of actions: entry, exit and transition action. The entry and exit actions are bound to a state and are executed whenever that state is entered of left (also when an external self transition is done).
+
+So to use sympleHFSM you will have to define the input and the actions it can perform. The input are just symbols that are passed to the 'event_handle' method of the statemachine instance. This method will handle the input and perform the corresponding actions. Therefore you need to pass in an instance of the actions class you want to use at creation time of the statemachine instance (this way you can use different 'backends', especially for testing). The second thing the statemachine instance needs to know is the structure. By structure I mean the number of states that exists and their relationship between them. This is the parent-child relationship and of course the transitions between them.
+
+So lets go through the mousefollower example [2]_ from the examples in [1]_:
+
+The mousefollower example has a pretty simple statemachine: just three states: idle, following and  a parent state
+Best way to visualize and plan a statemachine is on paper, here is the drawing for the example:
+::
+
+            +--------------------------------------------------------------------+
+            | parent                                                             |
+   init --->|                                            update/following_update |
+            |                                             +------------------+   |
+            |                                             |                  |   |
+            |      +-----------------+    in_range/    +------------------+  |   |
+            |  *-->| idle            |---------------->| following        |<-+   |
+            |      | /idle_entry     |                 | /following_entry |      |
+            |      |                 |                 |                  |      |
+            |      |                 | out_of_range/   |                  |      |
+            |      |                 |<----------------|                  |      |
+   exit <---|      +-----------------+                 +------------------+      |
+            |                                                                    |
+            +--------------------------------------------------------------------+
+
+
+Now I will describe the different sections of the example:
+
+    1.  The input events are define. The mouse follower has a sensor that give the event 'in_range' if the mouse is in 
+        range. Otherwise it will generate an 'out_of_range' event. There is also the 'update' event because it should 
+        behave differently depending in which state it is. In idle state it will just do nothing (there is no action 
+        registered for that) and in the following state it should just follow the mouse position.
+        
+    2.  The actions the state machine can perform are defined. There are two entry actions defined and an update action.
+        The entry action for the idle state named as 'idle_entry' and entry action for the following state named
+        'following_entry'.
+        
+    3.  Points 1 and 2 just define the 'interfaces' of the state machine, what its input is and what actions 
+        (sort of output) it will perform. Now in point 3 the structure and the transitions are defined. First 
+        the states are added. The spaces before 'idle' and 'following' state identifier should indicate that they are 
+        both children of the 'parent' state. The only state that has no parent is the root state (here called 'parent').
+        The initial flag is to determine which leaf state the state machine should change in case a transition ends in 
+        a state that is not a leaf. So in this case if a transition would end at the 'parent' state then it would 
+        change to the idle state. For each level there can only be one initial state. In those lines of code also 
+        the entry actions are defined (explanation methodcaller seel [3]_). In the next three lines the transitions 
+        are added.  There are three transitions (this are the arrows in the picture). Only the self transitions in 
+        the 'following' state for the update event has an action to call. Its the 'following_update' action. Now 
+        the structure and the relationships are defined.
+        
+    4.  Here the actual actions for the entity are implemented. The 'following_update' action just does move the 
+        entity in direction of the mouse. The other two actions are the entry actions for the two substates, they 
+        just change the color of the entity.
+        
+    5.  Now here the state machine will be instantiated. The structure is passed in and the well an instance of 
+        the actions pointing to that instance of the entity (so the action can manipulate the entity). To make 
+        the state machine work, input events need to be passed to the state machine. This will happen in the 
+        update method of the entity. There the proximity sensor will detect the mouse and then a corresponding 
+        event (in_range or out_of_range) is generated and passed to the state machine. This might change the state, 
+        depending in which state it is and what event it gets. Then the update event is also passed to the state 
+        machine, because it should show a behavior (updating the position in direction of the mouse if in 
+        state 'following').
+
+
+I hope this made some things clear. There is much more to it. Take a look at the symplehfsm_demo [4]_ for a more 
+complex and full featured example. Run it in the console, preferably with the -O option: 
+::
+
+    >>>python -O symplehfsm_demo.py
+
+
+I hope that helps. 
+
+.. rubric:: Footnotes
+
+
+.. [1] https://bitbucket.org/dr0id/symplehfsm/src/d9229897f4e6/trunk/symplehfsm/examples
+.. [2] https://bitbucket.org/dr0id/symplehfsm/src/d9229897f4e6/trunk/symplehfsm/examples/mousefollower/mousefollower.py
+.. [3] its a convenient way to call dynamically a method by its name on an object, see
+        http://docs.python.org/library/operator.html?highlight=methodcaller#operator.methodcaller
+.. [4] https://bitbucket.org/dr0id/symplehfsm/src/d9229897f4e6/trunk/symplehfsm/examples/TestHFSM/symplehfsm_demo.py
+
 """
 
 __version__ = "1.0.3.0"

trunk/symplehfsm/symplehfsm.py

         [events.update(list(x.events.keys())) for x in list(self.states.values())]
 
         if __debug__:
-            logger.info('>>> ' + str(events))
+            logger.info('all events: ' + str(events))
         # apply alle events to all leaf states to get optimization
         leafs = [x for x in list(self.states.values()) if not x.children]
         if __debug__:
-            logger.info('>>> ' + str(leafs))
+            logger.info('all states: ' + str(leafs))
 
         for leaf in leafs:
             for event in events:
 class SympleDictHFSM(object):
     """
     .. todo:: should transition.action be able to return something to the caller?
+    .. todo:: rename to SympleHFSM ?? (dict does explain an implementationdetail..., symple is actually also not good)
 
     Base state machine logic. It implements the state transition logic.
 

trunk/symplehfsm/test/test_symplehfsm.py

 # -----------------------------------------------------------------------------
 
 class StructureTests(unittest.TestCase):
+    """
+    ..todo: test _get_methodcalls() !!!!!
+
+    Tests the Structure class.
+    """
     
     def setUp(self):
         self.name = "asdlfkjdflkjUIPOIU"
         self.structure.add_trans(self.state2, event_id, None)
         self.structure.add_trans(self.state3, event_id, None)
         
-        
+    
         
 # -----------------------------------------------------------------------------
+
+class StructureOptimizationTests(unittest.TestCase):
+
+    def setUp(self):
+        self.structure = Structure("optimized structure")
+        self.structure.add_state("root", None, None)
+        self.structure.add_state("level1-1", "root", True)
+        self.structure.add_state("level1-2", "root", None)
+        self.structure.add_state("level1-3", "root", None)
+        
+        self.structure.add_state("level2-1-1", "level1-1", True)
+        self.structure.add_state("level2-1-2", "level1-1", None)
+        self.structure.add_state("level2-1-3", "level1-1", False)
+
+        self.structure.add_state("level2-2-1", "level1-2", True)
+        self.structure.add_state("level2-2-2", "level1-2", False)
+        self.structure.add_state("level2-2-3", "level1-2", False)
+        
+        self.structure.add_state("level2-3-1", "level1-2", False)
+        self.structure.add_state("level2-3-2", "level1-2", False)
+        self.structure.add_state("level2-3-3", "level1-2", False)
+
+        self.structure.add_state("level3-1-1-1", "level2-1-1", False)
+        self.structure.add_state("level3-1-1-2", "level2-1-1", None)
+        self.structure.add_state("level3-1-1-3", "level2-1-1", False)
+        
+        self.structure.add_state("level3-1-2-1", "level2-1-2", True)
+        self.structure.add_state("level3-1-2-2", "level2-1-2", None)
+        self.structure.add_state("level3-1-2-3", "level2-1-2", False)
+        
+        self.structure.add_state("level3-2-1-1", "level2-2-1", False)
+        self.structure.add_state("level3-2-1-2", "level2-2-1", None)
+        self.structure.add_state("level3-2-1-3", "level2-2-1", False)
+        
+        self.structure.add_state("level3-3-2-1", "level2-3-2", False)
+        self.structure.add_state("level3-3-2-2", "level2-3-2", None)
+        self.structure.add_state("level3-3-2-3", "level2-3-2", False)
+        
+        
+    def test_do_optimization_states(self):
+        try:
+            self.structure.do_optimize()
+        except Exception, e:
+            self.fail(str(e))
+        
+    def test_do_optimization_with_transitions(self):
+        #                   handling state,   event, next state,           action,                    guard
+        self.structure.add_trans("level1-1", "a", "level2-2-1")
+        self.structure.add_trans("level1-2", "b", "level2-3-1")
+        self.structure.add_trans("level2-1-1", "c", "level3-3-2-1")
+        try:
+            self.structure.do_optimize()
+        except Exception, e:
+            self.fail(str(e))
+            
 # -----------------------------------------------------------------------------
-        
-        
 
+class SympleDictHFSMTests(unittest.TestCase):
 
+    def setUp(self):
+        self.structure = Structure("SympleDictHFSMTests.structure")
+        self.actions = BaseHFSMTests.RecordingActions()
+        self.machine = SympleDictHFSM(structure, actions, "SympleDictHFSMTests.machine")
 
 
+# -----------------------------------------------------------------------------
 
 # TODO: redo tests using a real states and a fully implemented statemachine (event-, action-interface, etc)
 class SympleHFSMTests():#unittest.TestCase):