지난 시간에는 키보드에 따라 한 칸씩 움직이는 사각형을 만들었습니다. 이번에는 그 사각형을 Tetriminos(이하 미노라 하겠습니다)로 만들어보겠습니다.
오랜만에 공식 문서 https://tetris.fandom.com/wiki/Tetromino를 확인해 보겠습니다. 미노는 총 7가지가 있습니다.
이 미노를 어떻게 python에서 작성할 수 있을까요? 우선 간단하게 미노를 눈으로 보니 7개의 미노가 존재합니다.
I, O, T, S, Z, J, L로 각각 이름이 있으며 모양과 색깔도 있습니다. 이 정보를 가지고 class로 작성해 보았습니다.
Mino를 Class로 작성.
name에는 string을 작성, blocks에는 x, y가 들어갈 좌표 list를 작성, rgb 숫자를 저장할 tuple를 작성합니다.
class Mino:
def __init__(self, name:str, blocks:List[List], color:tuple):
self.name = name
self.blocks = blocks
self.color = color
Tetris Class에 Mino를 만들어서 저장합니다.
class Tetris:
def __init__(self, width: int, height: int,
field_w:int=10, field_h:int=20, tile_size:int=10):
self._size = self.width, self.height = width, height
self._field_w, self._field_h, self._tile_size = field_w, field_h, tile_size
self._display_surf = None
self._running = False
# self._object = pg.Rect(0, 0, self._tile_size, self._tile_size)
self._mino_list = [
Mino("I", [[-2, 0], [-1, 0], [ 0, 0], [ 1, 0]], (54, 167, 141)),
Mino("O", [[-1,-1], [ 0,-1], [-1, 0], [ 0, 0]], (166, 166, 81)),
Mino("S", [[-1, 0], [ 0, 0], [ 0,-1], [ 1,-1]], (177, 86, 90)),
Mino("Z", [[-1,-1], [ 0,-1], [ 0, 0], [ 1, 0]], (143, 173, 60)),
Mino("L", [[-1, 0], [ 0, 0], [ 1, 0], [ 1,-1]], (73, 94, 158)),
Mino("J", [[-1,-1], [-1, 0], [ 0, 0], [ 1, 0]], (202, 121, 65)),
Mino("T", [[-1, 0], [ 0, 0], [ 0,-1], [ 1, 0]], (183, 92, 165))
]
self._current_mino = self._mino_list[0]
self._object 대신 7개의 미노가 작성되었습니다. 이 미노를 그리기 위해 pygame.Rect로 그려내는 함수를 만듭니다.
def _update(self):
self._display_surf.fill((0, 0, 0))
self._draw_field_rect_list()
for block in self._current_mino.blocks:
_rect = pg.Rect(block[0]*self._tile_size, block[1]*self._tile_size, self._tile_size, self._tile_size)
pg.draw.rect(self._display_surf, (self._current_mino.color), _rect)
# pg.draw.rect(self._display_surf, (255, 127, 127), self._object)
pg.display.flip()
def _move(self, x, y):
for block in self._current_mino.blocks:
block[0] += x
block[1] += y
# self._object = self._object.move(x*self._tile_size, y*self._tile_size)
이제 실행을 시켜봅시다.
이제 블럭까지 만들었습니다만, 아직 tetris라고 하기에는 부족한 점들이 많습니다. 예를 들면...
이번에는 미노가 처음 나왔을 때의 위치 지정, 그리고 Field를 나가지 않도록 경계조건(boundary condition)을 만들어 보도록 합시다.
경계조건을 만들어 보자
우선, 미노가 x좌표의 한가운데 놓이기 위해서는 field_w의 절반 값을 알아야겠죠?
def _update(self):
self._display_surf.fill((0, 0, 0))
self._draw_field_rect_list()
for block in self._current_mino.blocks:
_rect = pg.Rect((block[0] + self._field_w//2)*self._tile_size, block[1]*self._tile_size, self._tile_size, self._tile_size)
pg.draw.rect(self._display_surf, (self._current_mino.color), _rect)
# pg.draw.rect(self._display_surf, (255, 127, 127), self._object)
pg.display.flip()
그리고, move에서 경계를 나가지 않도록 합니다. 단 지금은 좌, 우, 하단 이 3가지 방향만 합니다. 상단은 막지 않습니다. 이유는, 실제 tetris의 field 크기는 10 x 40이라고 하기 때문입니다. 사용자에게 보이는 부분이 10 x 20일뿐입니다.
move 함수를 수정해 주도록 합시다.
def _move(self, x, y):
move_enable = True
for block in self._current_mino.blocks:
if 0 > block[0] + x or block[0] + x >= self._field_w or block[1] + y >= self._field_h:
move_enable = False
break;
if not move_enable:
return False
for block in self._current_mino.blocks:
block[0] += x
block[1] += y
또 동작을 안한다.
이러면 동작... 을 안 합니다. 왜일까요? 바로 block의 좌표로 검사했을 뿐, field상의 좌표로 검사하지 않았기 때문입니다. 실제로 _update 함수에서 field에 좌표를 배치하는 것을 확인할 수 있습니다.
그럼 어떻게 해야 할까요? move에서 field의 배치를 예상해서 경계조건을 처리해야 할까요? 아닙니다. init 할 때부터 field의 center로 옮기는 작업을 하면 됩니다.
class Tetris:
def __init__(self, width: int, height: int,
field_w:int=10, field_h:int=20, tile_size:int=10):
self._size = self.width, self.height = width, height
self._field_w, self._field_h, self._t_size = field_w, field_h, tile_size
self._display_surf = None
self._running = False
self._mino_list = [
Mino("I", [[-2, 0], [-1, 0], [ 0, 0], [ 1, 0]], (54, 167, 141)),
Mino("O", [[-1,-1], [ 0,-1], [-1, 0], [ 0, 0]], (166, 166, 81)),
Mino("S", [[-1, 0], [ 0, 0], [ 0,-1], [ 1,-1]], (177, 86, 90)),
Mino("Z", [[-1,-1], [ 0,-1], [ 0, 0], [ 1, 0]], (143, 173, 60)),
Mino("L", [[-1, 0], [ 0, 0], [ 1, 0], [ 1,-1]], (73, 94, 158)),
Mino("J", [[-1,-1], [-1, 0], [ 0, 0], [ 1, 0]], (202, 121, 65)),
Mino("T", [[-1, 0], [ 0, 0], [ 0,-1], [ 1, 0]], (183, 92, 165))
]
self._current_mino = None
def _init(self):
pg.init()
self._display_surf = pg.display.set_mode(self._size)
self._draw_field_rect_list()
self._running = True
self._current_mino = self._set_mino_init_position(0)
def _set_mino_init_position(self, index):
curr_mino = self._mino_list[index]
for b in curr_mino.blocks:
b[0] += self._field_w//2
return curr_mino
_set_mino_init_position을 통해, mino가 생기자마자 x 중앙으로 배치되도록 했습니다.
def _update(self):
self._display_surf.fill((0, 0, 0))
self._draw_field_rect_list()
for b in self._current_mino.blocks:
_rect = pg.Rect(b[0]*self._t_size, b[1]*self._t_size, self._t_size, self._t_size)
pg.draw.rect(self._display_surf, (self._current_mino.color), _rect)
pg.display.flip()
_update 함수도 초기 배치를 신경 쓸 필요 없이, 그리는데 집중할 수 있게 되었습니다.
필드에서 화살표를 통해 자유롭게 움직이는 테트리스 블럭인 Tetrimono(이하 미노)를 만들어 보았습니다.
다음시간에는, 미노를 땅에 떨어뜨리고, 새로운 미노를 등장하는 시스템을 만들어 보도록 하겠습니다.
* 이번 글까지의 코드
import pygame as pg
from typing import List
class Mino:
def __init__(self, name:str, blocks:List[List], color:tuple):
self.name = name
self.blocks = blocks
self.color = color
class Tetris:
def __init__(self, width: int, height: int,
field_w:int=10, field_h:int=20, tile_size:int=10):
self._size = self.width, self.height = width, height
self._field_w, self._field_h, self._t_size = field_w, field_h, tile_size
self._display_surf = None
self._running = False
self._mino_list = [
Mino("I", [[-2, 0], [-1, 0], [ 0, 0], [ 1, 0]], (54, 167, 141)),
Mino("O", [[-1,-1], [ 0,-1], [-1, 0], [ 0, 0]], (166, 166, 81)),
Mino("S", [[-1, 0], [ 0, 0], [ 0,-1], [ 1,-1]], (177, 86, 90)),
Mino("Z", [[-1,-1], [ 0,-1], [ 0, 0], [ 1, 0]], (143, 173, 60)),
Mino("L", [[-1, 0], [ 0, 0], [ 1, 0], [ 1,-1]], (73, 94, 158)),
Mino("J", [[-1,-1], [-1, 0], [ 0, 0], [ 1, 0]], (202, 121, 65)),
Mino("T", [[-1, 0], [ 0, 0], [ 0,-1], [ 1, 0]], (183, 92, 165))
]
self._current_mino = None
def _init(self):
pg.init()
self._display_surf = pg.display.set_mode(self._size)
self._draw_field_rect_list()
self._running = True
self._current_mino = self._set_mino_init_position(0)
def _set_mino_init_position(self, index):
curr_mino = self._mino_list[index]
for b in curr_mino.blocks:
b[0] += self._field_w//2
return curr_mino
def _draw_field_rect_list(self):
for i in range(self._field_w):
for j in range(self._field_h):
_new_rect = pg.Rect(i*self._t_size, j*self._t_size, self._t_size, self._t_size)
pg.draw.rect(self._display_surf, (255, 255, 255), _new_rect, 1)
def _event(self):
for event in pg.event.get():
if event.type == pg.QUIT: ## Window 상단 X버튼
self._running = False
elif event.type == pg.KEYDOWN:
if event.key == pg.K_UP:
self._move( 0,-1)
elif event.key == pg.K_DOWN:
self._move( 0, 1)
elif event.key == pg.K_RIGHT:
self._move( 1, 0)
elif event.key == pg.K_LEFT:
self._move(-1, 0)
def _update(self):
self._display_surf.fill((0, 0, 0))
self._draw_field_rect_list()
for b in self._current_mino.blocks:
_rect = pg.Rect(b[0]*self._t_size, b[1]*self._t_size, self._t_size, self._t_size)
pg.draw.rect(self._display_surf, (self._current_mino.color), _rect)
pg.display.flip()
def _move(self, x, y):
move_enable = True
for block in self._current_mino.blocks:
if 0 > block[0] + x or block[0] + x >= self._field_w or block[1] + y >= self._field_h:
move_enable = False
break;
if not move_enable:
return False
for block in self._current_mino.blocks:
block[0] += x
block[1] += y
def _quit(self):
pg.quit()
def execute(self):
self._init()
while self._running:
self._event()
self._update()
self._quit()
def main():
tetris = Tetris(640, 400)
tetris.execute()
if __name__ == "__main__" :
main()
*reference
https://tetris.fandom.com/wiki/Tetris_Guideline
'Development > Tetris' 카테고리의 다른 글
Python - 테트리스(Tetris) 만들기 (8) - Field (0) | 2023.03.09 |
---|---|
Python - 테트리스(Tetris) 만들기 (7) - Drop Timer (0) | 2023.03.08 |
Python - 테트리스(Tetris) 만들기 (5) - Event (0) | 2023.03.05 |
Python - 테트리스(Tetris) 만들기 (4) - Draw Rect (0) | 2023.03.05 |
Python - 테트리스(Tetris) 만들기 (3) - Pygame Library (0) | 2023.03.04 |