코딩 53일차 C/C++

마스터피스·2023년 12월 5일
0

C/ C++ 

목록 보기
25/35
post-thumbnail

여러개의 헤더와 소스

1) 암묵적인 룰

-프로그래머들끼리는 암묵적인 룰이 있습니다. 이 룰을 지키지 않는다고 벌금이 있거나 프로그래밍을 하지 못하거나 하는 것은 아니지만 이 부분을 지키지 않으면 프로그래머 사이에서 왕따가 될 수 있습니다. 왕따 이전에 이 규칙을 무시하고 자신만의 방법으로 코드를 만들다간 다른 프로그래머가 만든 소스코드도 읽기 힘들어지게 됩니다.

2) 클래스의 멤버함수 분리

  • include를 통해 중복되는 내용이 들어가기 때문에 오류가 발생한다. 따라서 두가지 방법을 이용해 해결한다.
    #pragma once 모든 헤더파일 가장 위에 코드를 사용 // 헤더를 두번 넣지 않는다.
    #ifndef // 표준 코드 매크로
    ex)
    #ifndef ANIAML_H // 헤더파일 젤 처음에 ANIMAL_H 는 본인 마음대로 설정한다.
    #define ANIMAL_H // 헤더파일 두번째줄에
    #endif // 헤더파일 마지막줄에

  • 클래스의 전방 선언 : 헤더파일에서 include 를 하지 않고 소스파일에서 include를 하고 싶을 때 헤더파일에 해당 클래스를 위의 define 아래에 class 클래스명 을 전방 선언 해주면 클래스가 어디있는지 모르겠지만 다른 cpp 파일이나 헤더파일에서 실행중 찾아서 실행하는 것을 의미. => 함수끼리 꼬리를 무는 경우 뒷줄에 있는 클래스가 윗줄에 있는 클래스를 모르기 때문에 에러가 나지만 전방선언을 한다면 에러가 나지 않음. 이럴 경우 사용한다.

  • 우리가 일반적으로 함수를 만들 때에는 선언과 정의로 분리를 했습니다.우리는 여지껏 클래스에 대해서는 선언과 정의로 분리하지 않았는데요. 이 클래스에서 멤버함수의 선언과 정의를 분리하는 방법을 알아보겠습니다. 또한 이 선언과 정의를 나누는 이유에 대해서 한번 살펴보겠습니다.

2-2) 헤더파일 중복 include 가드

  • 여러 개의 헤더파일과 여러 개의 소스파일을 이용해 프로그래밍을 하다 보면 헤더파일 중복include 에 대한 문제가 생기게 됩니다.

이를 해결할 수 있는 방법을 알아보게 됩니다.

연습문제)

SOL)

tip) 헤더파일 함수를 소스파일로 옮길때 잘라낸 다음 Ctrl + . 누르면 자동으로 소스파일을 생성해준다.

Display 헤더)

#ifndef __DISPLAY_H__
#define __DISPLAY_H__

#include "Header.h"

class Display {
public:
    void draw();
};



#endif // !__DISPLAY_H__

Display 소스)

#include "Display.h"

void Display::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);
            }

        }
    }
}

GameEngine 헤더)

#ifndef __GAME_ENGINE_H__
#define __GAME_ENGINE_H__

#include "Header.h"

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();

    // while 루프에서 매번 불려지는 함수
    void next(float dt, char keyboardInput);
    // 블록이 아래로 내려갈 수 있냐
    bool canGoDown();

    //블록이 왼쪽으로 갈 수 있냐
    bool canGoLeft();

    //블록이 오른쪽으로 갈 수 잇냐
    bool canGoRight();

    //라인이 가득 찼는지 확인하는 변수
    bool isLineFilled(int y);

    // 라인이 가득 찼으면 y 좌표를 기준으로 한칸씩 아래로 내림
    void eraseLine(int y);

    //라인이 지우진후 당겨오는 함수
    void drop(int y);

    // userBlock 을 gameGrid에 전사하는 함수
    void trans();

    //게임 오버인지 판단하는 변수
    bool gameOverDecision();



    void makeUserBlock();

    //블록 회전 코드
    void rotate();

    bool canRotate();



    // 실제 게임 데이터를 화면에 출력할 수 있는 데이터로 바꿔준다.
    void makeDisplayData();
};

#endif

GameEngine 소스)

#include "GameEngine.h"


void GameEngine::init() {
    // 최초에 게임 엔진을 초기화 하는 과정을 맡는다.
    makeUserBlock();
}

void GameEngine::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 GameEngine::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 GameEngine::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 GameEngine::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 GameEngine::isLineFilled(int y) {
    for (int i = 0; i < GRID_WIDTH; i++) {
        if (gameGridData[y][i] == 0) return false;
    }
    return true;
}

void GameEngine::eraseLine(int y) {
    for (int i = 0; i < GRID_WIDTH; i++) {
        gameGridData[y][i] = 0;
    }
}

void GameEngine::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];
        }
    }
}

void GameEngine::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 GameEngine::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 GameEngine::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 GameEngine::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 GameEngine::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 GameEngine::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];
            }
        }
    }
}

Header 헤더)

#ifndef __HEADER_H__
#define __HEADER_H__


#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 좌표가 반대로 돼있따
extern int displayData[GRID_HEIGHT][GRID_WIDTH];


#endif // !__HEADER_H__

테트리스 헤더)

#ifndef __TETRIS_HELPER_H__
#define __TETRIS_HELPER_H__




#include <Windows.h>
#include <cstdio>



void putStringOnPosition(int x, int y, const char* content);

void showConsoleCursor(bool showFlag);

void drawPosition(int x, int y, bool filled);

bool keyState(char c);

#endif // !__TETRIS_HELPER_H__

테트리스 소스)

#include "Tetris.h"

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;
}

메인함수 소스)

#include "Header.h"
#include "Display.h"
#include "GameEngine.h"

#define INTERVAL 1.0/60.0

int displayData[GRID_HEIGHT][GRID_WIDTH] = { 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개의 댓글