Source code for ple.games.flappybird

import os
import sys
import numpy as np

import pygame
from pygame.constants import K_w
from .. import base


class BirdPlayer(pygame.sprite.Sprite):

    def __init__(self, 
            SCREEN_WIDTH, SCREEN_HEIGHT, init_pos,
            image_assets, rng, color="red", scale=1.0):

        self.SCREEN_WIDTH = SCREEN_WIDTH
        self.SCREEN_HEIGHT = SCREEN_HEIGHT
        
        self.image_order = [0, 1, 2, 1]
        #done image stuff

        pygame.sprite.Sprite.__init__(self)
      
        self.image_assets = image_assets
       
        self.init(init_pos, color)
        
        self.height = self.image.get_height()
        self.scale = scale
        
        #all in terms of y
        self.vel = 0 
        self.FLAP_POWER = 9*self.scale
        self.MAX_DROP_SPEED = 10.0
        self.GRAVITY = 1.0*self.scale

        self.rng = rng

        self._oscillateStartPos() #makes the direction and position random
        self.rect.center = (self.pos_x, self.pos_y) #could be done better
    
    def init(self, init_pos, color):
        #set up the surface we draw the bird too
        self.flapped = True #start off w/ a flap
        self.current_image = 0
        self.color = color
        self.image = self.image_assets[self.color][self.current_image]
        self.rect = self.image.get_rect()
        self.thrust_time = 0.0 
        self.tick = 0
        self.pos_x = init_pos[0]
        self.pos_y = init_pos[1]
        
    def _oscillateStartPos(self):
        offset = 8*np.sin( self.rng.rand() * np.pi )
        self.pos_y += offset

    def flap(self):
        if self.pos_y > -2.0*self.image.get_height():
            self.vel = 0.0
            self.flapped = True

    def update(self, dt):
        self.tick += 1

        #image cycle
        if (self.tick + 1) % 15 == 0:
            self.current_image += 1

            if self.current_image >= 3:
                self.current_image = 0
           
            #set the image to draw with.
            self.image = self.image_assets[self.color][self.current_image]
            self.rect = self.image.get_rect()
      
        if self.vel < self.MAX_DROP_SPEED and self.thrust_time == 0.0:
            self.vel += self.GRAVITY

        #the whole point is to spread this out over the same time it takes in 30fps.
        if self.thrust_time+dt <= (1.0/30.0) and self.flapped:
            self.thrust_time += dt
            self.vel += -1.0*self.FLAP_POWER
        else:
            self.thrust_time = 0.0
            self.flapped = False

        self.pos_y += self.vel
        self.rect.center = (self.pos_x, self.pos_y)

    def draw(self, screen):
        screen.blit(self.image, self.rect.center)

class Pipe(pygame.sprite.Sprite):
    
    def __init__(self, 
            SCREEN_WIDTH, SCREEN_HEIGHT, gap_start, gap_size, image_assets, scale,
            offset=0, color="green"):

        self.speed = 4.0*scale
        self.SCREEN_WIDTH = SCREEN_WIDTH
        self.SCREEN_HEIGHT = SCREEN_HEIGHT
        
        self.image_assets = image_assets
        #done image stuff

        self.width = self.image_assets["green"]["lower"].get_width()
        pygame.sprite.Sprite.__init__(self)
        
        self.image = pygame.Surface((self.width, self.SCREEN_HEIGHT))
        self.image.set_colorkey((0,0,0))
        
        self.init(gap_start, gap_size, offset, color)

    def init(self, gap_start, gap_size, offset, color):
        self.image.fill((0,0,0))
        self.gap_start = gap_start
        self.x = self.SCREEN_WIDTH+self.width+offset
        
        self.lower_pipe = self.image_assets[color]["lower"]
        self.upper_pipe = self.image_assets[color]["upper"]

        
        top_bottom = gap_start-self.upper_pipe.get_height() 
        bottom_top = gap_start+gap_size
        
        self.image.blit(self.upper_pipe, (0, top_bottom ))
        self.image.blit(self.lower_pipe, (0, bottom_top ))
        
        self.rect = self.image.get_rect()
        self.rect.center = (self.x, self.SCREEN_HEIGHT/2)

    def update(self, dt):
        self.x -= self.speed
        self.rect.center = (self.x, self.SCREEN_HEIGHT/2)

class Backdrop():

    def __init__(self, SCREEN_WIDTH, SCREEN_HEIGHT, image_background, image_base, scale):
        self.SCREEN_WIDTH = SCREEN_WIDTH
        self.SCREEN_HEIGHT = SCREEN_HEIGHT

        self.background_image =  image_background
        self.base_image = image_base

        self.x = 0
        self.speed = 4.0*scale 
        self.max_move = self.base_image.get_width() - self.background_image.get_width()
    
    def update_draw_base(self, screen, dt):
        #the extra is on the right
        if self.x > -1*self.max_move:
            self.x -= self.speed 
        else:
            self.x = 0
       
        screen.blit(self.base_image, (self.x, self.SCREEN_HEIGHT*0.79)) 

    def draw_background(self, screen):
        screen.blit(self.background_image, (0,0))

[docs]class FlappyBird(base.Game): """ Used physics values from sourabhv's `clone`_. .. _clone: https://github.com/sourabhv/FlapPyBird Parameters ---------- width : int (default: 288) Screen width. Consistent gameplay is not promised for different widths or heights, therefore the width and height should not be altered. height : inti (default: 512) Screen height. pipe_gap : int (default: 100) The gap in pixels left between the top and bottom pipes. """ def __init__(self, width=288, height=512, pipe_gap=100): actions = { "up": K_w } fps = 30 base.Game.__init__(self, width, height, actions=actions) self.scale = 30.0/fps self.allowed_fps = 30 #restrict the fps self.pipe_gap = 100 self.pipe_color = "red" self.images = {} #so we can preload images pygame.display.set_mode((1,1), pygame.NOFRAME) self._dir_ = os.path.dirname(os.path.abspath(__file__)) self._asset_dir = os.path.join( self._dir_, "assets/" ) self._load_images() self.pipe_offsets = [0, self.width*0.5, self.width] self.init_pos = ( int( self.width * 0.2), int( self.height / 2 ) ) self.pipe_min = int(self.pipe_gap/4) self.pipe_max = int(self.height*0.79*0.6 - self.pipe_gap/2) self.backdrop = None self.player = None self.pipe_group = None def _load_images(self): #preload and convert all the images so its faster when we reset self.images["player"] = {} for c in ["red", "blue", "yellow"]: image_assets = [ os.path.join( self._asset_dir, "%sbird-upflap.png" % c ), os.path.join( self._asset_dir, "%sbird-midflap.png" % c ), os.path.join( self._asset_dir, "%sbird-downflap.png" % c ), ] self.images["player"][c] = [ pygame.image.load(im).convert_alpha() for im in image_assets ] self.images["background"] = {} for b in ["day", "night"]: path = os.path.join( self._asset_dir, "background-%s.png" % b ) self.images["background"][b] = pygame.image.load(path).convert() self.images["pipes"] = {} for c in ["red", "green"]: path = os.path.join( self._asset_dir, "pipe-%s.png" % c ) self.images["pipes"][c] = {} self.images["pipes"][c]["lower"] = pygame.image.load(path).convert_alpha() self.images["pipes"][c]["upper"] = pygame.transform.rotate(self.images["pipes"][c]["lower"], 180) path = os.path.join( self._asset_dir, "base.png" ) self.images["base"] = pygame.image.load(path).convert() def init(self): if self.backdrop is None: self.backdrop = Backdrop( self.width, self.height, self.images["background"]["day"], self.images["base"], self.scale ) if self.player is None: self.player = BirdPlayer( self.width, self.height, self.init_pos, self.images["player"], self.rng, color="red", scale=self.scale ) if self.pipe_group is None: self.pipe_group = pygame.sprite.Group([ self._generatePipes(offset=-75), self._generatePipes(offset=-75+self.width/2), self._generatePipes(offset=-75+self.width*1.5) ]) color = self.rng.choice(["day", "night"]) self.backdrop.background_image = self.images["background"][color] #instead of recreating color = self.rng.choice(["red", "blue", "yellow"]) self.player.init(self.init_pos, color) self.pipe_color = self.rng.choice(["red", "green"]) for i,p in enumerate(self.pipe_group): self._generatePipes(offset=self.pipe_offsets[i], pipe=p) self.score = 0.0 self.lives = 1 self.tick = 0
[docs] def getGameState(self): """ Gets a non-visual state representation of the game. Returns ------- dict * player y position. * players velocity. * next pipe distance to player * next pipe top y position * next pipe bottom y position * next next pipe distance to player * next next pipe top y position * next next pipe bottom y position See code for structure. """ pipes = [] for p in self.pipe_group: if p.x > self.player.pos_x: pipes.append((p, p.x - self.player.pos_x)) sorted(pipes, key=lambda p: p[1]) next_pipe = pipes[1][0] next_next_pipe = pipes[0][0] if next_next_pipe.x < next_pipe.x: next_pipe, next_next_pipe = next_next_pipe, next_pipe state = { "player_y": self.player.pos_y, "player_vel": self.player.vel, "next_pipe_dist_to_player": next_pipe.x - self.player.pos_x, "next_pipe_top_y": next_pipe.gap_start, "next_pipe_bottom_y": next_pipe.gap_start+self.pipe_gap, "next_next_pipe_dist_to_player": next_next_pipe.x - self.player.pos_x, "next_next_pipe_top_y": next_next_pipe.gap_start, "next_next_pipe_bottom_y": next_next_pipe.gap_start+self.pipe_gap } return state
def getScore(self): return self.score def _generatePipes(self, offset=0, pipe=None): start_gap = self.rng.random_integers( self.pipe_min, self.pipe_max ) if pipe == None: pipe = Pipe( self.width, self.height, start_gap, self.pipe_gap, self.images["pipes"], self.scale, color=self.pipe_color, offset=offset ) return pipe else: pipe.init(start_gap, self.pipe_gap, offset, self.pipe_color) def _handle_player_events(self): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN: key = event.key if key == self.actions['up']: self.player.flap() def game_over(self): return self.lives <= 0 def step(self, dt): self.tick += 1 dt = dt / 1000.0 self.score += self.rewards["tick"] #handle player movement self._handle_player_events() for p in self.pipe_group: hit = pygame.sprite.spritecollide(self.player, self.pipe_group, False) for h in hit: #do check to see if its within the gap. top_pipe_check = ((self.player.pos_y - self.player.height/2) <= h.gap_start) bot_pipe_check = ((self.player.pos_y + self.player.height) > h.gap_start+self.pipe_gap) if top_pipe_check: self.lives -= 1 if bot_pipe_check: self.lives -= 1 #is it past the player? if (p.x - p.width/2) <= self.player.pos_x < (p.x - p.width/2 + 4): self.score += self.rewards["positive"] #is out out of the screen? if p.x < -p.width: self._generatePipes(offset=self.width*0.2, pipe=p) #fell on the ground if self.player.pos_y >= 0.79*self.height - self.player.height: self.lives -= 1 #went above the screen if self.player.pos_y < -self.player.height: self.lives -= 1 self.player.update(dt) self.pipe_group.update(dt) if self.lives <= 0: self.score += self.rewards["loss"] self.backdrop.draw_background(self.screen) self.pipe_group.draw(self.screen) self.backdrop.update_draw_base(self.screen, dt) self.player.draw(self.screen)