본문 바로가기
코스웨어/16년 스마트컨트롤러

2016-10-09_조재찬_ 프로젝트 일지_테트리스 게임 (4)

by 알 수 없는 사용자 2016. 10. 9.
728x90
반응형

<테트리스 게임> 프로젝트

- 블록의 소멸과 점수와 레벨 추가


작업 환경 : 윈도우 10, Visual Studio, C++ (Win32 Console)



블록의 소멸 (가득찬 줄의 블록 소멸)


RemoveFillUpLine 함수는 가득찬 줄을 제거하는 함수로, BlockDown함수 내부에서 호출된다.  이 블록소멸 함수 호출전에 반드시 AddCurrentBlockInfoToBoard 함수 호출이 필요하다.


AddCurrentBlockInfoToBoard 함수는, 이전 단계에서는 main함수내에서 블럭을 아래로 내리는데 실패했을 때에 블럭을 굳히기 위해 호출되던 함수였다. 이 함수를 통해 커서 위치 정보를 배열 index 정보로 변경해 굳어진 블록의 정보를 게임판에 추가할 수 있었다.


- BlockDown 함수 내부

1
2
3
4
5
6
7
8
9
10
BOOL BlockDown(void)
{
    if (!DetectCollision(curPosX, curPosY + 1, blockModel[GetCurrentBlockIdx()]))
    {
        /* 행 단위로 채워진 블록 정보 검사*/
        AddCurrentBlockInfoToBoard();
        RemoveFillUpLine();
        return FALSE;
    }
( . . . .)
cs



가득찬 줄을 제거하는 RemoveFillUpLine 함수 
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
void RemoveFillUpLine(void)
{
    int x, y;
    int line;
 
    for (y = GBOARD_HEIGHT - 1; y>0; y--)    // 게임판 배열의 경계를 제외하고 순회
    {
        for (x = 1; x < GBOARD_WIDTH + 1; x++
        {
            if (gameBoard[y][x] == 0// 채워지지 않은 부분(0)이 있으면 빠져나감
                break;
        }
 
        if (x == (GBOARD_WIDTH + 1))  // 한줄이 다 채워졌다면
        {
            for (line = 0; y - line>0; line++)    
            {
                memmove(
                    &gameBoard[y - line][1], &gameBoard[(y - line) - 1][1],
                    GBOARD_WIDTH * sizeof(int)
                    );
            } 
            y++// 배열 정보가 아래로 한 칸씩 이동했으므로
        }
    } 
    DrawSolidBlocks();
cs


먼저 게임판의 경계를 제외한 모든 배열을 for문을 통해 순회하며 검사하고, 한줄이 블럭으로 다 채워져있는지(1)를 비교연산한다.

1이 아닌 값이 있다는 건 한줄이 다 채워지지 않았음을 의미한다. 


for문안에서 증가한 x값이 WIDTH와 같아졌다는 것은 한줄이 모두 채워져있음을 의미한다. 
이 경우 채워진 라인 이외의 블럭들을 한칸씩 내리는 역할을 memmove함수가 하게 된다. (행단위 복사)


memmove 함수에 대한 레퍼런스 (출처 : http://www.soen.kr/lecture/ccpp/reference/)
1
2
void *memmove(void *dest, const void*src, size_t n);
// dest: 복사대상 메모리, src:복사원 메모리, n: 복사할 길이
cs

이제 블럭으로 채워진 줄의 정보는 지워지고 한칸씩 아래로 이동한 상태이다. 
이 때 for문을 통해 맨 아래 줄을 다시 검사하기 위해선 y값이 줄어들어선 안되기 때문에 23행에서 y값을 다시 증가시키고 있다. 

즉, 맨 아래줄에 채워진 블록이 있는 이상 전체 for문은 계속된다.
이러한 데이터의 복사를 통한 이동은 아래 그림과 같이, 가득찬 줄의 블럭이 삭제되고 블럭이 아래로 이동되는 것처럼 보이게 한다.

[ 첫번째 가득찬 줄의 삭제 과정 ]



[ 두번째 가득찬 줄의 삭제 과정 ]



이것으로 끝나선 안되고 게임판 배열의 정보를 콘솔에 그려내기 위해서 26행에서 DrawSolidBlocks함수를 호출하고 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void DrawSolidBlocks(void)
{
    int x, y;
    int cursX, cursY;
 
    for (y = 0; y<GBOARD_HEIGHT; y++)
    {
        for (x = 1; x<GBOARD_WIDTH + 1; x++)
        {
            cursX = x * 2 + GBOARD_ORIGIN_X;
            cursY = y + GBOARD_ORIGIN_Y;
            SetCurrentCursorPos(cursX, cursY);
 
            if (gameBoard[y][x] == 1)
            {
                cout<<"■";
            }
            else
            {
                cout << "  ";
            }
        }
    }
}
cs
위 함수는 배열 gameBoard의 변경된 데이터를 기반으로, 게임판의 블록들을 다시 그리기위한 함수이다.



블록의 soft drop 

아래 방향키를 입력하였을 때, 블록을 조금 더 빨리 내릴 수 있도록 한다. 이것은 space를 통해 한번에 바닥에 닿는 hard drop과는 다르다.

1
2
3
4
5
6
7
8
9
10
11
void BlockMoveDown(void)    // 아래 방향키 입력을 통한 soft drop (space bar입력은 hard drop)
{
    if (!DetectCollision(curPosX, curPosY + 1, blockModel[GetCurrentBlockIdx()]))
        return;
 
    DeleteBlock(blockModel[GetCurrentBlockIdx()]);
    curPosY += 1;    
 
    SetCurrentCursorPos(curPosX, curPosY);
    ShowBlock(blockModel[GetCurrentBlockIdx()]);
}
cs

물론 아래 방향키 버튼을 정의해주고, 키 입력을 처리하는 KeyInput 함수에서 관련 코드도 추가해줘야 한다.

1
enum key { LEFT = 75, RIGHT = 77, UP = 72, SPACE = 32DOWN = 80 }; // up=회전, down= soft drop, space=hard drop
cs

 KeyInput 함수 내부에 다음 코드 삽입
1
2
3
case DOWN:
                BlockMoveDown();
                break;
cs


점수와 게임레벨 추가

1
2
3
4
5
#define LEVER_UP_SCORE  2000   // 게임 레벨을 올리기위한 필요 점수
#define GameSpeed   2        // 값이 클수록 속도가 많이 증가
 
static int GameLevel = 1;    // 레벨 1로 시작
static int GameScore = 0;    // 점수 0으로 시작
cs

이와 같이 매크로 상수와 변수를 선언한다.



GameLevelUp 함수는 게임 레벨을 올리기위한 함수이다.

1
2
3
4
5
6
7
8
void GameLevelUp(void)
{
    if (GameLevel > 9)    // 레벨 10으로 제한
        GameLevel = 9;
 
    GameLevel++;
    increaseGameSpeed(GameSpeed);    
}
cs

3-4행 : 게임 레벨을 10으로 제한하기 위한 코드

6행 : 게임레벨의 증가

7행에서 호출하는 increaseGameSpeed 함수는 다음과 같다.



increaseGameSpeed 함수는 게임의 속도를 증가시키기 위한 함수이다.  

1
2
3
4
void increaseGameSpeed(int addSpeed)
{
    keyDelayRate += addSpeed;
}
cs

인자로 전달된 값이 KeyDelayRate값에 변화를 주며, 이 값이 커질수록 게임속도가 빨라지게 된다.



다음은 게임 점수를 추가하는 AddGameScore이다. 인자로 전달된 값이 점수에 추가된다.

점수의 증가외에도 위에서 define한 LEVER_UP_SCORE값을 만족하면 레벨이 상승한다.

이 함수는 블럭이 채워졌을 때 호출되어야 하므로 RemoveFillUpLine 함수내부에서 호출된다.

1
2
3
4
5
6
7
8
void AddGameScore(int score) // 게임 점수 추가
{
    GameScore += score;
 
    /* 레벨 상승 확인 */
    if (GameScore >= GameLevel*LEVER_UP_SCORE)
        GameLevelUp();
}
cs


위 함수는 게임 진행시 레벨과 점수를 출력하기 위한 것으로 블럭의 소멸시 값이 변화하므로, 마찬가지로 RemoveFillUpLine함수내부에서 호출된다.
1
2
3
4
5
6
7
8
void ShowCurrentScoreAndLevel(void)    // 점수와 레벨 정보 출력
{
    SetCurrentCursorPos(304);
    printf("§ 현재 레벨: %d    §", GameLevel);
 
    SetCurrentCursorPos(307);
    printf("§ 현재 점수: %d    §", GameScore);
}
cs



RemoveFillUpLine함수에서 AddGameScore와 ShowCurrentScoreAndLevel함수를 호출하는 것을 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void RemoveFillUpLine(void)
{
    int x, y;
    int line;
 
    for (y = GBOARD_HEIGHT - 1; y>0; y--)    // 바닥행부터 배열 순회
    {
        ( 생략 . . . .)
 
            y++// 배열 정보가 아래로 한 칸씩 이동했으므로
            AddGameScore(1000);
            ShowCurrentScoreAndLevel();
        }
    }
    DrawSolidBlocks();
}
cs



다음은 수정된 KeyInput 함수이다.

KeyDelayRate는 특정값으로 값을 초기화해 게임 시작시의 속도를 결정한다. 또한 increaseGameSpeed함수에 의해 레벨이 증가할수록 값이 작아지게 되어, 블럭이 빨리 내려오게 된다.


20-21행에서는 속도가 너무 빨라지지 않도록 KeyDelayRate값을 고정하고 있다.

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
static int keyDelayRate;   // 값이 클수록 속도 증가
 
int KeyInput(void)    // space bar입력시 1 반환, main함수에서 조건만족시 새로운 블록 생성
{
    int key;
    for (int i = 0; i < KEY_SENSITIVE; i++// #define KEY_SENSITIVE 100 (키 입력감도)
    {
        if (_kbhit() != 0)
        {
            key = _getch();
 
            switch (key)
            {
            case LEFT:
                BlockMoveLeft();
                break;
            ( 생략 . . . .) 
            }
        }        
        if (keyDelayRate < 30)    // 특정 속도 이상 증가하지않도록 고정
            keyDelayRate = 30;        
            
        Sleep(delay);
    }
    return 0;
}
cs



추가할 기능 

: 블럭 소멸시 애니메이션, 사운드, next block, 게임 정지, ↓입력시 빠른 하강, 아이템

 한꺼번에 많은 줄을 없앨수록 높은 점수 증가, 경과 시간 표시





728x90