Zpět na domů

Učím se C přes ASCII-RPG: kód a struktury

Článek rozebírá kód jednoduché ASCII-RPG v C pro studium jazyka. Popsány struktury dat, funkce boje, menu a typické chyby se vstupem. Vhodné pro middle/senior vývojáře.

ASCII-RPG v C: úplný rozbor kódu hry
Advertisement 728x90

Vývoj ASCII-RPG v jazyce C: od struktur po herní smyčku

Vývojáři se často učí C prostřednictvím praktických projektů. Vytvoření jednoduché ASCII-RPG vám umožní osvojit si datové struktury, ukazatele, funkce a práci se vstupem a výstupem. V tomto článku si rozebereme kód terminálové hry s bojem proti náhodným příšerám a upozorníme na typické začátečnické chyby.

Základní datové struktury

Projekt využívá typedef k definici struktur popisujících herní entity:

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: jméno (omezeno na 14 znaků), HP, útok.
  • Monster: jméno, HP, rozsah poškození, typ pro ASCII art.
  • Heal: rozsah obnovy HP.

Omezení vstupu pomocí %14s ve funkci scanf zabraňuje přetečení bufferu. Maximální HP hráče je pevně nastaveno na 20.

Google AdInline article slot

Inicializace entit

Inicializační funkce naplní struktury výchozími hodnotami:

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

Pro příšery se používá pole šablon s rand() pro náhodný výběr:

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];
}

srand(time(NULL)) v main() zajišťuje pseudonáhodnost při každém spuštění.

Google AdInline article slot

Zobrazení stavu boje

Funkce print_status vykreslí ASCII art příšery podle typu a zobrazí 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");
    } // ... další typy
    printf("%s HP: %d\n", player.Name, player.Hp);
    printf("%s HP: %d\n", monster.Name, monster.Hp);
}

Hodnoty se předávají hodnotou, protože funkce data pouze čte.

Bojové mechaniky

Útok hráče generuje poškození 1–5 a využívá ukazatele ke změně HP příšery:

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;
    }
}

Útok příšery využívá rozsah definovaný ve struktuře:

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;
    }
}

Léčení omezuje HP na maximum 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;
    }
}

Hlavní herní smyčka

Funkce start_game() řídí boj pomocí cyklu 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);  
}

Cyklus kontroluje HP obou stran. Po tahu hráče příšera protiútočí, pokud stále žije.

Menu a modularita

Menu je vyčleněno do samostatného souboru s hlavičkou menu.h:

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

Podobně game.h pro start_game(). main() inicializuje generátor náhodných čísel a spustí menu.

Typické problémy a dočasná řešení

  • Vyčištění vstupního bufferu: Po scanf("%d") zůstává v bufferu \n. getchar() jej pohltí před scanf("%s", &tmp).
  • Diakritika na vstupu: %c čte jeden bajt, zatímco znaky s diakritikou jsou vícebajtové. Nahrazení za %s problém řeší.
  • Pevné limity HP: Heal se inicializuje lokálně {1,6}, bez struktury v cyklu.

Do budoucna stojí za zvážení přidání dynamických polí příšer, ukládání postupu a rozšíření herních mechanik.

Na co si dát pozor

  • Používejte ukazatele pro úpravu struktur ve funkcích.
  • rand() vyžaduje srand(time(NULL)) pro správnou náhodnost při každém spuštění.
  • Po scanf s %d vždy vyčistěte vstupní buffer.
  • Omezujte délku řetězců v scanf (%14s) proti přetečení bufferu.
  • Vyčleňujte funkce do souborů .h/.c pro lepší modularitu.

— Editorial Team

Advertisement 728x90

Číst dál