2
\$\begingroup\$

Recently, I have been trying to improve my programming and coding skills in python so I have decided to make some games with the pygame module.

I have made this flappy bird clone with pygame and would love to get review of the code, what should I change/improve, any tips are welcome.

Code is here, in this github link : click here

import pygame
import random
import os

pygame.init()
SIZE = [400, 708]
FONT = pygame.font.SysFont('arialrounded', 50)


class Bird:
    def __init__(self):
        self.x = 50
        self.y = 350
        self.jump = 0
        self.jump_speed = 10
        self.gravity = 10
        self.dead = False
        self.sprite = 0
        self.bird_sprites = [pygame.image.load("images/1.png").convert_alpha(),
                             pygame.image.load("images/2.png").convert_alpha(),
                             pygame.image.load("images/dead.png").convert_alpha()]
        # self.img_rect =

    def move(self):
        if self.dead:  # dead bird
            self.sprite = 2  # change to dead.png
            # keeps falling until it hits the ground
            if self.y < SIZE[1] - 30:
                self.y += self.gravity
        elif self.y > 0:
            # handling movement while jumping
            if self.jump:
                self.sprite = 1  # change to 2.png
                self.jump_speed -= 1
                self.y -= self.jump_speed
            else:
                # regular falling (increased gravity)
                self.gravity += 0.2
                self.y += self.gravity
        else:
            # in-case where the bird reaches the top
            # of the screen
            self.jump = 0
            self.y += 3

    def bottom_check(self):
        # bird hits the bottom = DEAD
        if self.y >= SIZE[1] - 30:
            self.dead = True

    def get_rect(self):
        # updated bird image rectangle
        img_rect = self.bird_sprites[self.sprite].get_rect()
        img_rect[0] = self.x
        img_rect[1] = self.y
        return img_rect


class Pillar:
    def __init__(self, pos):
        # pos == True is top , pos == False is bottom
        self.pos = pos
        self.img = self.get_image()

    def get_rect(self):
        # returns the pillar image rect
        return self.img.get_rect()

    def get_image(self):
        if self.pos:  # image for the top pillar
            return pygame.image.load("images/top.png").convert_alpha()
        else:  # image for the bottom pillar
            return pygame.image.load("images/bottom.png").convert_alpha()


class Options:
    def __init__(self):
        self.score_img = pygame.image.load("images/score.png").convert_alpha()  # score board image
        self.play_img = pygame.image.load("images/play.png").convert_alpha()  # play button image
        self.play_rect = self.play_img.get_rect()
        self.score_rect = self.score_img.get_rect()
        self.align_position()
        self.score = 0
        self.font = FONT

    def align_position(self):
        # aligns the "menu" in certain positions
        self.play_rect.center = (200, 330)
        self.score_rect.center = (200, 220)

    def inc(self):
        # score increased by 1
        self.score += 1


class Game:
    def __init__(self):
        self.screen = pygame.display.set_mode((SIZE[0], SIZE[1]))
        pygame.display.set_caption("Flappy Bird")
        self.background = pygame.image.load("images/background.png").convert()  # background image
        self.pillar_x = 400
        self.offset = 0
        self.top_p = Pillar(1)  # top pillar
        self.bot_p = Pillar(0)  # bottom pillar
        self.pillar_gap = 135  # gap between pillars, (can be randomised as well)
        self.bird = Bird()  # bird object
        self.score_board = Options()
        self.passed = False  # allows to keep track of the score

    def pillar_move(self):
        # handling pillar movement in the background
        if self.pillar_x < -100:
            self.offset = random.randrange(-120, 120)
            self.passed = False
            self.pillar_x = 400
        self.pillar_x -= 5

    def run(self):
        clock = pygame.time.Clock()
        done = True
        while done:
            clock.tick(60)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    quit()
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        # bird jumps
                        self.bird.jump = 17
                        self.bird.gravity = 5
                        self.bird.jump_speed = 10
                if event.type == pygame.MOUSEBUTTONDOWN:
                    # clicking on the play button (game reset)
                    if self.bird.dead and self.score_board.play_rect.collidepoint(event.pos):
                        self.bird.dead = False
                        self.reset()

            self.screen.blit(self.background, (0, 0))
            self.screen.blit(self.top_p.img, (self.pillar_x, 0 - self.pillar_gap - self.offset))
            self.screen.blit(self.bot_p.img, (self.pillar_x, 360 + self.pillar_gap - self.offset))
            self.screen.blit(self.bird.bird_sprites[self.bird.sprite], (self.bird.x, self.bird.y))
            self.pillar_move()
            self.bird.move()
            self.bird.bottom_check()
            if not self.bird.dead:
                self.collision()
                self.show_score()
            else:
                self.game_over()
            pygame.display.flip()

    def get_pillar_rect(self, pillar):
        # returns current pillar rectangle on display
        rect = pillar.get_image().get_rect()
        rect[0] = self.pillar_x
        if pillar.pos:
            # current rect y position for top pillar
            rect[1] = 0 - self.pillar_gap - self.offset
        else:
            # current rect y position for bottom pillar
            rect[1] = 360 + self.pillar_gap - self.offset
        return rect

    def collision(self):
        top_rect = self.get_pillar_rect(self.top_p)
        bot_rect = self.get_pillar_rect(self.bot_p)
        # collision check bird <> pillars
        if top_rect.colliderect(self.bird.get_rect()) or bot_rect.colliderect(self.bird.get_rect()):
            # print(self.bird.bird_sprites[self.bird.sprite].get_rect())
            self.bird.dead = True
        # if bird passed the pillars
        elif not self.passed and top_rect.right < self.bird.x:
            self.score_board.inc()
            self.passed = True

    def reset(self):
        # game values reset
        self.score_board.score = 0
        self.bird = Bird()
        self.top_p = Pillar(1)
        self.bot_p = Pillar(0)
        self.pillar_x = 400
        self.bird.gravity = 10

    def show_score(self):
        # score font
        score_font = FONT.render("{}".format(self.score_board.score),
                                               True, (255, 80, 80))
        # score font rectangle
        font_rect = score_font.get_rect()
        font_rect.center = (200, 50)
        self.screen.blit(score_font, font_rect)  # show score board font

    def game_over(self):
        # score font
        score_font = FONT.render("{}".format(self.score_board.score),
                                     True, (255, 80, 80))
        # score font rectangle
        font_rect = score_font.get_rect()
        score_rect = self.score_board.score_rect
        play_rect = self.score_board.play_rect  # play button rectangle
        font_rect.center = (200, 230)
        self.screen.blit(self.score_board.play_img, play_rect)  # show play button
        self.screen.blit(self.score_board.score_img, score_rect)  # show score board image
        self.screen.blit(score_font, font_rect)  # show score font


os.chdir(os.path.dirname(__file__))
if __name__ == "__main__":
    game = Game()
    game.run()
\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Review! Unfortunately your question is off-topic as of now, as the code to be reviewed must be present in the question. Please add the code you want reviewed in your question. Thanks! \$\endgroup\$
    – Malachi
    Commented Aug 24, 2017 at 18:34

1 Answer 1

2
\$\begingroup\$

Nice, not many things to improve, so only some tips.


You use many pictures. Instead of hard-coding their names spread over your code, consider using constant variables near the top of your code, something like

import os.path

FOLDER = 'images'
PICS = dict(BACKGROUND='background.png',
            SCORE='score.png',
            PLAY='play.png'
       )
PICS = {k: os.path.join(FOLDER, v) for k, v in PICS.items()}

to make your code easy maintainable.

Note the construction of dictionary with keywords to avoid put the key names in apostrophes (BTW, apostrophes are preferred over quotes in Python) and dictionary comprehension to avoid repeated use of 'images'.


It's useful to follow the DRY principle - Don't Repeat Yourself.

You repeatedly use the long construction

pygame.image.load("images/score.png").convert_alpha()

Why don't dedicate a function to it, e. g.

def image_load(name):
    return pygame.image.load(name).convert_alpha()

and then use it - together with my previous tip - instead of commands as this one

self.score_img = pygame.image.load("images/score.png").convert_alpha()  # score board image

to shorten it to

self.score_img = image_load(PICS[SCORE])  # score board image

Sometimes you use so called magic numbers, e. g.

self.pillar_x = 400     # did you mean 'self-pillar_x = SIZE[0]' ?

or

(255, 80, 80)           # did you mean something which may have the name FONT_COLOR?

Instead of the superfluous construction

pygame.display.set_mode((SIZE[0], SIZE[1]))

you may simply use

pygame.display.set_mode(SIZE)

as the list of coordinates is as good as a tuple of them.

\$\endgroup\$
1
  • \$\begingroup\$ thanks a lot for your review and your time devoting to it! I will take these tips into account when writing my next programs. \$\endgroup\$ Commented Aug 26, 2017 at 9:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.