Time to introduce something to make it "game like". This program will still be a far cry from a game, but it's an important step nonetheless. To understand the logic, you need to understand what I'm trying to achieve. The goal is to create a "Grow" game, modeled off the Grow Series, which I find to be a huge source of both pleasure and entertainment. The way I view the logic behind these games is as a tree. Each time a button is selected, that button is disabled and you traverse down a branch of the tree. It's a bit of a combinatorics problem. The more buttons (options) there are, the more complicated the tree. The number of cases to deal with grows very very quickly, so I've chosen to pursue a 3-option game.
combinatorics and permutations
A side trip into math! Here I'll explain why I chose only 3 buttons. I will refer to these buttons as A, B, and C or as 0, 1 and 2. The short version is because of combinatorics. In the game logic, the order the buttons are selected is important. Not only that, but every step will have some sort of result or event. That means that every possible combination of buttons (excluding repeats) will have an event. There is (potentially) an event with 0 buttons selected. Once another button is selected (say, B), there is an event. The next choice (say, A) results in another unique event. That is, this event is distinct from choosing A first, or choosing C first, followed by A. With three buttons, there are events for selections of 0, 1, 2 or 3 button sequences. Mathematically, finding the number of possible combinations for a certain number of choices is a permutation problem.
Given N buttons, a sequence of choices of R buttons (such as a choice of 3 out of the 6 buttons), is n ! / (n - r) ! . So, for N = 6, and R = 3, this is 120. However, we need to know the number of choices for every length of sequence (such as length 0, 1, 2, 3, 4, 5 and 6 out of 6 buttons). So we sum it all up, for R=0..N: n!/(n-r)!
Going through the calculations for 3 buttons gives us 1+3+6+6 = 16 events to deal with. If we used 4 buttons instead, we get 1+4+12+24+24 = 65 events! You can see that the rate of growth of events is pretty ridiculous. For an actual game, it's probably worth having at least 4 buttons (probably more like 6 or 8), but for an example, 3 is more than enough for proof of concept.
Interacting with the user
Once upon a time, I set wrote Mouse Interface. I wrote that planning to write a Quit Interface (and keyboard, etc) to react to the user. It seemed like a good idea, but when I tried to implement it, the Quit Interface never recieved any events at all. Mouse Interface consumed all the pygame events. So instead I replaced it will User Interface, which deals with pygame events.
class UserInterface: def __init__(self, manager): self.manager = manager self.manager.register( TickEvent, self ) def notify(self, event): event = None for event in pygame.event.get(): if event.type == MOUSEBUTTONUP or event.type == MOUSEBUTTONDOWN or event.type == MOUSEMOTION: self.manager.post( MouseEvent(event) ) elif event.type == QUIT: self.manager.post( QuitEvent() )
Game Logic
As mentioned earlier, we need to have an event for every combination of buttons. I am using a dictionary, keyed by integer tuples. We also need to keep track of the current state, which is stored as a tuple to key the dictionary. The game logic has a single method, make_choice. Don't even ask why I randomly used underscores instead of my normal cases. I don't know. I'll fix it later. When we make a choice, we replace the state variable with a new tuple, with the value of the choice "appended" at the end. To keep it super simple, we will just print out the value of the logic tree.
class GameLogic: def __init__(self): self.state = () self.logictree = { () : "Root", (0,) : "0", (1,) : "1", (2,) : "2", (0,1) : "01", (0,2) : "02", (1,0) : "10", (1,2) : "12", (2,0) : "20", (2,1) : "21", (0,1,2): "012", (0,2,1): "021", (1,0,2): "102", (1,2,0): "120", (2,0,1): "201", (2,1,0): "210" } print self.state print self.logictree [ tuple(self.state) ] def make_choice(self, value): temp = list(self.state) temp.append(value) self.state = tuple( temp ) if self.logictree.has_key(self.state): print self.logictree[ self.state ] else: print self.state print "INVALID LOGIC STATE"The state could also be a list. In that case, it would need to be converted to a tuple to be used as a key. In the full code, you can see how this works in the commented out code.
The meat of the main functions is the action for the buttons. I define another method, chooseButton, to handle the button presses. The action disables the button and sends the choice to the game logic. It's important to disable the button because the permutation only works if each button can only be chosen once. The game is over when all the buttons have been hit.
def main(): game = GameLogic() manager = Mediator() user = UserInterface(manager) screen = ScreenView(manager, (400,400), black) buttons = ButtonMediator(manager) def chooseButton( value ): buttons.disableButton( value ) game.make_choice( value ) buttons.addButton( pygame.Rect(50,50,10,10), screen.screen, chooseButton, 0 ) buttons.addButton( pygame.Rect(100,50,10,10), screen.screen, chooseButton, 1 ) buttons.addButton( pygame.Rect(150,50,10,10), screen.screen, chooseButton, 2 ) controller = TickGeneratorController(manager) controller.run()
code
from commoncomponents import * from myevents import * from pygame.locals import * from mycolors import * import pygame class ButtonModel: # work around for now - I like the notation STATUS.normal better NORMAL = 'normal' DOWN = 'down' HOVER = 'hover' DISABLED = 'disabled' STATUS = { 'normal':0 , 'down':1 , 'hover':2 , 'disabled':3 } def __init__(self, rect): self.status = ButtonModel.STATUS[ButtonModel.NORMAL]#.normal self.rect = rect def enable(self): if self.status == ButtonModel.STATUS[ButtonModel.DISABLED]: #.disabled: self.status = ButtonModel.STATUS[ButtonModel.NORMAL]#.normal def disable(self): self.status = ButtonModel.STATUS[ButtonModel.DISABLED] #.disabled def mouseAction(self, mouseevent): if self.status == ButtonModel.STATUS[ButtonModel.DISABLED]: return #.disabled: return if self.rect.collidepoint( mouseevent.pos ) : if mouseevent.type == MOUSEBUTTONDOWN: self.status = ButtonModel.STATUS[ButtonModel.DOWN] #.down elif mouseevent.type == MOUSEBUTTONUP and self.status == ButtonModel.STATUS[ButtonModel.DOWN]: #.down: self.status = ButtonModel.STATUS[ButtonModel.NORMAL]#.normal return True # BUTTON HAS BEEN CLICKED else: self.status = ButtonModel.STATUS[ButtonModel.HOVER] #.hover else: self.status = ButtonModel.STATUS[ButtonModel.NORMAL]#.normal class ButtonView: def drawButton(screen, buttonSurface, position): screen.blit(buttonSurface, position) def __init__(self, screen): self.screen = screen def draw(self, rect, status): color = black if status == ButtonModel.STATUS[ButtonModel.NORMAL]: #.normal: color = white elif status == ButtonModel.STATUS[ButtonModel.DOWN]: #.down: color = blue elif status == ButtonModel.STATUS[ButtonModel.HOVER]: #.hover: color = green elif status == ButtonModel.STATUS[ButtonModel.DISABLED]: #.disabled: color = grey self.screen.fill( color, rect ) class ButtonMediator: def __init__(self, manager): self.buttons = [] self.actions = [] self.views = [] self.actionArgs = [] self.manager = manager self.manager.register(MouseEvent, self) self.manager.register(TickEvent, self) def addButton(self, rect, screen, action, actionargs): self.buttons.append( ButtonModel( rect ) ) self.actions.append( action ) self.views.append( ButtonView(screen) ) self.actionArgs.append( actionargs ) def disableButton(self, i): self.buttons[i].disable() def enableButton(self, i): self.buttons[i].enable() def notify(self, event): for i in range( len(self.buttons) ): b = self.buttons[i] if event.kind == MouseEvent.kind: if b.mouseAction(event.mouse): f = self.actions[i] f( self.actionArgs[i] ) self.views[i].draw(b.rect, b.status) # redraw on mouse event only = bad bad flicker issues # (black when mouse not being used) class MouseEvent(Event): kind = "mouse" def __init__(self, mouseevent): Event.__init__(self) self.mouse = mouseevent class UserInterface: def __init__(self, manager): #self.listeners = {} self.manager = manager self.manager.register( TickEvent, self ) #def add(self, kind, listener): # #self.listeners[ listener ] = 1 # if not self.listeners.has_key(kind): # self.listeners[kind] = {} # self.listeners[kind][listener] = 1 #def drop(self, kind, listener): # if self.listeners.has_key(kind): # if self.listeners[kind].has_key(listener): # del self.listeners[ kind ][ listener ] def notify(self, event): #event = None #for event in pygame.event.get(): # for listener in self.listeners[ event.type ]: # listener.notify(event) event = None for event in pygame.event.get(): if event.type == MOUSEBUTTONUP or event.type == MOUSEBUTTONDOWN or event.type == MOUSEMOTION: self.manager.post( MouseEvent(event) ) elif event.type == QUIT: self.manager.post( QuitEvent() ) #def post(self, event): # self.manager.post(event) class MouseInterface: def __init__(self, manager): self.manager = manager self.manager.add( MOUSEBUTTONUP, self ) self.manager.add( MOUSEBUTTONDOWN, self ) self.manager.add( MOUSEMOTION, self ) def notify(self, event): #event = None #for event in pygame.event.get(): # if event.type == MOUSEBUTTONUP or event.type == MOUSEBUTTONDOWN or event.type == MOUSEMOTION: # self.manager.post( MouseEvent (event) ) self.manager.post( MouseEvent (event) ) class QuitInterface: def __init__(self, manager): print "able to exit" self.manager = manager #self.manager.register( TickEvent, self ) self.manager.add( QUIT, self ) def notify(self, event): #print "checking for quit..." #event = None #for event in pygame.event.get(): # if event.type == QUIT: # self.manager.post( QuitEvent( ) ) self.manager.post( QuitEvent() ) class GameLogic: def __init__(self): #self.state = [] self.state = () self.logictree = { () : "Root", (0,) : "0", (1,) : "1", (2,) : "2", (0,1) : "01", (0,2) : "02", (1,0) : "10", (1,2) : "12", (2,0) : "20", (2,1) : "21", (0,1,2): "012", (0,2,1): "021", (1,0,2): "102", (1,2,0): "120", (2,0,1): "201", (2,1,0): "210" } print self.state print self.logictree [ tuple(self.state) ] def make_choice(self, value): temp = list(self.state) temp.append(value) self.state = tuple( temp ) #self.state.append(value) #if tuple(self.state) in self.logictree: # print self.logictree[ tuple(self.state) ] if self.logictree.has_key(self.state): print self.logictree[ self.state ] else: print self.state #print tuple(self.state) print "INVALID LOGIC STATE" def main(): game = GameLogic() manager = Mediator() #mouse = MouseInterface(manager) user = UserInterface(manager) #mouse = MouseInterface(user) #quitter = QuitInterface(user) screen = ScreenView(manager, (400,400), black) buttons = ButtonMediator(manager) def chooseButton( value ): buttons.disableButton( value ) game.make_choice( value ) buttons.addButton( pygame.Rect(50,50,10,10), screen.screen, chooseButton, 0 ) buttons.addButton( pygame.Rect(100,50,10,10), screen.screen, chooseButton, 1 ) buttons.addButton( pygame.Rect(150,50,10,10), screen.screen, chooseButton, 2 ) #buttons.addButton( pygame.Rect(200,50,10,10), screen.screen, chooseButton, 3 ) controller = TickGeneratorController(manager) controller.run() if __name__ == "__main__": main()
Labels: combinatorics, permutations, pygame, python, series 1, tutorial
Leave one.