The next step I chose was to implement a very simple program using myevents.py (see here). I decided to use one model, one view, and one controller. The controller is essentially the CPUSpinner from the tutorial, but I call mine TickGenerator and added the use of pygame.time.Clock to restrain the FPS. The controller is only interested in listening to events which tell it to quit running, and posts events every 30 milliseconds (my chosen FPS for this program. The view will be a simple EventLogger, which does no more than print out information about each event. The model is a simple Fibonacci number (it was originally going to be a counter, just this is just as simple and somewhat more interesting). It keeps track of two numbers, last and current. When an event is received from the TickGenerator, it calculates the next Fibonacci number.
Like the tutorial, I will not be categorizing events to prevent listeners from listening to more information than they need. I hope to be working that out later. Unlike the tutorial, I will not be doing any sort of isinstance checks. I will try to make checks for "event type" as general as possible.
Tick Generator Controller
class TickGeneratorController(myevents.Listener): """ Uses a clock to generate a 30 FPS tick. Currently 30 is a magic number. """I've started by using Listener as a super class. There is absolutely no need for this, whatsoever. I don't use it in the least, and it serves no purpose in any way, shape, or form. I put it there purely for consistency and to identify the controller class as a Listener.
def __init__(self, manager): self.manager = manager # we will be posting events self.manager.register(self) self.running = True self.clock = pygame.time.Clock() def run(self): while self.running: event = Event() # this should be a tick event, but I'm running super simple for now event.tick = pygame.time.get_ticks() self.manager.post(event) clock.tick(30) # throttle back the CPU def notify(self, event): if event.quit: # I need to define an event with this! self.running = FalseThe Tick Generator constructor registers the class as a listener, and saves a reference to the manager to use later. It also sets two attributes, running (as True) and clock. The run function creates a tick event approximately every 30 milliseconds and posts the event to the event manager. The tick event is simply a Generic Event with a tick attribute set. I chose to set it to pygame.time.get_ticks() as it seems the most useful value, but simply having a tick value at all defines a tick event. There are some flaws with this, but such is my current alternate to using isinstance all the time. I should have made a subclass of Event instead, but for such a short example chose not to. The notify method checks for a quit event (identified by having a quit attribute set), and changes running from True to False.
Event Logger View
class EventLoggerView(myevents.Listener): """ Logs events to the screen. Note that it does not define init - it uses the default Listener. """ def notify(self, event): print event print event.attributes()The Event Logger also extends Listener, but this time with purpose. This class just uses the constructor provided by Listener. It only listens to events, and does not provide them. It simply prints the event and attributes. If you're paying attention, you'll notice I never had an Event.attributes(self) function. I wrote this because all my events are Generic Events, and this is all that __repr__ is set to print - the Event name. If I had used subclasses and provided identifying names, I might not have needed an extra function. The attributes function does prove useful information however, because in addition to identifying the event type (in this lazy example) it also prints out what the value of the attribute is. Here is the Event attribute function:
def attributes(self): result = '' for attr in self.__dict__.keys(): if attr[:2] == '__': result = result + "\tattr %s=A final note on Event - it is actually missing something else, but I won't explain until later.\n" % attr else: result = result + "\tattr %s=%s\n" % (attr, self.__dict__[attr]) return result
Fibonacci Model
class FibonacciModel(myevents.Listener): """ Stores some simple information and changes itself on updates. This class will generate events so that we can log them. """ def __init__(self, manager): myevents.Listener.__init__(self, manager) # a long way of saying: register me self.manager = manager # to post events. If I used a different logging method, this would not be needed. self.last = 0 self.current = 1 event = Event() event.number = 1 manager.post(event)The constructor for the Fibonacci registers itself to and saves a reference to the Event Manager, since it will be both listening to and posting events. It has a last and current value to create the Fibonacci sequence from. It posts an event right away, so that the sequence will appear as "1,1,2..." instead of "1,2...".
def notify(self, event): self.last, self.current = self.current, self.last+self.current event = Event() event.number = self.current self.manager.post(event)The notify method updates the sequence and posts the event. The update statement is a nice python shortcut to:
newValue = last + current last = current current = newValue
the final bits come together
So here's the whole thing. Notice the main function, which creates a manager, model, view and controller, and runs the controller.
import myevents from myevents import Event # too lazy to fix this everywhere right now... """ Some of these will send out more events than are needed - and they are all of type Event. There is probably some use in having separate listeners cateorgies. I'll try to explore that later. It will also log a bunch of "Generic Events" since I haven't put forth any effort to use event types in a better way. """ class TickGeneratorController(myevents.Listener): """ Uses a clock to generate a 30 FPS tick. Currently 30 is a magic number. Note that this class is a Listener, but there was really no need to extend the superclass. It is done here for consistency only. """ def __init__(self, manager): self.manager = manager # we will be posting events self.manager.register(self) self.running = True self.clock = pygame.time.Clock() def run(self): while self.running: event = Event() # this should be a tick event, but I'm running super simple for now event.tick = pygame.time.get_ticks() self.manager.post(event) clock.tick(30) # throttle back the CPU def notify(self, event): if event.quit: # I need to define an event with this! self.running = False class EventLoggerView(myevents.Listener): """ Logs events to the screen. Note that it does not define init - it uses the default Listener. """ def notify(self, event): print event print event.attributes() class FibonacciModel(myevents.Listener): """ Stores some simple information and changes itself on updates. This class will generate events so that we can log them. """ def __init__(self, manager): myevents.Listener.__init__(self, manager) # a long way of saying: register me self.manager = manager # to post events. If I used a different logging method, this would not be needed. self.last = 0 self.current = 1 event = Event() event.number = 1 manager.post(event) def notify(self, event): self.last, self.current = self.current, self.last+self.current event = Event() event.number = self.current self.manager.post(event) def main(): manager = myevents.EventManager() model = FibonacciModel(manager) view = EventLoggerView(manager) controller = TickGeneratorController(manager) controller.run() if __name__ == "__main__": main()Everything seems pretty shiny now. Time to test it out and see how it works.
RuntimeError: maximum recursion depth exceededAck! This is clearly not quite what we are after.
Conclusion
As you can see from the results, this first attempt didn't fare well. The problem is the Fibonacci listener and a mistake in the Event Manager. I'll be working out the kinks in both shortly.
Labels: MVC, pygame, python, series 1, tutorial
Leave one.