Showing off some Tetris on py5 :)

Maybe someone will enjoy playing or modifying a very simple implementation of Tetris…

"""
To run this you'll need py5 (py5coding.org) with an imported mode runner
More about this at https://abav.lugaralgum.com/como-instalar-py5/index-EN.html

Use arrows to play - up to rotate, space bar to restart.
"""

well = {}  # a dictionary for the walls and already fallen pieces
falling_piece = []   # will contain ((x,y), color) tuples, the component blocks of a piece
pieces = (
    [((x, y), color(0, 200, 0)) for x, y in ((6, 0), (7, 0), (6, 1), (7, 1))], # O
    [((x, y), color(0, 0, 200)) for x, y in ((4, 0), (5, 0), (6, 0), (6, 1))], # J
    [((x, y), color(200, 200, 0)) for x, y in ((4, 1), (5, 1), (6, 1), (6, 0))], # L
    [((x, y), color(200, 0, 0)) for x, y in ((4, 0), (5, 0), (6, 0), (7, 0))], # I
    [((x, y), color(0, 200, 200)) for x, y in ((5, 0), (5, 1), (6, 1), (6, 2))], # S
    [((x, y), color(200, 0, 200)) for x, y in ((6, 0), (6, 1), (5, 1), (5, 2))], # N
    [((x, y), color(200)) for x, y in ((4, 0), (5, 0), (6, 0), (5, 1))], # T
)

W, H, s = 11, 20, 25   # Well Width, Well Height, single block size
frame_sample = 12  # a smaller number makes it faster, a bigger number makes it slower

def setup():
    size(525, 525)
    text_align(CENTER, CENTER)
    text_size(80)
    start()
    
def start():
    global game_over, score
    game_over, score = False, 0
    well.clear()
    for y in range(H):  # Set vertical well walls
        well[0, y] = color(128)
        well[W, y] = color(128)
    for x in range(W + 1):  # Set bottom of well
        well[x, H] = color(128)
    new_falling_piece()
    loop() # reactivates draw if it is paused
    
def draw():
    global score
    background(0)
    for (x, y), block_color in list(well.items()) + falling_piece:
        fill(block_color)
        square(x * s, y * s, s)
    fill(200)
    text(score, W * s + (width - W * s) / 2, s * 2)        
    if game_over:
         fill(255)
         text('GAME OVER',
              width / 2, height / 2)
         no_loop() # pause draw
    elif frame_count % frame_sample == 0:
        if check_move(falling_piece, 0, 1):
            move_falling_piece(0, 1)
        else:
            add_to_well()
            new_falling_piece()
        r = check_filled_row()
        if r != -1:
            collapse_on_row(r)
            score += 1
 
def new_falling_piece():
    global game_over
    falling_piece[:] = random_choice(pieces)
    if not check_move(falling_piece):
        game_over = True
    
def add_to_well():
    for (x, y), b in falling_piece:
        well[x, y] = b

def check_move(p, h=0, v=0):
    for (x, y), _ in p:
        if well.get((x + h, y + v)):
            return False
    return True

def move_falling_piece(h, v):
    falling_piece[:] = (((x + h, y + v), b) for (x, y), b in falling_piece)

def check_filled_row():
    for r in range(H):
        filled_blocks = 0
        for c in range(W):
            if well.get((c, r)):
                filled_blocks += 1
        if filled_blocks == W:
            return r  # return first filled row found
    return -1 # no filled row

def clear_row(r):
    for c in range(1, W):
        if (c, r) in well:
            del well[c, r]

def collapse_on_row(row):
    # clear passed row and move blocks down to it
    for r in range(row, -1, -1): # moving upwards, starting from passed row
        clear_row(r) 
        for c in range(1, W):
            if b := well.get((c, r - 1)): # get block from 1 row up
                well[c, r] = b  
    
def rounded_falling_piece_centroid():
    positions = ((x, y) for (x, y), _ in falling_piece)
    coords = tuple(zip(*positions))
    max_x, max_y = map(max, coords)
    min_x, min_y = map(min, coords)
    return (max_x + min_x) // 2, (max_y + min_y) // 2

def rotated_falling_piece():
    cx, cy = rounded_falling_piece_centroid() # causes y displacement of -1 offset later...
    # translated to origin falling piece copy
    ttofpc = (((x - cx, y - cy), b) for (x, y), b in falling_piece) 
    return tuple(((y + cx, -x + cy + 1), b) for (x, y), b in ttofpc) # note y + 1 offset

def key_pressed():
    if key == ' ':
        start()
    elif key_code == LEFT and check_move(falling_piece, -1, 0):
        move_falling_piece(-1, 0)
    elif key_code == RIGHT and check_move(falling_piece, 1, 0):
        move_falling_piece(1, 0);
    elif key_code == DOWN and check_move(falling_piece, 0, 1):
        move_falling_piece(0, 1);
    elif key_code == UP and check_move(rp := rotated_falling_piece()):
        falling_piece[:] = rp
5 Likes

I love this! Good job, @villares !

1 Like