레이어7 개인프로젝트 - 테트리스

풀소스는 이곳에 있다. 생 파일은 이곳에 있다.

실행파일은 이곳에서 받을 수 있다.

개발 환경

Windows, DEV C++

개발하게 된 이유

내가 기억하기로는 포프님이 이 말씀을 하신 것으로 기억하는데, 그 출처를 찾을 수가 없으므로 이렇게 말하겠다.
어떤 한 프로그래머가 말하길, "C언어를 공부했으면 테트리스 정도는 짤 줄 알아야 하는 것 아닌가?" 라고 말했던 것을 트위터에서 본 적이 있다.
때마침 레이어 7에서 C언어를 배운 뒤 개인 프로젝트를 하게 되었다.
나름의 콤플렉스(?)로 남아있던 테트리스를 만들어야겠다고 결심했다.

실행 화면


개발 기간

개발을 다 끝내고 수련회까지 다녀와서 작성하는 보고서라서, 정확하게 기억은 나지 않는다.
다만 5월 4일쯤에 구상을 하면서 대충 어떤 역할들을 하는 함수들이 필요할지 구상을 했다. 아니, 정확히는 영화를 보면서 코드를 작성한 탓에 못 써먹는 코드를 작성하게 됬다고 보는게 맞다.
그리고 어린이날, 기본적인 구조들을 구상하고 코드로 옮긴 뒤에, 블럭이 쌓이는 기능까지 구현했었다.
5/6에 학교 쉬는시간에 블럭이 깨지는 기능을 구현했고, 그날 점수기능 및 스테이지기능(블럭 떨어지는 속도 점점 빨라지는기능)을 구현했다.
5/7에는 블럭이 특정 i열을 수정하면 죽는 기능을 구현했었고, 발표준비를 했었다.
그런데 5/8, 발표일에 순서가 밀려서 동아리에서 테트리스를 정말 좋아하는 선배님한테 숙제를 받았는데, 이는 다음과 같다.

"반시계 방향 회전과 홀드기능, 하드드랍 기능을 구현하라"
그래서 5/9 쉬는시간에 홀드기능과 하드드랍 기능을 구현하였고, 5/10에 반시계방향 회전을 구현한거같다.
그리고 오늘 입력을 받는 부분에 Sleep(1);을 넣어서 cpu사용량을 최적화 했다.

대충 걸렸던 시일과 날짜를 맞춘거라 정확하지 않다.
그래도 확실한건 대충 모든 기능 구현에 5일정도가 걸렸다는점, 콘솔 api부분과 위에서 말한 cpu사용량 최적화를 빼면 누구의 도움도, 구글링도 필요하지 않았다는 점이다!

테트리스의 내부 구조

다른사람의 코드를 보고 코드를 짜는것은 원하지 않아서 보지 않았지만, 전역변수를 사용하여 구현하는 경우가 많다고 한다(!).
그러나 웹개발과 앱개발을 하면서 그러한 코드는 유지보수에 있어 최악 그 자체인것을 알고 있던 나는, C의 포인터를 적극 활용하기로 했다.

windowsAPI


콘솔의 커서를 이동하는 함수이다. 보통은 gotoxy() 이런식으로 만들어서 사용하지만, 내가 y와 x의 위치를 바꾼 이유는 2중 포문으로 block[i][j]에 보통 접근할때 i가 y이고 j가 x이기 때문이다.
개발 시 햇갈릴 수 있다고 생각해서 함수를 이렇게 설계했다.
또한, 콘솔에서는 커서가 보이기 때문에, 그 커서를 없에도록 설정하는 코드를 작성했다.

맵은 총 4개의 상태를 가진다.

- 비어있음
- 떨어지는 블럭(SOFT_BLOCK)
- 굳은 블럭(HARD_BLOCK)
- 벽

이러한것들에 대해 숫자나 캐릭터로 접근하는것보다는 열거형을 사용하는것이 개발상의 실수를 줄일 수 있을것같아서 열거형을 사용했다.


그 후 맵을 설계했다.



맵 배열의 양 끝과 바닥을 WALL로 설정해두었다.

블럭

블럭은 배열과 구조체의 합작(?)으로 작동한다. 

블럭 배열

블럭들의 모양과 회전상태는 전부 4차원 배열로 작성해두었다.
처음 구현했던 내용은 단순히 3차원 배열에 7가지 블록의 기본 상태를 저장해놓고, rotateBlock()과 같은 함수를 통해 block[i][j]의 내용을 block[j][3-i]로 옮기는것으로 하려 했었다.
그러나 블럭 회전시마다 2중 포문을 돌려서 값을 다시 넣어야 한다는 점, 가운데의 축을 중심으로 회전해야하는데 그런것 없이 통쨰로 돌려버린다는 점 때문에, 다음과 같은 구조를 채택했다.



각각 [블럭의 ID(종류)][블럭의 회전상태][블럭의 세로][블럭의 가로]를 의미한다.

블럭 구조체


블럭의 id, 회전상태, 그리고 블럭의 좌표값을 담고 있다. 즉, 블럭 구조체 자체는 별도의 블럭의 모양을 가진 2차원 배열을 갖고있지 않는다.

블럭 충돌 감지

일단, 0, 1, 2, 3과 같이 그저 숫자로 방향을 표현한다거나 char을 사용해서 방향을 표현 할 수 있겠지만, 그것보다는 열거형을 사용하는것이 좀 더 옳은 방법이라고 생각했다. 그래서 열거형을 먼저 만들었다.
그리고 나서는 현재 상태가 충돌인지 확인하는 함수를 하나 만들었다.


이후 isNowConflict와 moveBlock()함수를 활용하여 움직이는 행동이 충돌을 유발하는지 확인하는 함수 하나를 짰다.

그 알고리즘은 블럭의 좌표값을 옮긴뒤에, map의 [i][j]가 비어있지 않은데 블럭이 그곳을 침범한다면 충돌로 감지하는것이다.

회전시 충돌감지는 다음과 같이 구현했다.

각각 시계방향 회전과 반시계 방향 회전을 메인 함수에서 이런식으로 구현했다.

회전 시키는 코드는 회전 전의 상태코드를 동적할당을 통해 저장시켰다가, 회전을 시켜 본 뒤에 충돌이 난다면 회전 전의 상태코드를 복구시키는 내용이다.

다음블럭

블럭 하나가 완전히 고정되었을때, 다음 코드를 실행하게 해두었다.

홀드

홀드 기능 구현은 어렵지 않았다.
그냥 블럭 구조체 하나를 더 만들고, 다음과 같은 코드를 작성했다.


게임 시스템

점수


점수가 어떻게 올라가는지는 코드를 보면 확인 할 수 있으니 설명은 생략하겠다.
블럭의 크기는 4*4니까, 블럭의 y좌표를 받아와서 y+4까지 검사를 돌리는 코드이다.
맵의 가로 크기는 10이니까, counter가 10이된다면 점수를 낼 수 있다는 뜻이다.

그렇게 되면 맵에서 해당 줄을 지우게 된다.


이후에는 그만큼 맵을 재 출력해야하는데, 이는 다음과 같다.

삭제 하려하는 줄에 빈네모를 딜레이를 주며 출력하여 애니메이션을 구현하였고, 이후에는 블럭을 재 출력시킨다.

이 모든 내용은, 메인함수에서 블럭을 굳게하는 내용을 실행시킨뒤에 실행하는데 이는 다음과 같다.

구현에 있어 어려웠던 점

...은 솔직히 크게 없었다. 개발 기간만 보아도, 남는시간에 짬짬히 짰을 정도로 크게 어려운 난이도도 아니였고 말이다.
다만, 처음 충돌알고리즘을 설계할때 머리를 좀 썼던 기억이 난다. 처음에는 떨어지는 블럭의 맨 끝과 벽만을 검사해서 알고리즘이 작동이 안되기도 했었던 기억이 난다. (블럭 배열의 끝부분이 0인 경우에는 해당 알고리즘이 의미가 없다.)

개발하는동안 어려워서 머리를 싸매기 보다는, 자잘한 버그들을 고쳐가면서 즐겁게 개발했던것 같다!!
다만 goto를 사용하게되면 특정 함수의 실행횟수를 줄여서 최적화를 할 수 있는 코드에 대해서 goto를 사용하는것이 맞는가 아닌가에 대한 고민을 많이했다. 결론은 아직도 못냈지만..!

배운점

c언어로 콘솔창 위에서 하는 게임개발에 대해서, 늘 전혀 구체화가 되지않아 뜬구름만 잡는 소리같았는데, 실제로 구현해보면서 gotoyx나 cpu최적화 방법, cmd창에서 커서를 지우는 내용을 배웠다.

느낀점

c언어로 하는 개발에 대한 흥미가 다시 한번 생겼던것이 이번 개발에 있어 가장 큰 성과라고 생각한다.

Comments

Popular Posts