breakout.py

Created by ilyas-r

Created on May 09, 2023

7.5 KB

Breakout


Python Game 🎮 v1.0 for NumWorks, all models.

By Ilyas R. May 2023.

A free project carried out as part of the computer science specialty education.

Learn more about Breakout on: nsi.xyz/breakout (FR)

Changelog

Breakout v1.0 - 09/05/2023:
- Initial version


from kandinsky import fill_rect as rec, draw_string as txt, get_pixel as get_p
from ion import keydown
from time import sleep

# Breakout 1.0 NumWorks, 09.04.2023
# Par Ilyas R.
# https://nsi.xyz/breakout <3

dark_c, light_c, bright_c, game_c, void_c = (40,)*3, (142,)*3, (242,)*3, (148, 113, 222), (255,)*3
red, orange, yellow, green = (240, 160, 160), (240, 200, 160), (240, 240, 160), (200, 240, 200)

p_size = 40
p_speed = 3
p_x, p_y = 160, 210
p_color = bright_c

b_size = 7
b_dir = [2, -2]
b_pos = [160, 180]

br_width = 30
br_height = 15

game_speed = 0.01
game_state = "IN_MENU"

chars = (121579, 186351, 234191, 188271, 187117, 252783, 252781, 74903)

points = {"red": 100, "orange": 80, "yellow": 60, "green": 20}
stage, score, pv = 1, 0, 3
level = ""
cuboids = []


def menu():
    rec(0, 0, 320, 222, dark_c)
    rec(0, 200, 320, 22, game_c)
    for k in range(len(chars)):  # Display method inspired by @riko_schraf
        for j in range(19):
            if chars[k] >> j & 1 == 1:
                rec(110 + 12 * k + (j % 3) * 3, 40 + (j // 3) * 3, 3, 3, light_c)
    txt("Score: " + str(score), 160-5*(7+len(max(str(score), str(stage)))), 70, bright_c, dark_c)
    txt("Stage: " + str(stage), 160-5*(7+len(max(str(score), str(stage)))), 90, bright_c, dark_c)
    txt("Lives: " + str(pv), 160-5*(7+len(max(str(score), str(stage)))), 110, bright_c, dark_c)
    if pv > 0:
        txt("Press OK to play", 80, 150, bright_c, dark_c)
    else:
        txt("GAME OVER", 115, 150, bright_c, dark_c)
    txt("Gameplay by nsi.xyz/breakout", 20, 202, bright_c, game_c)


def pad(size, move=0):
    rec(p_x + move + move * (size // 2), p_y, move * p_speed, 3, dark_c)
    rec(p_x - size // 2, p_y, size, 3, p_color)


def level_generator(stg):
    lvl = "".join([" " for _ in range(10)])+"*"*(stg//11+1)*10
    col = set()
    for i in (2, 3, 5, 7):
        if not stg % i:
            for j in range(i, 10, i):
                col.add(j)
    for i in range(stg // 11 + 2):
        for j in range(10):
            if j in col:
                lvl = lvl[:i * 10 + j] + " " + lvl[i * 10 + j + 1:]
    return lvl


def level_constructor():
    global level, cuboids
    rec(0, 0, 320, 222, dark_c)
    level = level_generator(stage)
    for i in range(len(level)):
        if level[i] == "*":
            rec(1 + (br_width + 2) * (i % 10), 1 + (br_height + 2) * (i // 10), br_width, br_height, set_color(i // 10))
    cuboids = [[[1+(br_width+2)*(i % 10), 1+(br_height+2)*(i//10)], [1 + (br_width + 2) * (i % 10) + br_width,
                1 + (br_height + 2) * (i // 10) + br_height]] if level[i] == "*" else [] for i in range(len(level))]
    cuboids += (((0, 0), (0, 222)), ((0, 0), (320, 0)), ((320, 0), (320, 222)))


def set_color(line):
    if line in (0, 1):
        return red
    elif line in (2, 3):
        return orange
    elif line in (4, 5):
        return yellow
    else:
        return green


def speed(stg, hb):
    return max(0.01-(hb*10**-4)-((stg-1)*10**-5), 0)


def new_level():
    global game_state, b_pos, p_x, p_y, b_dir, game_speed
    b_pos, b_dir = [160, 180], [2, -2]
    p_x, p_y = 160, 210
    game_state = "IN_LEVEL"
    level_constructor()
    game_speed = speed(stage, len(cuboids)-sum([1 if not i else 0 for i in cuboids])-3)
    pad(p_size)
    engine()


def velocity(x):
    vec = [(-2, -1), (-2, -2), (-1, -2), (1, -2), (2, -2), (2, -1)]
    i = min(round((x-p_x)/(p_size/2)*3+3), len(vec)-1)
    b_dir[0], b_dir[1] = vec[i]


def collision(x, y):
    global game_state
    if p_y-abs(b_dir[1])-b_size//2 <= y <= p_y+abs(b_dir[1])-b_size//2 and p_x-p_size//2 <= x <= p_x+p_size//2:
        velocity(x)
        rec(p_x - p_size // 2, p_y, p_size, 3, p_color)
    elif y >= 222:
        game_state = "G_OVER"
        return
    elif entity_around(x, y, 15):
        collision_manager(x, y)


def entity_around(x, y, radius):
    if get_p(x, y-radius) != dark_c or get_p(x-radius, y-radius) != dark_c or get_p(x+radius, y-radius) != dark_c:
        return True
    if get_p(x-radius, y) != dark_c or get_p(x+radius, y) != dark_c:
        return True
    if get_p(x, y+radius) != dark_c or get_p(x-radius, y+radius) != dark_c or get_p(x+radius, y+radius) != dark_c:
        return True
    return False


def collision_manager(x, y):
    for i in range(len(cuboids)):
        if cuboids[i] and (i < cuboids[i][0][1]+10 or len(cuboids)-3 <= i <= len(cuboids)):
            h = b_size//2
            x_s, y_s, x_e, y_e = cuboids[i][0][0]-h, cuboids[i][0][1]-h, cuboids[i][1][0]+h, cuboids[i][1][1]+h
            if y_s-abs(b_dir[1]) <= y <= y_s+abs(b_dir[1]) and x_s <= x <= x_e:
                b_dir[1] = -b_dir[1]
                destroy_brick(i)
            elif x_s-abs(b_dir[0]) <= x <= x_s+abs(b_dir[0]) and y_s <= y <= y_e:
                b_dir[0] = -b_dir[0]
                destroy_brick(i)
            elif x_e-abs(b_dir[0]) <= x <= x_e+abs(b_dir[0]) and y_s <= y <= y_e:
                b_dir[0] = -b_dir[0]
                destroy_brick(i)
            elif y_e-abs(b_dir[1]) <= y <= y_e+abs(b_dir[1]) and x_s <= x <= x_e:
                b_dir[1] = -b_dir[1]
                destroy_brick(i)


def get_color(i):
    li = i//10
    return "red" if li in (0, 1) else "orange" if li in (2, 3) else "yellow" if li in (4, 5) else "green"


def destroy_brick(i):
    global score, game_speed
    if type(cuboids[i]) == list:
        cuboids[i] = []
        rec(1 + (br_width + 2) * (i % 10), 1 + (br_height + 2) * (i // 10), br_width, br_height, dark_c)
        score += points[get_color(i)]
        game_speed = speed(stage, len(cuboids) - sum([1 if not i else 0 for i in cuboids]) - 3)


def ball(x, y, width, height, old=0):
    rec(x - width // 2, y - height // 2, width, height, dark_c if old else void_c)
    for i in [(-1, -1,), (-1, 1), (1, -1), (1, 1)]:
        rec(x + i[0] * (width // 2), y + i[1] * (height // 2), 1, 1, dark_c)


def ball_manager():
    old_ball_pos = (b_pos[0], b_pos[1])
    ball(b_pos[0], b_pos[1], b_size, b_size)
    b_pos[0] += b_dir[0]
    b_pos[1] += b_dir[1]
    collision(b_pos[0], b_pos[1])
    sleep(game_speed)
    ball(old_ball_pos[0], old_ball_pos[1], b_size, b_size, 1)


def engine():
    global p_x, pv, game_state, stage
    while game_state not in ("L_CLEAR", "G_OVER"):
        if keydown(0) and p_x > b_size + p_size // 2:
            p_x -= p_speed
            pad(p_size, 1)
        if keydown(3) and p_x < 320 - b_size - p_size // 2:
            p_x += p_speed
            pad(p_size, -1)
        ball_manager()
        if len(cuboids)-sum([1 if not i else 0 for i in cuboids]) == 3:
            game_state = "L_CLEAR"
            stage += 1
            pv += 1
    pv -= 1 if game_state == "G_OVER" else 0
    menu()


def refresh_scoreboard():
    txt("Stage: " + str(stage), 160-5*(7+len(max(str(score), str(stage)))), 90, bright_c, dark_c)


def breakout():
    global stage
    menu()
    last_key = -1
    while not keydown(51):
        if keydown(4) or keydown(52):
            new_level()
        if keydown(39) and last_key != 39:
            stage += 10
            last_key = 39
        if keydown(45) and last_key != 45:
            stage += 1
            last_key = 45
        if keydown(40) and last_key != 40:
            stage = max(stage-10, 1)
            last_key = 40
        if keydown(46) and last_key != 46:
            stage = max(stage-1, 1)
            last_key = 46
        if keydown(39) or keydown(45) or keydown(40) or keydown(46):
            refresh_scoreboard()
        if not (keydown(39) or keydown(45) or keydown(40) or keydown(46)):
            last_key = -1
        if pv == 0:
            menu()
            return


breakout()