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.

Not long ago (this morning, actually), I managed to dig myself into an endless recursive hole. To prevent the need to add links to previous related posts on this topic, I've started a list in the side box. If I run out of topics, I may go back through and put the list in all the posts, but for now it's in the sidebar of the blog.

My short term solution was to remove any events posted by the Fibonacci model. Instead, it will print out values itself. Not as wonderous, perhaps, but I did say it was a short term solution.

The New (but not improved) Fibonacci Model

Basically, I removed all attempts for the model to post events, and replaced them with print statements. It's not glorious, but that's ok.

class FibonacciModel(myevents.Listener):

    def __init__(self, manager):
        myevents.Listener.__init__(self, manager)
        # no longer stores a reference to manager

        self.last = 0
        self.current = 1

        # replaces manager.post(event)
        print "NUMBER: %s" % self.current

    def notify(self, event):
        self.last, self.current = self.current, self.last+self.current

        # replaces a post(event)
        print "NUMBER: %s" % self.current

Other Problems

Sadly, this isn't nearly enough. Instead it brings to light some other problems with the code.

Checking Event Type

The notify methods (often) work on only one type of event. Here is the code I used before:

if event.quit:
    # do stuff
This works great... for quit events. But other events, like tick events and the (currently removed) fibonacci events don't have a 'quit' attribute, and the program promptly crashes. I've replaced that with the following.
if event.hasAttribute('quit'):
    self.running = False
This requires a hasAttribute function in the Event class. This is what I was hinting at in my last post when I said something was still missing from Event. We didn't catch the error because we were busy being caught in an infinite loop.
def hasAttribute(self, attr):
    return attr in self.__dict__.keys()

Scope Error

The Tick Generator also had a scope error, which was not caught before the previous program crashed from recursion depth. Make sure to change clock.tick(30) to self.clock.tick(30).

No Exit

Although we check for quit events, nothing generates them. Unlike most pygame programs, there is no window to close, and no user interaction at all. Here is a great opportunity to use the MVC design to our advantage. Adding something to generate events is easy - I'll add another controller for this. It will still not have any user input, but it will prevent the need to control+C the program to stop it.

I will call this new addition the QuitAtGoalController. It's not a very good name - the goal can only be a tick value and the only way to set it is to edit the code. We'll overlook these shortcomings.

class QuitAtGoalController(myevents.Listener):
    def __init__(self,manager):
        self.manager = manager
        self.manager.register(self)

        FPS = 30 # just so I don't forget what the magic number 30 is
        self.stopAfterTick = 5*FPS

    def notify(self, event):
        if event.hasAttribute('tick') and event.tick >= self.stopAfterTick:
            event = Event()
            event.quit = False # will STILL quit - should fix that
            self.manager.post(event)
This is some mediocre code right here. Notice the event I created - it has the quit attribute set, but to False. However, our quit condition is simply that this property exists, not its value. Then there's some good ole' magic numbers. I've identified FPS instead of just putting the value 30 in (or worse - 150, because that could mean anything at all), but this value should really be identified by a variable throughout the program, or by a global constant. Ah well, it's an example after all, and it gets the job done. Anyway, this class is set up to stop the program after 5 ticks, or approximately 150 milliseconds. So we're only looking about 5 steps into the Fibonacci sequence. We just need to create an instance of this class in the main function.

Full Example

The main code:

import myevents, pygame
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)
            self.clock.tick(30) # throttle back the CPU

    def notify(self, event):
        #if event.quit: # I need to define an event with this!
        if event.hasAttribute('quit'):
            self.running = False

class QuitAtGoalController(myevents.Listener):
    def __init__(self,manager):
        self.manager = manager
        self.manager.register(self)

        FPS = 30 # just so I don't forget what the magic number 30 is
        self.stopAfterTick = 5*FPS

    def notify(self, event):
        if event.hasAttribute('tick') and event.tick >= self.stopAfterTick:
            event = Event()
            event.quit = False # will STILL quit - should fix that
            self.manager.post(event)

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

        print "NUMBER: %s" % self.current
        #event = Event()
        #event.number = 1
        #manager.post(event)

    def notify(self, event):
        self.last, self.current = self.current, self.last+self.current

        print "NUMBER: %s" % 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)

    stop = QuitAtGoalController(manager)

    controller.run()

if __name__ == "__main__":
    main()

The Event code, since it's changed a bit:

from weakref import WeakKeyDictionary

class Listener:
    """
    A superclass for listeners. Note that a listener must save the reference
    to the manager if it needs to send events, as opposed to just recieve them.
    """

    def __init__(self, manager):
        """Register with the manager automatically."""
        manager.register(self)

    def notify(self, event):
        """Default implementation for recieving events."""
        pass

class Event:
    """
    A superclass for events to be handled by EventManager
    """
    
    def __init__(self):
        """Set the name of the Event. Subclasses may have other attributes."""
        self.name = "Generic"

    def __repr__(self):
        """Custom representation for the event."""
        return self.name + " Event"

    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

    def hasAttribute(self, attr):
        return attr in self.__dict__.keys()

class EventManager:
    """
    A manager for coordinating communication between Model, View and Controller
    """

    def __init__(self):
        """Set up a dictionary to hold references to listeners"""
        self.listeners = WeakKeyDictionary()

    def register(self, listener):
        """Register a listener. It must implement the function notify(event)"""        
        self.listeners[ listener ] = 1

    def unregister(self, listener):
        """Unregister a listener"""
        if listener in self.listeners:
            del self.listeners[ listener ]

    def post(self, event):
        """Inform all listeners about an event."""
        for listener in self.listeners:
            listener.notify(event)

"""Test the contents of this file."""
if __name__ == "__main__":

    e = Event()
    print e

    em = EventManager()

    class Test(Listener):
        """Uses the default init."""
        def notify(self, event):
            print "I have recieved a "+ str(event)

    test = Test(em)
    
    em.post(e)
    em.unregister(test)
    em.post(e)

Labels: , , , ,

0 comments.
Leave one.
Comments

Post a Comment