Here's the state machine class along with an example.

The example itself is highly contrived, not to mention overkill for the
problem it solves, but it demonstrates a simple game which has an attract
mode state, a playing state and a pause state that is a substate of playing.

When in attract mode, press [1] to start a one player game, [2] to start a
two player game and [Esc] to quit.

When in playing mode, move the mouse to the bat and try to keep the ball in
play. Press [Esc] to pause.

When in paused mode, press [Esc] to exit the game to attract mode and press
[space] to resume playing.

--- Rod

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pyglet-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/pyglet-users?hl=en
-~----------~----~----~----~------~----~------~--~---

#!/usr/bin/env python

__metaclass__ = type


class HSM():
    """A Hierarchical State Machine.

    Author: Rod Hyde
    
    This Hierarchical State Machine class is based, in large part, on the ideas
    in the book "Practical Statecharts in C/C++" by Miro Samek.

    The states of the state machine are methods, and are therefore their own
    state handlers. If a state doesn't know how to handle a signal then it
    returns its parent state, allowing states to be nested.

   
    Both composition and inheritance are possible with this class.

    There are certain conventions that should be followed:
    -   If a state handles a signal then it should return None.
    -   If a state does not handle a signal then it should return its parent
        state.
    -   If a state has an entry action then it should handle HSM.SIG_ENTER and
        return None.
    -   If a state has an exit action then it should handle HSM.SIG_EXIT and
        return None.
    -   If a state has an initial transition then it should handle HSM.SIG_INIT,
        use change_state() to transition to the desired substate, then return
        None.
    -   change_state() should only be called from the state handlers and not
        from any methods that they call.

    State history doesn't need any special handling - just store it in a
    variable on the way out of a state, then transition back to it on the way
    in.
    
    To use a state machine, create it, call its reset() method to put it into
    its initial state, then pass signals (and arguments) to it by calling the
    handle() method or by calling the object directly. Once finished with the
    state machine, call its terminate() method.

    eg,
    
    >>> microwave = MicrowaveHSM()
    >>> microwave.reset()
    >>> microwave(SIG_START_COOKING, time=90, watts=800)
    >>> microwave(SIG_UPDATE, time())
    >>> microwave(SIG_DOOR_EVENT, open)
    >>> microwave(SIG_DOOR_EVENT, close)
    >>> microwave.terminate()
    
    """

    # Reserved signals.
    SIG_PARENT = -1
    SIG_ENTER = SIG_PARENT - 1
    SIG_EXIT = SIG_ENTER - 1
    SIG_INIT = SIG_EXIT - 1

    # Base for user-defined signals.
    SIG_USER = 0
    
    def __init__(self):
        """Creates the hierarchical state machine."""
        self.current = None
        self.initial = None

    def _find_superstate(self, target):
        """Searches for a common superstate.

        Searches for a superstate that the current state has in common with the
        target state.
        """
        s = self.current
        while s is not None:
            t = target
            while t is not None:
                if s == t:
                    return s
                t = t(HSM.SIG_PARENT)
            s = s(HSM.SIG_PARENT)
        return None

    def _transition_to_superstate(self, substate, superstate):
        """Transitions from a substate up to a superstate."""
        s = substate
        while s is not None and s != superstate:
            s(HSM.SIG_EXIT)
            s = s(HSM.SIG_PARENT)
        return s

    def _transition_to_substate(self, superstate, substate):
        """Transitions from a superstate down to a substate."""
        t = substate
        path = list()
        while t is not None and t != superstate:
            path.append(t)
            t = t(HSM.SIG_PARENT)
        for t in reversed(path):
            t(HSM.SIG_ENTER)
        return substate
    
    def _do_init_chain(self):
        """Takes the initial condition of each state."""
        s = self.current
        result = s(HSM.SIG_INIT)
        while result is None:
            self.current(HSM.SIG_ENTER)
            s = self.current
            result = s(HSM.SIG_INIT)

    def change_state(self, target):
        """Changes state to the target state."""
        css = self._find_superstate(target)
        self._transition_to_superstate(self.current, css)
        self._transition_to_substate(css, target)
        self.current = target
        if self.active: self._do_init_chain()

    def reset(self):
        """Resets the state machine, taking its initial transition(s)."""
        self.active = True
        self.current = self.top
        self._do_init_chain()

    def terminate(self):
        """Terminates the state machine."""
        self.active = False
        self.change_state(self.top)
        self.current = None
        
    def top(self, sig, *args, **kwargs):
        """The top state of the state machine."""
        if sig == HSM.SIG_INIT:
            self.current = self.initial
        return None
        
    def __call__(self, sig, *args, **kwargs):
        """Pass a signal to the state machine."""
        s = self.current
        while s is not None:
            s = s(sig, *args, **kwargs)

    handle = __call__
#!/usr/bin/env python

__metaclass__ = type

from pyglet import font
from pyglet import window
from pyglet.gl import *
from pyglet.window import key

from hsm import HSM


class Game():
    WIDTH = 64
    HEIGHT = 8
    SIZE = 8

    def __init__(self, num):
        ft = font.load('Arial', 18)
        self.text = font.Text(ft, x=639, y=460, halign='right')
        self.game_text = font.Text(ft, text='Player %d' % num, x=320, y=460, halign='center', color=(1.0, 0.0, 0.0, 0.75))
        self.lives = 3

    def on_paddle_motion(self, dx):
        self.x += dx
        self.x = min(self.x, 640 - self.WIDTH/2)
        self.x = max(self.x, self.WIDTH/2)
        
    def get_lives(self):
        return self.lives_

    def set_lives(self, value):
        self.lives_ = value
        self.text.text = 'lives: %d' % self.lives_
        
    lives = property(get_lives, set_lives)

    def start_life(self):
        self.x = 320
        self.y = 32
        self.ball_x = 320
        self.ball_y = 240
        self.dx = 4.0
        self.dy = 4.0
        
    def lose_life(self):
        self.lives -= 1
        self.on_player_death()

    def ball_hit_bat(self):
        x1, y1 = self.x - self.WIDTH/2, self.y - self.HEIGHT/2
        x2, y2 = self.x + self.WIDTH/2, self.y + self.HEIGHT/2
        x3, y3 = self.ball_x - self.SIZE/2, self.ball_y - self.SIZE/2
        x4, y4 = self.ball_x + self.SIZE/2, self.ball_y + self.SIZE/2
        return x2 >= x3 and x1 <= x4 and y2 >= y3 and y1 <= y4
        
    def update(self):
        size = self.SIZE / 2
        self.ball_x += self.dx
        if self.ball_x - size < 0 or self.ball_x + size >= 640:
            self.dx = -self.dx
            self.ball_x += self.dx
        self.ball_y += self.dy
        if self.ball_y + size >= 480:
            self.dy = -self.dy
            self.ball_y += self.dy
        elif self.ball_y + size < 0:
            self.lose_life()
        if self.ball_hit_bat():
            self.dy = abs(self.dy)
            self.ball_y += self.dy

    def draw_rect(self, x, y, w, h):
        glBegin(GL_QUADS)
        glVertex2f(x - w/2, y - h/2)
        glVertex2f(x + w/2, y - h/2)
        glVertex2f(x + w/2, y + h/2)
        glVertex2f(x - w/2, y + h/2)
        glEnd()

    def draw_ball(self):
        self.draw_rect(self.ball_x, self.ball_y, self.SIZE, self.SIZE)

    def draw_bat(self):
        self.draw_rect(self.x, self.y, self.WIDTH, self.HEIGHT)

    def draw_status(self):
        self.text.draw()
        self.game_text.draw()
        
    def draw(self):
        glLoadIdentity()
        glColor3f(1.0, 1.0, 1.0)
        self.draw_bat()
        self.draw_ball()
        self.draw_status()

    
class GameWindow(window.Window):

    SIG_KEYDOWN = HSM.SIG_USER
    SIG_PLAYER_DEATH = SIG_KEYDOWN + 1
    SIG_START = SIG_PLAYER_DEATH + 1
    SIG_QUIT = SIG_START + 1
    SIG_PAUSE = SIG_QUIT + 1
    SIG_RESUME = SIG_PAUSE + 1
    SIG_PADDLE_MOTION = SIG_RESUME + 1
    
    def __init__(self):
        super(GameWindow, self).__init__()
        self.hsm = HSM()
        self.hsm.initial = self.attract_mode
        ft = font.load('Arial', 16)
        self.text = font.Text(ft, x=320, y=8, halign='center')

    # --- State handlers ------------------------------------------------------

    def attract_mode(self, sig, *args, **kwargs):
        def on_key_press(symbol, modifiers):
            if symbol == key._1:
                self.hsm(self.SIG_START, 1)
            elif symbol == key._2:
                self.hsm(self.SIG_START, 2)
            elif symbol == key.ESCAPE:
                self.hsm(self.SIG_QUIT)
            return True
        
        def on_enter():
            self.push_handlers(on_key_press)
            self.text.text = 'Attract Mode - [1] player game, [2] player game, [Esc] exit'
            self.draw = self.text.draw
            self.update = self.do_nothing

        def on_exit():
            self.pop_handlers()
            self.text.text = ''

        def on_start(players):
            self.num_games = players
            self.hsm.change_state(self.playing)

        def on_quit():
            self.hsm.terminate()
            
        if sig == HSM.SIG_ENTER: return on_enter()
        if sig == HSM.SIG_EXIT: return on_exit()
        if sig == self.SIG_START: return on_start(*args)
        if sig == self.SIG_QUIT: return on_quit()
        return self.hsm.top

    def playing(self, sig, *args, **kwargs):
        def on_key_press(symbol, modifiers):
            if symbol == key.ESCAPE:
                self.hsm(self.SIG_PAUSE)
            return True

        def on_mouse_motion(x, y, dx, dy):
            self.hsm(self.SIG_PADDLE_MOTION, dx)
            return True

        def on_pause():
            self.hsm.change_state(self.paused)
            
        def on_enter():
            self.set_exclusive_mouse(True)
            self.push_handlers(on_key_press, on_mouse_motion)
            self.create_games()
            self.start_game(self.games[0])

        def on_exit():
            self.pop_handlers()
            self.set_exclusive_mouse(False)
            
        def on_player_death():
            if not self.start_next_life():
                self.hsm.change_state(self.attract_mode)
        
        if sig == HSM.SIG_ENTER: return on_enter()
        if sig == self.SIG_PAUSE: return on_pause()
        if sig == self.SIG_PLAYER_DEATH: return on_player_death()
        if sig == self.SIG_PADDLE_MOTION: return self.game.on_paddle_motion(*args)
        
        return self.hsm.top

    def paused(self, sig, *args, **kwargs):
        def on_key_press(symbol, modifiers):
            if symbol == key.ESCAPE:
                self.hsm(self.SIG_QUIT)
            elif symbol == key.SPACE:
                self.hsm(self.SIG_RESUME)
            return True

        def on_mouse_motion(x, y, dx, dy):
            return True
        
        def on_enter():
            self.push_handlers(on_key_press, on_mouse_motion)
            self.text.text = 'Paused: press [Esc] to quit or [space] to continue'
            self.draw = self.draw_paused
            self.update = self.do_nothing
            
        def on_exit():
            self.pop_handlers()
            self.draw = self.draw_playing
            self.update = self.game.update
            self.text.text = ''

        def on_quit():
            self.hsm.change_state(self.attract_mode)

        def on_resume():
            self.hsm.change_state(self.playing)
            
        if sig == HSM.SIG_ENTER: return on_enter()
        if sig == HSM.SIG_EXIT: return on_exit()
        if sig == self.SIG_QUIT: return on_quit()
        if sig == self.SIG_RESUME: return on_resume()
        return self.playing
    
    # -------------------------------------------------------------------------
    
    def do_nothing(self):
        pass

    def draw_playing(self):
        self.game.draw()
        
    def draw_paused(self):
        self.game.draw()
        self.text.draw()

    def create_games(self):
        games = []
        for i in xrange(self.num_games):
            game = Game(i+1)
            game.on_player_death = lambda: self.hsm(self.SIG_PLAYER_DEATH, game)
            games.append(game)
        self.games = games
            
    def start_next_life(self):
        if sum(game.lives for game in self.games) == 0:
            return False
        while True:
            self.games[:] = self.games[1:] + self.games[:1]
            if self.games[0].lives > 0:
                self.start_game(self.games[0])
                return True
        
    def start_game(self, game):
        self.game = game
        self.update = game.update
        self.draw = self.draw_playing
        game.start_life()
        
    def run(self):
        self.hsm.reset()
        while self.hsm.current is not None and not self.has_exit:
            self.dispatch_events()
            self.update()
            self.clear()
            self.draw()
            self.flip()
        self.hsm.terminate()
            
win = GameWindow()
win.run()
win.close()

Reply via email to