Mario Vitale
import pygame
import cfg
class BaseState(object):
def __init__(self):
'''
Set the state default properties
'''
self.done = False
self.quit = False
self.next_state = None
self.screen_rect = pygame.display.get_surface().get_rect()
self.persist = {}
self.font = pygame.font.Font(cfg.FONT, cfg.FONT_BIG)
def startup(self, persistent):
'''
Pass the persistent data to the new state
'''
self.persist = persistent
def get_event(self, event):
'''
Listen for the events of the state
'''
# common to all the states
if event.type == pygame.QUIT:
self.quit = True
def update(self, dt):
'''
To use if the state changes in the dt (delta)
e.g.: the splash screen which last a number of seconds
'''
pass
def draw(self, surface):
'''
What the state must draw on the screen
'''
pass
import os
import cfg
import init
import pygame
from .base_state import BaseState
class InterLevel(BaseState):
def __init__(self):
super(InterLevel, self).__init__()
# default title to view
self.title = self.font.render("Level 1", True, pygame.Color("grey"))
self.title_rect = self.title.get_rect(center=(cfg.SCREENSIZE[0]/2,self.title.get_height()/2))
self.fontscore = pygame.font.Font(cfg.FONT, cfg.FONT_SMALL)
# inter-level screen background
# get a random image and scale it to the screen size
self.image = pygame.image.load(cfg.SPLASH_BKG_IMG)
self.image = pygame.transform.scale(self.image, cfg.SCREENSIZE)
self.img_rect = self.image.get_rect()
self.next_state = "SERVE"
self.time_active = 0
# set the background music
self.bkg_music = init.INTER_LEV_MUSIC
self.bkg_music.set_volume(cfg.BKG_MUSIC_VOL)
self.bkg_music.play(-1)
def startup(self, persistent):
self.persist = persistent
# reset the active time
self.time_active = 0
# set the message to show
if self.persist:
self.title = self.font.render(self.persist["message"], True, pygame.Color("grey"))
# set the score to show and the rect (score only if there's a persist)
self.score = self.fontscore.render("Your score: "+str(self.persist["score"]), True, pygame.Color("grey"))
self.score_rect = self.score.get_rect(center=self.screen_rect.center)
else:
self.title = self.font.render("Level 1", True, pygame.Color("grey"))
self.score = self.fontscore.render("Your score: 0", True, pygame.Color("grey"))
# title and score rect is always to get
self.title_rect = self.title.get_rect(center=(cfg.SCREENSIZE[0]/2,self.title.get_height()/2))
self.score_rect = self.score.get_rect(center=self.screen_rect.center)
def update(self, dt):
self.time_active += dt
if self.time_active >= cfg.INTER_LEV_TIME:
self.bkg_music.stop()
self.done = True
def draw(self, surface):
surface.fill(pygame.Color("black"))
#surface.blit(self.image, self.img_rect)
surface.blit(self.title, self.title_rect)
surface.blit(self.score, self.score_rect)
import os
import cfg
import init
import time
import pygame
import random
from states.base_state import BaseState
from actors.paddle import Paddle
from actors.boing import Boing
from actors.brick import Brick
from actors.heartswarm import HeartSwarm
from actors.score import Score
class Serve(BaseState):
def __init__(self):
super(Serve, self).__init__()
# instruction to move to play state
self.font = pygame.font.Font(cfg.FONT, cfg.FONT_MEDIUM)
self.fontscore = pygame.font.Font(cfg.FONT, cfg.FONT_SMALL)
self.title = self.font.render("Press spacebar to play", True, pygame.Color("grey"))
self.title_rect = self.title.get_rect(center=self.screen_rect.center)
# image and all the sizing are done in previous state
self.next_state = "GAMEPLAY"
### State-defined Functions ###
def startup(self, persistent):
'''
overwrites the default as initializes the board
persistent data are not available at init
'''
self.persist = persistent
# play the music
# if persistent data are coming from a previous state keep it otherwise init their values here as they must persist
if not "bkgimg" in self.persist:
self.persist["bkgimg"] = pygame.image.load(self.get_random_image(cfg.BKG_IMGS_PATH))
self.persist["bkgimg"] = pygame.transform.scale(self.persist["bkgimg"], cfg.SCREENSIZE)
self.persist["bkgimg_rect"] = self.persist["bkgimg"].get_rect()
if not "lives" in self.persist:
self.persist["lives"] = cfg.LIVES
if not "score" in self.persist:
self.persist["score"] = 0
if not "scrtxt" in self.persist:
self.persist["scrtxt"] = Score(self.persist["score"])
if not "level" in self.persist:
self.persist["level"] = 1
if not "bricks" in self.persist or len(self.persist["bricks"]) == 0:
self.persist["bricks"] = []
# set a new level brick schema
self.random_level_gen()
if not "paddle" in self.persist:
self.persist["paddle"] = Paddle(random.randint(0, 2))
if not "boing" in self.persist or self.persist["boing"].to_init:
print("init ball")
self.persist["boing"] = Boing(random.randint(0, 1), self.persist["level"])
if not "heartswarm" in self.persist:
self.persist["heartswarm"] = HeartSwarm(self.persist["lives"])
# init the paddle and ball positions
self.persist["boing"].reset()
self.persist["paddle"].reset()
def get_event(self, event):
if event.type == pygame.QUIT:
self.quit = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.done = True
def draw(self, surface):
'''
draw the board according to its status
and highlight the cursor
'''
# clean-up
surface.fill(pygame.Color("black"))
# draw the background
surface.blit(self.persist["bkgimg"],self.persist["bkgimg_rect"])
# other items
self.persist["paddle"].draw(surface)
self.persist["boing"].draw(surface)
self.persist["heartswarm"].draw(surface)
self.persist["scrtxt"].draw(surface)
# wall
for brk in self.persist["bricks"]:
brk.draw(surface)
surface.blit(self.title, self.title_rect)
def get_random_image(self, imgs_path):
'''
Gets the list of images in the path passed as parameter.
Returns a random image full path from the list
'''
imagenames = os.listdir(imgs_path)
assert len(imagenames) > 0
return os.path.join(imgs_path, random.choice(imagenames))
def random_level_gen(self):
'''
Generates a random brick schema
'''
# randomly set the nr of rows and columns (even) within a range (for a better shape)
rownum = random.randint(cfg.WALL_MIN_ROW_NR, cfg.WALL_MAX_ROW_NR)
colnum = random.randint(cfg.WALL_MIN_COL_NR, cfg.WALL_MAX_COL_NR)
# to be sure the number of columns it's even (for a better shape)
colnum = colnum % 2 == 0 and (colnum + 1) or colnum
print("The level has",rownum,"rows and",colnum,"columns")
# cycle on the y: horizontal drawing (line by line)
for y in range(0, rownum):
# check if skip the line (more probable to not skip), if only 2 lines then don't skip
if rownum > 2:
skip_line = random.choice([True, False, False])
else:
skip_line = False
# decide if line alternates the brick shape
alternate_shape = random.choice([True, False])
# if alternates then this flag is used to switch, start with a random value
altflag = random.choice([True, False])
# brick id define the shape: 2 shapes per level. It's the integer to get the image in the BRICKS array
# the more the level is hight, the more the images are various
leveller = min(self.persist["level"], len(init.BRICKS))
shape1 = random.randint(0, leveller)
shape2 = random.randint(0, leveller)
# basing on the rownum and colnum calculate the offset to center the bricks
wall_width = (cfg.BRICK_WIDTH + cfg.BRICK_CLEARANCE)*colnum + cfg.BRICK_CLEARANCE*2 # clearance is considered also to the beginning and to the end
x_offset = (cfg.GAME_WIDTH - wall_width)/2 + cfg.WALL_X_OFFSET_ADJ
wall_height = (cfg.BRICK_HEIGHT + cfg.BRICK_CLEARANCE)*colnum + cfg.BRICK_CLEARANCE*2 # clearance is considered also to the beginning and to the end
y_offset = (cfg.GAME_WIDTH - wall_width)/2 + cfg.WALL_Y_OFFSET_ADJ
# for each column in the current row, define brick position and shape and enter in the array
for x in range(0, colnum):
# if not skip the line place a brick
if not skip_line:
# if not skip the single brick (most of the probability to not skip)
skip_brick = random.randint(0, 3) == 1 and True or False
if not skip_brick:
# init an object having the brick characteristics and populate it
br = {}
br["x"] = x*(cfg.BRICK_WIDTH + cfg.BRICK_CLEARANCE) + x_offset
br["y"] = y*(cfg.BRICK_HEIGHT + cfg.BRICK_CLEARANCE) + y_offset
# deal with alternated shapes
if (alternate_shape and altflag):
br["id"] = shape2
# switch so next brick has a different shape
altflag = not altflag
else:
br["id"] = shape1
# assign the number of "lives" (hit it can stands) to the brick, depends on the level
br["lives"] = self.persist["level"] + random.randint(0, 1)
# create the brick item passing the data
brk = Brick(br)
# add the brick to the array
self.persist["bricks"].append(brk)
# if the bricks are 4 or less
if len(self.persist["bricks"]) < 5:
print("Due to the random, the wall has few bricks let's try again")
self.random_level_gen()
print("amount of bricks to draw",len(self.persist["bricks"]))
import os
import cfg
import init
import time
import pygame
import random
from states.base_state import BaseState
from actors.paddle import Paddle
from actors.boing import Boing
from actors.brick import Brick
from actors.heartswarm import HeartSwarm
class Gameplay(BaseState):
def __init__(self):
super(Gameplay, self).__init__()
# set the font size for this state
self.font = pygame.font.Font(cfg.FONT, cfg.FONT_SMALL)
# default next-state is inter-level
self.next_state = "INTERLEVEL"
### State-defined Functions ###
def startup(self, persistent):
'''
overwrites the default as initializes the level
persistent data are not available at init
'''
# gets the setup from previous state: serve
self.persist = persistent
# set a start random movement to the ball only if not already present (new play)
if "boing" in self.persist:
if self.persist["boing"].dx == 0:
self.persist["boing"].dx = random.randint(-1, 1)
if self.persist["boing"].dy == 0:
self.persist["boing"].dy = - random.randint(1, 1)
def get_event(self, event):
if event.type == pygame.QUIT:
self.quit = True
self.persist["paddle"].get_event(event)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
self.next_state = "PAUSE"
self.done = True
def update(self, dt):
# update the game components using their own functions
self.persist["paddle"].update(dt)
self.persist["boing"].update(dt)
# collision with paddle
if self.persist["boing"].hit(self.persist["paddle"]):
# ball is initialized outside the paddle and y is inverted
self.persist["boing"].y = self.persist["paddle"].y - self.persist["paddle"].height/2
self.persist["boing"].dy = - self.persist["boing"].dy
# x get a direction and an angle according to the hit point (paddle divided in 3 parts)
# first paddle part has been hit, then ball has an angle (paddle velocity/8 is added) - a little dx due to the position
if self.persist["boing"].x < self.persist["paddle"].x + (self.persist["paddle"].width / 3):
self.persist["boing"].dx = self.persist["boing"].dx + self.persist["paddle"].dx/8 - cfg.PADDLE_HIT_ANGLE_ADJ
# third paddle part has been hit, then ball has an angle (paddle velocity/8 is added) + a little dx due to the position
elif self.persist["boing"].x > self.persist["paddle"].x + (2*self.persist["paddle"].width / 3):
self.persist["boing"].dx = self.persist["boing"].dx + self.persist["paddle"].dx/8 + cfg.PADDLE_HIT_ANGLE_ADJ
# if is hit in the middle nothing change
#elif (self.persist["boing"].x > self.persist["paddle"].x + (self.persist["paddle"].width / 3) and self.persist["boing"].x < self.persist["paddle"].x + (2*self.persist["paddle"].width / 3)):
#self.persist["boing"].x = self.persist["boing"].x
# collision with brick
for brk in self.persist["bricks"]:
if self.persist["boing"].hit(brk):
# increase the score
self.persist["score"] = self.persist["score"] + cfg.BRICK_HIT_SCORE
self.persist["scrtxt"].set_score(self.persist["score"])
# set the ball's y behavior
# ball can hit bricks from top or bottom then must be differently initialized
# hit from below
if self.persist["boing"].y >= brk.y:
# set the ball's new position
self.persist["boing"].y = brk.y + brk.height/2
# hit from the top
elif self.persist["boing"].y < brk.y:
self.persist["boing"].y = brk.y - brk.height/2
# in all the cases invert the y
self.persist["boing"].dy = - self.persist["boing"].dy
# set the ball's x behavior
# if hits the first third of the brick then dx goes on left
if self.persist["boing"].x < brk.x + brk.width/3:
self.persist["boing"].dx = - self.persist["boing"].dx - cfg.BRICK_HIT_ANGLE_ADJ
elif self.persist["boing"].x > brk.x + (2*brk.width / 3):
self.persist["boing"].dx = self.persist["boing"].dx + cfg.BRICK_HIT_ANGLE_ADJ
# else hits in the middle and nothing change
# set the brick (move to brick class)
destroyed = brk.hit()
if destroyed:
self.persist["bricks"].remove(brk)
# if balls flows below the screen is lost
if self.persist["boing"].gone:
self.persist["lives"] = self.persist["lives"] - 1
# update the heart swarm removing a heart
self.persist["heartswarm"].set_lives(self.persist["lives"])
# if 0 lives then game lost so next state is gameover
if self.persist["lives"] == 0:
self.next_state = "GAMEOVER"
self.persist["message"] = "You lost!"
# otherwise it serve another ball
else:
self.next_state = "SERVE"
# move to the new state
self.done = True
# if all the bricks are destroyed then the level has been won
if len(self.persist["bricks"]) == 0:
self.persist["level"] = self.persist["level"]+1
self.persist["message"] = "Level "+str(self.persist["level"])
# renew wall and ball
self.persist["bricks"] = {}
self.persist["boing"].to_init = True
self.next_state = "INTERLEVEL"
# mov to the default next-state: interstate to recap
self.done = True
def draw(self, surface):
'''
draw the board according to its status
and highlight the cursor
'''
# clean up
surface.fill(pygame.Color("black"))
# draw the background
surface.blit(self.persist["bkgimg"],self.persist["bkgimg_rect"])
# other items
self.persist["heartswarm"].draw(surface)
self.persist["scrtxt"].draw(surface)
self.persist["paddle"].draw(surface)
self.persist["boing"].draw(surface)
# wall
for brk in self.persist["bricks"]:
brk.draw(surface)
import pygame
import cfg
class Game(object):
def __init__(self, screen, states, start_state):
'''
Initialize the game with a clock, loads the states
'''
self.done = False
self.screen = screen
self.clock = pygame.time.Clock()
self.fps = cfg.FPS
self.clk = cfg.CLK
self.states = states
self.state_name = start_state
self.state = self.states[self.state_name]
def event_loop(self):
'''
Listen for the event and pass them to the
event management function of the current state
'''
for event in pygame.event.get():
self.state.get_event(event)
def flip_state(self):
'''
Move to one state to the other passing
values that must persist in the landing state
'''
current_state = self.state_name
next_state = self.state.next_state
self.state.done = False
self.state_name = next_state
persistent = self.state.persist
self.state = self.states[self.state_name]
self.state.startup(persistent)
def update(self, dt):
'''
Listen for global events like quit or state change then
calls the update function of the current state passing the delta
'''
if self.state.quit:
self.done = True
elif self.state.done:
self.flip_state()
self.state.update(dt)
def draw(self):
'''
Calls the current state draw function on the passed screen
'''
self.state.draw(self.screen)
def run(self):
'''
Runs the game's main loop performing all the actions and setting the delta-time
until done is set to True.
'''
while not self.done:
dt = self.clock.tick(self.fps)
self.event_loop()
self.update(dt)
self.draw()
self.clock.tick(self.clk)
pygame.display.update()
import sys
import pygame
import cfg
import init
from states.menu import Menu
from states.inter_level import InterLevel
from states.game_play import Gameplay
from states.game_over import GameOver
from states.serve import Serve
from states.splash import Splash
from states.pause import Pause
from game import Game
__author__ = "Mario Vitale - m3o"
__version__= "v0.1"
__title__ = cfg.TITLE
def main():
welcome = "Welcome to "+__title__+" - "+__version__+" by "+__author__
print(welcome)
screen = pygame.display.set_mode(cfg.SCREENSIZE)
pygame.display.set_caption(__title__+" - "+__version__)
states = {
"SPLASH": Splash(),
"SERVE": Serve(),
"GAMEPLAY": Gameplay(),
"PAUSE": Pause(),
"INTERLEVEL": InterLevel(),
"GAMEOVER": GameOver(),
}
game = Game(screen, states, "SPLASH")
game.run()
pygame.mixer.quit()
pygame.quit()
sys.exit()
if __name__ == '__main__':
main()
import cfg
import pygame
### INIT PYGAME LIBS
pygame.init()
pygame.mixer.init()
### PARSING FUNCTIONS
def get_quads(tilew, tileh, start_x, start_y, end_x, end_y):
'''
Returns the quad (rectangle) of a certain size from a defined atlas portion.
tilew and tileh are width and height of the tile: the sprite in the atlas.
start_x and start_y define the point where to start the scan.
end_x and end_y define the point where to end the scan.
All the rects for the desired items are placed in the returned array.
'''
rects = []
y = start_y
while y < end_y:
x = start_x
while x < end_x:
#print("Item starts at: (",x,",",y,")")
rects.append(pygame.Rect(x, y, tilew, tileh))
x = x + tilew
y = y + tileh
return rects
def get_bricks():
'''
Basing on the atlas gets the bricks
Brick block is 32x16
Scans an area and remove the last 3 blocks (balls, hearts and key-brick)
'''
bricks = get_quads(32,16,0,0,192,48)
return bricks#[:-3]
def get_balls():
'''
Basing on the atlas gets the balls
Ball block is 8x8
Scans an area and remove the last block which is empty
'''
balls = get_quads(8,8,96,48,128,56) # gets only the first 3
return balls#[:-1]
def get_hearts():
'''
Basing on the atlas gets the hearts directly
Heart block is 10x10
'''
hearts = {
'full': pygame.Rect(128, 48, 10, 10),
'empty': pygame.Rect(138, 48, 10, 10)
}
return hearts
def get_bonus():
'''
Basing on the atlas gets the bonuses
bonus block is 16x16
The area to scan is precise so no further actions needed
'''
return get_quads(16,16,0,192,160,192)
def get_paddles():
'''
Basing on the atlas gets the paddles
Paddle's dimension is not regular: for each color there are several sizes.
Scans an area and fills the array with sub-arrays, one for each dimension
'''
paddles = []
# paddles are located in the following area
x = 0
y = 64
# 4 iterations, for each one gets all the sizes directly
for i in range(0,3):
paddles.append(
{
'small': pygame.Rect(x,y,32,16),
'medium': pygame.Rect(x+32,y,64,16),
'big': pygame.Rect(x+96,y,96,16),
'huge': pygame.Rect(x,y+16,128,16)
}
)
# set the position for the new paddle set of a different color
x = 0
y = y+32
return paddles
### GLOBAL IMAGES VARIABLES
# load the atlas file once
ATLAS = pygame.image.load(cfg.ATLAS)
# get the array of bricks quads (rectangles)
BRICKS = get_bricks()
# get the array of balls quads
BOINGS = get_balls()
# get the array of hearts quads
HEARTS = get_hearts()
# get the array of paddles quads
PADDLES = get_paddles()
# get the array of bonus quads
BONUS = get_bonus()
### GLOBAL SOUND VARIABLES
# load the splash screen background music
SPLASH_MUSIC = pygame.mixer.Sound(cfg.SPLASH_MUSIC)
# load the splash screen background music
INTER_LEV_MUSIC = pygame.mixer.Sound(cfg.INTER_LEV_MUSIC)
# load the game background music
GAME_MUSIC = pygame.mixer.Sound(cfg.GAME_MUSIC)
# load the brick hit sound
BRICK_HIT_SND = pygame.mixer.Sound(cfg.BRICK_HIT_SND)
# load the brick destroyed sound
BRICK_BROKEN = pygame.mixer.Sound(cfg.BRICK_BROKEN)
# load the paddle hit sound
PADDLE_HIT_SND = pygame.mixer.Sound(cfg.PADDLE_HIT_SND)
# load the ball lost sound
BALL_LOST_SND = pygame.mixer.Sound(cfg.BALL_LOST_SND)
# load the wall hit sound
WALL_HIT_SND = pygame.mixer.Sound(cfg.WALL_HIT_SND)
import pygame
import cfg
class Actor(object):
def __init__(self):
'''
Set the state default properties.
It's generic
'''
self.id = 0
self.lives = 0
self.score = 0
self.x = 0
self.y = 0
self.width = 0
self.height = 0
self.gone = False
self.next_state = None
self.persist = {}
def startup(self, persistent):
'''
Pass the persistent data to the new state
'''
self.persist = persistent
def update(self, dt):
'''
To use if the state changes in the dt (delta time)
e.g.: the splash screen which last a number of seconds
'''
pass
def draw(self, surface):
'''
What the state must draw on the screen
'''
pass
def hit(self, target):
'''
What to do when the object collides with something (the target)
basing on rectangles overlaps
'''
pass
import cfg
import init
import pygame
from actors.actor import Actor
class Boing(Actor):
def __init__(self, color, level):
super(Boing, self).__init__()
# initialise the ball size
self.width = cfg.BOING_WIDTH
self.height = cfg.BOING_HEIGHT
# init ball color
self.color = color
# level-speed increment
self.add_speed = (level-1) * cfg.BOING_SPEED_ADJ
# init ball image from atlas and rectangle
self.boing = init.ATLAS
self.boing_rect = init.BOINGS[self.color]
# init the ball position and velocity via reset function
self.to_init = False
self.reset()
#self.collision = False
def hit(self, target):
'''
Returns if the ball collides or not with the target
'''
if self.x > target.x + target.width or target.x > self.x + self.width:
return False
if self.y > target.y + target.height or target.y > self.y + self.height:
return False
else:
self.paddle_hit_sound = init.PADDLE_HIT_SND
self.paddle_hit_sound.play()
return True
def reset(self):
'''
when jumps in serve state set the ball stationary in the middle
constant values (4) are empiric adjustment
'''
# init position
self.x = cfg.GAME_WIDTH / 2 - (self.width/2)
self.y = cfg.GAME_HEIGHT - (self.height/2) - cfg.PADDLE_HEIGHT_ADJ - cfg.BOING_HEIGHT_ADJ
# init the velocity as stopped
self.dy = 0
self.dx = 0
# boing not lost
self.gone = False
def update(self, dt):
self.x = self.x + self.dx * (dt + self.add_speed)
self.y = self.y + self.dy * (dt + self.add_speed)
# bounce against the left wall
if self.x <= 0:
self.x = 0
self.dx = - self.dx
self.wall_hit_sound = init.WALL_HIT_SND
self.wall_hit_sound.play()
# bounce against the right wall
if self.x >= cfg.GAME_WIDTH - self.width:
self.x = cfg.GAME_WIDTH - self.width
self.dx = - self.dx
self.wall_hit_sound = init.WALL_HIT_SND
self.wall_hit_sound.play()
# bounce against top wall
if self.y <= 0:
self.y = 0 + self.width
self.dy = -self.dy
self.wall_hit_sound = init.WALL_HIT_SND
self.wall_hit_sound.play()
# doesn't bounce against bottom wall as you lose the ball (managed by play state)
if self.y >= cfg.GAME_HEIGHT:
self.ball_lost_sound = init.BALL_LOST_SND
self.ball_lost_sound.play()
self.gone = True
def draw(self, surface):
surface.blit(self.boing, (self.x, self.y), self.boing_rect)
from actors.actor import Actor
import init
import cfg
class Brick(Actor):
def __init__(self, brk):
super(Brick, self).__init__()
# default data, these must update and persist in level transition
self.id = brk["id"]
self.lives = brk["lives"]
self.x = brk["x"]
self.y = brk["y"]
self.width = cfg.BRICK_WIDTH
self.height = cfg.BRICK_HEIGHT
# init brick image and rect
self.brick = init.ATLAS
self.brick_rect = init.BRICKS[self.id]
def hit(self):
'''
decrease the brick life and returns if it has been destroyed
'''
self.lives = self.lives - 1
if self.lives == 0:
brick_broken_sound = init.BRICK_BROKEN
brick_broken_sound.play()
return True
else:
return False
def draw(self, surface):
surface.blit(self.brick, (self.x, self.y), self.brick_rect)