This is my final version. Uses sqlite3 database for keeping scores and shows top 5 scores on the gameover page.
Used tkinter as the form for getting player initials as it takes a lot of code to create a pygame form.
Anyhow, here it is for anyone wanting to play around with it.
Note: Still could not find the reason player will die when getting to close to the pipes. Finally figured it out. Was setting the size for the rotated pipe for top. Guess it was ghosting or something.
Updated final code.?
import pygame
from random import choice
from pathlib import Path
from os import listdir
import sqlite3 as sq
import tkinter as tk
# Get path to executing script
path = Path(__file__).parent
# some pygame variables
pygame.init()
clock = pygame.time.Clock()
fps = 60
screen_size = (1280, 740)
screen = pygame.display.set_mode(screen_size)
# Colors
black = (0,0,0)
white = (255,255,255)
# Create sprite groups
bird_sprite = pygame.sprite.Group()
pipe_sprite = pygame.sprite.Group()
allsprites = pygame.sprite.Group()
# Create channels
channel1 = pygame.mixer.Channel(1)
channel2 = pygame.mixer.Channel(2)
channel3 = pygame.mixer.Channel(3)
channel4 = pygame.mixer.Channel(4)
# Load logo image
logo = pygame.image.load(f'{path}/assets/my-python-logo.png').convert()
logo = pygame.transform.scale(logo,(logo.get_width()/2, logo.get_height()/2))
# Define the start and end page
def page():
screen.fill((0,0,0))
img = pygame.image.load(f'{path}/assets/message.png')
img = pygame.transform.scale2x(img)
screen.blit(img, (screen_size[0]/2 - img.get_width()/2, screen_size[1]/2 - img.get_height()/2))
font = pygame.font.SysFont('04B_19.TTF', 40)
text = 'Press spacebar to begin - esc to exit. During game play press q to end game.'
surf = font.render(text, True, (150,150,150))
screen.blit(surf, (screen_size[0]/2-font.size(text)[0]/2, screen_size[1]-font.size(text)[1]*2.5))
screen.blit(logo, (50,20))
surf = font.render(chr(169), True, (250,130,0))
screen.blit(surf, (35, 20))
surf = font.render(chr(8482), True, (250,130,0))
screen.blit(surf, (160, 25))
pygame.display.update()
def gameover(score):
row = screen_size[1]/2 - screen_size[1]/2*0.2
screen.fill((0,0,0))
screen.blit(logo, (50,20))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 35)
surf = font.render(chr(169), True, (250,130,0))
screen.blit(surf, (35, 20))
surf = font.render(chr(8482), True, (250,130,0))
screen.blit(surf, (160, 25))
img = pygame.image.load(f'{path}/assets/gameover.png').convert()
img = pygame.transform.scale2x(img)
img.set_colorkey(white)
screen.blit(img, ((screen_size[0]/2-img.get_width()/2, screen_size[1]*0.2)))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 50)
text1 = f'Your Final Score: {score}'
row_align = screen_size[0]/2 - font.size(text1)[0]/2
surface = font.render(text1, True, (255,180,0))
screen.blit(surface, (screen_size[0]/2 - font.size(text1)[0]/2, screen_size[1]*0.38-35))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 60)
text2 = 'Press enter to play again or esc to quit'
surface = font.render(text2, True, (255,180,0))
screen.blit(surface, (screen_size[0]/2 - font.size(text2)[0]/2, screen_size[1]*0.85))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 40)
font.set_underline(True)
text3 = 'Top Scores'
surface = font.render(text3, True, (255,0,0))
screen.blit(surface, (screen_size[0]/2-font.size(text3)[0]/2, row+10))
entries = db.top5()
if entries:
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 40)
for entry in entries:
name, score = entry
row += 40
surface = font.render(f'{name}: {score}', True, (255,255,255))
screen.blit(surface, ((screen_size[0]/2-font.size(text3)[0]/2)+30, row+10))
row = 105
pygame.display.update()
class Database:
def __init__(self):
self.connect = sq.connect(f'{path}/bird_score.db')
self.cursor = self.connect.cursor()
def insert(self, args):
''' Method will write player initials and score to database '''
if len(args) == 2:
query = 'insert into flappy (name, score) values (?,?)'
self.cursor.execute(query, args)
self.connect.commit()
return True
return False
def top5(self):
''' Method will get top 5 scores '''
query = '''select * from flappy order by score desc limit 5'''
data = [info for info in self.cursor.execute(query)]
if len(data) > 0:
return data
return False
def create(self):
''' Method will create a table in the database if not exists '''
query = '''create table if not exists flappy (
name text,
score integer
)'''
self.connect.execute(query)
self.connect.commit()
class Background:
''' Background class handles the creation of backgrounds
be it day or night. Also handles the floor creation '''
def __init__(self):
# set an instance variable for floor x pos
self.floor_x = 0
def day(self):
''' Method changes background to day mode '''
bg = pygame.image.load(f'{path}/assets/background-day.png').convert()
bg = pygame.transform.scale(bg, screen_size)
return bg
def night(self):
''' Method changes background to night mode '''
bg = pygame.image.load(f'{path}/assets/background-night.png').convert()
bg = pygame.transform.scale(bg, screen_size)
return bg
def floor(self):
''' Method displays the floor image '''
_floor = pygame.image.load(f'{path}/assets/base.png').convert()
_floor = pygame.transform.scale(_floor, (screen_size[0], 80))
screen.blit(_floor, (self.floor_x,screen_size[1]-80))
screen.blit(_floor, (self.floor_x + screen_size[0], screen_size[1]-80))
class Pipe(pygame.sprite.Sprite):
''' Class for creating pipes '''
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(f'{path}/assets/pipe-green.png')
self.rect = self.image.get_rect()
self.rect.x = screen_size[0] + self.rect.width
self.rect.bottom = screen_size[1] - 80
self.active = True
# Add pipe object to sprite groups
pipe_sprite.add(self)
allsprites.add(pipe_sprite)
def draw_pipe(self, top, bottom, rotate=False):
''' Method for drawing the pipes '''
if rotate:
self.rect.height = top
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
self.image = pygame.transform.flip(self.image, False, True)
self.rect.x = screen_size[0] + self.rect.width
self.rect.top = 0
else:
self.rect.height = bottom
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
self.rect = self.image.get_rect()
self.rect.x = screen_size[0] + self.rect.width
self.rect.bottom = screen_size[1] - 80
def update(self):
''' Method updates the sprite object '''
# If sprite goes off screen, kill it
if self.rect.right <= 0:
self.kill()
for sprite in bird_sprite:
if sprite.rect.left >= self.rect.right and self.active:
# Setting score to 0.5 because ther are two pipes.
# We only want one point
score.score += 0.5
self.active = False
channel2.play(sound.play('point'))
# Scrolls the pipes across the screen
self.rect.x -= speed
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.gravity = 0.25
# Load the images
down = pygame.image.load(f'{path}/assets/bluebird-downflap.png').convert_alpha()
mid = pygame.image.load(f'{path}/assets/bluebird-midflap.png').convert_alpha()
up = pygame.image.load(f'{path}/assets/bluebird-upflap.png').convert_alpha()
# Scale images
down = pygame.transform.scale2x(down)
mid = pygame.transform.scale2x(mid)
up = pygame.transform.scale2x(up)
# Load the frame list with images and set index
self.frames = [down, mid, up]
self.index = 0
# Load first image
self.image = self.frames[self.index]
# Get image rect and set start position to middle of screen
self.rect = self.image.get_rect()
self.rect.x = screen_size[0]/2 - self.rect.width/2
self.rect.y = screen_size[1]/2 - self.rect.height/2
# Add sprite to sprite groups
# bird_sprite for collison detection and allsprites for animation
bird_sprite.add(self)
allsprites.add(bird_sprite)
def rotate(self, angle):
''' Method for pointer bird up or down '''
img = pygame.transform.rotate(self.frames[self.index], angle)
rect = img.get_rect(center=self.rect.center)
return img
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.image = self.rotate(30)
# Speed for when pointing up
self.rect.y -= 3.6
else:
self.image = self.rotate(-30)
# Default speed when going down
self.rect.y += 2
# Keep bird from flying off top of screen
if self.rect.top <= 10:
self.rect.top = 10
self.image = self.frames[self.index]
class Score:
def __init__(self):
self.score = 0
def show(self):
font = pygame.font.SysFont('04B_19.TTF', 35)
text = f'Score: {self}'
surface = font.render(text, True, (0,0,0))
screen.blit(surface, (screen_size[0]*0.01, screen_size[1]*0.02))
def __repr__(self):
score = int(self.score)
return str(score)
class Sound:
def __init__(self):
self.files = listdir(f'{path}/sound')
def play(self, sfx):
for file in self.files:
if sfx in file:
return pygame.mixer.Sound(f'{path}/sound/{file}')
def play_wings(self, fx):
for file in self.files:
if fx in file:
pygame.mixer.music.load(f'{path}/sound/{file}')
pygame.mixer.music.play(-1)
class Window:
def __init__(self, parent):
self.parent = parent
font = (None, 14, 'normal')
label = tk.Label(parent, text='Enter Initials:', font=font)
label.grid(column=0, row=0, padx=(4,2), pady=(4,2))
self.myvar = tk.StringVar()
self.entry = tk.Entry(parent, textvariable=self.myvar, font=font)
self.entry.grid(column=1, row=0, padx=(2,4), pady=2)
self.entry.focus()
self.btn = tk.Button(parent, text='Submit', command=self.submit)
self.btn.grid(column=0, columnspan=2, sticky='e', padx=4, pady=(2,4))
self.entry.bind('<Return>', self.submit)
self.entry.bind('<KP_Enter>', self.submit)
self.myvar.trace('w', self.validate)
def validate(self, var=None, index=None, mode=None):
if len(self.myvar.get().strip()) == 0:
return False
elif len(self.myvar.get().strip()) > 3:
self.entry.delete(3, 'end')
return False
else:
return True
def submit(self, event=None):
if self.validate():
db.insert((self.myvar.get().strip(), score.score))
self.parent.destroy()
else:
return False
# Initialize some classes
sound = Sound()
score = Score()
# Inialize Database class and create the table
# if it doesnt exist
db = Database()
db.create()
# Game Timers
mode = True
# Mode timer changes from day/night every 30 seconds
timer = pygame.USEREVENT + 1
pygame.time.set_timer(timer, 30000)
# Timer for creating pipes
create_pipe = pygame.USEREVENT + 2
pygame.time.set_timer(create_pipe, 4500)
# Timer for bird wing flap
birdflap = pygame.USEREVENT + 3
pygame.time.set_timer(birdflap, 100)
# Set default background to day mode
background = Background()
bg = background.day()
# background speed
speed = 3
# Setting for going to the start page
gamestate = 'startpage'
running = True
active = True
while running:
######## Event Section ##########################
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == birdflap:
bird.index += 1
if bird.index >= len(bird.frames):
bird.index = 0
bird.image = bird.frames[bird.index]
# Event for creating pipes
if event.type == create_pipe:
top = choice([135,275,415])
bottom = 0
# We want the correct size for the bottom pipe to match top
if top == 135:
bottom = 415
elif top == 275:
bottom = 275
else:
bottom = 135
# Create the pipes
pipe = Pipe()
pipe.draw_pipe(top, bottom)
pipe = Pipe()
pipe.draw_pipe(top, bottom, True)
# Away to exit the game with esc key
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if event.key == pygame.K_SPACE:
if active:
channel3.play(sound.play('swooshing'))
channel4.play(sound.play('wing'), loops = -1)
if event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER:
gamestate = 'play'
score.score = 0
if event.key == pygame.K_q:
gamestate = 'startpage'
# Check if we are in day or night mode. Change accordingly
if event.key == pygame.K_m:
if mode:
bg = background.night()
mode = False
elif not mode:
bg = background.day()
mode = True
if event.type == timer:
mode = False if mode else True
if mode:
bg = background.day()
else:
bg = background.night()
################ gamestate section ##################
# Are we on the start page or play
if gamestate == 'play':
screen.fill((0,255,0))
screen.blit(bg, (0,0))
score.show()
active = True
# Disply and scroll the floor background image
background.floor_x -= speed
if background.floor_x <= -screen_size[0]:
background.floor_x = 0
background.floor()
# If bird crashes into ground, go to gameover page
if bird.rect.bottom >= screen_size[1] - 80:
bird.kill()
channel1.play(sound.play('hit'))
channel1.queue(sound.play('die'))
gamestate = 'form'
collisions = pygame.sprite.groupcollide(bird_sprite, pipe_sprite, True, False)
if collisions:
channel1.play(sound.play('hit'))
channel1.queue(sound.play('die'))
gamestate = 'form'
# Update all game sprites
allsprites.update()
allsprites.draw(screen)
pygame.display.update()
clock.tick(fps)
# gamestate has changed go to start/end page
if gamestate == 'startpage':
# Go to start page and clear all sprite groups
page()
pipe_sprite.empty()
allsprites.empty()
bird_sprite.empty()
bird = Bird()
active = False
if gamestate == 'gameover':
# Go to game over page
gameover(int(score.score))
pipe_sprite.empty()
allsprites.empty()
bird_sprite.empty()
bird = Bird()
active = False
if gamestate == 'form':
if score.score > 0:
channel4.stop()
root = tk.Tk()
Window(root)
root.mainloop()
active = False
channel4.stop()
gamestate = 'gameover'
pygame.quit()
screenshot of gameover page