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()