Retour à l'accueil

Apprendre C à travers ASCII-RPG : code et structures

L'article décompose le code d'un simple ASCII-RPG en C pour apprendre le langage. Décrit les structures de données, fonctions de combat, menu et erreurs d'entrée typiques. Convient aux développeurs intermédiaires/seniors.

ASCII-RPG en C : décomposition complète du code du jeu
Advertisement 728x90

Créer un RPG en ASCII avec C : Des structures à la boucle de jeu

De nombreux développeurs apprennent le C en se lançant dans des projets concrets. Créer un RPG simple en ASCII est une excellente méthode pour se familiariser avec les structures de données, les pointeurs, les fonctions et la gestion des entrées/sorties. Dans ce guide, nous allons parcourir le code d'un jeu de combat en terminal, avec des rencontres aléatoires contre des monstres, tout en soulignant les pièges classiques pour les débutants.

Structures de données fondamentales

Le projet s'appuie sur typedef pour définir les entités principales du jeu :

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 : Nom (limité à 14 caractères), points de vie (PV) et puissance d'attaque.
  • Monster : Nom, PV, plage de dégâts et identifiant de type pour l'art ASCII.
  • Heal : Plage de restauration des PV.

Limiter la saisie du nom avec %14s dans scanf évite les débordements de tampon. Les PV maximums du joueur sont fixés en dur à 20.

Google AdInline article slot

Initialisation des entités

Les fonctions d'initialisation remplissent les structures avec les valeurs de départ :

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

Les monstres sont choisis dans un tableau de modèles à l'aide de 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];
}

L'appel à srand(time(NULL)) dans main() garantit une nouvelle graine à chaque exécution du programme.

Google AdInline article slot

Affichage de l'état du combat

La fonction print_status dessine l'art ASCII du monstre selon son type et affiche les PV actuels :

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

Les valeurs sont passées par copie ici, car la fonction ne fait que lire les données.

Mécaniques de combat

L'attaque du joueur inflige 1 à 5 dégâts et utilise des pointeurs pour modifier les PV du monstre :

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

L'attaque du monstre puise dans sa plage de dégâts définie :

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

Le soin plafonne les PV du joueur au maximum de 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;
    }
}

La boucle de jeu principale

La fonction start_game() pilote la séquence de combat à l'intérieur d'une boucle 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);  
}

La boucle vérifie en continu les PV des deux entités. Après l'action du joueur, le monstre contre-attaque s'il est encore en vie.

Menu et modularité

Le menu principal réside dans un fichier séparé, accompagné de 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;
        }
    }
}

De la même manière, start_game() est déclarée dans game.h. La fonction main() initialise le générateur de nombres aléatoires et lance le menu.

Pièges courants et solutions

  • Vidage du tampon d'entrée : Après un scanf("%d"), un caractère de saut de ligne (\n) reste dans le tampon. getchar() le consomme avant le prochain scanf("%s", &tmp).
  • Saisie multioctet (ex. cyrillique) : %c ne lit qu'un seul octet, ce qui pose problème avec les caractères multioctets. Passer à %s contourne ce souci.
  • Limites de PV codées en dur : La structure Heal est initialisée localement sous la forme {1,6} sans être transmise à travers la boucle.

Les prochaines itérations devraient intégrer des tableaux de monstres dynamiques, un système de sauvegarde et des mécaniques de jeu enrichies.

Points clés à retenir

  • Utilisez des pointeurs pour modifier les structures à l'intérieur des fonctions.
  • Associez toujours rand() à srand(time(NULL)) pour une véritable randomisation.
  • Videz le tampon d'entrée après la lecture d'entiers avec scanf.
  • Imposez des limites de longueur aux chaînes dans scanf (ex. %14s) pour éviter les débordements de tampon.
  • Séparez votre code en fichiers .h et .c pour une meilleure modularité.

— Editorial Team

Advertisement 728x90

Lire ensuite