Source code for examples.mousefollower.mousefollower
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Example usage of the symplehfsm statemachine for a game entity using pygame. See inline comments!
"""
__version__ = "1.0.3.0"
__author__ = "dr0iddr0id {at} gmail [dot] com (C) 2012"
import math
import pygame
from operator import methodcaller
import symplehfsm
from symplehfsm import Structure
from symplehfsm import SympleDictHFSM
from symplehfsm import BaseHFSMTests
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
### 1. define the events the statemachin understands
### here we have the range sensor output and the update defined as events
[docs]class Events(object):
in_range = 1
out_of_range = 2
update = 3
# ------------------------------------------------------------------------------
### 2. define the actions (the action methods like entry, exit, guard and action methods of the
### states and transitions) the statemachine can call
### here only in the following state has something to be done
[docs]class Actions(object):
[docs] def idle_entry(self):
pass
[docs] def following_entry(self):
pass
[docs] def following_update(self):
pass
# ------------------------------------------------------------------------------
### 3. define the structure of the statemachine, e.g. what states it has and what transitions between those states
### also define the entry, exit, guard and action method to call (only if needed)
structure = Structure()
# state, parent, initial, entry, exit
structure.add_state("parent", None, False) # all containing state
structure.add_state( "idle", "parent", True, methodcaller("idle_entry"))
structure.add_state( "following", "parent", False, methodcaller("following_entry"))
# handling state, event, next state, action, (guard,) name
structure.add_trans( "following", Events.update, "following", methodcaller("following_update"), name="following->following") # self transition
structure.add_trans( "following", Events.out_of_range, "idle", name="following->idle")
structure.add_trans( "idle", Events.in_range, "following", name="idle->following")
# for greater execution speed, apply when statemachine is stable and works as expected
# structure.do_optimize()
# ------------------------------------------------------------------------------
### 4. implement the real actions used, e.g. the entity behavior in each state
[docs]class EntityActions(object):
def __init__(self, entity):
self.entity = entity
[docs] def following_update(self):
mx, my = pygame.mouse.get_pos()
dx = mx - self.entity.x
dy = my - self.entity.y
dist = math.hypot(dx, dy)
if dist > 0:
dirx = dx / dist
diry = dy / dist
self.entity.x += dirx * self.entity.speed
self.entity.y += diry * self.entity.speed
[docs] def idle_entry(self):
self.entity.color = (255, 255, 0) # yellow
[docs] def following_entry(self):
self.entity.color = (255, 0, 0) # red
# ------------------------------------------------------------------------------
### 5. the entity that will use the state machine. It has also the proximity sensor code
### to generate the in_range/out_of_range events
[docs]class Entity(Actions):
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = 100.0
self.speed = 1
self.sm = SympleDictHFSM(structure, EntityActions(self))
self.color = None
# self.mouse_in_range = False
self.blip_radius = (x * y) / (x + y)
self.sm.init()
[docs] def update(self, dt):
# # little caching, only send an event to statemachine if something actually changed
# in_range = self.proximity_sensor()
# if in_range != self.mouse_in_range:
# self.mouse_in_range = in_range
# if in_range:
# self.sm.handle_event(Events.in_range)
# else:
# self.sm.handle_event(Events.out_of_range)
# this code is without caching, just throwing an event at the state that gets ignored
if self.proximity_sensor():
self.sm.handle_event(Events.in_range)
else:
self.sm.handle_event(Events.out_of_range)
# update
self.sm.handle_event(Events.update)
# blip
self.blip_radius += 2
if self.blip_radius >= self.radius:
self.blip_radius = 1
[docs] def proximity_sensor(self):
mx, my = pygame.mouse.get_pos()
dx = self.x - mx
dy = self.y - my
dist = math.hypot(dx, dy)
if dist < self.radius:
return True
return False
[docs] def draw(self, surf):
pos = (int(self.x), int(self.y))
pygame.draw.circle(surf, (0, 100 * ( 1.0 - self.blip_radius / self.radius), 0), pos, int(self.blip_radius), 0)
pygame.draw.circle(surf, (0, 200, 0), pos, int(self.blip_radius), 1)
pygame.draw.circle(surf, self.color, pos, int(self.radius), 1)
pygame.draw.circle(surf, self.color, pos, 5, 0)
# ------------------------------------------------------------------------------
### little main loop
[docs]def main():
entities = [Entity(400, 300), Entity(500, 400), Entity(100, 300)]
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("mousefollower.py - move the mouse")
clock = pygame.time.Clock()
running = True
while running:
dt = clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
print(clock.get_fps())
elif event.key == pygame.K_ESCAPE:
running = False
for entity in entities:
entity.update(dt)
screen.fill((0, 0, 0))
for entity in entities:
entity.draw(screen)
pygame.display.flip()
# ------------------------------------------------------------------------------
if __name__ == '__main__':
main()