import pygame
import random
# 기본 설정
pygame.init()
pygame.mixer.init() # 믹서 초기화
width, height = 300, 600 # 게임판 크기 (10x20 타일 크기)
block_size = 30 # 각 블럭의 픽셀 크기
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Tetris with Sound")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 36)
# 색상 설정
colors = [
(0, 0, 0), # 빈 공간
(0, 240, 240), # I 블럭
(0, 0, 240), # J 블럭
(240, 160, 0), # L 블럭
(240, 240, 0), # O 블럭
(0, 240, 0), # S 블럭
(160, 0, 240), # T 블럭
(240, 0, 0) # Z 블럭
]
# 블럭 모양 정의 (4x4 그리드)
shapes = [
[[1, 1, 1, 1]], # I
[[1, 0, 0], [1, 1, 1]], # J
[[0, 0, 1], [1, 1, 1]], # L
[[1, 1], [1, 1]], # O
[[0, 1, 1], [1, 1, 0]], # S
[[0, 1, 0], [1, 1, 1]], # T
[[1, 1, 0], [0, 1, 1]] # Z
]
# 블럭 클래스
class Block:
def __init__(self, shape, x, y):
self.shape = shape
self.x = x
self.y = y
self.color = colors[shapes.index(shape) + 1]
def rotate(self):
# 시계 방향으로 블럭 회전
self.shape = [list(row) for row in zip(*self.shape[::-1])]
def draw(self, surface):
# 블럭을 화면에 그리기
for y, row in enumerate(self.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(
surface,
self.color,
(self.x + x * block_size, self.y + y * block_size, block_size, block_size),
0
)
# 게임판 클래스
class TetrisGame:
def __init__(self, line_clear_sound):
self.board = [[0] * 10 for _ in range(20)] # 10x20 게임판 초기화
self.current_block = self.create_new_block()
self.next_block = self.create_new_block()
self.score = 0
self.game_over = False
self.line_clear_sound = line_clear_sound # 라인 클리어 소리
def create_new_block(self):
shape = random.choice(shapes)
return Block(shape, 3 * block_size, 0)
def draw_board(self, surface):
# 게임판을 화면에 그리기
for y, row in enumerate(self.board):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(
surface,
colors[cell],
(x * block_size, y * block_size, block_size, block_size),
0
)
def clear_lines(self):
# 한 줄이 가득 찼는지 확인하고, 가득 찬 줄을 제거
new_board = [row for row in self.board if any(cell == 0 for cell in row)]
cleared_lines = len(self.board) - len(new_board)
self.score += cleared_lines * 100 # 점수 추가
self.board = [[0] * 10 for _ in range(cleared_lines)] + new_board
return cleared_lines # 클리어된 라인 수를 반환
def check_collision(self, block, offset_x=0, offset_y=0):
# 블럭이 벽이나 다른 블럭과 충돌하는지 확인
for y, row in enumerate(block.shape):
for x, cell in enumerate(row):
if cell:
new_x = (block.x // block_size) + x + offset_x
new_y = (block.y // block_size) + y + offset_y
if new_x < 0 or new_x >= 10 or new_y >= 20 or self.board[new_y][new_x]:
return True
return False
def place_block(self, block):
# 블럭을 게임판에 고정
for y, row in enumerate(block.shape):
for x, cell in enumerate(row):
if cell:
self.board[(block.y // block_size) + y][(block.x // block_size) + x] = colors.index(block.color)
cleared_lines = self.clear_lines() # 라인 클리어 후 갱신
if cleared_lines > 0:
self.line_clear_sound.play() # 라인 클리어 효과음
self.current_block = self.next_block
self.next_block = self.create_new_block()
if self.check_collision(self.current_block):
self.game_over = True
def move_block_down(self):
# 블럭을 아래로 한 칸 이동
if not self.check_collision(self.current_block, offset_y=1):
self.current_block.y += block_size
else:
self.place_block(self.current_block)
def move_block_sideways(self, dx):
# 블럭을 좌우로 이동
if not self.check_collision(self.current_block, offset_x=dx):
self.current_block.x += dx * block_size
def drop_block(self):
# 스페이스바를 눌렀을 때 블럭을 한 번에 내려놓기
while not self.check_collision(self.current_block, offset_y=1):
self.current_block.y += block_size
self.place_block(self.current_block)
def rotate_block(self):
# 블럭을 회전
old_shape = self.current_block.shape[:]
self.current_block.rotate()
if self.check_collision(self.current_block):
self.current_block.shape = old_shape # 회전 불가능 시 원상 복구
# 게임 실행 함수
def main():
pygame.mixer.music.load("TM.wav") # 배경 음악 파일 경로
pygame.mixer.music.play(-1, 0.0) # 배경 음악 반복재생
drop_sound = pygame.mixer.Sound("TD.wav") # 블록 떨어지는 소리 파일 경로
line_clear_sound = pygame.mixer.Sound("TL.wav") # 라인 클리어 소리 파일 경로
game = TetrisGame(line_clear_sound) # 라인 클리어 소리를 전달
fall_time = 0
speed = 500 # 블럭이 자동으로 떨어지는 속도
while True:
screen.fill((0, 0, 0))
fall_time += clock.get_rawtime()
clock.tick()
if game.game_over:
# 게임 종료 시 배경 음악 중지
pygame.mixer.music.stop()
# 게임 종료 시 스코어 및 재시작 메시지 출력
game_over_text = font.render(f"Game Over! Score: {game.score}", True, (255, 255, 255))
restart_text = font.render("Press R to Restart", True, (255, 255, 255))
screen.blit(game_over_text, (20, height // 2 - 40))
screen.blit(restart_text, (20, height // 2))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
main() # 게임 재시작
continue
if fall_time > speed:
fall_time = 0
game.move_block_down()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
game.move_block_sideways(-1)
elif event.key == pygame.K_RIGHT:
game.move_block_sideways(1)
elif event.key == pygame.K_DOWN:
game.move_block_down()
elif event.key == pygame.K_UP:
game.rotate_block()
elif event.key == pygame.K_SPACE:
game.drop_block()
drop_sound.play() # 블록 떨어지는 소리
game.draw_board(screen)
game.current_block.draw(screen)
# 점수 표시
score_text = font.render(f"Score: {game.score}", True, (255, 255, 255))
screen.blit(score_text, (10, 10))
pygame.display.update()
if __name__ == "__main__":
main()
위 파일을 받으면 파이썬 코드와 함께 효과음 파일이 있습니다 참고하셔서 여러분 만의 코드를 작성해 보시거나 추가를 해보시는 것을 추천드립니다.
이번 테트리스 게임 개발 과정은 개발자로서의 창의력과 논리적 사고를 동시에 활용할 수 있는 매우 흥미로운 경험이었습니다. 처음에는 게임의 기본 구조를 설계하며 각 구성 요소를 세밀하게 나누는 작업을 진행했습니다. 이를 통해 코드의 유지보수성을 높이고, 추후 확장성을 고려한 구조를 마련할 수 있었습니다.
코드를 작성하는 동안 가장 중점을 둔 부분은 게임의 핵심적인 동작인 블록 이동, 회전, 라인 클리어 등의 기능을 효율적으로 구현하는 것이었습니다. 이를 위해 객체 지향 프로그래밍의 장점을 활용해 각 블록과 게임판을 클래스로 정의하고, 이들 간의 상호작용을 명확히 했습니다. 예를 들어, 블록 클래스는 자체적인 회전과 그리기 기능을 담당하며, 게임판 클래스는 블록의 충돌 감지와 고정, 라인 클리어를 처리하도록 설계했습니다. 이러한 구조는 각 기능을 모듈화하여 코드의 가독성과 재사용성을 높이는 데 기여했습니다.
사운드 효과를 추가하는 작업도 매우 흥미로웠습니다. 배경 음악과 블록 이동, 라인 클리어 등의 효과음을 적절히 배치하여 게임의 몰입감을 향상시키는 데 성공했습니다. 특히, 라인 클리어 시 사운드를 재생하도록 구현한 것은 사용자에게 즉각적인 피드백을 제공함으로써 게임의 재미를 한층 더 높이는 요소가 되었습니다. 이 과정에서 Pygame의 믹서 모듈을 사용해 사운드를 관리했으며, 이는 다소 생소한 부분이었지만 결과적으로 성공적으로 구현할 수 있었습니다.
개발 중 가장 도전적이었던 부분은 블록의 충돌 감지와 회전 로직이었습니다. 특히, 블록이 벽이나 다른 블록에 닿았을 때 정확히 처리되도록 하는 것은 상당히 신중한 계산이 필요했습니다. 이를 해결하기 위해 다양한 테스트 데이터를 생성하고, 모든 경우의 수를 꼼꼼히 검증하며 문제를 해결해 나갔습니다. 이러한 경험은 문제 해결 능력을 한층 더 향상시키는 계기가 되었습니다.
또한, 이번 프로젝트에서는 사용자의 인터랙션에 즉각적으로 반응하는 동작을 구현하는 데 많은 노력을 기울였습니다. 키 입력에 따라 블록이 자연스럽게 움직이고, 회전하거나 떨어지도록 구현하면서 게임의 조작감을 최대한 현실감 있게 만드는 데 집중했습니다. 이 과정에서 Pygame의 이벤트 처리 시스템을 활용했으며, 이를 통해 효율적으로 사용자 입력을 처리할 수 있었습니다.
결과적으로, 이번 프로젝트는 단순한 게임 개발을 넘어 코드의 구조화와 효율적인 기능 구현, 사용자 경험을 고려한 설계 등의 다양한 측면에서 많은 것을 배울 수 있는 기회였습니다. 앞으로도 이러한 경험을 바탕으로 더 복잡한 게임이나 애플리케이션을 개발하는 데 도전할 계획입니다. 이번 프로젝트는 저에게 프로그래밍 실력을 키우고 창의성을 발휘할 수 있는 값진 시간이었습니다