소스파일 - 오브젝트 파일
소스 파일은 컴파일이 될 때 소스파일 하나하나가
오브젝트 파일을 생성하게 된다.
(.cpp -> .obj)
맥이나 리눅스의 경우 .o 파일이 된다.
c++의 컴파일은 이렇게 만들어진 obj 파일을 묶어서
하나의 실행파일을 만들어내게 된다.
obj파일은 cpp파일이 컴파일 되면서 나오는 중간 파일
그래서 obj파일에 문제가 있다면 cpp파일에 문제가 있다는 거
그런데 이것을 헤더파일에서 찾으려고 하면 안된다.
왜냐하면 헤더파일에 대한 에러는 절대 나오지 않는다.
c++는 cpp파일을 기준으로 컴파일을 하기 때문
cpp 파일이 모든 include를 다 한 다음에 그 cpp파일을 기준으로 obj 파일을 만들어내게 된다
이 각각의 헤더파일들을 include 한 cpp파일에서 obj 파일들이 만들어낸다.
obj 파일 이라는 것은 cpp파일과 헤더파일을 include로 다 합친 그것을
실제로 실행 파일로 만들기 직전에 만드는 실행 파일, 중간 단계의 파일
우리는 이렇게 include되어 있고 그렇게 만들어진 obj 파일들을 이용해 한 번에 실행 파일을 만들어내게 된다.
obj 파일들이 한꺼번에 뭉쳐져서 실제로 exe 파일이 만들어지게 된다.
Header.h Display.h GameEngine.h |
TetrisHelper.h | Header.h Display.h |
Header.h GameEngine.h |
Source.obj | TetrisHelper.obj | Display.obj | GameEngine.obj |
이 때 중요한 것은 이 헤더파일들에 선언되어 있는 변수, 함수들을 obj 파일들을 만드는 과정에서 역으로 가져온다.
함수의 선언부이 있는데 이 실제 함수의 몸체는 .cpp 파일에 지금 없다.
그럼 언제 선언부와 몸체가 합쳐지냐면
obj 파일이 전부 다 만들어 지고 나서 그 obj 파일이 한 번에 exe 파일이 만들어지는 순간
함수들을 전부 다 선언해서 다른 obj 파일을 검사하면서 함수의 원형들을 하나하나 찾아 참조하고 연결한다
그 연결하는 과정이 링크(Link)
동일한 이름을 활용한 변수를 만들었을 때 에러가 난다는 것을 알 수 있다.
변수가 만약에 진짜 완전한 선언이라면 동일하게 두 개가 있어도 절대로 에러가 나지않아야한다.
선언 (declaration)은 지금 이것을 어디 있는지는 모르겠는데 아무튼 있어라고 말하는게 선언이고 여러 번 돼도 상관 없다
함수의 정의 (definition)인 몸체는 꼭 하나가 있어야 됨
대신 변수는 선언이라고 하는 게 편해서 그런지 몰라도 변수를 선언한다고 하는데
정확한 것은 선언과 정의가 동시에 들어간다.
<Source Files>
Source.cpp
#include "Headers.hpp" #include "Displays.hpp" #include "GameEngine.hpp" #define INTERVAL 1.0/60.0 int displayData[GRID_HEIGHT][GRID_HEIGHT] = { 0, }; int main() { srand(time(0)); float prev = (float)clock() / CLOCKS_PER_SEC; // clock이라는 함수가 프로그램이 시작된 순간부터 프로그램에 clock을 계속 구하고 있다. // CLOCKS_PER_SEC은 보통 1000이기 때문에 나누게 되면 프로그램이 시작된 순간부터 몇 초가 지났는지 나오게 된다. 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; // 정해준 INTERVAL(1/60초) 보다 작으면 while 루프 처음으로 돌아감 prev = curr; // 크다면 while루프를 지속적으로 진행해서 prev를 curr로 바꿔준다. //printf("%.3f", curr); // curr을 출력 1/60초마다 printf 구문이 실행되고 있다. 테스트 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; // GAMEOVER라면 break로 무한루프 탈출하고 게임 종료 } } return 0; } |
Displays.cpp
#include "Displays.hpp" void Display::draw() { for (int i = 0; i < GRID_HEIGHT; i++){ for (int k = 0; k < GRID_WIDTH; k++){ //drawPosition(k, i, displayData[i][k] == 1); if (displayData[i][k] == 0) { drawPosition(k, i, false); } else { drawPosition(k, i, true); } } } } |
GameEngine.cpp
#include "GameEngine.hpp" void GameEngine::init() { // 최초의 게임 엔진을 초기화 하는 과정을 맡아 합니다. makeUserBlock(); } void GameEngine::next(float dt, char keyboardInput) { //blockY++; // 테트리스는 블록이 계속 떨어지니 blockY 값이 1씩 늘어난다. if (state == GameState::GAMEOVER) return; // GAMEOVER이라면 next 더이상 실행하지않음 elapsed += dt; // elapsed에 dt를 더해서 넣는다. if (elapsed >= 0.5f) { if (canGoDown()) { blockY++; } else { // userblock을 gameGridData에 전사하면 된다. trans(); if (gameOverDecision()) state == GameState::GAMEOVER; // gameOverDecision을 내리고 이게 true라면 GameState를 GAMEOVER로 } elapsed -= 0.5f; // // elapsed에 dt를 빼서 넣는다. } 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++; // blockY++를 한 번 더 빠르게 해준다 controlCheck = 0.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 (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]){ return false; } } } return true; } bool GameEngine::isLineFilled(int y) { for (int i = 0; i < GRID_WIDTH; i++) { if (gameGridData[y][i] == 0) return false; // 한 칸이라도 0이 되어있으면 안된다 } 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]; // i-1이 배열 범위를 벗어나기 전에 핸들링 해줘야한다. 쓰레기값 있으니까 // i-1이 0보다 작다면 0이 아니라면 배열번호를 넘어간 것, 배열번호를 넘어갔다면 0으로 drop 그게 아니라면 위쪽에 있는 값으로 drop } } } void GameEngine::trans() { // 현재 데이터를 gameGridData에 전사를 해야됨 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]; } } // TODO: 한 줄이 가득 차 있는지 확인 for (int i = 0; i < GRID_HEIGHT; i++) { if (isLineFilled(i)) { eraseLine(i); // i번째 칸의 라인이 채워져 있으면 eraseLine 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() % 3; // various에 3가지 중 랜덤 값을 가져오기 for(int i = 0; i < USERBLOCK_SIZE; i++) { for(int k = 0; k < USERBLOCK_SIZE; k++){ userBlock[i][k] = userBlockVarious[various][i][k]; // 랜덤한 배열의 i,k번째를 한꺼번에 for 루프로 복제 } } // TODO: 랜덤을 통해서 새로운 블록을 만든다. } void GameEngine::rotate() { // TODO: 회전을 구현해 보세요. } void GameEngine::makeDisplayData() { // gameGridData와 userBlock의 위치로 만들어지게 된다. for (int i = 0; i < GRID_HEIGHT; i++){ for (int k = 0; k < GRID_WIDTH; k++){ displayData[i][k] = gameGridData[i][k]; // gameGridData만 넣어 출력 } } for (int i = 0; i < USERBLOCK_SIZE; i++){ // i가 y축 줄 수를 의미 for (int k = 0; k < USERBLOCK_SIZE; k++){ // userBlock의 위치를 계산해서 넣어주기 if (i + blockY < 0 || i + blockY > GRID_HEIGHT) { // DO NOTHING } else if (k + blockX < 0 || k + blockX > GRID_WIDTH) { // DO NOTHING } else { // TODO: // 동일한 역할을 하는 코드 //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]; } } } } |
TetrisHelper.cpp
#include "TetrisHelper.hpp" bool consoleInitialized = false; int lastX = 0; int lastY = 0; void putStringOnPosition(int x, int y, const char* content) { char buff[100] = { 0, }; sprintf(buff, "\033[%d;%dH", y, x); printf("%s", buff); printf("%s", content); lastX = x; lastY = y; } void showConsoleCursor(bool showFlag) { } void drawPosition(int x, int y, bool filled) { if(!consoleInitialized) { consoleInitialized = true; printf("\033[2J"); } putStringOnPosition(x+1, y, filled ? "■" : "□"); } Boolean isPressed( unsigned short inKeyCode ){ unsigned char keyMap[16]; GetKeys((BigEndianUInt32*) &keyMap); putStringOnPosition(lastX, lastY, " "); // remove arrow input character return (0 != ((keyMap[ inKeyCode >> 3] >> (inKeyCode & 7)) & 1)); } bool keyState(char c) { bool ret; // keycode from http://web.archive.org/web/20100501161453/http://www.classicteck.com/rbarticles/mackeyboard.php switch (c) { case 'a': ret = isPressed(123); break; case 'd': ret = isPressed(124); break; case 's': ret = isPressed(125); break; case 'w': ret = isPressed(126); break; default: ret = false; } return ret; } |
<Header Files>
Headers.hpp
#ifndef __HEADERS_H__ #define __HEADERS_H__ #define _CRT_SECURE_NO_WARNINGS #include "TetrisHelper.hpp" #include <cstdio> #include <cstdlib> #include <ctime> #include <cstring> #include <cmath> #define GRID_WIDTH 7 #define GRID_HEIGHT 10 #define USERBLOCK_SIZE 3 extern int displayData[GRID_HEIGHT][GRID_HEIGHT]; #endif |
Displays.hpp
#ifndef __Displays_H__ #define __Displays_H__ #include "Headers.hpp" class Display{ public: void draw(); }; #endif /* Display_hpp */ |
GameEngine.hpp
#ifndef __Game_Engine_H__ #define __Game_Engine_H__ #include "Headers.hpp" // 게임 엔진 만들기 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[3][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,0}, {1,1,0} }, }; int blockX = 0; int blockY = 0; // 떨어지는 블록의 위치 좌표 float elapsed = 0.0f; float controlCheck = 0.0f; void init(); // next는 while 루프에서 매번 불려지는 함수가 될 것 입니다. void next(float dt, char keyboardInput); // 블록이 아래로 내려갈 수 있냐 bool canGoDown(); // 블록이 왼쪽으로 갈 수 있냐 bool canGoLeft(); // 블록이 오른쪽으로 갈 수 있냐 bool canGoRight(); // y번째 있는 블록들이 한 줄이 가득 차있는지 확인하는 함수 bool isLineFilled(int y); // 라인이 가득 찬 경우 라인 지우기 void eraseLine(int y); // 지운 후 y의 좌표를 기준으로 라인들을 위에서 부터 한 칸씩 아래로 내림 void drop(int y); // userblock을 gameGrid에 전사하는 함수 void trans(); // 게임오버인지 판단 bool gameOverDecision(); void makeUserBlock(); void rotate(); // 화면에 출력해줄 데이터는 전역으로 선언해준 displayData로 gameGridData를 전사 해주는 작업 // 실제 게임 데이터를 화면에 출력할 수 있는 데이터로 바꿔줍니다. void makeDisplayData(); }; #endif /* GameEngine_h */ |
TetrisHelper.hpp
#ifndef __TETRIS_HELPER_H__ #define __TETRIS_HELPER_H__ // 반드시 읽어주세요! 맥 사용자를 위한 주의사항 // 맥 사용자는 wasd 를 통해 코드를 만들지만 실제로는 방향키로 조작하게 됩니다. // 맥 사용자는 왼쪽 상단의 프로젝트를 클릭하고 // Frameworks And Libraries 에서 Carbon.framework를 추가해야합니다. // 추가로 Edit Scheme 를 열고 Options > Console > Use Terminal 을 사용해야 합니다. #include <termios.h> #include <cstdio> #include <thread> #include <Carbon/Carbon.h> //#include "TetrisHelper.hpp" void putStringOnPosition(int x, int y, const char* content); void showConsoleCursor(bool showFlag); void drawPosition(int x, int y, bool filled); Boolean isPressed( unsigned short inKeyCode ); bool keyState(char c); #endif // !__TETRIS_HELPER_H__ |
'C++' 카테고리의 다른 글
c++ 12 (문자열과 네임스페이스 그리고 레퍼런스 타입) (0) | 2022.02.17 |
---|---|
c++ 11 (실행구조 / Bitwise / 순수 가상함수) (0) | 2022.02.17 |
c++ 9 (여러개의 헤더와 소스) (0) | 2022.02.11 |
c++ 8 (추가 개념 정리 / 피드백) (0) | 2022.02.05 |
c++ 6 (열거형 - 가독성을 위한 타입) (0) | 2022.01.23 |