코딩 51일차 C/C++

마스터피스·2023년 11월 29일
0

C/ C++ 

목록 보기
23/35
post-thumbnail

콘솔 테트리스 게임 만들기

헤더파일)

  1. 테트리스 기초 함수 헤더파일)

    #ifndef __TETRIS_HELPER_H__
    #define __TETRIS_HELPER_H__
    #include <Windows.h>
    #include <cstdio>
    
    KEY_EVENT_RECORD krec;
    void putStringOnPosition(int x, int y, const char* content) {
        // Move Cursor
        COORD pos;
        pos.X = x;
        pos.Y = y;
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
        // Print string
        printf("%s", content);
    }
    
    void showConsoleCursor(bool showFlag) {
        HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_CURSOR_INFO     cursorInfo;
        GetConsoleCursorInfo(out, &cursorInfo);
        cursorInfo.bVisible = showFlag; // set the cursor visibility
        SetConsoleCursorInfo(out, &cursorInfo);
    }
    
    void drawPosition(int x, int y, bool filled) {
        putStringOnPosition(x * 2, y, filled ? "■" : "□");
    }
    
    bool keyState(char c) {
        short ret;
        switch (c) {
        case 'a': ret = GetKeyState(0x41); break;
        case 'd': ret = GetKeyState(0x44); break;
        case 's': ret = GetKeyState(0x53); break;
        case 'w': ret = GetKeyState(0x57); break;
        default: return false;
        }
        return ret != 0 && ret != 1;
    }
    
    #endif // !__TETRIS_HELPER_H__
  1. 클래스 정의 헤더파일)

    #define _CRT_SECURE_NO_WARNINGS
    
    #include "Tetris.h"
    
    #include <ctime>
    #include <cstring>
    #include <cmath>
    #include <cstdlib>
    #include <cstdio>
    
    #define GRID_WIDTH 9
    #define GRID_HEIGHT 12
    #define USERBLOCK_SIZE 3
    
    // 바깥족이 줄수 안쪽이 행수 = X,Y 좌표가 반대로 돼있따
    int displayData[GRID_HEIGHT][GRID_WIDTH] = { 0, };
    
    class Display {
    public:
        void draw() {
            for (int i = 0; i < GRID_HEIGHT; i++) {
                for (int k = 0; k < GRID_WIDTH; k++) {
                    //drawPositon은 x,y위치에 어떻게 출력을 할것인가 하는 함수
                    //한줄로 작성
                    //drawPosition(k, i, displayData[i][k] == 1);
    
                    //초보적인 방법
                    if (displayData[i][k] == 0) {
                        drawPosition(k, i, false);
                    }
                    else {
                        drawPosition(k, i, true);
                    }
    
                }
            }
        }
    };
    
    class GameEngine {
    public:
        enum class GameState {
            PLAYING, GAMEOVER
        };
        GameState state = GameState::PLAYING;
    
        int gameGridData[GRID_HEIGHT][GRID_WIDTH] = { 0,	};
        int userBlock[USERBLOCK_SIZE][USERBLOCK_SIZE] = { 0, };
    
        //랜덤한 유저블록을 만들기 위한 작업
        int userBlockVarious[8][USERBLOCK_SIZE][USERBLOCK_SIZE] = {
            {
                {0, 1, 0},
                {0, 1, 0},
                {0, 1, 0},
            },
    
            {
                {0, 0, 0},
                {0, 1, 1},
                {0, 1, 1},
    
            },
    
            {
                {0, 0, 0},
                {0, 1, 1},
                {0, 0, 1},
    
            },
            {
                {0, 0, 0},
                {1, 1, 0},
                {0, 1, 1},
            },
            {
                {1, 1, 1},
                {0, 0, 1},
                {0, 0, 0},
            },
            {
                {0, 0, 0},
                {1, 0, 0},
                {1, 1, 1},
            },
            {
                {0, 0, 0},
                {0, 1, 1},
                {1, 1, 0},
            },
            {
                {0, 0, 0},
                {0, 1, 1},
                {0, 1, 0},
            }	
    
        };
    
        int blockX = 0;
        int blockY = 0;
    
        float elapsed = 0.0f;
        float controlCheck = 0.0f;
    
        void init() {
            // 최초에 게임 엔진을 초기화 하는 과정을 맡는다.
            makeUserBlock();
        }
    
        // while 루프에서 매번 불려지는 함수
        void next(float dt, char keyboardInput) {
            if (state == GameState::GAMEOVER) return;
            //elapsed = elapsed + dt; 아래 구문과 똑같다
            elapsed += dt;
            if (elapsed >= 0.5f) {
                if (canGoDown()) {
                    blockY++;
                }
                else {
                    // userblock 을 gameGridData 에 전사한다. (나타낸다)
                    trans();
                    if (gameOverDecision()) state = GameState::GAMEOVER;
                }
    
                //elapsed = elapsed - 0.5f; 아래 구문과 같다
                elapsed -= 0.5f;
            }
            // 속도가 너무 빠르기 때문에 속도가 0.1초 이상이면 움직이게 만든것
            controlCheck = controlCheck + dt;
    
            if (keyboardInput == 'a' && canGoLeft() && controlCheck > 0.1) {
                blockX--;
                controlCheck = 0.0f;
            }
    
            if (keyboardInput == 'd' && canGoRight() && controlCheck > 0.1) {
                blockX++;
                controlCheck = 0.0f;
            }
            if (keyboardInput == 's' && canGoDown() && controlCheck > 0.1) {
                blockY++;
                controlCheck = 0.0f;
            }
            if (keyboardInput == 'w' && canRotate() && controlCheck > 10.0) {
                controlCheck = 2.0f;
            }
        }
        // 블록이 아래로 내려갈 수 있냐
        bool canGoDown() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    if (userBlock[i][k] == 1 && i + blockY + 1 >= GRID_HEIGHT) {
                        return false;
                    }
    
                    if (userBlock[i][k] == 1 && gameGridData[i + blockY + 1][k + blockX] == 1) {
                        return false;
                    }
    
                }
            }
            return true;
        }
    
        //블록이 왼쪽으로 갈 수 있냐
        bool canGoLeft() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    //한줄짜리 if 절은 {} 생략 후 바로 리턴을 사용해도된다.
                    if (userBlock[i][k] == 1 && k + blockX - 1 < 0)
                        return false;
    
                    if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX - 1] == 1) 
                        return false;
    
                }
            }
            return true;
    
        }
    
        //블록이 오른쪽으로 갈 수 잇냐
        bool canGoRight() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    if (userBlock[i][k] == 1 && k + blockX + 1 > GRID_WIDTH - 1) {
                        return false;
                    }
                    if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX + 1] == 1)
                        return false;
                }
            }
            return true;
        }
    
        //라인이 가득 찼는지 확인하는 변수
        bool isLineFilled(int y) {
            for (int i = 0; i < GRID_WIDTH; i++) {
                if (gameGridData[y][i] == 0) return false;
            }
            return true;
        }
    
        // 라인이 가득 찼으면 y 좌표를 기준으로 한칸씩 아래로 내림
        void eraseLine(int y) {
            for (int i = 0; i < GRID_WIDTH; i++) {
                gameGridData[y][i] = 0;
            }
        }
    
        //라인이 지우진후 당겨오는 함수
        void drop(int y) {
            for (int i = y; i >= 0; i--) {
                for (int k = 0; k < GRID_WIDTH; k++) {
                    gameGridData[i][k] = i - 1 < 0 ? 0 : gameGridData[i - 1][k];
                }
            }
        }
    
        // userBlock 을 gameGrid에 전사하는 함수
        void trans() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    // makeDisplayData 처럼 최적화 가능
                    //gameGridData[i + blockY][k + blockX] = userBlock[i][k] == 1 ? userBlock[i][k] : gameGridData[i + blockY][k + blockX];
                    int X = k + blockX;
                    int Y = i + blockY;
                    gameGridData[Y][X] = userBlock[i][k] | gameGridData[Y][X];
                }
            }
    
            // TODO: 한 줄이 가득차 있는지 확인
            for (int i = 0; i < GRID_HEIGHT; i++) {
                if (isLineFilled(i)) {
                    eraseLine(i);
                    drop(i);
                }
            }
    
            // 새로운 블록 생성
            makeUserBlock();
        }
    
        //게임 오버인지 판단하는 변수
        bool gameOverDecision() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    if (userBlock[i][k] == 1 && gameGridData[i + blockY][k + blockX] == 1)
                        return true;
                }
            }
            return false;
    
        }
        void makeUserBlock() {
            blockX = GRID_WIDTH / 2 - USERBLOCK_SIZE / 2;
            blockY = 0;
    
            int various = rand() % 7;
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    userBlock[i][k] = userBlockVarious[various][i][k];
                }
            }
            // TODO : 랜덤을 통해서 새로운 블록을 만든다.
        }
    
        //블록 회전 코드
        void rotate() {
            int tmp[USERBLOCK_SIZE][USERBLOCK_SIZE] = {0,};
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    tmp[i][k] = userBlock[USERBLOCK_SIZE - k - 1][i];
                }
            }
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    userBlock[i][k] = tmp[i][k];
                }
            }
    
        }
    
        bool canRotate() {
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    if (k < 0 || k >= GRID_HEIGHT) return false;
                    else if (USERBLOCK_SIZE - 1 - i || USERBLOCK_SIZE - 1 - i >= GRID_WIDTH) return false;
                    else if (gameGridData[k + blockY][USERBLOCK_SIZE - 1 - i + blockX] == 1) return false;
    
                }
            }
            return true;
        }
    
        // 실제 게임 데이터를 화면에 출력할 수 있는 데이터로 바꿔준다.
        void makeDisplayData() {
            for (int i = 0; i < GRID_HEIGHT; i++) {
                for (int k = 0; k < GRID_WIDTH; k++) {
                    displayData[i][k] = gameGridData[i][k];
                }
            }
    
            for (int i = 0; i < USERBLOCK_SIZE; i++) {
                for (int k = 0; k < USERBLOCK_SIZE; k++) {
                    if (i + blockY < 0 || i + blockY > GRID_HEIGHT) {
                        //DO NOTHING
    
                    }
                    else if (k + blockX < 0 || k + blockX > GRID_WIDTH) {
                        //DO NOTHING
                    }
                    else {
                        // TO DO:
                        // 아래코드를 줄이는 방법
                        // 줄이는 코드가 성능이 더 좋음
                        // 동일한 역할을 하는 함수 + 연산자를 함수화시켜서 줄 수를 줄임
                        //displayData[i + blockY][k + blockX] = userBlock[i][k] == 1 ? userBlock[i][k] : displayData[i + blockY][k + blockX];
                        int _x = k + blockX;
                        int _y = i + blockY;
                        displayData[_y][_x] = userBlock[i][k] | displayData[_y][_x];
                    }
                }
            }
        }
    
    };

소스파일)

#include "Header.h"

#define INTERVAL 1.0/60.0



int main() {

    srand(time(0));
    float prev = (float)clock() / CLOCKS_PER_SEC;
    Display* display = new Display();
    GameEngine* gameEngine = new GameEngine();
    gameEngine->init();
    //하단에 커서 지우는 함수 테트리스 헤더파일에 있음
    showConsoleCursor(false);


    while (true) {
        float curr = (float)clock() / CLOCKS_PER_SEC;
        float dt = curr - prev;

        if (dt < INTERVAL) continue;
        prev = curr;

        bool left = keyState('a');
        bool right = keyState('d');
        bool down = keyState('s');
        bool rotate = keyState('w');
        if (left) {
            // 왼쪽으로 블록 이동
            gameEngine->next(dt, 'a');
        }
        else if (right) {
            gameEngine->next(dt, 'd');
        }
        else if (down) {
            gameEngine->next(dt, 's');
        }
        else if (rotate) {
            gameEngine->rotate();
        }

        else {
            // 그냥 블록 떨어짐
            gameEngine->next(dt, 0);
        }


        // 화면출력
        gameEngine->makeDisplayData();
        display->draw();
        // 게임 상태 판별
        if (gameEngine->state == GameEngine::GameState::GAMEOVER) {
            break;
        }

    }

    return 0;

}
profile
코딩 일지

0개의 댓글