def handle_event(self, event): if event.type == pygame.QUIT: self._running = False if event.type == pygame.MOUSEBUTTONDOWN: ball = Ball(self.screen.get_width()/2,self.screen.get_height()/2,20,20,self) ball.setSpeed(random.random()*14-7, random.random()*14-7) self.balls.append(ball)
class Pong: def __init__(self, width=640, height=480, title="Prog Workshop - Pong!"): pygame.init() # Sets key repeat to a max of once every 5 ms #pygame.key.set_repeat(5, 5) self.screen = pygame.display.set_mode((width, height)) pygame.display.set_caption(title) self.clock = pygame.time.Clock() self.screen = pygame.display.get_surface() self._running = True self.player1 = Paddle(0,0,20,100,self) self.player2 = Paddle(width-20,0,20,100,self) self.ball = Ball(width/2,height/2,20,20,self) self.ball.setSpeed(random.random()*14-7, random.random()*14-7) self.balls = [] self.score = Scoreboard(self, 36) def handle_event(self, event): if event.type == pygame.QUIT: self._running = False if event.type == pygame.MOUSEBUTTONDOWN: ball = Ball(self.screen.get_width()/2,self.screen.get_height()/2,20,20,self) ball.setSpeed(random.random()*14-7, random.random()*14-7) self.balls.append(ball) def update_game(self): keys_pressed = pygame.key.get_pressed() if keys_pressed[pygame.K_UP]: self.player2.moveUp() if keys_pressed[pygame.K_DOWN]: self.player2.moveDown() if keys_pressed[pygame.K_w]: self.player1.moveUp() if keys_pressed[pygame.K_s]: self.player1.moveDown() self.ball.update() for ball in self.balls: ball.update() def render(self): background = pygame.Surface(self.screen.get_size()).convert() background.fill((250, 250, 250)) self.screen.blit(background, (0,0)) self.player1.render() self.player2.render() self.ball.render() for ball in self.balls: ball.render() self.score.renderScore() pygame.display.flip() def start(self): while self._running: self.clock.tick(60) # Limit to 60 fps # Handle events for event in pygame.event.get(): self.handle_event(event) # Handle game logic self.update_game() # Handle rendering self.render() pygame.quit()
class GameStateImpl(game_state_base.GameStateBase): __instance = None def __new__(cls): """Override the instantiation of this class. We're creating a singleton yeah""" return None def __init__(self): """ This shouldn't run.. We call __init__ on the __instance member""" #super(GameStateImpl, self).__init__() #self.SetName("Playing State") #print "GAMESTATE Playing State __init__ running (creating data members and method functions)" pass def Init(self, engineRef, takeWith=None): # Snag some vital object refs from the engine object self.game_size = engineRef.game_size self.screen_size = engineRef.screen_size self.cell_size = engineRef.cell_size self.surface_bg = engineRef.surface_bg self.game_viewport = engineRef.game_viewport self.bg_col = engineRef.bg_col self.mixer = engineRef.mixer # TODO perhaps move the loading of this config to the main game engine. Then, simply pass a reference to the config object into this function, rather than doing the loading here # Load config file with open(os.path.normpath(engineRef.exepath + '/../data/config/settings.json'), 'r+') as fd: cfgFromFile = json.load(fd) config = dot_access_dict.DotAccessDict(cfgFromFile) with open(os.path.normpath(engineRef.exepath + '/../data/scores/highscores.json'), 'r') as fd: highScores = json.load(fd) self.vital_stats = GameplayStats(config, highScores) # TODO - Fix Gameplaystats. Right now, it's a patchwork object that takes some items that were loaded in from a cfg file, but others that are hard-coded. The patchwork is an artifact of the patchwork design/implementation of this game (I'm adding things as I figure out how to, heh) self.ball = Ball() self.ball._accumulator_s[1] = 0.0 self.ball.setPosition(32, 0) # Position is given in coordinates on the 64x64 grid. Actual screen coordinates are calculated from grid coordinates self.ball.setSpeed(0,1) self.ball.setMaxSpeed(1,1) self.ball.changeGameState(BallGameState.FREEFALL) #print "Changing ball game state to FREEFALL" self._eventQueue = MessageQueue() # Event queue, e.g. user key/button presses, system events self._eventQueue.Initialize(64) self.mm = DisplayMessageManager() self.rm = RowManager() self.rm.initLevel(self.vital_stats.initialRowSpacing, self.vital_stats.initialRowUpdateDelay, self.cell_size) self.displayMsgScore = DisplayMessage() self.displayMsgScore.create(txtStr="Score: ", position=[66, 5], color=(192,192,192)) self.displayMsgTries = DisplayMessage() self.displayMsgTries.create(txtStr="Tries: ", position=[66, 10], color=(192,192,192)) self.displayMsgLevel = DisplayMessage() self.displayMsgLevel.create(txtStr="Level: ", position=[66, 20], color=(192,192,192)) self.displayMsgCrushed = DisplayMessage() self.displayMsgCrushed.create(txtStr="Crushed :-(", position=[66, 30], color=(192,192,192)) self.displayMsgGameOver = DisplayMessage() self.displayMsgGameOver.create(txtStr="GameOver :-(", position=[66, 32], color=(192,192,192)) # Register Event Listeners self._eventQueue.RegisterListener('ball', self.ball.controlState, 'PlayerControl') self._eventQueue.RegisterListener('rowmgr', self.rm, 'Difficulty') self._eventQueue.RegisterListener('mixer', self.mixer, 'PlaySfx') self._eventQueue.RegisterListener('engine', engineRef, 'Application') # Register the game engine to listen to messages with topic, "Application" # Initialize sound effects # TODO make a class or some other smarter way to manage loading the sound effects self.mixer.addSfxFileToMap('gap' , os.path.normpath(engineRef.exepath + '/../asset/audio/gap.wav')) self.mixer.addSfxFileToMap('crushed' , os.path.normpath(engineRef.exepath + '/../asset/audio/explosion2.wav')) self.mixer.addSfxFileToMap('levelUp' , os.path.normpath(engineRef.exepath + '/../asset/audio/powerup2.wav')) self.mixer.loadSfxFiles() self.mixer.setSfxVolume(config['mixer.sfxVol'] / 10.0) # TODO Design a way to avoid hard-coding the config keys here # A note on dependencies: setSfxVolume has to come after sound effects are loaded. That is because Pygame sets sfx levels per Sound object; the sounds must be loaded to set their volume # Music volume, on the other hand, appears to be configurable at any time # Add music files to mapping # NOTE: We could have initialized the playing state music files in the main menu (along with the main menu music), because the mixer object is shared across all gamestates. But, we're initializing them here because they logically belong to this gamestate self.mixer.addMusicFileToMap('level' , os.path.normpath(engineRef.exepath + '/../asset/audio/gameplay_music_01.ogg')) self.mixer.addMusicFileToMap('gameOver' , os.path.normpath(engineRef.exepath + '/../asset/audio/gameover.ogg')) self.mixer.setMusicVolume(config['mixer.musicVol'] / 10.0) # TODO Design a way to avoid hard-coding the config keys here self.mixer.loadMusicFile('level') self.mixer.playMusic(repeatMode=sound_and_music.SoundNMusicMixer.REPEAT_TRACK) # TODO revisit music playback. Maybe make a few songs to randomly shuffle? def Cleanup(self): # NOTE this class is a port from a C++ class. Because Python is garbage-collected, Cleanup() is probably not necessary here. But it's included for completeness #print "GAMESTATE Playing State cleaning up." pass @staticmethod def Instance(): """Return the instance reference. Create it if it doesn't exist This method is a static method because it does not use any object """ if GameStateImpl.__instance is None: GameStateImpl.__instance = super(GameStateImpl, GameStateImpl).__new__(GameStateImpl) GameStateImpl.__instance.__init__() GameStateImpl.__instance.SetName("Playing State") #print "GAMESTATE Playing State creating __instance object {}".format(GameStateImpl.__instance) #print "GAMESTATE Playing State getting __instance {}".format(GameStateImpl.__instance) return GameStateImpl.__instance # TODO Consider changing "pause" to "PushState" or something; doesn't HAVE to be 'pause' def Pause(self): # TODO check your design - you may need a pointer/reference to the engine here, to be able to push onto the stack. #print "GAMESTATE Playing State pausing" pass # TODO Consider changing "resume" to "PopState" or something; doesn't HAVE to be 'resume' def Resume(self): # TODO check your design - you may need a pointer/reference to the engine here, to be able to pop from the stack #print "GAMESTATE Playing State resume" pass def EnqueueApplicationQuitMessage(self): """Enqueue a message for the application to shut itself down """ self._eventQueue.Enqueue( { 'topic': 'Application', 'payload': { 'action': 'call_function' , 'function_name': 'setRunningFlagToFalse' , 'params' : '' } } ) def ProcessEvents(self, engineRef): for event in pygame.event.get(): if event.type == pygame.QUIT: # Create a quit request message to the application, to shut itself down. This allows the program to do any necessary cleanup before exiting self.EnqueueApplicationQuitMessage() # Basic state mgmt style here if self.vital_stats._gameState == "Playing": if event.type == pygame.KEYDOWN: # Left arrow key if (event.key == pygame.K_LEFT or event.key == pygame.K_j): #self.ball.controlState.setLeftKeyPressedTrue() # NOTE: Every message must have an 'action' key/val. The message parser will look for the 'action' in order to know what to do self._eventQueue.Enqueue( { 'topic': 'PlayerControl', 'payload': { 'action': 'call_function' , 'function_name': 'setLeftKeyPressedTrue' , 'params' : '' } } ) # here, the call keyword says that the message payload is an instruction to call a function # Right arrow key elif (event.key == pygame.K_RIGHT or event.key == pygame.K_l): #self.ball.controlState.setRightKeyPressedTrue() self._eventQueue.Enqueue( { 'topic': 'PlayerControl', 'payload': { 'action': 'call_function' , 'function_name': 'setRightKeyPressedTrue' , 'params': '' } }) # here, the call keyword says that the message payload is an instruction to call a function elif (event.key == pygame.K_p): engineRef.pushState(game_state_pause.GameStateImpl.Instance()) elif event.type == pygame.KEYUP: if (event.key == pygame.K_LEFT or event.key == pygame.K_j): #self.ball.controlState.setLeftKeyPressedFalse() self._eventQueue.Enqueue( { 'topic': 'PlayerControl', 'payload': { 'action': 'call_function' , 'function_name': 'setLeftKeyPressedFalse' , 'params': '' } } ) # here, the call keyword says that the message payload is an instruction to call a function elif (event.key == pygame.K_RIGHT or event.key == pygame.K_l): #self.ball.controlState.setRightKeyPressedFalse() self._eventQueue.Enqueue( { 'topic': 'PlayerControl', 'payload': { 'action': 'call_function' , 'function_name': 'setRightKeyPressedFalse' , 'params': '' } }) # here, the call keyword says that the message payload is an instruction to call a function elif self.vital_stats._gameState == "Crushed": if event.type == pygame.KEYUP: if event.key == pygame.K_RETURN: if self.vital_stats.tries > 0: self.vital_stats._gameState = "Playing" else: self.vital_stats._gameState = "GameOver" # TODO move high score check into a function # Note that for large dicts, keys() is sloowwww.. But here, we only have 10 high scores for rank in sorted(self.vital_stats.highScores.keys()): if self.vital_stats.score > self.vital_stats.highScores[rank]['score']: self.vital_stats.achievedHighScore = True self.vital_stats._newHighScore = { 'rank': rank, 'score': self.vital_stats.score, 'name':'AAA' } # create the new high score obj as part of vital_stats, so it belongs to a scope outside this function (to make doubly sure it still exists when this function exits (though it shouldn't matter, because all Python objs are created on the heap, no?)) break # NOTE: Here, we are not enqueuing the play music function call as a message because the Pygame music mixer is already asynchronous. We can simply call playMusic(), and Pygame handles the rest self.mixer.loadMusicFile('gameOver') self.mixer.playMusic() self.vital_stats._gotCrushed = False # Super janky way of resetting the ball. Running out of time self.ball._accumulator_s[1] = 0.0 self.ball.setPosition(32, 0) # Position is given in coordinates on the 64x64 grid. Actual screen coordinates are calculated from grid coordinates self.ball.setSpeed(0,1) self.ball.setMaxSpeed(1,1) self.ball.controlState.reset(engineRef) self.ball.changeGameState(BallGameState.FREEFALL) # TODO find a way to reuse the memory space used by the original queue Initialize() call (called during initialization of the Playing State). Right now, we're discarding the old and freshly allocating new space. self._eventQueue.Clear() self._eventQueue.Initialize(64) self.mm.clear() self.rm.initLevel(self.vital_stats.initialRowSpacing, self.vital_stats.initialRowUpdateDelay, self.cell_size) elif self.vital_stats._gameState == "GameOver": if event.type == pygame.KEYUP: if event.key == pygame.K_RETURN or event.key == pygame.K_ESCAPE: #print "PlayingState: GameOver KEYUP" if self.vital_stats.achievedHighScore: #print "You reached a high score!! Better than rank #{}".format(int(self.vital_stats._newHighScore['rank'])+1) # rank is stored as a string repr of an int, zero-based. #print "Passing in newHighScore object {} to changeState()".format(self.vital_stats._newHighScore) # Transition to the state that allows the user to enter a new high score. NOTE We may need to keep that in this data scope, rather than switching to a stand-alone state) engineRef.changeState( game_state_new_high_score.GameStateImpl.Instance(), takeWith=self.vital_stats._newHighScore ) else: engineRef.changeState( game_state_main_menu.GameStateImpl.Instance() ) def ProcessCommands(self, engineRef): # TODO maybe put this command extraction logic into a function at the application class level (or base gamestate level). We're reusing the logic in every gamestate instance msg = self._eventQueue.Dequeue() while msg: #print "DEBUG Dequeued message: {}".format(msg) topic = msg['topic'] for listener_obj_dict in self._eventQueue.RegisteredListeners(topic): #print "DEBUG Registered Listener {} processing message {}".format(listener_obj_dict['name'], msg['payload']) # Evaluate the 'action' key to know what to do. The action dictates what other information is required to be in the message if msg['payload']['action'] == 'call_function': # The registered listener had better have the function call available heh... otherwise, kaboom objRef = listener_obj_dict['ref'] fn_ptr = getattr(objRef, msg['payload']['function_name']) argsDict = eval("dict({})".format(msg['payload']['params'])) # params should be a comma-separated list of key=value pairs, e.g. "a = 5, b = 3" #print "function args: {}".format(argsDict) if argsDict: # In this particular gamestate, we want an argsdict to translate to kwargs. This is because the mixer class is written with kwargs (other classes in other gamestates use dicts, in which case, we pass in argsDict as-is)) fn_ptr(self, **argsDict) else: fn_ptr(self) msg = self._eventQueue.Dequeue() def Update(self, engineRef, dt_s, cell_size): # NOTE: How should we Update() and PreRender()? (Acceptable answers also include _not_distinguishing and simply making the PreRender() do what Update() does, and getting rid of Update()) if self.vital_stats._gameState == "Playing": self.ball.update(dt_s, cell_size, self.vital_stats) self.rm.update(dt_s, cell_size, self.vital_stats) self.mm.update(dt_s, cell_size, self.vital_stats) self.updateDifficulty() def PreRenderScene(self, engineRef): self.doCollisions() self.enforceConstraints() def RenderScene(self, engineRef): self.surface_bg.fill((0,0,0)) self.game_viewport.fill(self.bg_col) self.drawGrid(self.game_viewport, self.cell_size, self.game_size) self.rm.draw(self.game_viewport, self.cell_size) self.ball.draw(self.game_viewport, self.cell_size) def drawGrid(self, screen, cell_size, screen_size): for i in range(0, 63): color = 192, 192, 192 pygame.draw.line(screen, color, ( (i + 1) * cell_size[0], 0 ), ( (i + 1) * cell_size[1], screen_size[1] ) ) pygame.draw.line(screen, color, ( 0 , (i + 1) * cell_size[0] ), ( screen_size[1] , (i + 1) * cell_size[1] ) ) def PostRenderScene(self, engineRef): self.updateScore() self.displayMessages() self.displayGameStats() #for i in range(0, ball.getGameState()): # print "BallGameState:{}".format(self.ball.getGameState()), #print self.surface_bg.blit(self.game_viewport, (0, 0)) # blit the game viewport onto the bigger surface_bg pygame.display.flip() def enforceConstraints(self): # (e.g. constrain ball to screen space) for i in range(0, 2): if self.ball._position[i] < 0: self.ball._position[i] = 0 if i == 1 and not self.vital_stats._gotCrushed: self.vital_stats._gotCrushed = True self.vital_stats._gameState = "Crushed" self.vital_stats.tries -= 1 self._eventQueue.Enqueue( { 'topic': 'PlaySfx' , 'payload': { 'action': 'call_function' , 'function_name': 'playSfx' , 'params': 'nameId="crushed"' } } ) if self.ball._position[i] + self.ball._size[i] > 64: self.ball._position[i] = 64 - self.ball._size[i] def updateDifficulty(self): # Some hard-coded stuff here -- would like to make a more robust level management system, but I'm scrambling to meet the submission deadline for Low Rez Jam 2016. Maybe I'll update later if self.vital_stats.score % 200 == 0 and self.vital_stats._lastDifficultyIncreaseScore < self.vital_stats.score: self.vital_stats.level += 1 self.vital_stats._lastDifficultyIncreaseScore = self.vital_stats.score self.mm.setMessage("Level Up!".format(self.vital_stats.GAP_SCORE), [ self.ball._position[0], self.ball._position[1] - self.ball._size[1] ], (192, 64, 64), 8 ) self._eventQueue.Enqueue( { 'topic': 'Difficulty' , 'payload': { 'action': 'call_function' , 'function_name': 'updateDifficulty' , 'params': '' } } ) self._eventQueue.Enqueue( { 'topic': 'PlaySfx' , 'payload': { 'action': 'call_function' , 'function_name': 'playSfx' , 'params': 'nameId="levelUp"' } } ) def doCollisions(self): # There can only ever be 1 collision/contact in this game.. # O(n^2)... terrible for i in xrange(0, len(self.rm._rows)): row = self.rm._rows[i] rowCollisionFound = False # Perhaps put this variable in a class/object? (Purpose of var is to stop testing collisions against all rows upon 1st collision) #print "DEBUG row={} gap={}".format(i, row._gap) #print "\tDEBUG row._collGeoms={}".format(row._collGeoms) # Test collisions for geom in row._collGeoms: if geom: # NOTE: We need to test for collision with a gap first, then check for the row, because the ball can possibly be in contact with both the row and gap at the same time. That way, the test will indicate when the ball is in contact with the gap but not the row at the same time. if geom.isColliding(self.ball._collGeoms[0], self.cell_size): if geom._type == Row.COLLISION_TYPE_GAP: #print "\t\tDEBUG gap collision! ball geom={} row geom={}".format(row._collGeoms[0], geom) if self.ball.getGameState() != BallGameState.FREEFALL: self.ball.changeGameState(BallGameState.FREEFALL) #print "Changing ball game state to FREEFALL" if self.ball._lastRowScored != i: self.ball._lastRowScored = i self.vital_stats.scoredFlag = True # This flag could also be handled as a message from the ball or row or whatever, to the game logic (in a better designed game), to trigger the game logic's score handler self._eventQueue.Enqueue( { 'topic': 'PlaySfx' , 'payload': { 'action': 'call_function' , 'function_name': 'playSfx' , 'params': 'nameId="gap"' } } ) elif geom._type == Row.COLLISION_TYPE_ROW: #print "\t\tDEBUG row collision! ball geom={} row geom={}".format(row._collGeoms[0], geom) # Compute the collision normal (from the center of one AABB to the center of the other. Note: this is what a true contract resolution system should be doing) # Multiply by 0.5 to force Python to convert the numbers from int to float ballCenter = [ self.ball._position[0] + self.ball._size[0] * 0.5, self.ball._position[1] + self.ball._size[1] * 0.5 ] geomCenter = [ geom._position[0] + geom._size[0] * 0.5, geom._position[1] + geom._size[1] * 0.5 ] # This is a straight-up vector subtraction. No vector class needed :-) We're taking madd shortcuts contactNormal = [ ballCenter[0] - geomCenter[0], ballCenter[1] - geomCenter[1] ] # At this point, we know we're colliding already, so we can calculate the penetration depths along each AABB axis penDepth = [ self.ball._collGeoms[0]._maxPt[0] - geom._minPt[0], self.ball._collGeoms[0]._maxPt[1] - geom._minPt[1] ] correctionVector = [0, 0] if abs(penDepth[0]) < abs(penDepth[1]): correctionVector[0] = -penDepth[0] / self.cell_size[0] else: correctionVector[1] = -penDepth[1] / self.cell_size[1] self.ball.setPosition( self.ball._position[0] + correctionVector[0], self.ball._position[1] + correctionVector[1] ) self.ball._computeCollisionGeometry(self.cell_size) # TODO: the ball should recompute its geometry on a setPosition() call. Perhaps a fn override is needed (right now, setPosition is part of base class) self.ball.resetUpdateDelay() # Note: The following draw statements will be invisible unless you also disable screen filling in the draw() fn. But then, the screen won't clear, and you'll have a trail #pygame.draw.circle(screen, (128,0,0), (int(ballCenter[0] * cell_size[0]), int(ballCenter[1] * cell_size[1])), 16, 2) #pygame.draw.circle(screen, (128,0,128), (int(geomCenter[0] * cell_size[0]), int(geomCenter[1] * cell_size[1])), 16, 2) # Change gamestate of ball if self.ball.getGameState() != BallGameState.ON_ROW and self.ball._lastRowTouched != i: self.ball.changeGameState(BallGameState.ON_ROW) self.ball._lastRowTouched = i #print "Changing ball game state to ON_ROW" rowCollisionFound = True break # break out of for loop if we have a collision if rowCollisionFound: # If we found a collision against any row, we can stop testing for any collisions, because we're done. Reset the flag and _exit all for loops_ rowCollisionFound = False break #print # blank line def updateScore(self): # If ball state is FREEFALL at this point, then we can register a score if self.vital_stats.scoredFlag: self.vital_stats.score += self.vital_stats.GAP_SCORE # GAP_SCORE can increase as the difficulty level increases self.vital_stats.scoredFlag = False #print "Jyeaw! Score={}".format(self.vital_stats.score) self.mm.setMessage("+{}".format(self.vital_stats.GAP_SCORE), [ self.ball._position[0], self.ball._position[1] - self.ball._size[1] ] ) def displayMessages(self): self.mm.draw(self.game_viewport, self.cell_size) def displayGameStats(self): # Janky hardcoding here... Just trying to meet the game submission deadline self.displayMsgScore.changeText("Score: {}".format(self.vital_stats.score)) textSurfaceScore = self.displayMsgScore.getTextSurface(self.mm._font) self.surface_bg.blit(textSurfaceScore, (self.displayMsgScore._position[0] * self.cell_size[0], self.displayMsgScore._position[1] * self.cell_size[1] )) self.displayMsgTries.changeText("Tries: {}".format(self.vital_stats.tries)) textSurfaceTries = self.displayMsgTries.getTextSurface(self.mm._font) self.surface_bg.blit(textSurfaceTries, (self.displayMsgTries._position[0] * self.cell_size[0], self.displayMsgTries._position[1] * self.cell_size[1] )) self.displayMsgLevel.changeText("Level: {}".format(self.vital_stats.level)) textSurfaceLevel = self.displayMsgLevel.getTextSurface(self.mm._font) self.surface_bg.blit(textSurfaceLevel, (self.displayMsgLevel._position[0] * self.cell_size[0], self.displayMsgLevel._position[1] * self.cell_size[1] )) if self.vital_stats._gameState == "Crushed": textSurfaceCrushed = self.displayMsgCrushed.getTextSurface(self.mm._font) self.surface_bg.blit(textSurfaceCrushed, (self.displayMsgCrushed._position[0] * self.cell_size[0], self.displayMsgCrushed._position[1] * self.cell_size[1] )) if self.vital_stats._gameState == "GameOver": textSurfaceGameOver = self.displayMsgGameOver.getTextSurface(self.mm._font) self.surface_bg.blit(textSurfaceGameOver, (self.displayMsgGameOver._position[0] * self.cell_size[0], self.displayMsgGameOver._position[1] * self.cell_size[1] ))