Zurück zur Startseite

Lernen von C durch ASCII-RPG: Code und Strukturen

Der Artikel zerlegt den Code eines einfachen ASCII-RPG in C zum Lernen der Sprache. Beschreibt Datenstrukturen, Kampf-Funktionen, Menü und typische Eingabefehler. Geeignet für Entwickler mittleren/senioren Niveaus.

ASCII-RPG in C: vollständige Code-Zerlegung des Spiels
Advertisement 728x90

Ein ASCII-RPG in C entwickeln: Von Structs bis zum Game Loop

Viele Entwickler lernen C durch praktische Projekte. Ein einfaches ASCII-basiertes RPG zu programmieren ist eine hervorragende Methode, um sich mit Datenstrukturen, Zeigern, Funktionen und der Ein-/Ausgabeverwaltung vertraut zu machen. In dieser Anleitung gehen wir Schritt für Schritt den Code für ein terminalbasiertes Kampfspiel mit zufälligen Monsterbegegnungen durch und beleuchten dabei typische Anfängerfehler.

Kern-Datenstrukturen

Das Projekt nutzt typedef, um die zentralen Spielobjekte zu definieren:

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: Name (auf 14 Zeichen begrenzt), LP (Lebenspunkte) und Angriffskraft.
  • Monster: Name, LP, Schadensbereich und ein Typ-Bezeichner für die ASCII-Grafik.
  • Heal: Bereich für die Wiederherstellung von LP.

Die Begrenzung der Namenseingabe mit %14s in scanf verhindert Pufferüberläufe. Die maximalen LP des Spielers sind fest auf 20 codiert.

Google AdInline article slot

Initialisierung der Entitäten

Initialisierungsfunktionen füllen die Strukturen mit Startwerten:

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

Monster werden mithilfe von rand() aus einem Template-Array ausgewählt:

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

Der Aufruf von srand(time(NULL)) in main() stellt sicher, dass bei jedem Programmstart ein neuer Seed verwendet wird.

Google AdInline article slot

Darstellung des Kampfstatus

Die Funktion print_status zeichnet die ASCII-Grafik des Monsters basierend auf seinem Typ und zeigt die aktuellen LP an:

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

Hier werden die Werte als Kopie (Call-by-Value) übergeben, da die Funktion die Daten nur liest.

Kampfmechaniken

Der Angriff des Spielers verursacht 1–5 Schaden und nutzt Zeiger, um die LP des Monsters zu modifizieren:

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

Der Angriff des Monsters basiert auf seinem definierten Schadensbereich:

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

Die Heilung begrenzt die LP des Spielers auf das Maximum von 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;
    }
}

Die Hauptschleife des Spiels

Die Funktion start_game() steuert die Kampfabfolge innerhalb einer while-Schleife:

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

Die Schleife prüft kontinuierlich die LP beider Entitäten. Nach der Aktion des Spielers kontert das Monster, sofern es noch am Leben ist.

Menü und Modularität

Das Hauptmenü befindet sich in einer separaten Datei zusammen mit 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;
        }
    }
}

Ebenso wird start_game() in game.h deklariert. Die main()-Funktion initialisiert den Zufallsgenerator und startet das Menü.

Häufige Fallstricke und Lösungen

  • Leeren des Eingabepuffers: Nach scanf("%d") verbleibt ein Zeilenumbruch (\n) im Puffer. getchar() entfernt diesen vor dem nächsten scanf("%s", &tmp).
  • Kyrillische/Multibyte-Eingaben: %c liest nur ein einzelnes Byte, was bei Multibyte-Zeichen wie kyrillischen Buchstaben zu Problemen führt. Die Umstellung auf %s umgeht dieses Problem.
  • Fest codierte LP-Grenzen: Die Heal-Struktur wird lokal als {1,6} initialisiert, ohne durch die Schleife übergeben zu werden.

Zukünftige Versionen sollten dynamische Monster-Arrays, ein Speichersystem und erweiterte Spielmechaniken einführen.

Wichtige Erkenntnisse

  • Verwenden Sie Zeiger, um Strukturen innerhalb von Funktionen zu ändern.
  • Kombinieren Sie rand() immer mit srand(time(NULL)) für eine echte Zufallsverteilung.
  • Leeren Sie den Eingabepuffer nach dem Lesen von Ganzzahlen mit scanf.
  • Begrenzen Sie die String-Länge in scanf (z. B. %14s), um Pufferüberläufe zu vermeiden.
  • Teilen Sie Ihren Code in .h- und .c-Dateien auf, um die Modularität zu verbessern.

— Editorial Team

Advertisement 728x90

Weiterlesen