홈으로 돌아가기

ASCII-RPG를 통해 C 학습: 코드와 구조체

이 기사는 C 언어 학습을 위한 간단한 ASCII-RPG 코드 분석을 다룹니다. 데이터 구조체, 전투 함수, 메뉴와 일반적인 입력 오류를 설명합니다. 중급/시니어 개발자에게 적합.

C의 ASCII-RPG: 게임 전체 코드 분석
Advertisement 728x90

C 언어로 ASCII RPG 만들기: 구조체부터 게임 루프까지

많은 개발자가 직접 프로젝트를 만들어 보며 C 언어를 익힙니다. 간단한 ASCII 기반 RPG를 만드는 것은 데이터 구조, 포인터, 함수, 입출력 처리에 익숙해지는 데 더할 나위 없이 좋은 방법입니다. 이 가이드에서는 터미널 기반 전투 게임의 코드를 단계별로 살펴보며, 랜덤 몬스터 조우 시스템을 구현하는 과정에서 초보자가 자주 빠지는 함정들을 함께 짚어보겠습니다.

핵심 데이터 구조

이 프로젝트는 게임의 핵심 개체를 정의하기 위해 typedef를 활용합니다:

typedef struct {
    char Name[15];
    int Hp;
    int Attack;
} Player;

typedef struct {
    char Name[15];
    int Hp;
    int min_attack;
    int max_attack;
    int type;
} Monster;

typedef struct {
    int min_heal;
    int max_heal;
} Heal;
  • Player(플레이어): 이름(최대 14자), HP, 공격력
  • Monster(몬스터): 이름, HP, 데미지 범위, ASCII 아트용 타입 식별자
  • Heal(회복): HP 회복 범위

scanf에서 %14s를 사용해 이름 입력 길이를 제한하면 버퍼 오버플로우를 방지할 수 있습니다. 플레이어의 최대 HP는 20으로 하드코딩되어 있습니다.

Google AdInline article slot

개체 초기화

초기화 함수는 구조체에 시작 값을 할당합니다:

void init_player(Player* player) {
    printf("Send Nickname: ");
    scanf("%14s", player->Name);
    player->Hp = 20;
}

몬스터는 rand()를 사용해 템플릿 배열에서 무작위로 선택됩니다:

void init_monster(Monster* monster) {
    Monster monster_list[3] = {
        {"Amogus",30,3,6,0},
        {"Slime",20,1,3,1},
        {"Spider",25,2,4,2}
    };
    int id = rand() % 3;
    *monster = monster_list[id];
}

main() 함수 내에서 srand(time(NULL))를 호출하면 프로그램이 실행될 때마다 새로운 난수 시드가 적용됩니다.

Google AdInline article slot

전투 상태 렌더링

print_status 함수는 몬스터의 타입에 따라 ASCII 아트를 출력하고 현재 HP를 표시합니다:

void print_status(Player player, Monster monster){
    printf("\n--- MerRPG ---\n");
    if (monster.type == 0) {
        printf(" 0             (AMOGUS)\n");
        printf("/|\\             (00)\n");
        printf("/ \\             /___\\\n");
    } // ... other types
    printf("%s HP: %d\n", player.Name, player.Hp);
    printf("%s HP: %d\n", monster.Name, monster.Hp);
}

이 함수는 데이터 읽기만 수행하므로 값이 값에 의한 전달(pass by value) 방식으로 전달됩니다.

전투 메커니즘

플레이어의 공격은 1~5의 데미지를 입히며, 포인터를 사용해 몬스터의 HP를 직접 수정합니다:

Google AdInline article slot
void player_attack(Player* player, Monster* monster){
    int damage_player = rand() % 5 + 1;
    monster->Hp -= damage_player;
    printf("You caused %d damage\n", damage_player);
    if (monster->Hp < 0) {
        monster->Hp = 0;
    }
}

몬스터의 공격은 정의된 데미지 범위 내에서 계산됩니다:

void monster_attack(Player* player, Monster* monster){
    int damage_monster = rand() % (monster->max_attack - monster->min_attack + 1) + monster->min_attack;
    player->Hp -= damage_monster;
    printf("Enemy caused %d damage\n", damage_monster);
    if (player->Hp < 0) {
        player->Hp = 0;
    }
}

회복 시 플레이어의 HP는 최대치인 20을 초과하지 않도록 제한됩니다:

void player_heal(Player* player, Heal* heal) {
    int heal_player = rand() % (heal->max_heal - heal->min_heal + 1) + heal->min_heal;
    player->Hp += heal_player;
    printf("You have restored %d Hp\n", heal_player);
    if (player->Hp > 20) {
        player->Hp = 20;
    }
}

메인 게임 루프

start_game() 함수는 while 루프 안에서 전투 시퀀스를 구동합니다:

void start_game() {    
    Player player;
    Monster monster;
    Heal heal = {1,6};
    init_player(&player);
    init_monster(&monster);

    while(player.Hp > 0 && monster.Hp > 0 ) {
        print_status(player, monster);
        printf("[0] - Exit\n");
        printf("[1] - Attack\n");
        printf("[2] - Healing\n");
        scanf("%d", &choice);
        if (choice == 1) {
            player_attack(&player, &monster);
            if (monster.Hp > 0) {
                monster_attack(&player, &monster);
            }
        } else if (choice == 2) {
            player_heal(&player, &heal);
            monster_attack(&player, &monster);
        } else if (choice == 0) {
            printf("You are out of the game\n");
            break;
        } else {
            break;
        }
    }
    if (player.Hp == 0) {
        printf("You Lose...\n");
    } else if (monster.Hp == 0) {
        printf("You Win!\n");
    }
    printf("Press any button to return to menu\n");
    char tmp;
    getchar();
    scanf("%s", &tmp);  
}

루프는 두 개체의 HP를 지속적으로 확인합니다. 플레이어가 행동을 마치면 몬스터가 생존해 있는 경우 반격합니다.

메뉴 및 모듈화

메인 메뉴는 menu.h와 함께 별도의 파일에 구현됩니다:

void menu_game(void) {
    while (1) {
        // ASCII banner art
        printf("1 - Start game\n");
        printf("2 - Exit\n");
        scanf("%d", &choice);
        if (choice == 1) {
            start_game();
        } else if (choice == 2) {
            break;
        }
    }
}

마찬가지로 start_game()game.h에 선언됩니다. main() 함수는 난수 생성기(RNG)의 시드를 설정하고 메뉴를 실행합니다.

자주 발생하는 함정과 해결 방안

  • 입력 버퍼 비우기: scanf("%d") 실행 후 개행 문자(\n)가 버퍼에 남습니다. 다음 scanf("%s", &tmp) 호출 전 getchar()로 이를 제거해야 합니다.
  • 다중 바이트 문자 입력: %c는 단일 바이트만 읽으므로 키릴 문자 같은 다중 바이트 문자에서 오류가 발생합니다. %s로 변경하면 이 문제를 우회할 수 있습니다.
  • 하드코딩된 HP 제한: Heal 구조체가 루프 외부에서 {1,6}으로 로컬 초기화되어 유연성이 떨어집니다.

향후 개선 버전에서는 동적 몬스터 배열, 저장 시스템, 확장된 게임플레이 메커니즘을 도입하는 것이 좋습니다.

핵심 요약

  • 함수 내에서 구조체를 수정할 때는 포인터를 활용하세요.
  • 올바른 난수 생성을 위해 rand()는 항상 srand(time(NULL))와 함께 사용하세요.
  • scanf로 정수를 읽은 후에는 입력 버퍼를 반드시 비우세요.
  • 버퍼 오버플로우를 방지하려면 scanf에서 문자열 길이 제한(예: %14s)을 적용하세요.
  • 코드 모듈화를 위해 .h.c 파일로 분리해 관리하세요.

— Editorial Team

Advertisement 728x90

다음 읽기