[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[pygame] GUI Frames and classmethod



I'm currently implementing a game GUI, where there are three
components to the screen: A battle, a command button area and a city
view area. Each of these are rectangular, and handle interactions in
different ways.

For example, on the battle "frame" you can select units, right-click
to deliver context-sensitive commands, etc. On the command frame, you
select buttons to deliver commands, change the state of the battle
frame (i.e., a move command sets the battle frame to accept a move-to
location). This sort of thing.

So I'm implementing the frame code right now. Each Frame object has a
StateMachine object.  Which mean that in the __init__ code of a child
of Frame, you add rules to the StateMachine object:

###
class ButtonFrame(Frame):
    def __init__(self, owner, pos, size=None):
        Frame.__init__(self, owner, pos, size)
        ism = self.input_state_machine

        ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
        ##
        ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
        ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))

###

This is all fine and good. Now, when creating a Frame child class, you
make an implicit agreement that you'll create methods for each state,
such as ButtonFrame.enter_state_LWait() or
ButtonFrame.leave_state_None().

My current approach is that in the Frame class, I have a method to
call _after_ initialization that creates a bunch of dummy methods so
the user doesn't have to implement EVERY state change method in a
Frame child:

###
##    @classmethod -- Python 2.4
    def _empty_state_method(self):
        pass
    _empty_state_method = classmethod(_empty_state_method)

    def create_empty_state_methods(self):
        ism = self.input_state_machine
        for timing in ('enter','during','leave'):
            for state in ism.states:
                method = '%s_state_%s' % (timing, state)
                if not hasattr(self.__class__, method):
                    setattr(self.__class__, method, Frame._empty_state_method)
###

This means that if the user hasn't implemented
ButtonFrame.during_state_LWait(), for example, an empty function will
be provided (pointing to _empty_state_method().

(Aside: I'm considering putting all these state methods in a
dictionary of dictionaries so you can do a quick
myButtonFrame.state_dict['during']['LWait'] to access the proper
method)

What this now means is that when implementing a child, I am not only
forcing a call to Frame.__init__(), but also
Frame.create_empty_state_methods(). So my ButtonFrame class looks
like:

###
class ButtonFrame(Frame):
    def __init__(self, owner, pos, size=None):
        Frame.__init__(self, owner, pos, size)
        ism = self.input_state_machine

        ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
        ##
        ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
        ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))

        self.create_empty_state_methods()
###

Note the last line. When programming EVERY child I have to remember to
add this self.create_empty_state_methods() line.

My question: What are Pythonic alternatives to this sort of thing?

I can think of a few solutions, but none of them seem to be obviously
advantageous:

1. All children don't call __init__, but have an _init() method that
is called by Frame.__init__(). That way ButtonFrame has an _init()
method rather than an __init__() method. This may be my best option.
###
class Frame:
    def __init__(self, owner, pos, size=None):
        self.owner = owner
        self.children = []  
        # more setup code here.

        self._init()

        self.create_empty_state_methods()
###

2. Frame has an init() function that needs to be called following the
instantiation of a Frame (or Frame child object). I'm not too keen on
this, because it requires creating the Frame object and _then_ running
an init() method. I try to keep that sort of thing to a minimum
because it makes quick object creation a little funky. (init() has to
return self so you can do a myList.append(ButtonFrame().init()))
###
class Frame:
    def __init__(self, owner, pos, size=None):
        self.owner = owner
        self.children = []  
        # more setup code here.

    def init(self):
        self.create_empty_state_methods()
        return self

b = Frame()
b.init()
myList.append(b)
###

Phew! Hope that's not overly long! Now I can't be the first person to
want a pre- and post- child init code! I'm worried that I'm
overlooking something or there's an even more Pythonic way to do
things than above.

-- 
Zak Arntson
http://www.harlekin-maus.com - Games - Lots of 'em