1. main.cpp
#include "Status.h"
#include "Player.h"
#include "Warrior.h"
#include "Magician.h"
#include "Thief.h"
#include "Archer.h."
#include "Item.h"
#include "Monster.h" //step7
#include "MonsterSpawn.h" //step7
#include "Battle.h" //step7
#include "AlchemyWorkshop.h" //step7
#include <cstdlib>
#include <iostream>
#include <vector> //인벤토리 관리
using namespace std;
//step1 ~ step2
int main() {
string name;
const int SIZE = 4;
int stat[SIZE] = { 0 };
//stat[0] = hp, stat[1] = mp, stat[2] = attack, stat[3] = defense
cout << "===========================================" << endl;
cout << " [ 던전 탈출 텍스트 RPG ] " << endl;
cout << "===========================================" << endl;
cout << "용사의 이름을 입력해주세요: ";
cin >> name;
while (true) {
cout << "\nHP와 MP를 입력해주세요: ";
cin >> stat[0] >> stat[1];
if (stat[0] > 50 && stat[1] > 50) {
break;
}
else {
cout << "HP나 MP의 값이 너무 작습니다. 다시 입력해주세요." << endl;
}
}
while (true) {
cout << "공격력과 방어력을 입력해주세요: ";
cin >> stat[2] >> stat[3];
if (stat[2] > 50 && stat[3] > 50) {
break;
}
else {
cout << "공격력이나 방어력이 너무 작습니다. 다시 입력해주세요." << endl;
}
}
printStatus(name, stat); //Status.h, Status.cpp
//step3.
int hpPotion = 5;
int mpPotion = 5;
bool isGameStart = false;
cout << "\n* HP 포션 5개, MP 포션 5개가 기본 지급되었습니다." << endl;
system("pause > nul");
while (!isGameStart) {
system("cls");
cout << "\n============================================" << endl;
cout << "< 캐릭터 강화 >" << endl;
cout << "1. HP UP 2. MP UP 3. 공격력 2배" << endl;
cout << "4. 방어력 2배 5. 현재 능력치 0. 게임 시작" << endl;
cout << "============================================" << endl;
cout << "번호를 선택해주세요: ";
int choice;
cin >> choice;
switch (choice) {
case 1: //hp포션 사용
if (hpPotion > 0) {
stat[0] += 20;
hpPotion--;
cout << "* HP가 20 증가했습니다. (HP 포션 차감: 남은 포션 " << hpPotion << "개)" << endl;
system("pause > nul");
}
else {
cout << "포션 부족! HP 포션이 없습니다." << endl;
system("pause > nul");
}
break;
case 2: //mp포션 사용
if (mpPotion > 0) {
stat[1] += 20;
mpPotion--;
cout << "* MP가 20 증가했습니다. (MP 포션 차감: 남은 포션 " << mpPotion << "개)" << endl;
system("pause > nul");
}
else {
cout << "포션 부족!MP 포션이 없습니다." << endl;
system("pause > nul");
}
break;
case 3: //공격력 2배
stat[2] *= 2;
cout << "* 공격력이 2배가 되었습니다. (현재 공격력: " << stat[2] << ")" << endl;
system("pause > nul");
break;
case 4: //방어력 2배
stat[3] *= 3;
cout << "* 방어력이 2배가 되었습니다. (현재 방어력: " << stat[3] << ")" << endl;
system("pause > nul");
break;
case 5: //상태창 출력
printStatus(name, stat);
system("pause > nul");
break;
case 0: //게임 시작 -> isGameStart = true
cout << "게임을 시작합니다!" << endl;
isGameStart = true;
system("pause > nul");
break;
default: //번호 입력 잘못했을 때 다시 while문 처음으로
cout << "잘못된 번호입니다. 다시 선택해주세요." << endl;
system("pause > nul");
break;
}
}
//step4. 직업 선택
Player* player = nullptr; //Player* Player타입의 객체를 가리키는 포인터(player)
//= nullptr; 로 일단 포인터를 비워둠. 나중에 직업을 가리키기 위함
//new를 이용해서 각자 직업을 케이스별로 할당
bool isjConfirmed = false;
while (!isjConfirmed) {
cout << "\n< 전직 시스템 >" << endl;
cout << name << "님, 직업을 선택해주세요!" << endl;
cout << "1. 전사 2. 마법사 3. 도적 4. 궁수" << endl;
cout << "선택: ";
int jChoice;
cin >> jChoice;
//혹시 모르니까 한번 더 메모리를 비워줌
if (player != nullptr) {
delete player;
player = nullptr;
}
switch (jChoice) {
case 1: //전사
player = new Warrior(name, stat[0], stat[1], stat[2], stat[3]);
cout << "* " << player->getJob() << "로 전직하였습니다. (HP + 30)" << endl;
player->attack();
break;
case 2: //마법사
player = new Magician(name, stat[0], stat[1], stat[2], stat[3]);
cout << "* " << player->getJob() << "로 전직하였습니다. (MP + 30)" << endl;
player->attack();
break;
case 3: //도적
player = new Thief(name, stat[0], stat[1], stat[2], stat[3]);
cout << "* " << player->getJob() << "로 전직하였습니다. (Power + 10)" << endl;
player->attack();
break;
case 4: //궁수
player = new Archer(name, stat[0], stat[1], stat[2], stat[3]);
cout << "* " << player->getJob() << "로 전직하였습니다. (Power + 5, Defence + 5)" << endl;
player->attack();
break;
default:
cout << "다시 선택해주세요. 1~4 사이의 숫자를 입력해야 합니다." << endl;
break;
}
//직업 선택 이후 확정 or 다시 선택
if (player != nullptr) {
int QChoice;
cout << "\n[" << player->getJob() << "] 을 선택하셨습니다. 이대로 확정하시겠습니까?" << endl;
cout << "1. 확정 2. 다시 선택\n1 또는 2를 입력하세요: ";
cin >> QChoice;
if (QChoice == 1) { //확정
isjConfirmed = true;
cout << "전직이 완료되었습니다." << endl;
player->printPlayerStatus();
system("pause > nul");
}
else { //다시 선택
delete player;
player = nullptr;
cout << "\n직업을 다시 선택하겠습니다." << endl;
}
}
}
//인벤토리 관련
vector<Item> inventory;
const int inventory_MAX = 10;
//메인메뉴 관련 (While + switch case)
bool isMenuRunning = true;
int MenuChoice;
AlchemyWorkshop Workshop; //true면 이제 함수가 실행
while (isMenuRunning) {
system("cls");
cout << "\n=== 메인 메뉴 ===" << endl;
cout << "[현재 상태] HP: " << player->getHP() << " | MP: " << player->getMP() << endl;
cout << "1. 던전 입장" << endl;
cout << "2. 인벤토리 확인" << endl;
cout << "3. 포션 제작소" << endl;
cout << "0. 게임 종료" << endl;
cout << "==================" << endl;
cout << "선택: ";
cin >> MenuChoice;
switch (MenuChoice) {
//던전에 입장하면 랜덤 몬스터(MonsterSpawn)가 소환되고, 전투 루프가 시작 + 루프가 끝나면 인벤토리에 아이템 더하기
case 1: {
//MonsterSpawn 클래스의 정적함수인 RandomNormalMonster()를 호출
Monster currentMonster = MonsterSpawn::RandomNormalMonster();
//Battle 클래스의 startBattle함수 실행. 전투가 끝난 후 droppedItem 변수에 아이템 정보 받음.
Item droppedItem = Battle::startBattle(player, currentMonster);
//인벤토리에 이미 같은 아이템이 있는지 확인
if (droppedItem.name != "") {
bool isInventoryFound = false;
//같은 아이템이 있는 경우
//inventory는 vector를 사용하기에 auto& 으로 처음부터 끝까지 스캔.
for (auto& item : inventory) {
if (item.name == droppedItem.name) {
item.count += droppedItem.count; //아이템 카운트를 1 늘림.
isInventoryFound = true;
break;
}
}
//같은 아이템이 없는 경우
if (!isInventoryFound) {
if (inventory.size() < inventory_MAX) {
inventory.push_back(droppedItem); //인벤토리에 새 칸을 추가하기 위해 push_back 사용.
}
else {
cout << " -> 인벤토리가 가득 차서 [" << droppedItem.name << "]을(를) 버렸습니다." << endl;
}
}
}
break;
}
//인벤토리 확인, 아이템 리스트
case 2: {
system("cls");
cout << "[ 인벤토리 (" << inventory.size() << "/" << inventory_MAX << ") ]" << endl;
if (inventory.empty()) {
cout << "비어 있음" << endl;
}
//인벤토리의 아이템 출력하기. range-for --> for (요소데이터타입 변수이름 : 컨테이너이름) {}
else {
int i = 1;
for (const auto& item : inventory) {
cout << i << ". "; //번호 매기기
item.PrintItemInfo();
cout << endl;
i++;
}
}
cout << "\n0. 돌아가기" << endl;
system("pause > nul");
break;
}
//포션 제작소.
case 3:
Workshop.RunWorkshopMenu(inventory);
break;
//게임 종료.
case 0:
isMenuRunning = false;
cout << "게임을 종료합니다." << endl;
break;
//잘못 입력했을 때 크래시 방지
default:
cout << "잘못된 선택입니다." << endl;
system("pause > nul");
break;
}
}
//메모리 해제 후 게임종료
if (player != nullptr) {
delete player;
player = nullptr;
}
return 0;
}
2. Monster.h
#ifndef MONSTER_H_
#define MONSTER_H_
#include <iostream>
#include <string>
#include "Player.h"
using namespace std;
class Monster {
private:
string name;
int hp, power, defence;
string dropItemName;
int dropItemPrice;
public: //생성자
Monster(string name, int hp, int power, int defence, string item, int price)
: name(name), hp(hp), power(power), defence(defence), dropItemName(item), dropItemPrice(price) {
}
//getter, setter
string getName() { return name; }
int getHP() { return hp; }
void setHP(int value) { hp = value; }
int getPower() { return power;}
int getDefence() { return defence; }
string getDropItemName() { return dropItemName; }
//몬스터의 공격을 정의하는 메서드(멤버함수)
void attack(Player* player) {
int damage = power - player->getDefence(); //몬스터의 공격력 - 플레이어의 방어력
if (damage <= 0) { //데미지가 0보다 작을 때는 데미지 1.
damage = 1;
}
player->setHP(player->getHP() - damage); //데미지만큼 플레이어의 체력을 깎음
cout << name << "의 공격!" << player->getName() << "에게 " << damage << " 데미지!" << endl;
}
};
#endif
3-1. MonsterSpawn.h
#ifndef MONSTERSPAWN_H_
#define MONSTERSPAWN_H_
#include "Monster.h"
class MonsterSpawn {
public:
//일반 몬스터
static Monster RandomNormalMonster();
//보스 몬스터는 나중에 작성
};
#endif;
3-2. MonsterSpawn.cpp
#include "MonsterSpawn.h"
#include <cstdlib> //rand 사용
//현재 등록된 몬스터
//0슬라임, 1허브자라버섯, 2베리스무디, 3고스트, 4에비안
//일반 몬스터 랜덤 소환
Monster MonsterSpawn::RandomNormalMonster() {
int randNum = rand() % 5; //일단 5마리.
switch (randNum) {
//randNum을 돌려서 Monster.h의 생성자에 값을 채운다.
case 0:
return Monster("슬라임", 30, 20, 10, "슬라임의 끈적한 젤리", 30);
case 1:
return Monster("허브자라버섯", 50, 30, 15, "허브", 20);
case 2:
return Monster("베리스무디", 40, 25, 20, "베리", 20);
case 3:
return Monster("고스트", 200, 20, 10, "마나 결정", 30);
case 4:
return Monster("에비안", 120, 10, 20, "맑은물", 20);
default:
return Monster("미싱노", 1000, 1000, 1000, "에러 아이템", 1000);
}
}
//보스 몬스터 소환
4-1. Battle.h
#ifndef BATTLE_MANAGER_H_
#define BATTLE_MANAGER_H_
#include "Player.h"
#include "Monster.h"
#include "Item.h"
class Battle {
public:
static Item startBattle(Player* player, Monster& monster);
};
#endif
4-2. Battle.cpp
#include "Battle.h"
#include "Item.h"
#include <iostream>
using namespace std;
Item Battle::startBattle(Player* player, Monster& monster) {
cout << "\n===========================================" << endl;
cout << "[ 전투 시작! ] " << player->getName() << "(" << player->getJob() << ") vs " << monster.getName() << endl;
cout << "===========================================" << endl;
while (player->getHP() > 0 && monster.getHP() > 0) { //둘다 살아있을 때 전투루프
cout << "\n--- 플레이어 턴 ---" << endl; //플레이어 턴, 플레이어 공격
player->attack();
int playerDamage = player->getPower() - monster.getDefence(); //플레이어의 공격 데미지
if (playerDamage <= 0) {
playerDamage = 1;
}
int beforeMonsterHP = monster.getHP(); //몬스터의 현재 체력
monster.setHP(beforeMonsterHP - playerDamage); //몬스터 이전 체력 - 플레이어 공격 데미지
cout << monster.getName() << "에게 " << playerDamage << " 데미지!" << endl;
cout << monster.getName() << " HP: " << beforeMonsterHP << " -> " << monster.getHP();
//몬스터가 사망했을 경우 문자열을 추가로 출력 -> Win 방향으로
if (monster.getHP() <= 0) {
cout << " (사망)" << endl;
break; //몬스터가 사망했으므로 전투루프 종료
}
//몬스터의 공격
cout << "\n--- 몬스터 턴 ---" << endl;
monster.attack(player);
cout << player->getName() << " HP: " << player->getHP() << endl;
//플레이어가 패배했을 경우 문자열을 추가로 출력 -> Lose 방향으로
if (player->getHP() <= 0) {
cout << "\n[ 패배 ] " << player->getName() << "님은 패배했습니다." << endl;
break;
}
}
//이제 전투 루프 탈출했으니까 승리 or 패배 텍스트 출력
if (monster.getHP() <= 0) {
cout << "\n★ 전투 승리!" << endl;
cout << " -> " << monster.getDropItemName() << " 획득!" << endl;
cout << " -> 인벤토리에 저장되었습니다." << endl;
cout << "\n계속하려면 아무 키나 누르세요." << endl;
system("pause > nul");
return Item(monster.getDropItemName(), 50, 1); //생성자를 활용해서 가독성을 높였음.
}
if (player->getHP() < 0) {
cout << "..전투에서 패배하였습니다.." << endl;
cout << "..메인 메뉴로 돌아가세요.." << endl;
system("pause > nul");
return Item(); //함수를 종료하고 startBattle 시작 전으로 돌아감.
}
}
5-1. Item.h
#ifndef ITEM_H_
#define ITEM_H_
#include <iostream>
#include <string>
using namespace std;
//struct. Item 구조체 작성. 나만의 데이터 타입(자료형) 생성. 여러 자료형을 하나로 묶는 형태.
struct Item {
string name;
int price;
int count;
//아이템 생성자
Item(string name = "", int price = 0, int count = 1)
: name(name), price(price), count(count) {
}
//인벤토리에서 아이템 정보 출력하는 멤버 함수
void PrintItemInfo() const {
if (name == "") {
cout << "(비어 있음)";
}
else {
cout << name << " x" << count << " (" << price << "G)" << endl;
}
}
};
#endif
6-1. PotionRecipe.h
#ifndef POTIONRECIPE_H_
#define POTIONRECIPE_H_
#include <iostream>
#include <string>
using namespace std;
struct PotionRecipe {
string potionName;
string ingredient1;
string ingredient2;
PotionRecipe(string potionName, string ingredient1, string ingredient2)
: potionName(potionName), ingredient1(ingredient1), ingredient2(ingredient2) {
}
void PrintRecipe() const {
cout << "-> " << potionName << ": " << ingredient1 << " x1, " << ingredient2 << " x1" << endl;
}
};
#endif
6-2. AlchemyWorkshop.h
#ifndef ALCHEMYWORKSHOP_H_
#define ALCHEMYWORKSHOP_H_
#include "PotionRecipe.h"
#include "Item.h"
#include <string>
#include <vector>
class AlchemyWorkshop {
private:
vector<PotionRecipe> recipes; //레시피 저장소
public:
AlchemyWorkshop();
// 포션 제작소에서 사용할 메뉴 메서드.
void RunWorkshopMenu(vector<Item>& inventory);
//포션 제작 메서드. 참조자로 받아서 원본 인벤토리에서 재료가 빠져나가게 함.
void CraftPotion(vector<Item>& inventory);
private:
void ShowAllRecipes() const;
void SearchByName(string potionName) const;
void SearchByIngredient(string ingredient) const;
//포션 제작 담당 helper함수 (재료가 있는지 체크하고, 제작하면 재료를 제거)
bool HasIngredient(const vector<Item>& inventory, string ingredientName) const;
void RemoveIngredient(vector<Item>& inventory, string ingredientName);
};
#endif
6-3. AlchemyWorkshop.cpp
#include "AlchemyWorkshop.h"
#include <iostream>
AlchemyWorkshop::AlchemyWorkshop() {
//데이터 삽입은 맨 마지막에 되도록 push_back. vector니까 가능하다.
//PotionRecipe(string potionName, string ingredient1, string ingredient2) //PotionRecipe.h
recipes.push_back(PotionRecipe("HP포션", "허브", "맑은물"));
recipes.push_back(PotionRecipe("MP포션", "마나 결정", "맑은물"));
recipes.push_back(PotionRecipe("스태미나포션", "허브", "베리"));
recipes.push_back(PotionRecipe("강화포션", "슬라임의 끈적한 젤리", "베리"));
}
//포션 제작(레시피 검색, 재료 확인, 재료 소모, 인벤토리에 추가)
void AlchemyWorkshop::CraftPotion(vector<Item>& inventory) {
string potionName;
cout << "\n제작할 포션 이름을 입력하세요: ";
cin >> potionName;
//potionName 입력받고 쭉 훑어봐서 같은게 있으면 그걸 출력. 같은게 없으면 별도의 문자열 출력
//레시피 검색해서 같은 거 찾기.
PotionRecipe* targetRecipe = nullptr; //포인터 targetRecipe를 생성하고 nullptr로 초기화
for (auto& r : recipes) {
if (r.potionName == potionName) { //1부터 주소값에 있는 potionName을 찾아서 검색하고.
targetRecipe = &r; //값을 가져와서 가리키시오.
break;
}
}
// 포션 이름 검색 시 존재하지 않는 경우.
if (targetRecipe == nullptr) {
cout << "-> [" << potionName << "] 레시피가 존재하지 않습니다." << endl;
system("pause");
return;
}
//재료 확인
bool hasIngredient1 = AlchemyWorkshop::HasIngredient(inventory, targetRecipe->ingredient1);
bool hasIngredient2 = AlchemyWorkshop::HasIngredient(inventory, targetRecipe->ingredient2);
if (!hasIngredient1 || !hasIngredient2) {
cout << "-> 재료가 부족합니다! (필요: " << targetRecipe->ingredient1 << ", " << targetRecipe->ingredient2 << ")" << endl;
system("pause");
return;
}
//재료 소모 즉, hasIngredient 1, 2가 모두 true값일때.
//void RemoveIngredient(vector<Item>& inventory, string ingredientName);
AlchemyWorkshop::RemoveIngredient(inventory, targetRecipe->ingredient1);
AlchemyWorkshop::RemoveIngredient(inventory, targetRecipe->ingredient2);
//만든 포션이 인벤토리에 있는 것과 중복되는 지 체크하고 중복된다면 item.count만 늘리면 되고,
bool isinventoryFound = false;
for (auto& item : inventory) {
if (item.name == targetRecipe->potionName) {
item.count++;
isinventoryFound = true;
break;
}
}
//중복되지 않으면 인벤토리에 1개 추가.
//아이템 생성자. Item(string name = "", int price = 0, int count = 1)
if (!isinventoryFound) {
inventory.push_back(Item(targetRecipe->potionName, 100, 1));
}
cout << " [" << targetRecipe->potionName << "] 제작을 완료했습니다! 인벤토리를 확인해보세요." << endl;
system("pause");
}
//인벤토리에 해당 재료가 1개 이상 있는지 확인하는 멤버함수
//bool HasIngredient(const vector<Item>& inventory, string ingredientName) const;
bool AlchemyWorkshop::HasIngredient(const vector<Item>& inventory, string ingredientName) const {
for (const auto& item : inventory) {
if (item.name == ingredientName && item.count > 0) {
return true;
}
}
return false;
}
//인벤토리에서 재료 1개를 차감하는 멤버함수
//void RemoveIngredient(vector<Item>&inventory, string ingredientName);
void AlchemyWorkshop::RemoveIngredient(vector<Item>& inventory, string ingredientName) {
//인벤토리를 처음부터 끝까지 훑어보고 이름이 같은거를 count 하나 빼기.
for (auto item = inventory.begin(); item != inventory.end(); ++item) {
if (item->name == ingredientName) {
item->count--;
if (item->count == 0) { //개수가 0이되면 인벤토리에서 지운다.
inventory.erase(item);
}
return;
}
}
}
// 전체 레시피 검색(제일 쉬움)
//void ShowAllRecipes() const;
void AlchemyWorkshop::ShowAllRecipes() const {
cout << "\n[ 전체 레시피 목록 ]: " << endl;
for (const auto& r : recipes) {
r.PrintRecipe();
}
}
//포션 이름 일치하는 거 검색
//void SearchByName(string potionName) const;
void AlchemyWorkshop::SearchByName(string potionName) const {
bool found = false;
for (const auto& r : recipes) {
if (r.potionName == potionName) {
r.PrintRecipe();
found = true;
break;
}
}
if (!found) {
cout << "-> '" << potionName << "' 레시피를 찾을 수 없습니다." << endl;
}
}
//재료 이름 하나라도 일치하면 검색(제일 어려움)
//void SearchByIngredient(string ingredient) const;
void AlchemyWorkshop::SearchByIngredient(string ingredient) const {
int count = 0; //레시피 개수 출력하려고.
for (const auto& r : recipes) {
if (r.ingredient1 == ingredient || r.ingredient2 == ingredient) {
r.PrintRecipe();
count++;
}
}
//재료 검색 실패시
if (count == 0) {
cout << "-> '" << ingredient << "'(이)가 포함된 레시피를 찾을 수 없습니다." << endl;
}
//재료 검색 성공시 (함수 반환)
else {
cout << "총 " << count << "개의 레시피를 찾았습니다." << endl;
}
}
//포션 제작소 메뉴 만들기
void AlchemyWorkshop::RunWorkshopMenu(vector<Item>& inventory) {
bool inWorkshop = true;
int WorkshopChoice;
while (inWorkshop) {
system("cls");
cout << "\n=== 포션 제작소 ===" << endl;
cout << "1. 전체 레시피 보기\n2. 포션 이름으로 검색\n3. 재료로 검색\n4. 포션 제작\n0. 돌아가기" << endl;
cout << "\n선택: ";
cin >> WorkshopChoice;
if (WorkshopChoice == 1) {
ShowAllRecipes();
system("pause");
}
else if (WorkshopChoice == 2) {
string potionName;
cout << "검색할 포션 이름: ";
cin >> potionName;
SearchByName(potionName);
system("pause");
}
else if (WorkshopChoice == 3) {
string ingredientName;
cout << "검색할 재료: ";
cin >> ingredientName;
SearchByIngredient(ingredientName);
system("pause");
}
else if (WorkshopChoice == 4) {
CraftPotion(inventory);
}
else if (WorkshopChoice == 0) {
inWorkshop = false;
}
else {
cout << "잘못된 선택입니다." << endl;
system("pause");
}
}
}
여기까지 작성하느라 꽤 고생했다. 필수 과제를 처음에 시작하려고 했을 때 막막한 기분만 들었는데, 기본적인 C++을 활용해서 로직을 작성해보는 연습도 하고 실제로 실행도 해보니까 재미를 느끼고 있었다. 뭐든지 결과물을 잘 보는게 중요한 것 같다.
더 나아가서는 오류도 잘 점검하고, 원하는 결과값이 잘 나오는지 꼼꼼히 체크하고, 코드 작성을 시작하기에 앞서 무엇을 먼저 작성할 것인지를 잘 판단하는 실력도 필요하다고 느꼈다.
