💡 '뭘 만들어야 하지?' 생각이 들 땐...
- 클래스 만들기
- 무기, 몬스터, 맵 등 개념을 만들어야 할 때
- 클래스 만들었으면 객체화 시키기!
- 로직 만들기
- 메모리맵을 머릿속에 그리기
- n회 반복 ⇒ 반복문
- 만약 ~ ⇒ 조건문
class FightUnit
{
public:
// [Virtual]
// 자식클래스가 부모클래스와 완전히 동일한 함수를 구현할 경우
// 자식의 함수를 우선적으로 실행한다는 뜻
virtual int GetAtt()
{
return Att;
}
private:
int Att = 10;
};
class Monster : public FightUnit
{
};
class Player : public FightUnit
{
protected:
// [Override]
// 재구현한 함수에는 (필수는 아니지만) override를 붙여주자
int GetAtt() override
{
return FightUnit::GetAtt() + WeaponAtt; // (O)
// return GetAtt() + WeaponAtt; // (X)
// 명시해주지 않으면 재귀가 되어 무한루프에 갇히기 때문
}
private:
int WeaponAtt = 10;
};
void Fight(FightUnit* Left, FightUnit* Right)
// [다형성]
// 겉으로 보기에는 같은 FightUnit이지만, 동작할 때에는 다르게 동작하는 것
// Fight는 인자가 Player인지 Monster인지 알지 못하고, 알 필요도 없다
// 그래서 형변환하여 인자로 받는 것이다
{
Left->GetAtt(); // 20
Right->GetAtt(); // 10
}
int main()
{
Player NewPlayer;
Monster NewMonster;
Fight(&NewPlayer, &NewMonster);
}
// [TextRPG.cpp]
#include <iostream>
#include "Player.h"
#include "FightZone.h"
#include "Town.h"
#include <conio.h>
int main()
{
Player NewPlayer = Player();
NewPlayer.SetName("Player");
FightZone NewFightZone;
Town NewTownZone;
// 메인 메뉴
while (true)
{
NewPlayer.StatusRender();
printf_s("어디로 가시겠습니까?\n");
printf_s("1. 마을.\n");
printf_s("2. 사냥터.\n");
int Select = _getch();
switch (Select)
{
case '1':
NewTownZone.In(NewPlayer); // 플레이어를 마을에 넣기
break;
case '2':
NewFightZone.In(NewPlayer); // 플레이어를 전투공간에 넣기
break;
default:
break;
}
system("cls");
}
}
// [Player.h]
#pragma once
#include "FightUnit.h"
#include "Weapon.h"
class Player : public FightUnit
{
public:
Player();
// 다른 클래스를 값형의 멤버로 두고 있는 경우, public으로 하는 편이다
// 어짜피 Weapon 클래스의 멤버는 알아서 보호되고 있기 때문
Weapon Weapon;
// 사실은... 동적할당을 배운 후, 포인터로 받는 것이 좋다
// 값형일 경우 => 플레이어가 생성될 때부터 손에 들려 있음
// 포인터일 경우 => 없다가 생기는 것이 가능
// 절대로 사라지지 않는다면 값형
// 존재하거나 존재하지 않을 가능성이 있다면 포인터
void StatusRender() override; // Monster와 달리 소지금을 화면에 표시해주기 위해
protected:
int GetRAtt() override; // Monster와 달리 무기의 공격력을 더해주기 위해
};
// [Player.cpp]
#include "Player.h"
#include <iostream>
Player::Player()
{
Weapon.SetName("Steel Sword");
Weapon.SetAtt(10);
}
void Player::StatusRender()
{
int Size = printf_s("%s", Name);
for (int i = 0; i < 50 - Size; i++)
{
int a = 0;
printf_s("-");
}
printf_s("\n");
printf_s("공격력 %d ~ %d\n", MinAtt, MaxAtt);
printf_s("체력 %d\n", Hp);
printf_s("소지금 %d\n", Gold);
for (int i = 0; i < 50; i++)
{
printf_s("-");
}
printf_s("\n");
}
int Player::GetRAtt()
{
CurAtt = FightUnit::GetRAtt() + Weapon.GetAtt() + Weapon.GetEquipUp();
return CurAtt;
}
// [Weapon.h]
#pragma once
#include "NameUnit.h"
// Player에게 무기를 들려주기 위해 생성
class Weapon : public NameUnit
{
public:
// Get 함수
inline int GetAtt() const
{
return Att;
}
inline int GetEquipUp() const
{
return EquipUp;
}
// Set 함수
inline void SetAtt(int _Att)
{
Att = _Att;
}
inline void SetEquipUp(int _EquipUp)
{
EquipUp= _EquipUp;
}
private:
int Att;
int EquipUp = 0;
};
// [StatusUnit.h]
#pragma once
#include "NameUnit.h"
class StatusUnit : public NameUnit
{
public:
// Get 함수
inline int GetHp() const
{
return Hp;
}
inline int GetMaxHp() const
{
return MaxHp;
}
inline int GetGold() const
{
return Gold;
}
// Set? 함수
inline void HpReset()
{
Hp = MaxHp;
}
inline void AddGold(int _Gold)
{
Gold += _Gold;
}
// 추가로 얻는 Gold를 난수로 계산해주는 함수
int RandomGold(int _Min, int _Max);
// 화면에 Status를 띄워주는 함수
virtual void StatusRender();
protected:
int Hp = 100;
int MaxHp = 100;
int MinAtt = 10;
int MaxAtt = 20;
int Speed = 20;
int Gold = 0;
};
// [StatusUnit.cpp]
#include "StatusUnit.h"
#include <iostream>
#include <random>
int StatusUnit::RandomGold(int _Min, int _Max)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(_Min, _Max);
return dist(gen);
}
void StatusUnit::StatusRender()
{
int Size = printf_s("%s", Name);
for (int i = 0; i < 50 - Size; i++)
{
int a = 0;
printf_s("-");
}
printf_s("\n");
printf_s("공격력 %d ~ %d\n", MinAtt, MaxAtt);
printf_s("체력 %d\n", Hp);
for (int i = 0; i < 50; i++)
{
printf_s("-");
}
printf_s("\n");
}
// [NameUnit.h]
#pragma once
// 다른 Status들과 구분하기 위해 분리
class NameUnit
{
public:
inline char* GetName()
{
return Name;
}
void SetName(const char* _Name);
protected:
char Name[100] = "None";
};
// [NameUnit.cpp]
#include "NameUnit.h"
void NameUnit::SetName(const char* _Name)
{
int Cnt = 0;
while (_Name[Cnt])
{
++Cnt;
}
for (int i = 0; i < Cnt + 1; i++)
{
Name[i] = _Name[i];
}
}
// [FightUnit.h]
#pragma once
#include "StatusUnit.h"
class FightUnit : public StatusUnit
{
public:
// Get 함수
inline int GetMinAtt()
{
return MinAtt;
}
inline int GetMaxAtt()
{
return MaxAtt;
}
inline bool IsDeath()
{
return Hp == 0;
}
// 난수로 공격값과 선공을 정하기 위한 함수
virtual int GetRAtt(); // Monster와 Player에게 다르게 적용된다
int GetRSpeed() const;
// Fight용 함수
void DamageLogic(FightUnit& _Unit);
void DamageRender();
protected:
int CurAtt = 0;
};
// [FightUnit.cpp]
#include "FightUnit.h"
#include <iostream>
#include <random>
int FightUnit::GetRAtt()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(MinAtt, MaxAtt);
CurAtt = dist(gen);
// DamageRender 함수에서 주체를 공격하는 사람으로 하기 위해
// CurAtt를 DamageLogic 함수가 아닌, GetRAtt 함수에서 정해준다
return CurAtt;
}
int FightUnit::GetRSpeed() const
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(MinAtt, MaxAtt);
return dist(gen);
}
void FightUnit::DamageLogic(FightUnit& _Unit)
{
Hp -= _Unit.GetRAtt();
if (Hp < 0)
{
Hp = 0;
}
}
void FightUnit::DamageRender()
{
printf_s("%s 가 %d의 공격력으로 공격했습니다.\n", Name, CurAtt);
}
// [ZoneBase.h]
#pragma once
// 지역들의 틀을 만들어주기 위해
class ZoneBase
{
public:
virtual void In(class Player& _Player) // 플레이어가 들어갈 수 있는 공간이라는 뜻
// FightZone과 Town용이 다르다
{
}
};
// [FightZone.h]
#pragma once
#include "Monster.h"
#include "ZoneBase.h"
class FightZone : public ZoneBase
{
public:
FightZone();
// 승패가 갈릴 때까지 전투하는 함수
void In(class Player& _Player) override; // 전방선언 => 헤더 파일을 추가하지 않아도 된다
protected:
Monster NewMonster = Monster();
// 1회 합을 주고 받는 함수
bool FightLogic(FightUnit& _First, FightUnit& _Last, FightUnit& _Top, FightUnit& _Bot);
};
// [FightZone.cpp]
#include "FightZone.h"
#include <conio.h>
#include <iostream>
#include "Player.h"
FightZone::FightZone()
{
NewMonster.SetName("Monster");
}
void FightZone::In(Player& _Player)
{
system("cls");
while (true)
{
_Player.StatusRender();
NewMonster.StatusRender();
bool IsEnd = false;
if (_Player.GetRSpeed() >= NewMonster.GetRSpeed())
{
printf_s("%s의 선공\n", _Player.GetName());
IsEnd = FightLogic(_Player, NewMonster, _Player, NewMonster);
}
else {
printf_s("%s의 선공\n", NewMonster.GetName());
IsEnd = FightLogic(NewMonster, _Player, _Player, NewMonster);
}
if (IsEnd == true)
{
// <과제 4. 몬스터가 죽었을 때 플레이어가 골드를 얻게 하기>
// => 골드의 범위는 100 ~ 500
if (NewMonster.IsDeath() == true)
{
int RGold = _Player.RandomGold(100, 500);
_Player.AddGold(RGold);
printf_s("%d만큼의 골드를 벌었다!\n", RGold);
int Input = _getch();
}
// <과제 3. 전투가 끝난 후에 다시 몬스터의 Hp가 차게 만들기>
NewMonster.HpReset();
return;
}
}
}
bool FightZone::FightLogic(FightUnit& _First, FightUnit& _Second, FightUnit& _Top, FightUnit& _Bot)
{
{
int Input = _getch();
}
system("cls");
// First가 선공
_Second.DamageLogic(_First);
_Top.StatusRender();
_Bot.StatusRender();
_First.DamageRender();
if (_Second.IsDeath() == true)
{
printf_s("게임 종료\n");
int Input = _getch();
return true;
}
{
int Input = _getch();
}
system("cls");
// Second가 후공
_First.DamageLogic(_Second);
_Top.StatusRender();
_Bot.StatusRender();
_First.DamageRender();
_Second.DamageRender();
if (_First.IsDeath() == true)
{
printf_s("게임 종료\n");
int Input = _getch();
return true;
}
{
int Input = _getch();
}
system("cls");
return false;
};
// [Town.h]
#pragma once
#include "ZoneBase.h"
// 마을이라는 새로운 지역 생성
class Town : public ZoneBase
{
public:
void In(class Player& _Player) override;
void WeaponUp(Player& _Player);
void Heal(Player& _Player);
};
// [Town.cpp]
#include "Town.h"
#include "Player.h"
#include <iostream>
#include <conio.h>
#include <random>
void Town::In(Player& _Player)
{
while (true)
{
system("cls");
_Player.StatusRender();
printf_s("마을에서 무엇을 하시겠습니까?\n");
printf_s("1. 강화.\n");
printf_s("2. 치료.\n");
printf_s("3. 나간다.\n");
int Select = _getch();
switch (Select)
{
case '1':
WeaponUp(_Player);
break;
case '2':
Heal(_Player);
break;
case '3':
return;
default:
break;
}
}
}
/*
<과제 1. 무기 강화하기>
=> 플레이어의 무기 강화 상태를 보여주기
=> "1. 강화한다, 2. 나간다."의 선택지 보여주기
=> 실제로 무기 강화수치 올리기
0 ~ 9강 : 실패해도 강화수치가 떨어지지 않는다
10 ~ 19강 : 실패하면 강화수치가 5씩 떨어진다
20 ~ 29강 : 실패하면 강화수치가 0이 된다
30강보다 강화할 수는 없다
*/
void Town::WeaponUp(Player& _Player)
{
while (true)
{
int EquipUp = _Player.Weapon.GetEquipUp();
system("cls");
_Player.StatusRender();
printf_s("강화상점에서 무엇을 하시겠습니까?\n");
printf_s("현재 무기 강화수치 : %d\n", EquipUp);
printf_s("1. 강화한다.\n");
printf_s("2. 나간다.\n");
int Select = _getch();
system("cls");
_Player.StatusRender();
switch (Select)
{
case '1':
{
// <과제 5. 무기 강화 1회당 골드를 소모하게 만들기>
// => 강화 수치마다 강화비용이 증가되도록
int EquipUpGold = (EquipUp + 1) * 100;
if (_Player.GetGold() < EquipUpGold)
{
printf_s("소지금이 부족하여 강화할 수 없습니다.\n");
break;
}
if (EquipUp == 30)
{
printf_s("더이상 강화할 수 없습니다.\n");
break;
}
_Player.AddGold(-EquipUpGold);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(0, 1);
system("cls");
_Player.StatusRender();
if (dist(gen) == 1)
{
_Player.Weapon.SetEquipUp(EquipUp + 1);
printf_s("강화에 성공했습니다!\n");
}
else
{
printf_s("강화에 실패했습니다!\n");
if (EquipUp >= 20)
{
_Player.Weapon.SetEquipUp(0);
}
else if (EquipUp >= 10)
{
_Player.Weapon.SetEquipUp(EquipUp - 1);
}
}
printf_s("무기 강화수치 : %d\n", _Player.Weapon.GetEquipUp());
break;
}
case '2':
return;
default:
break;
}
int Input = _getch();
}
}
// <과제 2. 플레이어가 치료되게 만들기>
void Town::Heal(Player& _Player)
{
system("cls");
_Player.StatusRender();
if (_Player.GetHp() == _Player.GetMaxHp())
{
printf_s("체력이 가득 차 있습니다.\n");
int Input = _getch();
return;
}
int HealAmount = _Player.GetMaxHp() - _Player.GetHp();
if (_Player.GetGold() < HealAmount)
{
printf_s("소지금이 부족하여 치료할 수 없습니다.\n");
int Input = _getch();
return;
}
_Player.AddGold(-HealAmount);
_Player.HpReset();
system("cls");
_Player.StatusRender();
printf_s("%s가 치료되었습니다.", _Player.GetName());
int Input = _getch();
}