C++로 간단하게 만든 Text RPG

C++ 강의를 들으면서 Text RPG를 만들어 보았다.

아래는 게임 실행 화면의 일부이다.

그리고 소스파일이다 한 500줄정도 되는거같다..

위 실행파일 외에도 상점기능, 인벤토리 기능이 구현되어있다. 

#include <iostream>
#include <time.h>

using namespace std;

/*
1. 무기상점에서 판매할 아이템 목록을 만들어주고 해당 목록을 출력해준다.
2. 출력할때 앞에 번호를 부여해주고 번호를 입력받아서 해당 번호의 아이템을
구입하게 해준다. 가장 마지막 번호에 뒤로가기 기능을 만든다.
단, 가방이 꽉 찼거나 돈이 부족할 경우 구매하지 못하게 한다.
3. 가방을 선택하면 가방안의 아이템을 보여준다.
*/
enum MAIN_MENU
{
	MM_NONE,
	MM_MAP,
	MM_STORE,
	MM_INVENTORY,
	MM_EXIT
};

enum MAP_TYPE
{
	MT_NONE,
	MT_EASY,
	MT_NORMAL,
	MT_HARD,
	MT_BACK
};

enum JOB
{
	JOB_NONE,
	JOB_KNIGHT,
	JOB_ARCHER,
	JOB_WIZARD,
	JOB_END
};

enum BATTLE
{
	BATTLE_NONE,
	BATTLE_ATTACK,
	BATTLE_BACK
};

enum ITEM_TYPE
{
	IT_NONE,
	IT_WEAPON,
	IT_ARMOR,
	IT_BACK
};

enum ITEM_ATTACK_TYPE
{
	IAT_NONE,
	IAT_WOODSWORD,
	IAT_STONESWORD
};

enum STORE_MENU
{
	SM_NONE,
	SM_WEAPON,
	SM_ARMOR,
	SM_BACK
};

#define NAME_SIZE 32
#define ITEM_DESC_LENGTH 512
#define INVENTORY_MAX 20
#define STORE_WEAPON_MAX 3
#define STORE_ARMOR_MAX 3

struct _tagItem
{
	char	strName[NAME_SIZE];
	char	strTypeName[NAME_SIZE];
	ITEM_TYPE	eType;
	int		iMin;
	int		iMax;
	int		iPrice;
	int		iSell;
	char	strDesc[ITEM_DESC_LENGTH];
};

struct _tagInventory
{
	_tagItem    tItem[INVENTORY_MAX];
	int			iItemCount;
	int			iGold;
};

struct _tagPlayer
{
	char	strName[NAME_SIZE];
	char	strJobName[NAME_SIZE];
	JOB		eJob;
	int		iAttackMin;
	int		iAttackMax;
	int		iArmorMin;
	int		iArmorMax;
	int		iHP;
	int		iHPMax;
	int		iMP;
	int		iMPMax;
	int		iExp;
	int		iLevel;
	_tagInventory tInventory;
};

struct _tagMonster
{
	char strName[NAME_SIZE];
	int		iAttackMin;
	int		iAttackMax;
	int		iArmorMin;
	int		iArmorMax;
	int		iHP;
	int		iHPMax;
	int		iMP;
	int		iMPMax;
	int		iLevel;
	int		iExp;
	int		iGoldMin;
	int		iGoldMax;
};

int main() 
{
	srand((unsigned int) time(0));
	
	// 게임을 시작할때 플레이어 정보를 설정하게 한다.
	_tagPlayer		tPlayer = {};
	// 플레이어 이름을 입력받는다.
	cout << "이름 : ";
	cin.getline(tPlayer.strName, NAME_SIZE - 1);

	int iJob = JOB_NONE;
	while (iJob == JOB_NONE)
	{
		system("cls");
		cout << "1. 기사" << endl;
		cout << "2. 궁수" << endl;
		cout << "3. 마법사" << endl;
		cout << "직업을 선택하세요 : ";
		cin >> iJob;

		if (cin.fail())
		{
			cin.clear();
			cin.ignore(1024, '\n');
			continue;
		}

		else if (iJob <= JOB_NONE || iJob >= JOB_END)
		{
			iJob = JOB_NONE;
		}

		tPlayer.iLevel = 1;
		tPlayer.iExp = 0;
		tPlayer.eJob = (JOB)iJob;
		tPlayer.tInventory.iGold = 10000;
		tPlayer.tInventory.iItemCount = 0;

		switch (tPlayer.eJob)
		{
		case JOB_KNIGHT:
			strcpy_s(tPlayer.strJobName, "기사");
			tPlayer.iAttackMin = 5;
			tPlayer.iAttackMax = 10;
			tPlayer.iArmorMin = 15;
			tPlayer.iArmorMax = 20;
			tPlayer.iHPMax = 500;
			tPlayer.iHP = 500;
			tPlayer.iMP = 100;
			tPlayer.iMPMax = 100;
			break;

		case JOB_ARCHER:
			strcpy_s(tPlayer.strJobName, "궁수");
			tPlayer.iAttackMin = 10;
			tPlayer.iAttackMax = 15;
			tPlayer.iArmorMin = 10;
			tPlayer.iArmorMax = 15;
			tPlayer.iHPMax = 400;
			tPlayer.iHP = 400;
			tPlayer.iMP = 200;
			tPlayer.iMPMax = 200;
			break;

		case JOB_WIZARD:
			strcpy_s(tPlayer.strJobName, "마법사");
			tPlayer.iAttackMin = 15;
			tPlayer.iAttackMax = 20;
			tPlayer.iArmorMin = 5;
			tPlayer.iArmorMax = 10;
			tPlayer.iHPMax = 300;
			tPlayer.iHP = 300;
			tPlayer.iMP = 300;
			tPlayer.iMPMax = 300;
			break;
		}
	}

	// 몬스터를 생성한다.
	_tagMonster		tMonsterArr[MT_BACK - 1] = {};

	// 고블린 생성
	strcpy_s(tMonsterArr[0].strName, "고블린");
	tMonsterArr[0].iAttackMin = 20;
	tMonsterArr[0].iAttackMax = 30;
	tMonsterArr[0].iArmorMin = 2;
	tMonsterArr[0].iArmorMax = 5;
	tMonsterArr[0].iHP = 100;
	tMonsterArr[0].iHPMax = 100;
	tMonsterArr[0].iMP = 10;
	tMonsterArr[0].iMPMax = 10;
	tMonsterArr[0].iLevel = 1;
	tMonsterArr[0].iExp = 1000;
	tMonsterArr[0].iGoldMin = 500;
	tMonsterArr[0].iGoldMax = 1500;

	// 트롤 생성
	strcpy_s(tMonsterArr[1].strName, "트롤");
	tMonsterArr[1].iAttackMin = 80;
	tMonsterArr[1].iAttackMax = 130;
	tMonsterArr[1].iArmorMin = 60;
	tMonsterArr[1].iArmorMax = 90;
	tMonsterArr[1].iHP = 2000;
	tMonsterArr[1].iHPMax = 2000;
	tMonsterArr[1].iMP = 100;
	tMonsterArr[1].iMPMax = 100;
	tMonsterArr[1].iLevel = 5;
	tMonsterArr[1].iExp = 7000;
	tMonsterArr[1].iGoldMin = 6000;
	tMonsterArr[1].iGoldMax = 8000;

	// 드래곤 생성
	strcpy_s(tMonsterArr[2].strName, "드래곤");
	tMonsterArr[2].iAttackMin = 250;
	tMonsterArr[2].iAttackMax = 500;
	tMonsterArr[2].iArmorMin = 200;
	tMonsterArr[2].iArmorMax = 400;
	tMonsterArr[2].iHP = 30000;
	tMonsterArr[2].iHPMax = 30000;
	tMonsterArr[2].iMP = 20000;
	tMonsterArr[2].iMPMax = 20000;
	tMonsterArr[2].iLevel = 10;
	tMonsterArr[2].iExp = 30000;
	tMonsterArr[2].iGoldMin = 20000;
	tMonsterArr[2].iGoldMax = 50000;
				
	// 상점에서 판매할 아이템 목록 생성한다.
	_tagItem	tStoreWeapon[STORE_WEAPON_MAX] = {};
	_tagItem	tStoreArmor[STORE_ARMOR_MAX] = {};

	// 각 아이템 정보들을 설정해준다.
    
	//나무검 생성
	strcpy_s(tStoreWeapon[0].strName, "나무검");
	strcpy_s(tStoreWeapon[0].strTypeName, "무기");
	tStoreWeapon[0].eType = (ITEM_TYPE)1;
	tStoreWeapon[0].iMin = 20;
	tStoreWeapon[0].iMax = 40;
	tStoreWeapon[0].iPrice = 10000;
	tStoreWeapon[0].iSell = 4000;
	strcpy_s(tStoreWeapon[0].strDesc, "나무로 만든 나무검입니다.");

	//돌검 생성
	strcpy_s(tStoreWeapon[1].strName, "돌검");
	strcpy_s(tStoreWeapon[1].strTypeName, "무기");
	tStoreWeapon[1].eType = (ITEM_TYPE)2;
	tStoreWeapon[1].iMin = 80;
	tStoreWeapon[1].iMax = 100;
	tStoreWeapon[1].iPrice = 1000;
	tStoreWeapon[1].iSell = 40000;
	strcpy_s(tStoreWeapon[1].strDesc, "돌로 만든 돌검입니다.");


	while (true) 
	{
		system("cls");
		cout << "******************************* 로비 ***************************" << endl;
		cout << "1. 맵" << endl;
		cout << "2. 상점" << endl;
		cout << "3. 가방" << endl;
		cout << "4. 종료" << endl;
		cout << "메뉴를 선택하세요 : " << endl;
		int iMenu;
		cin >> iMenu;

		if (cin.fail())
		{
			cin.clear();
			cin.ignore(1024, '\n');
			continue;
		}

		if (iMenu == MM_EXIT)
		{
			break;
		}

		switch (iMenu)
		{
		case MM_MAP:
			while (true)
			{
				system("cls");
				cout << "******************************* 맵 ***************************" << endl;
				cout << "1. 쉬움" << endl;
				cout << "2. 보통" << endl;
				cout << "3. 어려움" << endl;
				cout << "4. 뒤로가기" << endl;
				cout << "맵을 선택하세요 : ";
				cin >> iMenu;

				if (cin.fail())
				{
					cin.clear();
					cin.ignore(1024, '\n');
					continue;
				}

				// 이 if문에 속한 break는 맵 메뉴를 돌려주기 위한 while에 속해 있으므로
				// 이 while문을 빠져나간다.
				if (iMenu == MT_BACK)
				{
					break;
				}

				// 선택한 메뉴에서 1을 빼주면 몬스터 배열의 인덱스가 된다.
				// 그렇게 해서 해당 맵의 몬스터를 생성해준다.
				_tagMonster tMonster = tMonsterArr[iMenu - 1];

				while (true)
				{
					system("cls");
					switch (iMenu)
					{
					case MT_EASY:
						cout << "******************************* 쉬움 ***************************" << endl;
						break;
					case MT_NORMAL:
						cout << "******************************* 보통 ***************************" << endl;
						break;
					case MT_HARD:
						cout << "******************************* 어려움 ***************************" << endl;
						break;
					}

					// 플레이어 정보를 출력한다.
					cout << "====================== Player ======================" << endl;
					cout << "이름 : " << tPlayer.strName << "\t직업 : " <<
						tPlayer.strJobName << endl;
					cout << "레벨 : " << tPlayer.iLevel << "\t 경험치 : " <<
						tPlayer.iExp << endl;
					cout << "공격력 : " << tPlayer.iAttackMin << " - " <<
						tPlayer.iAttackMax << "\t방어력 : " << tPlayer.iArmorMin <<
						" - " << tPlayer.iArmorMax << endl;
					cout << "체력 : " << tPlayer.iHP << " / " << tPlayer.iHPMax <<
						"\t마나 : " << tPlayer.iMP << " / " << tPlayer.iMPMax << endl;
					cout << "보유골드 : " << tPlayer.tInventory.iGold << " Gold" << endl;

					// 몬스터 정보 출력
					cout << "====================== Monster ======================" << endl;
					cout << "이름 : " << tMonster.strName << "\t레벨 : " <<
						tMonster.iLevel << endl;
					cout << "공격력 : " << tMonster.iAttackMin << " - " <<
						tMonster.iAttackMax << "\t방어력 : " << tMonster.iArmorMin <<
						" - " << tMonster.iArmorMax << endl;
					cout << "체력 : " << tMonster.iHP << " / " << tMonster.iHPMax <<
						"\t마나 : " << tMonster.iMP << " / " << tMonster.iMPMax << endl;
					cout << "획득경험치 : " << tMonster.iExp << " Exp" << "\t획득골드 : " <<
						tMonster.iGoldMin << " - " << tMonster.iGoldMax << endl;

					cout << "1. 공격" << endl;
					cout << "2. 도망가기" << endl;
					cout << "메뉴를 선택하세요 : ";
					cin >> iMenu;

					if (cin.fail())
					{
						cin.clear();
						cin.ignore(1024, '\n');
						continue;
					}

					else if (iMenu == BATTLE_BACK) 
					{
						tPlayer.iHP = tPlayer.iHPMax;
						tPlayer.iMP = tPlayer.iMPMax;
						tMonster.iHP = tMonster.iHPMax;
						tMonster.iMP = tMonster.iMPMax;
						break;
					}

					switch (iMenu)
					{
					case BATTLE_ATTACK:
					{
						int iAttack = rand() % (tPlayer.iAttackMax - tPlayer.iAttackMin + 1) +
							tPlayer.iAttackMin;		
						int iArmor = rand() % (tMonster.iArmorMax - tMonster.iArmorMin + 1) +
							tMonster.iArmorMin;

						int iDamage = iAttack - iArmor;
						iDamage = iDamage < 1 ? 1 : iDamage;

						tMonster.iHP -= iDamage;

						cout << tPlayer.strName << " 가 " << tMonster.strName <<
							"에게 " << iDamage << " 피해를 입혔습니다." << endl;
						

						// 몬스터가 죽었을 경우 처리
						if (tMonster.iHP <= 0)
						{
							cout << tMonster.strName << " 몬스터가 죽었습니다." << endl;

							tPlayer.iExp += tMonster.iExp;
							int iGold = (rand() % (tMonster.iGoldMax - tMonster.iGoldMin + 1) +
								tMonster.iGoldMin);
							tPlayer.tInventory.iGold += iGold;

							cout << tMonster.iExp << " 경험치를 획득하였습니다." << endl;
							cout << iGold << " Gold를 획득하였습니다." << endl;
							
							tPlayer.iHP = tPlayer.iHPMax;
							tPlayer.iMP = tPlayer.iMPMax;
							tMonster.iHP = tMonster.iHPMax;
							tMonster.iMP = tMonster.iMPMax;

							system("pause");
							break;
						}
							// 몬스터가 살아있다면 플레이어를 공격한다
							iAttack = rand() % (tMonster.iAttackMax - tMonster.iAttackMin + 1) +
								tMonster.iAttackMin;
							iArmor = rand() % (tPlayer.iArmorMax - tPlayer.iArmorMin + 1) +
								tPlayer.iArmorMin;

							iDamage = iAttack - iArmor;
							iDamage = iDamage < 1 ? 1 : iDamage;

							// 플레이어의 HP를 감소시킨다.
							tPlayer.iHP -= iDamage;
							cout << tMonster.strName << " 가 " << tPlayer.strName <<
								"에게 " << iDamage << " 피해를 입혔습니다." << endl;
							

							// 플레이어가 죽었을 경우
							if (tPlayer.iHP <= 0)
							{
								cout << tPlayer.strName << " 플레이어가 사망하였습니다." << endl;

								int iExp = tPlayer.iExp * 0.1f;
								int iGold = tPlayer.tInventory.iGold * 0.1f;

								tPlayer.iExp -= iExp;
								tPlayer.tInventory.iGold -= iGold;

								cout << iExp << " 경험치를 잃었습니다." << endl;
								cout << iGold << " Gold를 잃었습니다." << endl;

								// 플레이어의 HP와 MP를 회복한다.
								tPlayer.iHP = tPlayer.iHPMax;
								tPlayer.iMP = tPlayer.iMPMax;
								tMonster.iHP = tMonster.iHPMax;
								tMonster.iMP = tMonster.iMPMax;
							}

							system("pause");
					}
						break;
					}

				}
			}

			break;
		case MM_STORE:
			while (true)
			{
				system("cls");
				cout << "******************************* 상점 ***************************" << endl;
				cout << "1. 무기상점" << endl;
				cout << "2. 방어구상점" << endl;
				cout << "3. 뒤로가기" << endl;
				cout << "상점을 선택하세요 : ";
				cin >> iMenu;

				if (cin.fail())
				{
					cin.clear();
					cin.ignore(1024, '\n');
					continue;
				}

				else if (iMenu == SM_BACK)
					break;

				switch (iMenu)
				{
				case SM_WEAPON:
					while (true)
					{
						system("cls");

						cout << "******************************* 무기상점 ***************************" << endl;
						// 판매 목록을 보여준다.
						cout << "1. 나무검" << endl;
						cout << "2. 돌검" << endl;
						cout << "3. 뒤로가기" << endl;
						cout << "아이템을 선택하세요 : ";
						cin >> iMenu;

						if (cin.fail())
						{
							cin.clear();
							cin.ignore(1024, '\n');
							continue;
						}

						else if (iMenu == IT_BACK)
							break;

						switch (iMenu)
						{
						case IAT_WOODSWORD:
						{
							if (tPlayer.tInventory.iItemCount >= 20)
							{
								cout << "가방이 꽉 찼습니다." << endl;
								system("pause");
								break;
							}
							else if (tPlayer.tInventory.iGold < tStoreWeapon[iMenu - 1].iPrice)
							{
								cout << "돈이 모자랍니다." << endl;
								system("pause");
								break;
							}
							else
							{
								strcpy_s(tPlayer.tInventory.tItem[tPlayer.tInventory.iItemCount].strName, "나무검");
								tPlayer.tInventory.iItemCount++;
								tPlayer.iAttackMin += tStoreWeapon[iMenu - 1].iMin;
								tPlayer.iAttackMax += tStoreWeapon[iMenu - 1].iMax;
								tPlayer.tInventory.iGold -= tStoreWeapon[iMenu - 1].iPrice;
								cout << "구매에 성공하였습니다." << endl;
								system("pause");
							}
							break;
						}
						case IAT_STONESWORD:
						{
							if (tPlayer.tInventory.iItemCount >= 20)
							{
								cout << "가방이 꽉 찼습니다." << endl;
								system("pause");
								break;
							}
							else if (tPlayer.tInventory.iGold < tStoreWeapon[iMenu - 1].iPrice)
							{
								cout << "돈이 모자랍니다." << endl;
								system("pause");
								break;
							}
							else
							{
								strcpy_s(tPlayer.tInventory.tItem[tPlayer.tInventory.iItemCount].strName, "돌검");
								tPlayer.tInventory.iItemCount++;
								tPlayer.iAttackMin += tStoreWeapon[iMenu - 1].iMin;
								tPlayer.iAttackMax += tStoreWeapon[iMenu - 1].iMax;
								tPlayer.tInventory.iGold -= tStoreWeapon[iMenu - 1].iPrice;
								cout << "구매에 성공하였습니다." << endl;
								system("pause");
							}
							break;
						}
						}
					}
					break;
				case SM_ARMOR:
					break;
				}
			}
			break;
		case MM_INVENTORY:
			system("cls");
			cout << "******************************* 가방 ***************************" << endl;
			cout << tPlayer.tInventory.iItemCount << endl;
			for (int i = 0; i < tPlayer.tInventory.iItemCount; i++) {
				cout << tPlayer.tInventory.tItem[i].strName << endl;
			}
			system("pause");
			break;
		default:
			cout << "잘못 선택하였습니다." << endl;
			break;
		}
	}

	return 0;
}

+ Recent posts