<테트리스 게임> 프로젝트
- 게임판 위에서의 이동
작업 환경 : 윈도우 10, Visual Studio, C++ (Win32 Console)
참고 서적 : C프로그래밍 파워 업그레이드 (윤성우 저)
게임판(stage) 그리기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include <iostream> #include <Windows.h> using namespace std; // 블록이 이동가능한 게임 판의 크기 (경계면 미포함) #define GBOARD_WIDTH 5 #define GBOARD_HEIGHT 10 /* 게임 판을 그릴 기준이 되는 위치 */ #define GBOARD_ORIGIN_X 4 #define GBOARD_ORIGIN_Y 2 void SetCurrentCursorPos(int x, int y); void DrawGameBoard(void); int main(void) { DrawGameBoard(); return 0; } void SetCurrentCursorPos(int x, int y) // 커서 위치를 이동시키는 함수 { COORD pos = { x, y }; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); } void DrawGameBoard(void) // 실제 게임판의 경계면을 그림 { int x, y; /* 시각적인 부분 처리 */ for (y = 0; y <= GBOARD_HEIGHT; y++) { SetCurrentCursorPos(GBOARD_ORIGIN_X, GBOARD_ORIGIN_Y + y); if (y == GBOARD_HEIGHT) // y좌표가 게임판 높이와 같아지면 ┗ 출력 printf("┗"); else printf("┃"); } for (y = 0; y <= GBOARD_HEIGHT; y++) { SetCurrentCursorPos(GBOARD_ORIGIN_X + (GBOARD_WIDTH + 1) * 2, GBOARD_ORIGIN_Y + y); if (y == GBOARD_HEIGHT) printf("┛"); // y좌표가 게임판 높이와 같아지면 ┛ 출력 else printf("┃"); } for (x = 1; x < GBOARD_WIDTH + 1; x++) { SetCurrentCursorPos(GBOARD_ORIGIN_X + x * 2, GBOARD_ORIGIN_Y + GBOARD_HEIGHT); printf("━"); // 특수 문자는 2칸 잡아먹음에 유의 } SetCurrentCursorPos(0, 0); } | cs |
DrawGameBoard 함수는 for문을 통해 게임판이 어떻게 그려지는지, 종이에 그려가며 분석해보는 것이 좋다.
게임판을 그릴 기준이 되는 GBOARD_ORIGIN_X, GBOARD_ORIGIN_Y 값을 생각해야하며, 이 것은 게임판 밖의 상하좌우 여백(경계면이 아님!)을 결정한다.
Output :
(RemoveCursor 함수를 적용하지 않아 커서가 깜박거림)
게임판의 정보를 담기위한 배열
static int gameBoardInfo[GBOARD_HEIGHT+1][GBOARD_WIDTH+2]={0,};
[GBOARD_HEIGHT+1] // 게임판의 바닥면
[GBOARD_WIDTH+2] // 게임판의 좌우 경계면
충돌 검사 알고리즘
CollisionDetection (int posX, int posY, char blockModel[][4])
위 함수의 첫번째 두번째 인자는 블록의 이동위치(기준점) 정보를 전달받는다. 그리고 이는 배열 index의 x,y값으로 변환된다.
세 번째 인자는 이동 및 회전 할 블록 정보를 전달한다.
이동 및 회전할 블록 정보(4x4)와 게임판 배열을 비교하여, 겹치는지 확인하여 충돌 여무를 확인하는 것이다.
(경계면과 블록은 1, 빈 공간은 0로 정하고 값을 비교)
콘솔 출력창 좌표 정보 -> 게임판 배열 정보 변환
콘솔의 x좌표 값은 2씩 증가한다. (특문이 2칸을 잡아먹으므로 오른쪽으로 이동한다면 2씩 증가)
다만 이 때 단순히 블록의 좌표의 x값을 2로 나눠서만은 안되고 게임 판이 그려지는 시작 위치(GBOARD_ORIGIN_X, Y)도 고려해야한다.
배열의 인덱스 값과 콘솔 위치 정보가 일치하지 않기 때문에 이를 위한 좌표간 변환 공식이 필요하다.
1 2 | int arrCurX=(PosX-GBOARD_ORIGIN_X) / 2; int arrCurY=PosY-GBOARD_ORIGIN_Y; | cs |
또한 충돌 검사를 위해 블럭의 모든 인덱스를 조회해서, 겹치면 0 (FALSE)를 반환, 겹치지 않으면 1 (TRUE)를 반환한다.
1 2 3 4 5 6 7 8 9 10 11 12 | /* 충돌 검사 */ for (x = 0; x<4; x++) { for (y = 0; y<4; y++) { /* Short Circuit Evaluation에 의해 배열 일부만 검사 */ if (blockModel[y][x] == 1 && gameBoard[arrY + y][arrX + x] == 1) return 0; } } return 1; | cs |
Short Circuit Evaluation은 연산의 최소화를 위한 것으로,
위의 if문의 인자에서 첫번째 조건으로 결과값이 판단된다면, 뒤의 연산은 불필요하므로 생략하게 되는데, 이를 Short Circuit Evaluation이라 한다.
이 충돌 검사(DetectCollision) 알고리즘은 블록의 이동과 회전 모두에 적용해야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | BOOL BlockDown(void) { if (!DetectCollision(curPosX, curPosY + 1, blockModel[GetCurrentBlockIdx()])) return FALSE; DeleteBlock(blockModel[GetCurrentBlockIdx()]); curPosY += 1; SetCurrentCursorPos(curPosX, curPosY); ShowBlock(blockModel[GetCurrentBlockIdx()]); return TRUE; } | cs |
if (!DetectCollision(curPosX, curPosY + 1, blockModel[GetCurrentBlockIdx()])) // if문 조건의 두 번째 인자는 이동 위치에 따라 다르게 적용하면 된다.
게임 오버 처리 함수
게임 종료 조건 만족시 TRUE를 반환하는 함수
처음 블록이 등장했을 때 충돌한다면 이는 내릴 공간이 없다는 뜻이므로, 게임오버를 뜻한다.
1 2 3 4 5 6 7 | BOOL IsGameOver(void) { if (!DetectCollision(curPosX, curPosY, blockModel[GetCurrentBlockIdx()])) return TRUE; else return FALSE; } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int main(void) { RemoveCursor(); DrawGameBoard(); while (TRUE) { InitNewBlockPos(START_CURPOS_X, START_CURPOS_Y); // 커서 위치를 [ , ] 으로 초기화 RandomBlock(); if (IsGameOver()) break; // 게임의 종료 조건을 만족한다면 break 문에 의해 바깥쪽 while문을 빠져나가게 된다. ( . . . .) } SetCurrentCursorPos(10, 10); puts("Game Over"); | cs |
굳어진 블록의 표현
굳어진 블록의 정보를 게임판(Board)에 추가하는 함수 // 배열을 1로 채움
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void AddCurrentBlockInfoToBoard(void) { int x, y; int arrCurX; int arrCurY; for (y = 0; y<4; y++) { for (x = 0; x<4; x++) { /* 커서 위치 정보를 배열 index 정보로 변경 */ arrCurX = (curPosX - GBOARD_ORIGIN_X) / 2; arrCurY = curPosY - GBOARD_ORIGIN_Y; if (blockModel[GetCurrentBlockIdx()][y][x] == 1) { gameBoard[arrCurY + y][arrCurX + x] = 1; } } } } | cs |
main 함수안의 흐름
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | while (TRUE) { InitNewBlockPos(START_CURPOS_X, START_CURPOS_Y); // 커서 위치를 [ , ] 으로 초기화 RandomBlock(); if (IsGameOver()) break; while (TRUE) { if (BlockDown() == FALSE) { AddCurrentBlockInfoToBoard(); break; } ( . . . .) | cs |
BlockDown함수가 0 (FALSE)를 반환했다는 것은
더 이상 블록을 아래로 이동시킬 수 없음을 뜻한다.
따라서 현재의 블럭을 굳히고 새로운 블럭을 내리게 한다.
스페이스 키로 블록 바로 내리기
space키에 대한 정의 추가
enum key {LEFT=75, RIGHT=77, UP=72, SPACE=32}; // up=회전, space=블록 즉시 내리기
case문 추가
1 2 | case SPACE: SolidCurrentBlock(); | cs |
1 2 3 4 | void SolidCurrentBlock(void) { while (BlockDown()); } | cs |
'코스웨어 > 16년 스마트컨트롤러' 카테고리의 다른 글
아두이노 부트로더 복구 (0) | 2016.10.27 |
---|---|
2016.03.02 구조체와 응용 (0) | 2016.10.19 |
2016-10-13_조재찬_스터디일지_CPP-상속의 이해 (0) | 2016.10.13 |
2016-10-09_조재찬_ 프로젝트 일지_테트리스 게임 (4) (0) | 2016.10.09 |
2016-10-05_조재찬_ 프로젝트 일지_테트리스 게임 (2) (0) | 2016.10.05 |
2016-10-04_조재찬_ 프로젝트 일지_테트리스 게임 (0) | 2016.10.04 |
2016-09-29_조재찬_스터디일지_CPP-'상속'의 기본 개념, 핸들러 클래스 (0) | 2016.09.30 |
2016-09-29_조재찬_스터디일지_CPP-const, friend, static, mutable 선언 (0) | 2016.09.29 |