Beauty of Mathematical Curiousity

Once there was a girl who thought math was pretty, and wanted to program her own games. This is her story.

Unfortunatly for her, one of her setbacks was impatience.

In this story, I lay the groundwork for a brighter tomorrow. I have prepared a basic home for my thoughts, and filled it with bright, cheery colors which hopefully don't look too awful. I have neglected spell checking, but everything looks okay. If I've misspelled something please correct me. I don't approve of careless spelling errors any more than lazy capitalization and punctuation.

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 = False
The 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=\n" % attr
            else:
                result = result + "\tattr %s=%s\n" % (attr, self.__dict__[attr])
        return result
A final note on Event - it is actually missing something else, but I won't explain until later.

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 exceeded
Ack! 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: , , , ,

0 comments.
Leave one.
Comments

Post a Comment