오븐 노트

[C++] TextRPG #3 본문

Develop/C++

[C++] TextRPG #3

오 븐 2022. 8. 13. 22:12
#include <iostream>
using namespace std;

// main
// - EnterLobby (PlayerInfo)
// -- CreatePlayer
// -- EnterGame (MonsterInfo)
// --- CreateMonsters
// --- EnterBattle

enum PlayerType
{
	PT_Knight = 1,
	PT_Archer = 2,
	PT_Mage = 3,
};

enum MonsterType
{
	MT_Slime = 1,
	MT_Orc = 2,
	MT_Skeleton = 3,
};

struct StatInfo
{
	int hp = 0;
	int attack = 0;
	int defence = 0;
};

void EnterLobby();
void PrintMessage(const char* msg);
void CreatePlayer(StatInfo* playerInfo);
void PrintStatInfo(const char* name, const StatInfo& info); // info의 경우 pointer로 사용해도 별다른 문제는 없지만 데이터량이 커질 경우 복사에 의해서 성능 저하가 발생할 수 있기에 참조로 넘긴다면 주소를 이용하기에 성능상 단순 복사보다 우위에 있다.
void EnterGame(StatInfo* playerInfo);
void CreateMonsters(StatInfo monsterInfo[], int count);
bool EnterBattle(StatInfo* playerInfo, StatInfo* monsterInfo);

int main()
{
	srand((unsigned)time(nullptr));
	EnterLobby();
	return 0;
}

void EnterLobby()
{
	while (true)
	{
		PrintMessage("로비에 입장했습니다");
		// PLAYER!
		// 이전 textRPG의 경우 전역 메모리에 info를 생성했지만 지금은 스택 메모리에 생성 했다는 차이가 있다.
		// 스택의 주소를 위부로 발설하는 것은 매우 위험한 행동이지만, 내부에서는 유효하기에 다음 함수로 넘기는것은 안전하다.
		StatInfo playerInfo;
		CreatePlayer(&playerInfo);
		PrintStatInfo("Player", playerInfo);
		EnterGame(&playerInfo);
	}
}

void PrintMessage(const char* msg)
{
	cout << "************************" << endl;
	cout << msg << endl;
	cout << "************************" << endl;
}

void CreatePlayer(StatInfo* playerInfo)
{
	bool ready = false;

	while (ready == false)
	{
		PrintMessage("캐릭터 생성창");
		PrintMessage("[1] 기사 [2] 궁수 [3] 법사");
		cout << "> ";

		int input;
		cin >> input;

		switch (input)
		{
		case PT_Knight:
			playerInfo->hp = 100;
			//(*playerInfo).hp = 100; 와 같다
			playerInfo->attack = 10;
			playerInfo->defence = 5;
			ready = true;
			break;
			//return; 시 ready 필요 없이 바로 함수 종료되지만 일반적인 문법으로 사용함
		case PT_Archer:
			playerInfo->hp = 80;
			playerInfo->attack = 15;
			playerInfo->defence = 3;
			ready = true;
			break;
		case PT_Mage:
			playerInfo->hp = 50;
			playerInfo->attack = 25;
			playerInfo->defence = 3;
			ready = true;
			break;
		}
	}
}

void PrintStatInfo(const char* name, const StatInfo& info)
{
	cout << "*********************" << endl;
	cout << name << " : HP = " << info.hp << " ATT = " << info.attack << " DEF = " << info.defence << endl;
	cout << "*********************" << endl;
}

void EnterGame(StatInfo* playerInfo)
{
	const int MONSTER_COUNT = 2;

	PrintMessage("게임에 입장했습니다");

	while (true)
	{
		StatInfo monsterInfo[MONSTER_COUNT];
		// 해당 스택에서만 유효한 정보이므로 EnterGame 밖으로 빠져나가게끔 주소 전달은 불가하다. 하지만 몬스터의 경우 Game 내부에서만 유효하고 Lobby까지 오지는 않으므로 일단 이렇게 사용
		CreateMonsters(monsterInfo, MONSTER_COUNT);
		// 스택 메모리의 생명 주기(라이프 사이클)를 항상 생각해야한다.

		for (int i = 0; i < MONSTER_COUNT; i++)
		{
			PrintStatInfo("Monster", monsterInfo[i]);
			// 해당 변수에서는 포인터가 아닌 const 참조를 받도록 해두었기에 monsterInfo[i]의 주소를 넘겨주는것이 아닌 바구니 자체를 넘겨주는 형태로 제작함
		}

		PrintStatInfo("Player", *playerInfo);
		// 해당 변수에서 StatInfo를 참조값으로 받고 있지만 EnterGame에서 playerInfo를 포인터로 받고 있었음
		// 그저 주소값일뿐이기에 해당 주소로 이동하기 위한 *를 추가하여야한다.
		// 포인터는 무조건 주소를 넘겨주어야하고 참조 형태는 마치 복사를 하듯 유도를 해줘야함

		PrintMessage("[1] 전투 [2] 전투 [3] 도망");
		int input;
		cin >> input;

		if (input == 1 || input == 2)
		{
			int index = input - 1;
			bool victory = EnterBattle(playerInfo, &monsterInfo[index]);
			// playerInfo는 현재 함수의 매개변수에서 포인터값으로 받고 있기에 그대로 전달
			// 현재 사용중인 monsterInfo는 배열. monsterInfo라는 이름은 주소값을 가리킨다. 사실상 포인터와 같은 느낌이다.
			// 하지만 배열에서 특정 순서를 전달하기 위해서 [index]를 넣게 되면 주소값이 아닌 값을 찾게됨.
			// monsterInfo[index]의 형태로 사용하게 되면 결국 주소값이 아닌 배열의 index번째에 저장된 StatInfo 값을 찾아오기 때문에 주소값을 지칭하는 &를 붙여야함.
			if (victory == false)
				break;
		}
	}
}

void CreateMonsters(StatInfo monsterInfo[], int count)
{
	for (int i = 0; i < count; i++)
	{
		int randValue = 1 + rand() % 3;

		switch (randValue)
		{
		case MT_Slime:
			monsterInfo[i].hp = 30;
			monsterInfo[i].attack = 5;
			monsterInfo[i].defence = 1;
			// 1차 포인터와 1차 배열은 완전히 호환이 가능하므로 매개변수로 받은 포인터를 배열 형식으로 사용 가능하다.
			break;
		case MT_Orc:
			monsterInfo[i].hp = 40;
			monsterInfo[i].attack = 8;
			monsterInfo[i].defence = 2;
			break;
		case MT_Skeleton:
			monsterInfo[i].hp = 50;
			monsterInfo[i].attack = 15;
			monsterInfo[i].defence = 3;
			break;
		}
	}
}

bool EnterBattle(StatInfo* playerInfo, StatInfo* monsterInfo)
{
	while (true)
	{
		int damage = playerInfo->attack - monsterInfo->defence;
		if (damage < 0)
			damage = 0;

		monsterInfo->hp -= damage;
		if (monsterInfo->hp < 0)
			monsterInfo->hp = 0;

		PrintStatInfo("Monster", *monsterInfo);
		// const 참조로 받고있는데 현재 함수에는 포인터로 받고있기에 해당 주소값으로 들어가는 *를 추가

		if (monsterInfo->hp == 0)
		{
			PrintMessage("몬스터를 처치했습니다");
			return true;
		}

		damage = monsterInfo->attack - monsterInfo->defence;
		if (damage < 0)
			damage = 0;

		playerInfo->hp -= damage;
		if (playerInfo->hp < 0)
			playerInfo->hp = 0;

		PrintStatInfo("Player", *playerInfo);
		// 참조값은 실질적인 포인터가 아닌 내부에 저장된 값 자체의 복사값으로 넘겨줘야함

		if (playerInfo->hp == 0)
		{
			PrintMessage("Game Over");
			return false;
		}
	}
}

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 - 인프런 | 강의
 
C++ 카테고리의 글은 인프런 Rookiss님의 강의를 공부하며 정리하는 내용입니다.
이미 알고 있는 내용도 다시 정리 되어있을 수 있습니다.

 

모든 글은 제가 공부하기 위해 작성합니다.

'Develop > C++' 카테고리의 다른 글

[C++] 연습문제 (달팽이)  (0) 2022.08.22
[C++] 연습 문제 (문자열) #1 ~ 2  (0) 2022.08.16
[C++] 포인터 마무리  (0) 2022.07.15
[C++] 다차원 배열  (0) 2022.07.15
[C++] 다중 포인터  (0) 2022.07.14