들어가며
예전부터 한 번쯤은 만들어보고 싶었던 게임이 있다면 단연코 테트리스였다. 간단한 구조지만 의외로 중독성이 있고, 적당한 난이도 조절과 함께 구현할 수 있다면 파이썬의 GUI 및 로직 처리 능력을 익히기에 최적의 게임이라고 생각했다. 처음엔 tkinter로 UI를 만들려 했는데, 도형이 부드럽게 움직이지 않아서 포기했고, 다음엔 pygame이라는 라이브러리를 시도했다. 처음엔 설치부터 안됐고, 경로 문제로 이미지가 안 불러와지는 등 수많은 시행착오를 겪었다. 결국 필요한 모듈을 제대로 설치하고 도형들을 직접 그리면서 움직임을 제어하는 방식으로 접근했더니, 제법 훌륭한 결과물을 만들 수 있었다.
1. 필요한 라이브러리 설치
테트리스는 그래픽 기반 게임이기 때문에 pygame이라는 게임 개발 라이브러리를 사용했다. 다음 명령어로 설치할 수 있다.
pip install pygame
2. 게임 화면 및 설정 초기화
import pygame
import random
pygame.init()
# 화면 크기와 색상 정의
display_width = 300
display_height = 600
block_size = 30
cols = display_width // block_size
rows = display_height // block_size
screen = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption("파이썬 테트리스")
이 코드는 게임 창을 생성하고 테트리스 블럭 하나가 차지할 공간을 정의한다. block_size는 각 테트로미노 조각이 차지하는 정사각형의 픽셀 크기다. cols와 rows는 화면을 블럭 기준으로 나눈 그리드의 너비와 높이다. 이렇게 하면 각 블럭이 일정한 규격으로 움직이고, 충돌 감지 시 그리드에 맞게 처리가 가능하다. pygame.display.set_mode로 실제 창이 뜨고, 제목도 설정해준다.
3. 테트로미노 도형 정의하기
shapes = [
[[1, 1, 1, 1]], # I
[[1, 1], [1, 1]], # O
[[0, 1, 0], [1, 1, 1]], # T
[[1, 0, 0], [1, 1, 1]], # J
[[0, 0, 1], [1, 1, 1]], # L
[[0, 1, 1], [1, 1, 0]], # S
[[1, 1, 0], [0, 1, 1]], # Z
]
이 배열은 테트리스에서 사용할 기본 도형들을 2차원 리스트 형태로 정의한 것이다. 1은 블록이 있는 부분을 의미하고 0은 공백이다. 도형마다 회전이 가능해야 하므로, 이 리스트는 이후에 .rotate() 같은 함수에 넘겨 회전 처리를 할 수 있다. 리스트 형태이기 때문에 계산하기 쉽고, 화면 그리기나 충돌 감지에도 유리하다. 여기서는 기본적인 테트로미노 7가지를 정의했다.
4. 도형 클래스 만들기
class Piece:
def __init__(self, x, y, shape):
self.x = x
self.y = y
self.shape = shape
self.color = (random.randint(50,255), random.randint(50,255), random.randint(50,255))
self.rotation = 0
이 Piece 클래스는 각 도형을 생성할 때 사용된다. x와 y는 현재 위치, shape는 위에서 정의한 도형 중 하나를 받아서 해당 조각을 구성한다. color는 도형마다 랜덤하게 색을 부여해서 시각적으로 식별이 되게 해준다. 회전을 위해 rotation이라는 변수를 두었고, 이후 게임 루프에서 방향키 입력을 통해 값이 변하게 된다. 이 구조를 통해 테트리스의 핵심인 도형 이동과 회전을 제어한다.
5. 보드 초기화 및 충돌 판정
def create_board():
return [[(0, 0, 0) for _ in range(cols)] for _ in range(rows)]
def valid_space(piece, board):
shape = piece.shape[piece.rotation % len(piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
x = piece.x + j
y = piece.y + i
if x < 0 or x >= cols or y >= rows or board[y][x] != (0, 0, 0):
return False
return True
create_board()는 전체 보드를 2차원 RGB값 리스트로 초기화하는 함수다. (0,0,0)은 공백, 즉 블럭이 없는 상태를 의미한다. valid_space() 함수는 도형이 움직일 수 있는지 확인하는 역할을 한다. 만약 현재 위치에서 벗어나거나 다른 도형과 겹치면 False를 반환한다. 이 두 함수는 테트리스 로직에서 도형이 벽이나 다른 도형과 충돌했는지를 판별하는 데 필수적이다.
6. 게임 루프 및 블록 그리기
def draw_window(screen, board, current_piece):
screen.fill((0, 0, 0))
for y in range(rows):
for x in range(cols):
color = board[y][x]
pygame.draw.rect(screen, color, (x * block_size, y * block_size, block_size, block_size), 0)
shape = current_piece.shape[current_piece.rotation % len(current_piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, current_piece.color, ((current_piece.x + j) * block_size, (current_piece.y + i) * block_size, block_size, block_size), 0)
pygame.display.update()
이 함수는 게임의 화면을 실제로 그려주는 역할을 한다. 기존 보드에 있는 정적인 블록과, 현재 움직이고 있는 블록을 동시에 그려야 하므로 이중 반복문을 통해 각각의 색상과 위치를 계산한다. pygame.draw.rect를 통해 각 블럭을 사각형으로 표현하고, pygame.display.update()를 통해 실제 화면에 그린다. 이 함수는 매 프레임마다 호출되어 실시간으로 화면을 갱신하는 데 중요한 역할을 한다.
7. 메인 게임 루프 실행하기
board = create_board()
clock = pygame.time.Clock()
current_piece = Piece(3, 0, random.choice(shapes))
running = True
while running:
clock.tick(5)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
current_piece.y += 1
if not valid_space(current_piece, board):
current_piece.y -= 1
shape = current_piece.shape[current_piece.rotation % len(current_piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
board[current_piece.y + i][current_piece.x + j] = current_piece.color
current_piece = Piece(3, 0, random.choice(shapes))
draw_window(screen, board, current_piece)
pygame.quit()
이 메인 루프는 게임의 심장이다. clock.tick(5)는 1초에 5프레임씩 진행되도록 속도를 조절한다. 키 입력이나 종료 이벤트를 처리하고, 일정 시간마다 도형이 아래로 떨어지도록 y 값을 증가시킨다. valid_space로 공간 유효성을 체크하고, 도형이 멈췄다면 보드에 해당 도형의 색상을 기록한 뒤 새 도형을 생성한다. draw_window()는 현재 상태를 매번 갱신해서 플레이어가 실시간으로 확인할 수 있도록 도와준다.
결과 코드 전체 보기
전체 코드는 위에 작성한 각 코드 블럭을 순서대로 붙이면 실행됩니다. pygame이 설치되어 있고, Python 3.8 이상이라면 바로 실행해볼 수 있습니다. 아래는 모든 기능이 통합된 최종 코드입니다.
# 전체 실행 가능한 테트리스 코드
import pygame
import random
pygame.init()
display_width, display_height = 300, 600
block_size = 30
cols, rows = display_width // block_size, display_height // block_size
screen = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption("파이썬 테트리스")
shapes = [
[[1, 1, 1, 1]],
[[1, 1], [1, 1]],
[[0, 1, 0], [1, 1, 1]],
[[1, 0, 0], [1, 1, 1]],
[[0, 0, 1], [1, 1, 1]],
[[0, 1, 1], [1, 1, 0]],
[[1, 1, 0], [0, 1, 1]],
]
class Piece:
def __init__(self, x, y, shape):
self.x, self.y = x, y
self.shape = shape
self.color = (random.randint(50,255), random.randint(50,255), random.randint(50,255))
self.rotation = 0
def create_board():
return [[(0,0,0) for _ in range(cols)] for _ in range(rows)]
def valid_space(piece, board):
shape = piece.shape[piece.rotation % len(piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
x, y = piece.x + j, piece.y + i
if x < 0 or x >= cols or y >= rows or (y >= 0 and board[y][x] != (0,0,0)):
return False
return True
def clear_rows(board):
full_rows = [i for i in range(rows) if all(cell != (0,0,0) for cell in board[i])]
for i in full_rows:
del board[i]
board.insert(0, [(0,0,0)] * cols)
return len(full_rows)
def draw_text(text, size, x, y):
font = pygame.font.SysFont("comicsans", size)
label = font.render(text, True, (255,255,255))
screen.blit(label, (x, y))
def draw_window(screen, board, current_piece, score):
screen.fill((0,0,0))
for y in range(rows):
for x in range(cols):
pygame.draw.rect(screen, board[y][x], (x*block_size, y*block_size, block_size, block_size), 0)
shape = current_piece.shape[current_piece.rotation % len(current_piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, current_piece.color, ((current_piece.x + j) * block_size, (current_piece.y + i) * block_size, block_size, block_size), 0)
draw_text(f"SCORE: {score}", 24, 10, 10)
pygame.display.update()
board = create_board()
clock = pygame.time.Clock()
current_piece = Piece(3, 0, random.choice(shapes))
score = 0
fall_speed = 5
frame_count = 0
running = True
while running:
clock.tick(30)
frame_count += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_piece.x -= 1
if not valid_space(current_piece, board):
current_piece.x += 1
elif event.key == pygame.K_RIGHT:
current_piece.x += 1
if not valid_space(current_piece, board):
current_piece.x -= 1
elif event.key == pygame.K_DOWN:
current_piece.y += 1
if not valid_space(current_piece, board):
current_piece.y -= 1
elif event.key == pygame.K_UP:
current_piece.rotation += 1
if not valid_space(current_piece, board):
current_piece.rotation -= 1
if frame_count >= fall_speed:
current_piece.y += 1
if not valid_space(current_piece, board):
current_piece.y -= 1
shape = current_piece.shape[current_piece.rotation % len(current_piece.shape)]
for i, row in enumerate(shape):
for j, cell in enumerate(row):
if cell:
board[current_piece.y + i][current_piece.x + j] = current_piece.color
score += clear_rows(board) * 100
current_piece = Piece(3, 0, random.choice(shapes))
frame_count = 0
draw_window(screen, board, current_piece, score)
pygame.quit()
'개발 > Python' 카테고리의 다른 글
[Python] 파이썬으로 고전 게임 슈팅 게임 만들기: 총알, 적, 그리고 리듬감 (0) | 2025.04.22 |
---|---|
[Python] 파이썬으로 고전 게임 벽돌깨기 만들기 (0) | 2025.04.22 |
[Python] 파이썬으로 고전 게임 스네이크 게임 만들기 - 삽질의 연속 ㅠㅠ (1) | 2025.04.22 |
[Python] 파이썬으로 네이버 실시간 뉴스 타이틀 수집하기 (1) | 2025.04.20 |
[Python] 파이썬으로 엑셀 파일 한 번에 정리하기 (0) | 2025.04.20 |