이번 편의 목표: 가로 10칸, 세로 20칸 판을 2차원 리스트로 표현하고, 격자와 채워진 칸을 화면에 그립니다.

판은 리스트의 리스트

판은 줄(row) 20개를 담은 리스트이고, 줄 하나는 칸 10개를 담은 리스트입니다. 빈 칸은 None, 블록이 굳은 칸은 그 블록의 색을 담습니다. 그래서 board[y][x]로 y번째 줄의 x번째 칸을 읽습니다. 바깥 리스트가 세로, 안쪽 리스트가 가로라서 y를 먼저 씁니다.

좌표는 왼쪽 위가 (0, 0)이고, x는 오른쪽으로, y는 아래쪽으로 커집니다. 화면 픽셀 좌표와 방향이 같아서, 칸 좌표에 CELL(30픽셀)을 곱하면 바로 그 칸의 픽셀 위치가 됩니다.

new_board는 [[None] * COLS for _ in range(ROWS)]로 판을 만듭니다. [[None] * COLS] * ROWS라고 쓰면 안 됩니다. 그렇게 하면 같은 줄 객체 하나를 20번 가리키게 되어, 한 칸을 칠하면 같은 열의 20칸이 모두 칠해집니다. 리스트 내포는 반복할 때마다 새 줄을 만듭니다.

그리기

창의 크기도 칸 수에서 계산합니다. 판의 너비는 10 × 30 = 300픽셀이고, 오른쪽에 점수와 다음 블록을 보여 줄 180픽셀을 더해 480 × 600 창을 만듭니다.

draw 함수는 배경을 칠한 뒤 세로선 11개와 가로선 21개로 격자를 긋고, 판을 한 칸씩 돌면서 None이 아닌 칸만 draw_cell로 칠합니다. enumerate는 순서 번호와 값을 함께 꺼내 주므로, 줄 번호가 y, 칸 번호가 x가 됩니다.

draw_cell은 칸 크기의 사각형을 만들고 inflate(-2, -2)로 사방을 1픽셀씩 줄여서 칠합니다. 이웃한 칸 사이에 틈이 생겨 블록 하나하나가 구분됩니다.

main에서는 시험 삼아 맨 아랫줄 왼쪽 두 칸을 빨강으로, 그 위 한 칸을 파랑으로 채웠습니다. 다음 편부터는 이 시험용 세 줄을 지웁니다.

전체 코드

Python
import sys

import pygame

COLS, ROWS = 10, 20
CELL = 30
BOARD_WIDTH = COLS * CELL
SIDE_WIDTH = 180
WIDTH, HEIGHT = BOARD_WIDTH + SIDE_WIDTH, ROWS * CELL
FPS = 60

BACKGROUND = (18, 18, 24)
GRID = (40, 40, 52)


def new_board():
    return [[None] * COLS for _ in range(ROWS)]


def draw_cell(screen, x, y, color):
    rect = pygame.Rect(x * CELL, y * CELL, CELL, CELL)
    pygame.draw.rect(screen, color, rect.inflate(-2, -2))


def draw(screen, board):
    screen.fill(BACKGROUND)
    for x in range(COLS + 1):
        pygame.draw.line(screen, GRID, (x * CELL, 0), (x * CELL, HEIGHT))
    for y in range(ROWS + 1):
        pygame.draw.line(screen, GRID, (0, y * CELL), (BOARD_WIDTH, y * CELL))

    for y, row in enumerate(board):
        for x, color in enumerate(row):
            if color is not None:
                draw_cell(screen, x, y, color)


def main():
    pygame.init()
    pygame.display.set_caption("Tetris")
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    board = new_board()
    board[19][0] = (220, 60, 60)
    board[19][1] = (220, 60, 60)
    board[18][0] = (60, 100, 220)

    while True:
        clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        draw(screen, board)
        pygame.display.flip()


if __name__ == "__main__":
    main()

격자 왼쪽 아래에 빨간 칸 두 개와 파란 칸 하나가 보이면 성공입니다.

직접 해 보기

맨 아랫줄 10칸을 모두 초록 (80, 200, 80)으로 채우는 코드를 main에 한 줄로 적어 보세요.

해설

board[19] = [(80, 200, 80)] * COLS 입니다. 여기서는 * 로 리스트를 늘려도 괜찮습니다. 안에 든 값이 튜플이라 칸마다 따로 바꿀 일이 없고, 줄 자체는 새 리스트 하나이기 때문입니다. 위에서 피한 문제는 리스트 안에 리스트를 곱할 때만 생깁니다.