강께르의 개발일지

[C++] 몬스터와 일대일 턴제 전투 게임 본문

연습

[C++] 몬스터와 일대일 턴제 전투 게임

강께르 2021. 6. 8. 17:31

1. 이것은 무엇인가?

 새로 배운 함수와 배열을 이용해 몬스터와 일대일로 싸움을 주고받는 게임을 만들었다. 플레이어와 몬스터는 미리 설정된 능력치를 얻고 기본공격 하나와 강한 공격 하나, 그리고 그들만의 스킬 두개로 서로 턴을 주고 받으며 서로 체력을 감소시키게 된다. 둘 중 하나라도 체력이 1 미만이 되면 그에 따른 출력문과 함께 게임은 종료된다. 게임 진행은 "게임 시작 출력문 - (플레이어 선택 - 데미지 계산 - 몬스터 선택 - 데미지 계산) - 게임 종료 출력문"으로 된다. 스킬은 다른 특별한 기능을 넣기보단 데미지를 높이는 대신 명중률을 낮추거나 명중률을 올리는 대신 데미지를 낮추는 등 수치적인 부분으로 차별점을 두었다. 그리고 특별히 플레이어는 회복으로 소모된 체력을 증가시킬 수 있고, 몬스터는 플레이어의 회피율을 감소시키는 디버프 스킬이 있다. 몰입감을 위해서 문맥 있는 배경 상황을 깔아주는 출력문도 넣어보았다.

 

2. 새로 알게 된 점

- Window.h 헤더파일을 이용해 콘솔창의 크기를 조절하거나 타이틀 이름을 바꾸는 등 콘솔창 조작에 대해 알게 됐다.

- 수도 코드를 대강 그리고 만든 코드라서 그런지 은근 스파게티에다가 완성하는데 오래 걸렸다. 제대로 된 밑바탕 그림이 없이는 완성된 결과물을 얻을 수 있어도 질 좋은 결과물을 얻을 수 없다는 것을 느꼈다.

- Sleep()을 사용하니 좀 더 게임스러운 느낌이 들었다. 플레이어에게 많은 정보를 한꺼번에 주지 않고, 출력문 하나하나 인지할 수 있게 도와주니 그럴싸한 느낌을 받았다.

- 이 토대로 살을 더 붙이면 그것이 RPG 장르의 게임이 될 것 같다. 한참 멀었겠지만...

 

3. 코드

#include<iostream>
#include<Windows.h>
#include<ctime>

using namespace std;

enum STAT {
	HP,
	MP,
	ATK,
	CRI,
	EVD
};

enum SKILL {
	BAS = 1,
	SK1,
	SK2,
	SK3
};

void SetConsoleView();
void ShowStartText(void);
void ShowAll(int* player, int* monster);
void ShowStats(int* arr, const char*);
void ShowDrawText();
void ShowWinText();
void ShowLoseText();

bool IsCorrectPercent(int val); // 회피율 치명율을 매개변수로 전달하면 성공했는지 여부 반환
bool IsDead(int hp);

int BasicAtk(int* chara, int* enemy);
int PowerAtk(int* chara, int* enemy);
int BottomAtk(int* chara, int* enemy);
int Heal(int* arr);

int DashAtk(int* chara, int* enemy);
int Howl(int* chara, int* enemy);

int main(void)
{				
	SetConsoleView();

	// 체력, 마나, 공격력, 치명율, 회피율
	int player[5] = { 20, 10, 5, 15, 20 };
	const char* playerSkill[4] = { "베기", "강타", "하단베기", "회복" };

	int orc[5] = { 30, 10, 3, 11, 20 };
	const char* orcSkill[4] = { "둔기 휘두르기", "흉포한 일격", "돌진", "괴성" };

	bool isPlayerAlive = true;
	bool isMonsterAlive = true;
	bool isGameover = isPlayerAlive && isMonsterAlive;

	int playerChooseNum;
	int orcChooseNum;

	ShowStartText();
	while (isGameover)
	{
		srand((unsigned int)time(NULL));
		ShowAll(player, orc);

		// 플레이어 행동 선택 관련 구문
		cout << endl;
		cout << "=================================================================" << endl;
		cout << "\t\t전사여, 자네의 행동을 고르시게." << endl;
		cout << "1." << playerSkill[SKILL::BAS - 1] << "\t\t2." << playerSkill[SKILL::SK1 - 1]
			<< "\t\t3." << playerSkill[SKILL::SK2 - 1] << "\t\t4." << playerSkill[SKILL::SK3 - 1] << endl;
		cout << "전사의 선택 : ";
		cin >> playerChooseNum;
		while (!(playerChooseNum > 0 && playerChooseNum < 5))
		{
			cout << "미안하지만, 전사는 그 행동을 모릅니다." << endl;
			cout << "전사의 선택 : ";
			cin >> playerChooseNum;
		}
		cout << "=================================================================" << endl;

		// 플레이어 행동 관련 구문
		switch (playerChooseNum)
		{
		case SKILL::BAS:
			cout << "전사의 " << playerSkill[0] << " 공격!" << endl;
			Sleep(1000);
			cout << "오크에게 ";
			orc[STAT::HP] = BasicAtk(player, orc);
			break;
		case SKILL::SK1:
			cout << "전사의 " << playerSkill[1] << " 공격!" << endl;
			Sleep(1000);
			cout << "오크에게 ";
			orc[STAT::HP] = PowerAtk(player, orc);
			break;
		case SKILL::SK2:
			cout << "전사의 하단베기 공격!" << endl;
			Sleep(1000);
			cout << "오크에게 ";
			orc[STAT::HP] = BottomAtk(player, orc);
			break;
		case SKILL::SK3:
			cout << "전사의 회복 스킬!" << endl;
			if (player[STAT::HP] == 20)
				cout << "전사는 지금 다친 곳이 없다." << endl;
			else
				player[STAT::HP] = Heal(player);
			break;
		}
		cout << endl;
		Sleep(1500);

		// 오크 행동 관련 구문
		orcChooseNum = (rand() % 4) + 1;
		switch (orcChooseNum)
		{
		case SKILL::BAS:
			cout << "오크의 " << orcSkill[0] << " 공격!" << endl;
			Sleep(1000);
			cout << "전사에게 ";
			player[STAT::HP] = BasicAtk(orc, player);
			break;
		case SKILL::SK1:
			cout << "오크의 " << orcSkill[1] << " 공격!" << endl;
			Sleep(1000);
			cout << "전사에게 ";
			player[STAT::HP] = PowerAtk(orc, player);
			break;
		case SKILL::SK2:
			cout << "오크의 " << orcSkill[2] << " 공격!" << endl;
			Sleep(1000);
			cout << "전사에게 ";
			player[STAT::HP] = DashAtk(orc, player);
			break;
		case SKILL::SK3:
			cout << "오크의 " << orcSkill[3] << " 공격!" << endl;
			Sleep(1000);
			cout << "전사에게 ";
			if (player[STAT::EVD] < 1)
				cout << "더 이상 낮출 회피율이 없다." << endl;
			else
				player[STAT::EVD] = Howl(orc, player);
			break;
		}

		// 게임 오버 판단 구문
		isPlayerAlive = IsDead(player[STAT::HP]);
		isMonsterAlive = IsDead(orc[STAT::HP]);
		isGameover = isPlayerAlive && isMonsterAlive;
		Sleep(1500);
		system("cls");
	}

	// 게임 엔딩 문구 출력
	if ((!isPlayerAlive) && (!isMonsterAlive))
	{
		player[0] = 0;
		orc[0] = 0;
		ShowAll(player, orc);
		ShowDrawText();
	}
	else if (!isMonsterAlive)
	{
		orc[0] = 0;
		ShowAll(player, orc);
		ShowWinText();
	}
	else if (!isPlayerAlive)
	{
		player[0] = 0;
		ShowAll(player, orc);
		ShowLoseText();
	}
	
	return 0;
}

// 윈도우 콘솔창 크기 조절 및 타이틀
void SetConsoleView()
{
	system("mode con:cols=65 lines=30");
	system("title Figthing with Orc");
}

// 게임 시작 출력 문구
void ShowStartText(void)
{
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "===============F====i====g====h====t===i===n===g=================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=====================w====i=====t====h===========================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "==================O==========r==========c========================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;
	cout << "=================================================================" << endl;

	Sleep(3000);
	system("cls");
	Sleep(2000);
	cout << "======================= 냄새 나는 구덩이 ========================" << endl << endl;
	Sleep(2000);
	cout << "냄새나는 구덩이에 발을 들여놓았다. 기분 나쁜 곳이다..." << endl << endl;
	Sleep(2000);
	cout << "찐득한 진흙이 내 군화를 뒤덮고," << endl << endl;
	Sleep(2000);
	cout << "끔찍한 악취는 내 정신을 놓게 만든다." << endl << endl;
	Sleep(3000);
	system("cls");
	cout << "======================= 냄새 나는 구덩이 ========================" << endl << endl;
	Sleep(2000);
	cout << "저기 뒤룩뒤룩 꿈틀거리는 뒷모습은 아마 산 자들이라면 " << endl << "증오하는 오크일 것이다. " << endl << endl;
	Sleep(2000);
	cout << "굳이 혐오스러운 저 괴물과 칼을 맞댈 필요는 없다. 조심히 지나가자." << endl << endl;
	Sleep(2000);
	system("cls");
	cout << "======================= 냄새 나는 구덩이 ========================" << endl << endl;
	Sleep(2000);
	cout << "저벅..." << endl;
	Sleep(2000);
	cout << "저벅..." << endl;
	Sleep(2000);
	cout << "저벅..." << endl << endl;
	Sleep(2000);
	cout << "첨벙!" << endl;
	Sleep(2000);
	system("cls");
	cout << "======================= 냄새 나는 구덩이 ========================" << endl << endl;
	Sleep(2000);
	cout << "이런 들켰다! 싸울 수 밖에 없다!" << endl;
	Sleep(2000);
	system("cls");
}

// 플레이어, 몬스터의 정보 출력
void ShowAll(int* player, int* monster)
{
	ShowStats(player, "전 사");
	ShowStats(monster, "오 크");
}

// 주어진 배열의 정보 출력
void ShowStats(int* arr, const char* str)
{
	cout << "=================================================================" << endl;
	cout << "============================= " << str << " =============================" << endl;
	cout << "============================ H P : " << arr[STAT::HP] << " ===========================" << endl;
	cout << "============================ M P : " << arr[STAT::MP] << " ===========================" << endl;
	cout << "=================================================================" << endl;
}

// 둘 다 동시에 체력을 잃었을 경우
void ShowDrawText()
{
	Sleep(2000);
	cout << endl;
	cout << "\"혼자 가지는 않겠다!!! 비열한 오크!\"" << endl;
	Sleep(1000);
	cout << "전사의 칼을 타고 초록색 피가 전사의 손을 적십니다." << endl;
	Sleep(1000);
	cout << "전사도 자신의 가슴팍에 차가운 날붙이가 들어온 것을 느낍니다." << endl;
	Sleep(1000);
	cout << "치열하게 싸웠지만 장렬하게 두 생명은" << endl;
	Sleep(1000);
	cout << "더러운 진흙 속에 사라집니다..." << endl;
	Sleep(1000);
	cout << "Game Over..." << endl << endl;
	cout << "=================================================================" << endl;
}

// 몬스터가 체력이 없을 경우
void ShowWinText()
{
	Sleep(2000);
	cout << endl;
	cout << "더러운 오크는 내게 상대도 안된다!" << endl;
	Sleep(1000);
	cout << "전사는 냄새 나는 구덩이, 그 속으로 천천히" << endl;
	Sleep(1000);
	cout << "더 깊숙이 발걸음을 옮겼다..." << endl;
	Sleep(1000);
	cout << "To be continue..." << endl << endl;
	cout << "=================================================================" << endl;
}

// 플레이어의 체력이 없을 경우
void ShowLoseText()
{
	Sleep(2000);
	cout << endl;
	cout << "내 모험은 여기가 끝인 것 같네..." << endl;
	Sleep(1000);
	cout << "전사의 몸은 질척거리는 진흙 위로 떨어졌습니다." << endl;
	Sleep(1000);
	cout << "진흙은 마치 살아 있다는 듯, 점점 그 몸을 삼킵니다." << endl;
	Sleep(1000);
	cout << "아무도 그가 여기 있었다는 것을 모를 정도로..." << endl;
	Sleep(1000);
	cout << "Game Over..." << endl << endl;
	cout << "=================================================================" << endl;
}

// 일정 확률을 매개변수로 전달하면 그 확률에 들어맞는지 판단하는 함수
bool IsCorrectPercent(int val)
{
	return val > (rand() % 100);
}

// 체력이 0이면 false, 죽었다고 알리는 함수
bool IsDead(int hp)
{
	bool result = true;
	if (hp < 1)
		result = false;
	return result;
}

// 일반 공격
int BasicAtk(int* chara, int* enemy)
{
	srand((unsigned int)time(NULL));
	int HealthPoint = enemy[STAT::HP];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI]);
	bool isEvd = IsCorrectPercent(enemy[STAT::EVD]);
	int atkPoint;
	
	if (!isEvd)
	{
		if (isCrit)
		{
			cout << "치명적인 공격!!!" << endl;
			atkPoint = chara[STAT::ATK] * 2;
		}
		else
			atkPoint = chara[STAT::ATK];
		HealthPoint -= atkPoint;
		cout << atkPoint << " 데미지를 주었다!" << endl;
	}
	else
	{
		cout << "공격을 했으나" << endl;
		cout << "재빠르게 피했다!" << endl;
	}

	return HealthPoint;
}

// 강한 공격(강타, 흉포한 일격) / 크리 4배 데미지, 일반 2배 데미지
// 하지만 상대방의 회피율을 2배로 하여 명중률이 1/2로 감소한 효과
int PowerAtk(int* chara, int* enemy)
{
	srand((unsigned int)time(NULL));
	int HealthPoint = enemy[STAT::HP];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI]);
	bool isEvd = IsCorrectPercent((enemy[STAT::EVD] * 2) % 100);
	int atkPoint;

	if (!isEvd)
	{
		if (isCrit)
		{
			cout << "치명적인 공격!!!" << endl;
			atkPoint = chara[STAT::ATK] * 4;
		}
		else
			atkPoint = chara[STAT::ATK] * 2;
		HealthPoint -= atkPoint;
		cout << atkPoint << " 데미지를 주었다!" << endl;
	}
	else
	{
		cout << "강하게 무기를 휘둘렀으나" << endl;
		cout << "가까스로 피했다!" << endl;
	}
	return HealthPoint;
}

// 플레이어의 하단 공격 / 치명율이 1/2이지만 적 회피율도 1/2
// 명중률이 2배이지만 데미지도 1/2 확정적인 데미지용으로 사용
int BottomAtk(int* chara, int* enemy)
{
	srand((unsigned int)time(NULL));
	int HealthPoint = enemy[STAT::HP];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI] / 2);
	bool isEvd = IsCorrectPercent(enemy[STAT::EVD] / 2);
	int atkPoint;

	if (!isEvd)
	{
		if (isCrit)
		{
			cout << "치명적인 공격!!!" << endl;
			atkPoint = chara[STAT::ATK];
		}
		else
			atkPoint = chara[STAT::ATK] / 2;
		HealthPoint -= atkPoint;
		cout << atkPoint << " 데미지를 주었다!" << endl;
	}
	else
	{
		cout << "공격을 했으나" << endl;
		cout << "육중한 몸을 던져 피했다.!" << endl;
	}
	return HealthPoint;
	return 0;
}

// 플레이어의 회복
// 따로 회복계수로 healStat과 실패확률로 10을 부여함.
// 치명율도 적용하여 대량으로 회복도 가능하게 만듦
int Heal(int* chara)
{
	srand((unsigned int)time(NULL));
	const int healStat = 3;
	int HealthPoint = chara[STAT::HP];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI]);
	bool isFail = IsCorrectPercent(10);
	int healPoint;

	if (!isFail)
	{
		if (isCrit)
		{
			cout << "상당한 효과의 치유!!!" << endl;
			healPoint = healStat * 3;
		}
		else
			healPoint = healStat;
		HealthPoint += healPoint;
		if (HealthPoint >= 20)
			HealthPoint = 20;
		cout << healPoint << " 만큼 치유하였다." << endl;
	}
	else
	{
		cout << "치유하려 했으나" << endl;
		cout << "별 효과가 없다..." << endl;
	}
	return HealthPoint;
}

// 오크의 돌진 공격
// 플레이어의 회피율을 3배 올려 명중하기 힘들게 만들지만
// 데미지는 기본 데미지의 4배로 들어가 일단 적중하면 치명타
int DashAtk(int* chara, int* enemy)
{
	srand((unsigned int)time(NULL));
	int HealthPoint = enemy[STAT::HP];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI]);
	bool isEvd = IsCorrectPercent((enemy[STAT::EVD] * 3) % 100);
	int atkPoint;

	if (!isEvd)
	{
		if (isCrit)
		{
			cout << "치명적인 돌진 공격!!!" << endl;
			atkPoint = chara[STAT::ATK] * 5;
		}
		else
			atkPoint = chara[STAT::ATK] * 4;
		HealthPoint -= atkPoint;
		cout << atkPoint << " 데미지를 주었다!" << endl;
	}
	else
	{
		cout << "기세 좋게 달려왔으나" << endl;
		cout << "옆으로 굴러 피했다!" << endl;
	}
	return HealthPoint;
}

// 오크의 괴성 공격
// 회피 감소 계수 reduceEvd를 선언
// 그에 따라서 치명율이나 일반 성공 시 플레이어의
// 회피율을 감소시킨다.
int Howl(int* chara, int* enemy)
{
	int enemyEvd = enemy[STAT::EVD];
	bool isCrit = IsCorrectPercent(chara[STAT::CRI]);
	bool isFail = IsCorrectPercent(enemy[STAT::EVD]);
	const int reduceEvd = 5;
	int atkPoint;

	if (!isFail)
	{
		if (isCrit)
		{
			cout << "상당히 듣기 싫은 괴성!" << endl;
			atkPoint = reduceEvd * 2;
		}
		else
			atkPoint = reduceEvd;
		enemyEvd += atkPoint;
		cout << atkPoint << " 만큼 회피율이 감소했다." << endl;
	}
	else
	{
		cout << "뭐라 꽥꽥거리는데" << endl;
		cout << "거슬리지는 않다..." << endl;
	}
	return enemyEvd;
}

4. 결과

몰입감을 위해 조미료 한 스푼...

 

 

??? : 아 감나빗
내가 졌으니 존망겜이다 / 조미료 한 스푼 더...