Programming

C++ 테트리스 게임 제작

LittleDev0617 2022. 12. 31. 13:04

때는 2022.12.29일 목요일

바킹독 선생님의 실전 알고리즘 강의 영상을 보던 중

C++ 공부 중인 해온이의 대결 신청

나도 C++ 익힐 겸 하기로 했다.

 

오랜만에 하루종일 앉아서 코드만 짠 것 같다.

이런 기분을 중학생 때 게임 만들 때 느꼈었는데, 고등학교 와서는 못 느꼈다.

노래 반복재생 - 코드짜기 - 버그 발생 - 노래 멈추고 디버깅 - 다시 노래 재생

 

시간은 대략 29일 하루종일 + 30일 반나절 정도 걸렸다.

 

 

 

 

클래스는 다음과 같이 나눴다

 

Tetromino / Tetris / TetrisUI / Display

TetrisUI와 Display는 static method로 구성되어있고, TetrisUI는 미노 정보만 받아서 Display 내 함수를 호출하는 역할,

Display 는 테트리스와 분리되어서 화면 출력하는 역할을 맡도록 했다.

 

Tetromino 클래스는 각 미노의 좌표들을 가지고 있다. 각 미노의 중심 블록으로부터 상대적인 나머지 블록들의 대한 좌표를 담은 points 벡터를 계속 불러와 화면에 그려줄 수 있게 하였다.

 

꽤 오랜 고민을 했던 부분이 미노의 회전, 움직임, 드랍에대한 처리였는데, 

 

1. 입력 

2. 회전/움직임/드랍 처리 (실제로 움직이지 않고 움직였을 때의 정보 처리) 

3. 검사 

4. Ok or No

 

2번에서 Rotate 함수를 호출하는데 실제로 Rotate 되어선 안된다.

근데 또 4번에서 Ok 되었을 때 Rotate 함수를 호출하지 않고 setRotation으로 회전시켜버린다.

이부분은 개인적으로 수정해야할 부분인 것 같다.

void Tetris::StartGame() {
    while (true) {
        Init();
        TetrisUI::DrawMap(width, height);
        int fallTick = 0;
        while (true) {
            if (!isFalling) {
                minoIndex = ChooseMino();
                TetrisUI::NextUpdate(minos, minoQueue);
                UpdateMino({ 0,0 }, 0);
                isFalling = true;
            }
            Command cmd = EXIT;
            Point dp = { 0,0 };
            bool isMoved = false; bool isRotated = false; bool isDropped = false; int rot = minos[minoIndex]->getRotation();

            if (fallTick == 20) {
                dp.y += 1;
                fallTick = 0;
                if (!IsPlacable(dp,rot)) {
                    dp.y -= 1;
                    cmd = DROP;
                }
                Tetris::UpdateMino(dp, rot);
            }
            if(cmd == EXIT)
                cmd = Input();
            switch (cmd) {
            case LEFT:
                dp.x -= 1; isMoved = true;
                break;
            case RIGHT:
                dp.x += 1; isMoved = true;
                break;
            case DOWN:
                dp.y += 1; isMoved = true;
                break;
            case R_ROTATE:
            case L_ROTATE:
                if (minoIndex == 6) break;
                if (minoIndex >= 0 && minoIndex <= 4) {
                    if (rot == 1)
                        dp.x += 5 - cmd;
                    else if (rot == 0)
                        dp.x += cmd - 5;
                }
                rot = minos[minoIndex]->Rotate(cmd | FAKE_ROTATE); isRotated = true;
                break;
            case DROP:
                dp.y = CalculateDropPos().y - minos[minoIndex]->centerPos.y; isDropped = true;
                break;
            case HOLD:
                if (canHold) {
                    UpdateMino(dp, rot, UPDATE_ONLY_ERASE);
                    minos[minoIndex]->centerPos = DEFAULT_CENTER;

                    if (holdIndex != -1) {
                        int tmp = minoIndex;
                        minoIndex = holdIndex;
                        holdIndex = tmp;
                    }
                    else {
                        holdIndex = minoIndex;
                        minoIndex = ChooseMino();
                        TetrisUI::NextUpdate(minos, minoQueue);
                    }

                    TetrisUI::HoldUpdate(minos[holdIndex]);
                    //isFalling = false;
                    isMoved = true;
                    canHold = false;
                }
                break;
            default:
                break;
            }
            if (isMoved || isRotated) {
                if (!IsPlacable(dp, rot))
                    goto SKIP;

                Tetris::UpdateMino(dp, rot);
            }
            if (isDropped) {
                if (CheckGameOver(dp))
                    break;
                Tetris::UpdateMino(dp, rot);
                Tetris::LineClear();    // clear line if line was filled
                TetrisUI::PrintScore(score);
                minos[minoIndex]->Init();
                isFalling = false;
                canHold = true;

            }
        SKIP:
            fallTick += 1;
            Sleep(tickSpeed);
        }

        TetrisUI::RetryMenu(score);
        if (_getch() == 'R')
            break;
    }
}

핵심은 위에서 말했듯 입력 받고 변화하는 정도를 저장한다.( move면 dp 변경, rotate면 rotation 저장)

이후 isMoved 거나 isRotated 거나 isDropped 이면 검사를 하는데, IsPlacable 함수에 dp 와 rot 정보를 넘겨준다.

placable 하면 UpdateMino 함수를 호출하여 진짜로 움직이게 한다.

 

UpdateMino 에서는 현재 미노를 지우고, 움직이거나, 회전하거나, 드랍한 후에 다시 그린다.

 

 

여기까지.

 

 

소량의 업데이트 전.