Python Forum
Thread Rating:
  • 2 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
PyBreakout
#1
A simple little breakout program I wrote using PyGame.

#############################################################################################################
#  ________  ___    ___ ________  ________  _______   ________  ___  __    ________  ___  ___  __________   #
# |\   __  \|\  \  /  /|\   __  \|\   __  \|\  ___ \ |\   __  \|\  \|\  \ |\   __  \|\  \|\  \|\___   ___\  #
# \ \  \|\  \ \  \/  / | \  \|\ /\ \  \|\  \ \   __/|\ \  \|\  \ \  \/  /|\ \  \|\  \ \  \\\  \|___ \  \_|  #
#  \ \   ____\ \    / / \ \   __  \ \   _  _\ \  \_|/_\ \   __  \ \   ___  \ \  \\\  \ \  \\\  \   \ \  \   #
#   \ \  \___|\/  /  /   \ \  \|\  \ \  \\  \\ \  \_|\ \ \  \ \  \ \  \\ \  \ \  \\\  \ \  \\\  \   \ \  \  #
#    \ \__\ __/  / /      \ \_______\ \__\\ _\\ \_______\ \__\ \__\ \__\\ \__\ \_______\ \_______\   \ \__\ #
#     \|__||\___/ /        \|_______|\|__|\|__|\|_______|\|__|\|__|\|__| \|__|\|_______|\|_______|    \|__| #
#          \|___|/                                                                                          #
#                                                                                                   v1.0    #
#############################################################################################################

# PyBreakout 1.0
# Terry Ritchie
# 04/24/17

# -----------------------------------------------------------------------------------------------------------
# - IMPORT LIBRARIES ----------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------

import pygame                   # pygame graphics engine
import random                   # random number library
import os                       # operating system library
import winsound                 # windows sound library
import sys                      # system functions
from pygame.locals import *     # pygame constants

# -----------------------------------------------------------------------------------------------------------
# - DEFINE CONSTANTS ----------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------

FPS       = 240                 # frames per second of game play
BLACK     = (  0,   0,   0)     # define bright colors
BLUE      = (  0,   0, 255)
GREEN     = (  0, 255,   0)
CYAN      = (  0, 255, 255)
RED       = (255,   0,   0)
PURPLE    = (255,   0, 255)
YELLOW    = (255, 255,   0)
WHITE     = (255, 255, 255)
DBLUE     = (  0,   0, 127)     # define dark colors
DGREEN    = (  0, 127,   0)
DCYAN     = (  0, 127, 127)
DRED      = (127,   0,   0)
DPURPLE   = (127,   0, 127)
DYELLOW   = (127, 127,   0)
GRAY      = (127, 127, 127)
SILVER    = (191, 191, 191)
COLOR     = (BLUE, GREEN, CYAN, RED, PURPLE, YELLOW, DBLUE, DGREEN, DCYAN, DRED, DPURPLE, DYELLOW)
STARS     = 100                 # number of stars in starfield
STARX     = 0                   # star x index within star list
STARY     = 1                   # star y index within star list
STARSPEED = 2                   # star speed index within star list
STARCOLOR = 3                   # star color index within star list
STARCLDIR = 4                   # star color direction index within star list
INPLAY    = 4                   # brick in play index within brick list
BRICKHIT  = 5                   # number of times brick hit index within brick list

# -----------------------------------------------------------------------------------------------------------
# - DEFINE VARIABLES ----------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------

brick = []                      # master brick list
star = []                       # master star list
saying = []                     # rendered font surface images of each level text
scoretext = []                  # custom number font
launch = False                  # ball has been launched
newgame = False                 # new game in progress
levelshown = False              # has level saying been shown
extra = False                   # extra paddle awarded
spacebar = 0                    # rendered font surface image
p1txt = 0                       # rendered font surface image
hitxt = 0                       # rendered font surface image
welcome = 0                     # rendered font surface image
screen = 0                      # window surface
px = 0                          # player paddle x coordinate
py = 516                        # player paddle y coordinate
prect = 0                       # paddle collision rectangle
paddles = 3                     # number of player paddles
bx = 0.0                        # ball x coordinate 
by = 0.0                        # ball y coordinate
brect = 0                       # ball collision rectangle
bxdir = 0.0                     # ball x direction  
bydir = 0.0                     # ball y direction
bspeed = 0.0                    # ball speed
maxbspeed = 0.0                 # ball maximum speed
minxspeed = 0.0                 # ball minimum horizontal speed
bspeedinc = 0.0                 # ball speed increment
trajectory = 0.0                # ball trajectory when hit paddle
score = 0                       # player score
hiscore = 0                     # high score
level = 0                       # current level of play
bhit = 0                        # number of times brick must be hit
bricks = 0                      # total number of bricks
clock = 0                       # FPS timer
countdown = 0                   # count down timer
frame = 0                       # frame counter

# -----------------------------------------------------------------------------------------------------------
# - DEFINE FUNCTIONS ----------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------

# -----------------------------------------------------------------------------------------------------------
def Setup(): # creates the assets needed to play the game                                             Setup()
# -----------------------------------------------------------------------------------------------------------

    global screen, clock, star, brick, spacebar, p1txt, hitxt, saying, welcome

    os.environ["SDL_VIDEO_CENTERED"] = "1"                          # center the surface on desktop
    pygame.init()                                                   # start pygame engine
    screen = pygame.display.set_mode((768, 576))                    # create the surface window
    pygame.display.set_caption("PyBreakout!")                       # give window a caption
    pygame.mouse.set_visible(False)                                 # hide mouse pointer
    clock = pygame.time.Clock()                                     # start clock routines
    font = pygame.font.SysFont("Calibri", 40, True, False)          # create font and rendered surfaces
    spacebar = font.render("PRESS SPACEBAR TO LAUNCH", True, WHITE)
    p1txt = font.render("PLAYER 1", True, YELLOW)
    hitxt = font.render("HIGH", True, YELLOW)
    welcome = font.render("WELCOME TO PYBREAKOUT!", True, WHITE)
    saying.append(font.render("Try Again", True, CYAN))
    saying.append(font.render("Level 1 - Easy Peasy", True, CYAN))
    saying.append(font.render("Level 2 - Here We Go!", True, CYAN))
    saying.append(font.render("Level 3 - Twice As Fun", True, CYAN))
    saying.append(font.render("Level 4 - Even Faster", True, CYAN))
    saying.append(font.render("Level 5 - Outstanding!", True, CYAN))
    saying.append(font.render("Level 6 - Double Whammy", True, CYAN))
    saying.append(font.render("Level 7 - Super Human", True, CYAN))
    saying.append(font.render("Level 8 - Unstoppable!", True, CYAN))
    saying.append(font.render("Level 9 - Here Be Dragons", True, CYAN))
    saying.append(font.render("Breakout Master!", True, CYAN))
    scoretext.append(["0000 00 00 0000"])                           # create custom number strings list
    scoretext.append(["  1  1  1  1  1"])
    scoretext.append(["222  22222  222"])
    scoretext.append(["333  3333  3333"])
    scoretext.append(["4 44 4444  4  4"])
    scoretext.append(["5555  555  5555"])
    scoretext.append(["6666  6666 6666"])
    scoretext.append(["777  7  7  7  7"])
    scoretext.append(["8888 88888 8888"])
    scoretext.append(["9999 9999  9999"])
    for i in range(STARS):                                          # create starfield
        sx = random.randrange(0, 767)
        sy = random.randrange(0, 575)
        cd = 0
        while not(cd):
            cd = random.randrange(-1, 2)
        ss = 0
        while ss < .25:
            ss = random.random()
        star.append([sx, sy, ss, random.randrange(1, 255), cd])
    for i in range(6):                                              # create bricks
        b = []
        for j in range(16):
            b.append([j * 48 + 1, 96 + (i * 24) + 1, 47, 23, True, 0])
        brick.append(b)

# -----------------------------------------------------------------------------------------------------------
def Draw_Digit(dx, dy, d, c, s): # draw a large digit to the screen                              Draw_Digit()
# -----------------------------------------------------------------------------------------------------------

    # dx = digit top left x coordinate
    # dy = digit top left y coordinate
    # d  = digit (0 - 9)
    # c  = digit color
    # s  = size of digit squares

    # The large digits are drawn as follows:
    #
    #              x 
    #          ---------
    #          0   1   2
    #        +---+---+---+      +---+---+---+   If there is a character (non-space) in the text a
    #    | 0 | * | * | * |      | * | * | * |   square is drawn in that position. As the nested for
    #    |   +---+---+---+      +---+---+---+   statements progress a number is drawn from the text
    #    | 1 | * |   | * |      |   |   | * |   that describes where a square should be placed in
    #    |   +---+---+---+      +---+---+---+   each row.
    #  y | 2 | * |   | * |      | * | * | * |
    #    |   +---+---+---+      +---+---+---+
    #    | 3 | * |   | * |      |   |   | * |
    #    |   +---+---+---+      +---+---+---+
    #    | 4 | * | * | * |      | * | * | * |
    #    |   +---+---+---+      +---+---+---+
    # 
    #      "0000 00 00 0000"  "333  3333  3333"
    #       |||---|||---|||    |||---|||---|||
    # Row->  0  1  2  3  4      0  1  2  3  4

    p = -1                                                          # current position in text string
    for y in range(5):
        for x in range(3):
            p += 1                                                  # increment text position
            if scoretext[d][0][p] != " ":                           # if character in position draw square
                pygame.draw.rect(screen, c, [dx + x * s, dy + y * s, s, s], 0)


# -----------------------------------------------------------------------------------------------------------
def New_Game(): # starts a new game of pybreakout                                                  New_Game()
# -----------------------------------------------------------------------------------------------------------

    global level, launch, newgame, levelshown, score, paddles, extra

    launch = False                                                  # reset booleans
    newgame = True
    levelshown = False
    while newgame:                                                  # loop while in new game mode
        Play_Round()
    score = 0                                                       # reset player score
    level = 0                                                       # reset level of play
    paddles = 3                                                     # reset number of player paddles
    extra = False                                                   # reset extra paddle

# -----------------------------------------------------------------------------------------------------------
def Next_Level(): # sets up the next level of the game                                           Next_Level()
# -----------------------------------------------------------------------------------------------------------

    global level, bspeed, bxdir, bydir, brick, bhit, maxbspeed, bspeedinc, bricks
    global launch, trajectory, levelshown, countdown, minxspeed, frame

    level += 1                                                      # increment level of play
    bspeed = 1                                                      # reset ball characteristics
    bxdir = .5
    bydir = -1
    if not(level % 3):                                              # bricks hit twice every third level
        bhit = 2
    else:
        bhit = 1
    for i in range(6):                                              # reset brick characteristics
        for j in range(16):
            brick[i][j][INPLAY] = True
            brick[i][j][BRICKHIT] = 0
    bricks = 96
    maxbspeed = 2 + (level / 8)                                     # calculate maximum ball speed
    if maxbspeed > 4:
        maxbspeed = 4
    minxspeed = maxbspeed / 4 * (1 + (maxbspeed / 4))               # calculate minimum ball horizontal speed
    bspeedinc = level / 96                                          # calculate ball speed increments
    trajectory = (100 / (level * 100)) * 200                        # calculate level trajectory
    launch = True                                                   # reset booleans
    levelshown = False
    countdown = 9                                                   # reset countdown
    frame = 0

# -----------------------------------------------------------------------------------------------------------
def Draw_Frame(): # draws a frame of the game                                                    Draw_Frame()
# -----------------------------------------------------------------------------------------------------------

    global countdown, frame, launch

    screen.fill(BLACK)
    Draw_Stars()                                                    # draw starfield
    Draw_Bricks()                                                   # draw bricks
    pygame.draw.circle(screen, RED, [px - 40, 526], 10, 0)          # draw paddle
    pygame.draw.circle(screen, RED, [px + 40, 526], 10, 0)
    pygame.draw.rect(screen, WHITE, [px - 40, 516, 80 , 20], 0)
    for i in range(paddles):                                        # draw paddles remaining
        x = (i + 1) * 60
        pygame.draw.circle(screen, RED, [x - 20, 560], 5, 0)
        pygame.draw.circle(screen, RED, [x + 20, 560], 5, 0)
        pygame.draw.rect(screen, WHITE, [x - 19, 555, 38, 10], 0)
    screen.blit(hitxt, [525, 0])                                    # display text
    screen.blit(p1txt, [155, 0])
    scoretxt = ((6 - len(str(score))) * "0") + str(score)           # format scores
    hiscoretxt = ((6 - len(str(hiscore))) * "0") + str(hiscore)
    for i in range(6):                                              # draw scores
        Draw_Digit(326 + (i * 20), 5, int(scoretxt[i]), WHITE, 5)
        Draw_Digit(628 + (i * 20), 5, int(hiscoretxt[i]), WHITE, 5)
    leveltxt = ((2 - len(str(level))) * "0") + str(level)
    for i in range(2):
        Draw_Digit(740 + (i * 8), 555, int(leveltxt[i]), WHITE, 2) 
    if launch:                                                      # display level text
        lev = 0
        if not(levelshown):
            lev = level
            if lev > 10:
                lev = 10
        if paddles > 0:
            screen.blit(saying[lev], [(768 - pygame.Surface.get_width(saying[lev])) // 2, 265])
            screen.blit(spacebar, [140, 400])
            if countdown > 6:                                       # display count down timer
                clr = GREEN
            elif countdown > 3:
                clr = YELLOW
            else:
                clr = RED
            Draw_Digit(369, 325, countdown, clr, 10)
            frame += 1
            if frame == FPS:
                frame = 0
                countdown -= 1
                winsound.Beep(2200, 5)
                if countdown == 0:
                    launch = False
    if newgame:
        screen.blit(welcome, [145,300])
    else:
        pygame.draw.circle(screen, CYAN, [int(bx), int(by)], 7, 0)  # draw ball
    pygame.display.flip()

# -----------------------------------------------------------------------------------------------------------
def Draw_Stars(): # draws the moving starfield                                                   Draw_Stars()
# -----------------------------------------------------------------------------------------------------------

    global star

    for i in range(STARS):                                          # cycle through all stars
        star[i][STARY] += star[i][STARSPEED]                        # update speed
        star[i][STARCOLOR] += star[i][STARCLDIR]                    # update color
        if star[i][STARCOLOR] == 0 or star[i][STARCOLOR] == 255:    # keep color in limits
            star[i][STARCLDIR] *= -1
        if star[i][STARY] >= 767:                                   # star went off bottom of screen
            star[i][STARY] = random.randrange(-30, -10)
            star[i][STARX] = random.randrange(0, 768)
        c = star[i][STARCOLOR]
        pygame.draw.circle(screen, [c, c, c], [star[i][STARX], int(star[i][STARY])], 0, 0)
        
# -----------------------------------------------------------------------------------------------------------
def Draw_Bricks(): # check for ball/brick collisions while bricks being drawn                   Draw_Bricks()
# -----------------------------------------------------------------------------------------------------------

    global bxdir, bydir, score, hiscore, bspeed, bricks, brick, extra, paddles

    alreadyhit = False
    for i in range(6):                                              # cycle through all bricks
        for j in range(16):
            if brick[i][j][INPLAY]:                                 # draw brick if in play
                brick_rect = pygame.Rect(brick[i][j][0:4])
                pygame.draw.rect(screen, COLOR[i + brick[i][j][BRICKHIT] * 6], brick_rect, 0)
                if not(newgame):
                    if brect.colliderect(brick_rect) and not(alreadyhit): # first brick hit?
                        brick[i][j][BRICKHIT] += 1                  # update brick hits
                        if brick[i][j][BRICKHIT] == bhit:           # max number of hits?
                            brick[i][j][INPLAY] = False             # brick out of play
                            bricks -= 1
                            score += (6 - i) * 10
                            winsound.Beep((6 - i) * 200, 10)
                        else:                                       # brick still in play
                            score += (6 - i) * 5        
                            winsound.Beep(2200, 10)
                        if score >= 10000 and not(extra):           # award extra ship
                            paddles += 1
                            extra = True
                            for i in range(5):
                                winsound.Beep((i + 1) * 1000, 5)
                        alreadyhit = True                           # no more collision checks this loop
                        bspeed += bspeedinc                         # increase ball speed
                        if bspeed > maxbspeed:                      # keep ball speed in limits
                            bspeed = maxbspeed
                        if score > hiscore:                         # update high score
                            hiscore = score
                        if sgn(bxdir) == 1:                         # ball traveling right
                            bdx = int(brick_rect[0] - bx)           # ball distance from left side of brick
                        else:                                       # ball traveling left
                            bdx = int(bx - brick_rect[0]) - 46      # ball distance from right side of brick
                        if sgn(bydir) == 1:                         # ball traveling down
                            bdy = int(brick_rect[1] - by)           # ball distance from top of brick
                        else:                                       # ball traveling up
                            bdy = int(by - brick_rect[1]) - 22      # ball distance from bottom of brick
                        if bdx == bdy:                              # hit corner of brick
                            bxdir = -bxdir
                            bydir = -bydir
                        elif bdx > bdy:                             # hit left/right side of brick
                            bxdir = -bxdir
                        else:                                       # hit top/bottom of brick
                            bydir = -bydir

# -----------------------------------------------------------------------------------------------------------
def Get_Input(): # gets user input and processes                                                  Get_Input()
# -----------------------------------------------------------------------------------------------------------

    global px, launch, levelshown, newgame

    pygame.event.pump()                                             # update keyboard buffer
    key = pygame.key.get_pressed()                                  # get all keys pressed
    if key[K_SPACE] and launch:                                     # launch ball with space bar?
        launch = False
        levelshown = True
    for event in pygame.event.get():                                # cycle through events
        if event.type == QUIT or key[K_ESCAPE]:                     # leave game if closed or ESC pressed
            pygame.quit()
            sys.exit()
        elif (event.type == KEYUP or event.type == MOUSEBUTTONUP) and newgame:  # start a new game?
            newgame = False
            launch = True
        elif event.type == MOUSEBUTTONUP and launch:                # launch ball with mouse button?
            launch = False
            levelshown = True
        elif event.type == MOUSEMOTION:                             # mouse moved?
            px = event.pos[0]                                       # set player paddle x to mouse x
            if px < 50:                                             # keep paddle on screen
                px = 50
            elif px > 718:
                px = 718
            pygame.mouse.set_pos(px, py)                            # bind mouse to paddle

# -----------------------------------------------------------------------------------------------------------
def Play_Round(): # plays a round of pybreakout                                                  Play_Round()
# -----------------------------------------------------------------------------------------------------------

    global bx, by, bxdir, bydir, px, brect, launch, paddles, bspeed, countdown

    Get_Input()                                                     # get user input and process
    if launch or newgame:                                           # keep ball on paddle
        bx = px
        by = 511
    else:                                                           # update ball position
        bx += bxdir
        by += bydir * bspeed
        if bx <= 7:                                                 # hit left side of screen?
            bx = 7
            bxdir = -bxdir
        elif bx >= 760:                                             # hit right side of screen?
            bx = 760
            bxdir = -bxdir
        if by <= 7:                                                 # hit top of screen?
            bydir = -bydir
        elif by >= 568:                                             # player missed ball?
            paddles -= 1
            launch = True
            bspeed = 1
            bxdir = .5
            bydir = -1
            countdown = 9
    brect = pygame.Rect(bx - 5, by - 5, 10, 10)                     # create ball rectangle object
    prect = pygame.Rect(px - 50, 516, 100, 1)                       # create paddle rectangle object
    if brect.colliderect(prect):                                    # did the objects collide?
        bydir = -bydir
        by = 511
        bxdir += (bx - px) / trajectory                             # add some trajectory
        if abs(bxdir) > maxbspeed:                                  # keep horizontal ball speed in limits
            bxdir = sgn(bxdir) * maxbspeed
        elif abs(bxdir) < minxspeed:
            bxdir = sgn(bxdir) * minxspeed
        winsound.Beep(440, 10)
    Draw_Frame()                                                    # draw the next frame of game
    clock.tick(FPS)                                                 # limit FPS

# -----------------------------------------------------------------------------------------------------------
def sgn(n): # returns the sign of a number (-1, 0, or 1)                                                sgn()
# -----------------------------------------------------------------------------------------------------------

    # n = any numeric value
    
    if n > 0:                                                       # positive
        return 1
    elif n < 0:                                                     # negative
        return -1
    else:                                                           # must be zero
        return 0

# -----------------------------------------------------------------------------------------------------------
# - MAIN PROGRAM LOOP ---------------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------------------------------

Setup()                                                             # generate game assets
while True:                                                         # main loop
    New_Game()                                                      # initiate a new game
    while paddles > 0:                                              # loop until player loses all paddles
        Next_Level()                                                # set up next level of play
        while paddles > 0 and bricks:                               # loop until no paddles or bricks cleared
            Play_Round()                                            # play the round
Reply
#2
This games works.
Excellent work.
I  Heart it.
Good Skills
Reply
#3
Thank you :)
Reply
#4
global very, good, job
But, in all seriousness, it is great that you are making some working complete programs but there are a lot of things you need to change if you are going to write more complex code.

My guess is you learned pygame from the Invent book (correct me if I'm wrong).
Your main issue is use of globals.  You should consider the global keyword completely off limits until you become more experienced.  The one use case for it where it is acceptable is for declaring a global CONSTANT inside a function.  I do this in pygame for instance when I want to load a graphic that I want to be available globally but can't be loaded until pygame and the display is set up.  Rather than having all this setup code in the global namespace I do it in a function but declare the resource loads global.

But, and this is the important part, they are constants.  After they are created they are never changed.

Also more trivial, but none of those color definitions (or at least the vast majority) are necessary.  Nearly every color you could need (all the ones defined with names in html) are predefined for you so instead of writing (255, 0, 0) you just write pg.Color("red") and achieve much more readable code.

Here is a template for a very simple pygame game:  
https://gist.github.com/Mekire/f67d83087...0fb2939796  
Even the simplest pygame programs should really adhere to that.

With the complexity you will soon be approaching though you are going to need to look into some form of scene manager.
Here is my scene manager template:  
https://github.com/Mekire/pygame-mutisce...with-movie
And for a slightly softer intro to the concept, check here:  
https://www.reddit.com/r/pygame/comments...e_example/

Also for a number of basic pygame examples written in a sane style check here:
https://github.com/Mekire/pygame-samples
Reply
#5
Thank you very much for all of the constructive help you have provided. Yes, Invent was one of the resources I have used to learn Python as you have pointed out. My main problem is that I started coding in 1980 using the many different dialects of BASIC offered on the many different systems of the time (TRS-80, TI99/4A, Commodore, etc..) I eventually moved into Pascal, then QuickBASIC, VBDOS/VBWin, and the like. I realize I have many old-school habits that I need to get rid of when using Python. OOP was never something I delved into but I realize with Python this is going to be required.

I really appreciate the examples and templates you have provided me. I want to learn the Python way and helpful posts such as yours will help me get there.

Thank you :)
Reply
#6
(Apr-26-2017, 08:14 PM)TerryRitchie Wrote: Thank you :)
No.Thank you.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020