여러개의 헤더와 소스
1) 암묵적인 룰

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

include를 통해 중복되는 내용이 들어가기 때문에 오류가 발생한다. 따라서 두가지 방법을 이용해 해결한다.
#pragma once 모든 헤더파일 가장 위에 코드를 사용 // 헤더를 두번 넣지 않는다.
#ifndef // 표준 코드 매크로
ex)
#ifndef ANIAML_H // 헤더파일 젤 처음에 ANIMAL_H 는 본인 마음대로 설정한다.
#define ANIMAL_H // 헤더파일 두번째줄에
#endif // 헤더파일 마지막줄에
클래스의 전방 선언 : 헤더파일에서 include 를 하지 않고 소스파일에서 include를 하고 싶을 때 헤더파일에 해당 클래스를 위의 define 아래에 class 클래스명 을 전방 선언 해주면 클래스가 어디있는지 모르겠지만 다른 cpp 파일이나 헤더파일에서 실행중 찾아서 실행하는 것을 의미. => 함수끼리 꼬리를 무는 경우 뒷줄에 있는 클래스가 윗줄에 있는 클래스를 모르기 때문에 에러가 나지만 전방선언을 한다면 에러가 나지 않음. 이럴 경우 사용한다.
우리가 일반적으로 함수를 만들 때에는 선언과 정의로 분리를 했습니다.우리는 여지껏 클래스에 대해서는 선언과 정의로 분리하지 않았는데요. 이 클래스에서 멤버함수의 선언과 정의를 분리하는 방법을 알아보겠습니다. 또한 이 선언과 정의를 나누는 이유에 대해서 한번 살펴보겠습니다.
2-2) 헤더파일 중복 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;
}