Tutorial #2

Subscribe to Tech With Tim!

Drawing the Grid

This function will simply draw the grey grid lines in the play area so that we can see which square our pieces are in. You may recall us calling this function in our draw_window function from the last tutorial.

def draw_grid(surface, row, col):
# This function draws the grey grid lines that we see
    sx = top_left_x
    sy = top_left_y
    for i in range(row):
        pygame.draw.line(surface, (128,128,128), (sx, sy+ i*30), (sx + play_width, sy + i * 30))  # horizontal lines
        for j in range(col):
            pygame.draw.line(surface, (128,128,128), (sx + j * 30, sy), (sx + j * 30, sy + play_height))  # vertical lines

Converting Shape Formats

At this point in time each of our pieces is represented by a multidimensional list. We need something that can translate this list into a form that the computer can understand. Ideally given a shape format we want to convert it to a list of positions that we can then return. That is what convert_shape_format() will do for us.

If you'd like a detailed explanation of this code please watch the video starting at 4:30.

def convert_shape_format(shape):
    positions = []
    format = shape.shape[shape.rotation % len(shape.shape)]
    for i, line in enumerate(format):
        row = list(line)
        for j, column in enumerate(row):
            if column == \'0\':
                positions.append((shape.x + j, shape.y + i))
    for i, pos in enumerate(positions):
        positions[i] = (pos[0] - 2, pos[1] - 4)
    return positions

Determine a Valid Space

When we are moving and rotating our shape we need to make sure that it is moving into a valid space. We are going to use the valid_space() function to check this. This function will have two parameters: grid and shape. We will check the grid to ensure that the current position we are trying to move into is not occupied. We can do this by seeing if any of the positions in the grid that the shape is attempting to move into have a color. If they have a color other than black than that means they are occupied, otherwise they are free.

def valid_space(shape, grid):
    accepted_positions = [[(j, i) for j in range(10) if grid[i][j] == (0,0,0)] for i in range(20)]
    accepted_positions = [j for sub in accepted_positions for j in sub]
    formatted = convert_shape_format(shape)
    for pos in formatted:
        if pos not in accepted_positions:
            if pos[1] > -1:
                return False
    return True

Checking if We Lose the Game

In order to end the game we need to constantly be checking if the user has lost the game. The check_lost() function will do this for us. We are simply going to check if any position in the given list is above the screen. If it is we have reached the top and therefore lost the game.

def check_lost(positions):
    for pos in positions:
        x, y = pos
        if y < 1:
            return True
    return False

Modifying the Game Loop

Now we are going to add some code into game loop that will move our pieces down the screen at a certain time interval.

Look for the comments to see which code it new.

def main():
    global grid

    locked_positions = {}  # (x,y):(255,0,0)
    grid = create_grid(locked_positions)
    change_piece = False
    run = True
    current_piece = get_shape()
    next_piece = get_shape()
    clock = pygame.time.Clock()
    fall_time = 0

    while run:
        # -------NEW CODE---------
        fall_speed = 0.27
        grid = create_grid(locked_positions)
        fall_time += clock.get_rawtime()
        if fall_time/1000 >= fall_speed:
            fall_time = 0
            current_piece.y += 1
            if not (valid_space(current_piece, grid)) and current_piece.y > 0:
                current_piece.y -= 1
                change_piece = True
        # --------END NEW CODE---------

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    current_piece.x -= 1
                    if not valid_space(current_piece, grid):
                        current_piece.x += 1

                elif event.key == pygame.K_RIGHT:
                    current_piece.x += 1
                    if not valid_space(current_piece, grid):
                        current_piece.x -= 1
                elif event.key == pygame.K_UP:
                    # rotate shape
                    current_piece.rotation = current_piece.rotation + 1 % len(current_piece.shape)
                    if not valid_space(current_piece, grid):
                        current_piece.rotation = current_piece.rotation - 1 % len(current_piece.shape)

                if event.key == pygame.K_DOWN:
                    # move shape down
                    current_piece.y += 1
                    if not valid_space(current_piece, grid):
                        current_piece.y -= 1

        # -------NEW CODE--------
        shape_pos = convert_shape_format(current_piece)

        # add color of piece to the grid for drawing
        for i in range(len(shape_pos)):
            x, y = shape_pos[i]
            if y > -1: # If we are not above the screen
                grid[y][x] = current_piece.color
       if change_piece:
            for pos in shape_pos:
                p = (pos[0], pos[1])
                locked_positions[p] = current_piece.color
            current_piece = next_piece
            next_piece = get_shape()
            change_piece = False

        #-------END NEW CODE-------

        draw_window(win, grid)

        #------NEW CODE--------
        # Check if user lost
        if check_lost(locked_positions):
            run = False
        #------END NEW CODE--------