항해플러스에서 코육대를 한다는 얘기를 처음 들었을 때, 그리고 그 주요 종목들이 세뱃돈 계산기 행맨 테트리스 등 간단한 게임이나 프로그램이라는 얘기를 듣는순간! 이건 파이썬을 위한 대회라는 생각을 했다.
파이썬에는 이런것들을 만들기 위한 다양한 라이브러리가 있다.
조금더 생각해보니 파이썬에게 아주 적합하지는 않다는 생각이 들었다. 문제는 웹배포였다. 파이썬으로 프론트엔드까지 구현된 웹베포를 어떻게 할지 막막했다. 뿐만 아니라 db에 저장하지 않을꺼라면 않을꺼라면 불필요하게 백엔드에서 웹소켓을 쓰는 것보다 프론트에 바로 붙이는게 더 자연스럽다는 생각이 들었다. 그렇다면 백엔드에 한정되 있는 파이썬을 쓰는게 맞는걸까?
프론트만 구현한다면 자바스크립트가 낫겠지만...
그때 내 머리를 스치는 것이 있었다.
작년쯤 anaconda가 배포한 파이스크립트!!!
파이스크립트는 Web Assembly등 최신 기술을 사용해서 파이썬을 프론트엔드에서 사용할수 있도록 컴파일을 해주는 프레임워크 프로젝트다. 단순 컴파일 뿐만 아니라 html과 직접 상호작용할 수 있도록 여러가지 메소드도 함께 제공한다.
호기롭게 파이스크립트에 도전했지만 처음부터 마음먹은 것처럼 코딩이 되지 않았다.
간단하게 프론트 페이지를 구성한 다음 파이썬으로 계산된 부분들과 연동될 수 있도록 했는데 거기서 부터 막혔다.
시작 버튼을 누르면 실제 게임이 구동되는 함수가 작동해야 한다.
하지만 시작버튼을 어떻게 인지할 것인가?!
자바스크립트에는 누구나 아는 getElementById가 있다 하지만 파이스크립트에는 그에 해당하는 Element()라는 메소드가 있었다.
근데 이걸 알려주는 곳이 없었다.
파이스크립트는 아직 초기단계라 다큐먼트가 자세히 작성되어 있지 않았기 때문이다.
이처럼 인터넷에 있는 코드 스니펫을 참고해서 메소드를 하나하나 알아내야 했기에 시간이 오래 걸렸다.
그리드를 만드는건 더 힘들었다.
시작버튼을 누르면 세로줄10+1 가로줄 20+1개로 구성된 그리드가 만들어져야 하는데, 어째서인지 가로줄은 7개 이상 늘어나지 않았다.
분명 가로줄=4로 루프를 둘리면 4개의 줄이 만들어지고 가로줄=5로 루프를 돌리면 5개의 줄이 만들어지는데 왜 가로줄=7은 안되는 것일까?
무엇보다 날 좌절스럽게 만들었던건 파이스크립트를 컴파일하느라 시간이 꽤 걸린다는 것이었다.
과연이걸로 만들어진 게임이 불편함 없이 구동될지 자신이 없었다.
결국난 파이스크립트를 포기하고 익숙한 파이게임으로 돌아왔다.
파이게임의 너무너무 좋은 점은 UI에 해당하는 부분을 아주 쉽게 만들수 있다는 것이다.
다음은 기능 구현 단계이다.
1) 그리드
self.grid = [[0 for _ in range(self.num_cols)] for _ in range(self.num_rows)]
한줄에 10개의 0가 생기는 리스트를 20번 반복해서 그리드를 표현했다.
여기서 0은 기본색을 뜻하고, 7개 블록에 해당하는 1~7의 숫자가 입력될 수 있다.
2) 블록
각 블록 아이디를 갖는 블록을 만들었다.
각 블록은 3x3 크기의 공간을 기준으로 4개의 블록을 차지 하고, 또 각각 회전을 할 수가 있어서 4개의 상태가 존재한다.
때문에 블록이 갖고 있는 cells라는 init값에는 각각 0~3의 키값에 해당하는 밸류 리스트가 존재한다. 각 밸류 리스트는 회전한 상태를 말하고, 회전하지 않았을 때는 0의 값이된다.
각 포지션 리스트 4개의 셀을 의미하는 포지션 객체 4개를 갖는다. 앞으로 블록이라 부르는 값은 4개 길이의 cell 딕셔너리 값을 갖는 객체이다.
블록은 0,0이 아닌 가운데서 리젠되야 함으로 각 블록 클래스 init에서 오른쪽을 3칸 이동 시킨다.
최종 위치 값은 tiles = self.cells[self.rotation_state]
이렇게 확인하고 그곳에 블록 id값과 맞는 색을 입혀서 그린다.
class LBlock(Block):
def __init__(self):
super().__init__(id = 1) # Block init 클래스를 상속받아서 사용
self.cells = {
0: [Position(0, 2), Position(1, 0), Position(1, 1), Position(1, 2)],
1: [Position(0, 1), Position(1, 1), Position(2, 1), Position(2, 2)],
2: [Position(1, 0), Position(1, 1), Position(1, 2), Position(2, 0)],
3: [Position(0, 0), Position(0, 1), Position(1, 1), Position(2, 1)]
}
self.move(0, 3)
3)블록 이동
pygame event listener를 사용해서 이벤트 처리를 한다. 예를들어 왼쪽 키다운 버튼을 누를때 블록의 위치가 왼쪽으로 이동한다. 블록은 4개의 cell을 가지고 있으므로 모든 셀이 왼쪽으로 이동한다. 같은 방식으로 오른쪽 이동, 아래 이동이 가능한다.
4)블록 그리드 밖으로 이동 감지
블록이 그리드 밖으로 나가는 경우를 제거하기 위해 is_inside라는 함수를 만들었다. 각 블록 셀의 row, col을 추출해서 그리드의 row, col과 비교해서 빠져나가는게 있는지 확인하는 함수다. 블록은 4개의 셀로 구성되어 있기 때문에 각각 셀에 대해 is_inside를 확인해야 하고, 하나라도 빠져나가면, move함수를 반대로 실행해서 빠져나가지 못하도록 했다.
def is_inside(self, row, col):
"""블록이 그리드 안에 있는지 확인하는 함수"""
return 0 <= row < self.num_rows and 0 <= col < self.num_cols
5) 회전
각 블록은 이미 0, 1, 2, 3의 회전에 대한 위치 값을 갖고 있기 때문에 이 숫자 즉 rotation_state만 키보드 위 방향키를 누를 때마다 바꾸게 해준다.
3이 넘어가면 다시 0으로 바꿔준다.
6) 블록 변경 및 이전 블록 고정
블록이 바닥 밑으로 내려가거나 다른 블록(공간 값이 0이 아닌)과 만나면 이동 한걸 되돌린다. 그리고 새 블록을 생성하고, 이전 블록은 움직이지 않는다
7) 한줄 클리어 및 라인 카운터
한줄 클리어 된 경우 line_cleared라는 변수에 저장해서 화면에 표시한다.
8) 타이머
time 라이브러리를 임포트해서 게임 시작 할때 start_time에 저장해둔 뒤에 현재 시간과 빼서 elapsed_time이라는 string변수에 저장해두고 이 elapsed_time을 화면에 표시한다. 이 get_elapsed_time이라는 함수는 게임 루프마다 계속 실행한다.
def get_elapsed_time(self):
if self.start_time is not None and not self.game_over:
elapsed_seconds = int(time.time() - self.start_time)
minutes = elapsed_seconds // 60
seconds = elapsed_seconds % 60
self.elapsed_time = f"{minutes:02}:{seconds:02}"
9) 점수
10) 게임 시작 및 게임오버
11) 스마트폰 최적화
친구에게 보여줬더니 첫마디가 핸드폰으로 플레이가 가능하지 않냐는 것이었다. 원래 게임은 방향키로 블록을 움직이게 만들었지만 요즘 테트리스 같은 게임을 컴퓨터로 하는 사람은 없기에 업데이트 필요성을 느꼈다.
가장 중요한것은 왼쪽 오른쪽 블록 이동과 회전을 할 수 있도록 만드는 것이다. 그리드는 화면의 0~320의 위치를 차지하고 있다는걸 알기 때문에 이를 반으로 나눠서 왼쪽을 클릭하면 왼쪽 이동 오른쪽을 클릭하면 오른족 이동이 되게 만들었다. 핸드폰에서 적용하면 왼쪽 화면을 터치하면 왼쪽으로 이동하고 오른쪽을 클릭하면 오른쪽 이동이 될 것이다. 그리고 game.game_over이 아닌 상태일때( 즉 게임 플레이 도중일 때) 블록을 회전할 수 있는 버튼이 오른쪽 아래에 나타나게 만들었다.
유저 반응을 보고 바로 업데이트를 하는 과정이 매우 즐거웠다.
파이게임은 웹배포 용으로 만든 것이 아니지만 프론트로 배포하고 싶다고 생각한 사람이 나만 있진 않았을거라 생각했다.
그리고 찾은게 pygbag라는 라이브러리였다.
사실 이것도 pyscript랑 크게 보면 같은 개념이다. 파이썬을 웹어셈블리로 html과 호환가능하게끔 컴파일을 해주는 것이다.
[https://github.com/pygame-web/pygbag](pygbag 깃허브 링크)
로컬테스트
await asyncio.sleep(0)
를 추가한다. 자세한 설명은 pygbag 깃허브 링크를 참조pip install pygbag
를 한다pygbag tetris
명령어를 입력한다. 여기서 tetris는 게임이 있는 디렉토리명이다서버 실행
sudo apt install nginx
로 엔지닉스를 설치한다sudo systemctl start nginx
로 엔지닉스를 시작한다/var/www/html
이라는 폴더 안에 있는 것을 실행한다. 따라서 web파일 안의 컨텐츠를 mv 명령어를 사용해서 해당 디렉토리 안에 넣어둔다.https://hanghaeplus-coyukdae.oopy.io/
https://github.com/Madung2/thanksgiving-tetris
EC2 배포 링크: http://3.39.216.163/