main → 로비 → 필드 → 전투 흐름을 머릿속에 그리고, 코드를 따라갈 수 있다.이미지가 없어도 흐름이 잡히도록, 텍스트로 “길”부터 고정합니다.
main()
└─ EnterLobby()
├─ SelectPlayer()
├─ (입력) 1: EnterField()
│ ├─ CreateRandomMonster()
│ ├─ (입력) 1: EnterBattle()
│ │ └─ WaitForNextKey()
│ └─ (입력) 그 외: 로비로 복귀
└─ (입력) 그 외: 종료
핵심은 “함수 이름만 봐도 흐름이 읽히는 구조”를 만드는 것입니다.
정석 게임 루프:
입력(Input) → 로직(Logic) → 출력(Render)
텍스트RPG 예제에서는 출력/입력이 코드에 섞여 있습니다(콘솔 기반이라 어쩔 수 없는 타협).
하지만 “머릿속에서는” 항상 Input/Logic/Render를 분리해서 생각하는 습관이 중요합니다.
강의에서는 “구조를 단순하게 만들기 위해” 전역 변수를 사용했습니다.
학습 초반에는 괜찮지만, 규모가 커지면 전역은 디버깅 지옥이 되기 쉽습니다.
그래서 “일단 전역으로 만들고(학습용), 다음 단계에서 인자로 넘겨서 전역을 제거한다”를 목표로 잡으면 좋습니다.
enum PlayerType { PT_None, PT_Knight, PT_Archer, PT_Mage };
enum MonsterType { MT_None, MT_Slime, MT_Orc, MT_Skeleton };
struct StatInfo {
int hp;
int attack;
int defence;
};
PlayerType playerType;
StatInfo playerStat;
MonsterType monsterType;
StatInfo monsterStat;
enum으로 매직 넘버를 없애서 읽히는 코드를 만든다.StatInfo로 데이터를 묶어서 “HP/ATT/DEF” 관련 로직을 반복하지 않는다.int main() {
srand((unsigned int)time(0)); // 난수 시드 초기화
EnterLobby(); // 로비 함수로 진입 → 게임 시작
return 0;
}
srand(time(0))는 rand()가 매번 같은 패턴을 뱉지 않게(의사난수 시드) 설정합니다.main()은 최대한 짧게: “세팅하고 시작한다”만 남기면, 디버깅/구조 파악이 쉬워집니다.void EnterLobby() {
while (true) {
SelectPlayer(); // 직업 선택
int input;
cin >> input;
if (input == 1)
EnterField(); // 필드로 입장
else
return; // 게임 종료
}
}
학습 포인트:
while (true) 루프를 쓸 때는 “어떤 조건에서 나가는지(return/break)”가 코드에 반드시 드러나야 합니다.return으로 빠져나오면 로비로 복귀합니다.void SelectPlayer() {
while (true) {
int choice;
cin >> choice;
playerType = (PlayerType)choice;
if (choice == PT_Knight) {
playerStat = {150, 10, 5};
break;
} else if (choice == PT_Archer) {
playerStat = {100, 15, 3};
break;
} else if (choice == PT_Mage) {
playerStat = {80, 25, 0};
break;
}
}
}
학습 포인트:
while(true) + break가 자주 등장합니다.void EnterField() {
while (true) {
cout << "[PLAYER] HP : " << playerStat.hp
<< " / ATT : " << playerStat.attack
<< " / DEF : " << playerStat.defence
<< '\n';
CreateRandomMonster(); // 랜덤 몬스터 생성
int input;
cin >> input;
if (input == 1) {
EnterBattle(); // 전투 시작
if (playerStat.hp == 0)
return; // 사망 시 리턴
} else {
return; // 도주 시 리턴
}
}
}
학습 포인트:
EnterField()는 “전투를 여러 번 할 수 있는 상태”이므로 루프가 자연스럽습니다.playerStat.hp == 0 체크는 “전투 종료 후 상위 상태가 후처리”를 하는 전형적인 흐름입니다.실전 디버깅 포인트:
EnterField()의 리턴 조건에 브레이크포인트EnterBattle()에서 HP 감소 직후 조건부 브레이크(hp < 0)void CreateRandomMonster() {
int randomChoice = 1 + rand() % 3;
switch (randomChoice) {
case MT_Slime:
monsterStat = {30, 2, 0};
break;
case MT_Orc:
monsterStat = {80, 15, 5};
break;
case MT_Skeleton:
monsterStat = {100, 20, 10};
break;
}
}
학습 포인트:
rand() % 3의 범위는 0~2이므로 +1로 1~3을 만든다는 감각을 확실히 잡아야 합니다.void EnterBattle() {
while (true) {
int damage = playerStat.attack - monsterStat.defence;
if (damage < 0) damage = 0;
monsterStat.hp -= damage;
if (monsterStat.hp <= 0) {
cout << "몬스터 처치!" << '\n';
WaitForNextKey();
return;
}
int damage2 = monsterStat.attack - playerStat.defence;
if (damage2 < 0) damage2 = 0;
playerStat.hp -= damage2;
if (playerStat.hp <= 0) {
cout << "플레이어 사망..." << '\n';
WaitForNextKey();
playerStat.hp = 0; // 상위 상태에서 조건 체크가 명확해짐
return;
}
}
}
학습 포인트(전투 로직 핵심):
<= 0 로 잡는 습관이 중요합니다.실전 디버깅 포인트(추천):
monsterStat.hp -= damage; 줄에 브레이크포인트 → 몬스터 HP가 줄어드는지 확인playerStat.hp -= damage2; 줄에 조건부 브레이크포인트 → playerStat.hp <= 0일 때만 멈추기void WaitForNextKey() {
cout << "계속하려면 1을 누르세요 >> ";
int input;
cin >> input;
system("cls");
}
학습 포인트:
주의:
system("cls")는 Windows 콘솔에 의존합니다. (다른 환경에서는 동작이 다를 수 있음)system() 호출은 보안/성능 관점에서 지양되는 경우가 많습니다. (학습용으로만 사용)StatInfo로 데이터 뭉치기 → 이후 “인자로 넘기는 설계”로 확장 가능<= 0 체크/0으로 고정(clamp) 확인monsterStat.hp 대신 playerStat.hp를 깎는 오타(브레이크포인트로 즉시 발견)while(true)에서 나가는 조건이 실제로 실행되는지(Call Stack + 조건부 브레이크)EnterBattle()에서 return을 하게 만들었을까?”return이 맞고, break면 왜 부족할까?”