이번 편의 목표: 테트로미노 일곱 가지를 좌표 목록으로 적고, 블록 하나를 판 위에 올려 그립니다.
모양을 좌표로 적기
테트리스 블록은 정사각형 네 개를 붙인 모양이고, 붙이는 방법은 회전을 빼면 일곱 가지뿐입니다. 모양이 닮은 알파벳으로 I, O, T, S, Z, J, L이라고 부릅니다.
모양 하나는 작은 정사각형 상자 안에서 칸 네 개의 (x, y) 좌표로 적습니다. T라면 위 가운데 (1, 0)과 아랫줄 (0, 1), (1, 1), (2, 1)입니다. 상자 크기는 SIZES에 따로 적었습니다. I는 4칸, O는 2칸, 나머지는 3칸 상자입니다. 상자 크기는 다음 다음 편의 회전에서 쓰입니다.
색은 COLORS에 모양별로 적었습니다. 판의 칸에 색을 그대로 넣기 때문에, 블록이 굳은 뒤에도 원래 색이 남습니다.
Piece 클래스
지금 떨어지고 있는 블록은 모양의 종류(kind), 칸 좌표 목록(cells), 상자 크기(size), 그리고 상자의 왼쪽 위 모서리가 판의 어디 있는지(x, y)를 기억합니다. 처음 위치는 가로 가운데 맨 위입니다. 10칸 판에 3칸 상자라면 (10 - 3) // 2 = 3번 칸에서 시작합니다.
positions는 상자 안 좌표에 상자 위치를 더해 판 위의 실제 좌표 네 개를 돌려줍니다. dx, dy를 주면 그만큼 옮겼을 때의 좌표를, cells를 주면 다른 모양일 때의 좌표를 계산합니다. 블록을 실제로 옮기기 전에 옮긴 뒤의 좌표를 먼저 받아 볼 수 있게 하려는 것이고, 다음 편의 충돌 검사가 이 기능을 씁니다.
Game 클래스
판과 지금 블록처럼 게임 한 판의 상태를 Game 객체 하나에 모았습니다. 앞으로 점수, 레벨, 다음 블록이 모두 이 객체에 붙습니다. draw는 이제 판 대신 game을 받아서 판을 그린 뒤 그 위에 지금 블록을 그립니다.
키 입력은 handle_key 함수로 따로 뺐습니다. 이번 편에서는 스페이스를 누르면 무작위로 새 블록을 만들어서 일곱 가지 모양을 차례로 구경할 수 있습니다. random.choice(list(SHAPES))는 딕셔너리의 키 목록에서 하나를 고릅니다.
전체 코드
import random
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)
SHAPES = {
"I": [(0, 1), (1, 1), (2, 1), (3, 1)],
"O": [(0, 0), (1, 0), (0, 1), (1, 1)],
"T": [(1, 0), (0, 1), (1, 1), (2, 1)],
"S": [(1, 0), (2, 0), (0, 1), (1, 1)],
"Z": [(0, 0), (1, 0), (1, 1), (2, 1)],
"J": [(0, 0), (0, 1), (1, 1), (2, 1)],
"L": [(2, 0), (0, 1), (1, 1), (2, 1)],
}
SIZES = {"I": 4, "O": 2, "T": 3, "S": 3, "Z": 3, "J": 3, "L": 3}
COLORS = {
"I": (0, 200, 220),
"O": (230, 200, 0),
"T": (160, 80, 200),
"S": (80, 200, 80),
"Z": (220, 60, 60),
"J": (60, 100, 220),
"L": (240, 140, 30),
}
class Piece:
def __init__(self, kind):
self.kind = kind
self.cells = list(SHAPES[kind])
self.size = SIZES[kind]
self.x = (COLS - self.size) // 2
self.y = 0
def positions(self, dx=0, dy=0, cells=None):
if cells is None:
cells = self.cells
return [(self.x + cx + dx, self.y + cy + dy) for cx, cy in cells]
def new_board():
return [[None] * COLS for _ in range(ROWS)]
class Game:
def __init__(self):
self.board = new_board()
self.piece = Piece(random.choice(list(SHAPES)))
def handle_key(game, key):
if key == pygame.K_SPACE:
game.piece = Piece(random.choice(list(SHAPES)))
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, game):
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(game.board):
for x, color in enumerate(row):
if color is not None:
draw_cell(screen, x, y, color)
color = COLORS[game.piece.kind]
for x, y in game.piece.positions():
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()
game = Game()
while True:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
handle_key(game, event.key)
draw(screen, game)
pygame.display.flip()
if __name__ == "__main__":
main()판 맨 위 가운데에 블록이 하나 보이고, 스페이스를 누를 때마다 모양이 바뀌면 성공입니다.
직접 해 보기
S의 좌표 (1, 0), (2, 0), (0, 1), (1, 1)을 종이 3×3 격자에 칠해 보고, Z와 좌우가 뒤집힌 모양인지 확인하세요. 같은 방법으로 J와 L도 비교해 보세요.
해설
S는 윗줄 오른쪽 두 칸과 아랫줄 왼쪽 두 칸, Z는 윗줄 왼쪽 두 칸과 아랫줄 오른쪽 두 칸입니다. x를 2 - x로 바꾸면 서로가 됩니다. J와 L도 윗줄의 한 칸이 왼쪽 끝이냐 오른쪽 끝이냐만 다릅니다. 회전만으로는 서로가 될 수 없어서 따로 적어야 합니다.
COMMENTS
댓글 0
이 글에 대한 의견이나 질문을 남겨 주세요.
댓글은 구글이나 카카오로 시작한 뒤에 남길 수 있습니다.
등록된 댓글이 없습니다.