class CPUSpinnerController(object): """Controls the game clock -- generating an Event for each game tick, and throttling the game to limit CPU usage""" def __init__(self, maxfps=40): self.evManager = EventManager() self.evManager.RegisterListener(self) self.keepGoing = 1 self.clock = pygame.time.Clock() self.maxfps = maxfps def Run(self): """Start the game by telling the game clock to go!""" while self.keepGoing: event = TickEvent(pygame.time.get_ticks(), self.clock.tick(self.maxfps)) self.evManager.Notify(event) def Notify(self, event): """handled events: Quit Event: Stop the clock from ticking (the app will then close) """ if isinstance(event, QuitEvent): #this will stop the while loop from running self.keepGoing = 0
class Game(object): """Responsible for setting the stage: Namely, making sure that the appropriate Actors exist and know the appropriate things """ __metaclass__ = SingletonType STATE_PREPARING = 0 STATE_RUNNING = 1 STATE_PAUSED = 2 STATE_GAMEOVER = 3 def __init__(self): self.evManager = EventManager() self.evManager.RegisterListener(self) self.state = Game.STATE_PREPARING self.heroes = {} self.enemies = {} self.enemyCount = 0 self.problem = (0, 0) self.time = 0 self.solutionWait = 5000 #milliseconds self.solEndTime = 0 def Start(self): """starts the game action -- spawning any Actors needed, setting required variables and generating the GameStartedEvent""" self.SpawnHero() self.SpawnEnemy() self.state = Game.STATE_RUNNING self.evManager.Notify(GameStartedEvent(self)) def SpawnHero(self): self.heroes["hero"] = HeroModel("hero", self.enemies) self.evManager.Notify(SpawnHeroEvent(self.heroes["hero"].evID)) def SpawnEnemy(self): self.enemyCount += 1 enemyID = "enemy%s" % self.enemyCount self.enemies[enemyID] = EnemyModel(enemyID, self.heroes, self.time) self.evManager.Notify(SpawnEnemyEvent(enemyID)) def myOpponents(self, actor): if isinstance(actor, Hero): return self.enemies elif isinstance(actor, Enemy): return self.heroes def Notify(self, event): """Handled events: TickEvent: progress the game action by one game tick (currently just starts game if it's not already set up) DieEvent: Actor has died. If hero, game is over, if (last) enemy, victory is achieved NextBattleEvent: Player has requested next battle, so spawn an enemy """ if self.state == Game.STATE_GAMEOVER: return #don't do anything if the game is over elif self.state == Game.STATE_PREPARING: if isinstance(event, TickEvent): self.Start() if isinstance(event, TickEvent): self.time = event.time if self.state == Game.STATE_RUNNING: if isinstance(event, DieEvent): if event.subject in self.heroes.keys(): self.state = Game.STATE_GAMEOVER self.evManager.Notify(GameOverEvent()) #del self.heroes[event.subject] elif event.subject in self.enemies.keys(): self.evManager.Notify(VictoryEvent()) del self.enemies[event.subject] elif isinstance(event, NextBattleEvent): self.SpawnEnemy()
class HUD(pygame.sprite.Sprite): """Handles drawing any displayed info, namely: * text for the problem, solution, and any other messages * timer bar for problem * the lines separating the screens """ def __init__(self, rect, group=None): pygame.sprite.Sprite.__init__(self, group) self.evManager = EventManager() self.evManager.RegisterListener(self) self.time = 0 #self.image = pygame.Surface(rect.size, SRCALPHA) self.image = pygame.Surface(rect.size) self.image.set_colorkey((0, 0, 0)) self.image.fill((0, 0, 0, 0)) self.color = (255, 255, 255) self.rect = self.image.get_rect() self.hero = None #set hud areas w = rect.width h = rect.height self.heroBox = self.image.subsurface( (0, 0, w / 2, h * 2 / 3)) #top left box #pygame.draw.rect(self.heroBox, self.color, self.heroBox.get_rect(), 4) self.DrawBorder(self.heroBox) self.enemyBox = self.image.subsurface( (w / 2, 0, w / 2, h * 2 / 3)) #top right box #pygame.draw.rect(self.enemyBox, self.color, self.enemyBox.get_rect(), 4) self.DrawBorder(self.enemyBox) self.dataBox = self.image.subsurface((0, h * 2 / 3, w, h / 3)) #pygame.draw.rect(self.dataBox, self.color, self.dataBox.get_rect(), 4) self.DrawBorder(self.dataBox) self.solutionBox = self.dataBox.subsurface((50, 50, 100, 50)) self.timerBox = self.dataBox.subsurface((30, 100, 300, 20)) self.timer = (0, 0) #beginning and end of timer self.instrBox = self.dataBox.subsurface((200, 20, w / 2, 100)) #set hud font self.font = pygame.font.SysFont("Arial", 18) self.font_height = 20 #initialize instruction text: self.instr_wait = "Press SPACE to attack" self.instr_prob = "%s \nType solution and press ENTER to solve" self.instr_win = "Victory! \nPress SPACE for next battle" self.instr_loose = "Game Over! \nPress ESC to quit" self.instr = self.instr_wait self.sol = "" def DrawBorder(self, surf): surf.fill(self.color) rect = surf.get_rect().inflate(-4, -4) surf.fill((200, 200, 200, 127), rect) rect = rect.inflate(-4, -4) surf.fill((0, 0, 0), rect) return rect def Victory(self): self.instr = self.instr_win def Defeat(self): self.instr = self.instr_loose def DrawInstructions(self, color=False): if not color: color = self.color vpos = 0 self.instrBox.fill((0, 0, 0)) for text in self.instr.split('\n'): pImg = self.font.render(text, False, color) self.instrBox.blit(pImg, (0, vpos)) vpos += self.font_height def DrawSolution(self, color=False): if not color: color = self.color sImg = self.font.render(self.sol, False, color) self.solutionBox.fill((0, 0, 0)) self.solutionBox.blit(sImg, (0, 0)) def UpdateTimer(self): #rect = self.DrawBorder(self.timerBox) rect = self.timerBox.get_rect() w = rect.width p = 1. * (self.timer[1] - self.time) / (self.timer[1] - self.timer[0]) w = w * p rect = (0, 0, w, rect.height) self.timerBox.fill((0, 0, 0)) self.timerBox.fill(self.color, rect) def Notify(self, event): """events handled: TickEvent: shrink the timer (if we're counting) RequestSolutionEvent: display the problem & timer SolutionUpdateEvent: update the solution to the current state SolveEvent: Hide problem & timer """ if isinstance(event, TickEvent): self.time = event.time self.DrawInstructions() self.DrawSolution() if self.time < self.timer[1]: #timer on self.UpdateTimer() elif isinstance(event, SpawnHeroEvent): self.hero = event.evID elif isinstance(event, RequestSolutionEvent): self.instr = self.instr_prob % event.problem self.timer = (self.time, event.endTime) elif isinstance(event, SolutionUpdateEvent): self.sol = event.solution elif isinstance(event, SolveEvent): self.sol = "" self.instr = self.instr_wait self.timer = (0, 0) self.timerBox.fill((0, 0, 0)) elif isinstance(event, VictoryEvent): self.instr = self.instr_win elif isinstance(event, NextBattleEvent): self.instr = self.instr_wait
class PygameView: """Creates the game window, and handles drawing everything inside it""" def __init__(self): self.evManager = EventManager() self.evManager.RegisterListener(self) #set up pygame requriements pygame.init() self.window = pygame.display.set_mode((640, 480)) pygame.display.set_caption('Mathemagics') self.background = pygame.Surface(self.window.get_size()) self.background.fill((0, 0, 0)) self.backSprites = pygame.sprite.RenderUpdates() self.menuSprites = pygame.sprite.RenderUpdates() self.actorSprites = pygame.sprite.RenderUpdates() self.mapspr = MapSprite(self.window.get_rect(), self.backSprites) self.hud = HUD(self.window.get_rect(), self.menuSprites) def SpawnHero(self, evID): x = self.window.get_width() / 4 y = self.window.get_height() / 3 hero = HeroSprite(x, y, evID, self.actorSprites) def SpawnEnemy(self, evID): x = self.window.get_width() * 3 / 4 y = self.window.get_height() / 3 enemy = EnemySprite(x, y, evID, self.actorSprites) def GetActor(self, evID): for sprite in self.actorSprites.sprites(): if sprite.evID == evID: return sprite def Notify(self, event): """Handled events: TickEvent: redraw back & front sprites using double buffering DieEvent: SpawnEvent: Create the spawned actor """ if isinstance(event, TickEvent): #Draw Everything self.backSprites.clear(self.window, self.background) self.menuSprites.clear(self.window, self.background) self.actorSprites.clear(self.window, self.background) self.backSprites.update() self.menuSprites.update() self.actorSprites.update() dirtyRects1 = self.backSprites.draw(self.window) dirtyRects2 = self.menuSprites.draw(self.window) dirtyRects3 = self.actorSprites.draw(self.window) pygame.display.update(dirtyRects1 + dirtyRects2 + dirtyRects3) #..should go to HUD? elif isinstance(event, DieEvent): subject = self.GetActor(event.subject) if subject.evID == 'hero': self.hud.Defeat() #placeholder else: self.hud.Victory() elif isinstance(event, SpawnEvent): if isinstance(event, SpawnHeroEvent): self.SpawnHero(event.evID) if isinstance(event, SpawnEnemyEvent): self.SpawnEnemy(event.evID)
class ActorSprite(pygame.sprite.Sprite): """ Virtual sprite for a generic game actor subclasses must define the following: self.waitImage self.attackImage self.defendImage self.hurtImage """ def __init__(self, x, y, evID, group=None): self.evManager = EventManager() self.evManager.RegisterListener(self) pygame.sprite.Sprite.__init__(self, group) self.evID = evID self.pos = (x, y) self.InitImages() self.image = self.waitImage #start out waiting self.rect = self.image.get_rect() self.rect.center = self.pos self.healthBox = pygame.Surface((self.rect.width, 15)) self.healthBox.fill((0, 255, 0)) #self.healthBox.set_colorkey((0,0,0)) def InitImages(self): # predefine images self.deadImage = pygame.Surface((0, 0)) self.waitImage = pygame.Surface((64, 64)) #Idle image self.attackImage = pygame.Surface((64, 64)) #Attack image self.defendImage = pygame.Surface((64, 64)) #Defend image self.hurtImage = pygame.Surface((64, 64)) #Hurt image def update(self): pygame.sprite.Sprite.update(self) self.image.blit(self.healthBox, (0, 0)) def UpdateHealth(self, newHealth): y = self.healthBox.get_rect().height x = self.healthBox.get_rect().width dx = x * newHealth grey = 255 * newHealth r = min((255 - grey) * 2, 255) g = min(grey * 2, 255) b = 0 self.healthBox.fill((0, 0, 0)) self.healthBox.fill((r, g, b), (0, 0, dx, y)) def Wait(self): #if self.image == self.attackImage: self.image = self.waitImage self.rect = self.image.get_rect() self.rect.center = self.pos def Attack(self): if self.image == self.waitImage: self.image = self.attackImage self.rect = self.image.get_rect() self.rect.center = self.pos def Defend(self): if self.image == self.waitImage: self.image = self.defendImage self.rect = self.image.get_rect() self.rect.center = self.pos def Hurt(self, newHealth): self.UpdateHealth(newHealth) self.image = self.hurtImage self.rect = self.image.get_rect() self.rect.center = self.pos def Die(self): self.image = self.deadImage #todo: actor objects should only get notifications for themselves (and maybe clock tick events) def Notify(self, event): """handled events: WaitEvent: Display Wait AttackEvent: Display Attack Defend: Display Defend HurtEvent: Dislpay Hurt DieEvent: Display Die """ if isinstance(event, ActorStateChangeEvent) and self.evID == event.subject: if isinstance(event, WaitEvent): self.Wait() elif isinstance(event, AttackEvent): self.Attack() elif isinstance(event, DefendEvent): self.Defend() elif isinstance(event, HurtEvent): self.Hurt(event.newHealth) elif isinstance(event, DieEvent): self.Die()
class KeyboardController(object): """Takes input from the keboard and translates that into game Events to trigger action in the rest of the system""" STATE_ACTION = 0 STATE_SOLVE = 1 STATE_VICTORY = 2 def __init__(self): self.evManager = EventManager() self.evManager.RegisterListener(self) self.state = KeyboardController.STATE_ACTION self.solution = "" self.solveTime = 0 def Notify(self, event): """Takes mostly keyboard inputs and generates events based on state. STATE_ACTION: Generic state -- basically just waiting for player to attack RequestSolutionEvent: Change state to STATE_SOLVE TickEvent: Check to see if game is quit or attack is requested based on key presses STATE_VICTORY: Player has defeated all enemies (waiting for next battle) STATE_SOLVE: Player has attacked, and is in the process of entering the solution SolveEvent: Attack has completed, change state back to STATE_ACTION and reset solution TickEvent: Listen for any key presses (quit, numbers, delete, submit) and update appropriately """ events = [] #events to be raised at the end if necessary #always quit (regardless of state) if isinstance(event, TickEvent): pgEventList = pygame.event.get() for pgEvent in pgEventList: if pgEvent.type == QUIT: events.append(QuitEvent()) elif pgEvent.type == KEYDOWN: if pgEvent.key == K_ESCAPE: events.append(QuitEvent()) #always switch to victory state (regardless of current state) elif isinstance(event, VictoryEvent): print "controller victory" self.state = KeyboardController.STATE_VICTORY if self.state == KeyboardController.STATE_ACTION: if isinstance(event, RequestSolutionEvent): self.state = KeyboardController.STATE_SOLVE elif isinstance(event, TickEvent): #Handle Input Events for pgEvent in pgEventList: if pgEvent.type == KEYDOWN: if pgEvent.key == K_SPACE: events.append(RequestAttackEvent()) elif self.state == KeyboardController.STATE_SOLVE: if isinstance(event, SolveEvent): self.state = KeyboardController.STATE_ACTION self.solution = "" elif isinstance(event, TickEvent): for pgEvent in pgEventList: if pgEvent.type == KEYDOWN: if pgEvent.unicode in u'0123456789': #typed a digit self.solution += pgEvent.unicode events.append(SolutionUpdateEvent(self.solution)) elif pgEvent.key == K_BACKSPACE: self.solution = self.solution[:-1] events.append(SolutionUpdateEvent(self.solution)) elif pgEvent.key in (K_RETURN, K_KP_ENTER): events.append(SolveEvent(self.solution)) elif self.state == KeyboardController.STATE_VICTORY: if isinstance(event, TickEvent): for pgEvent in pgEventList: if pgEvent.type == KEYDOWN: if pgEvent.key == K_SPACE: events.append(NextBattleEvent()) self.state = KeyboardController.STATE_ACTION for ev in events: self.evManager.Notify(ev)
class ActorModel(object): """Generic class for anything 'alive' -- namely anything that can be involved in combat Tracks the following data: * Current state of Actor (Waiting, Attacking, Defending, Hurting or Dead) * The amount of time that should be spent in the various states * The current and maximum amount of health the Actor has """ STATE_WAITING = 0 STATE_ATTACKING = 1 STATE_DEFENDING = 2 STATE_HURTING = 3 STATE_DEAD = 4 def __init__(self, evID, opponents): self.evManager = EventManager() self.evManager.RegisterListener(self) self.evID = evID self.opponents = opponents self.victim = None self.state = ActorModel.STATE_WAITING self.AttackWait = 300 #milliseconds self.HurtWait = 200 #milliseconds self.time = 0 self.AttackEndTime = self.time self.HurtEndTime = self.time self.maxHealth = 100 self.health = self.maxHealth self.attackPower = 0 # to be defined in subclasses def Wait(self): """Sets Actor's current state to waiting (neutral)""" self.state = ActorModel.STATE_WAITING event = WaitEvent(self.evID) self.evManager.Notify(event) def nextVictim(self): """Determines next victim from list of known opponents""" victims = self.opponents.keys() Debug("available victims: %s" % victims, 5) if len(victims) == 0 or self.victim not in victims: self.victim = None for a in ( 1, 2 ): #search list twice to wrap, since current victim might be last opponent in list for v in victims: if self.victim == None: self.victim = v return if v == self.victim: self.victim = None #will set victim and return on next go-round def Attack(self, damage): """Sets Actor's current state to attacking -- and hurts victim based on given damage value""" if self.state == ActorModel.STATE_WAITING: self.state = ActorModel.STATE_ATTACKING self.AttackEndTime = self.time + self.AttackWait self.nextVictim( ) #currently there is no targeting system -- so just pick the next opponent in line Debug("victim %s has been chosen" % self.victim, 5) event = AttackEvent(self.evID, self.victim, damage) self.evManager.Notify(event) def Defend(self): """Sets Actor's current state do defending (not yet used)""" if self.state == ActorModel.STATE_WAITING: self.state = ActorModel.STATE_DEFENDING event = DefendEvent(self.evID) self.evManager.Notify(event) def Hurt(self, damage): """Sets Actors current state to hurting and deducts damage from health""" self.state = ActorModel.STATE_HURTING self.HurtEndTime = self.time + self.HurtWait if self.health >= 0: self.health -= damage if self.health < 0: self.health = 0 event = HurtEvent(self.evID, 1.0 * self.health / self.maxHealth) self.evManager.Notify(event) if self.health == 0: self.Die() def Die(self): """Sets Actor's current state to dead""" self.state = ActorModel.STATE_DEAD event = DieEvent(self.evID) self.evManager.Notify(event) self.evManager.UnregisterListener(self) def Notify(self, event): """Handles the following events: TickEvent: verify whether actor is currently still attacking or hurting based on duration values for these states HurtEvent: if this actor is being attacked, call hurt """ if isinstance(event, TickEvent): self.time = event.time #ready to stop attacking if self.state == ActorModel.STATE_ATTACKING and self.AttackEndTime <= self.time: self.Wait() if self.state == ActorModel.STATE_HURTING and self.HurtEndTime <= self.time: self.Wait() elif isinstance(event, AttackEvent) and event.object == self.evID: self.Hurt(event.damage)