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.
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.
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 :
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 prochainscanf("%s", &tmp). - Saisie multioctet (ex. cyrillique) :
%cne lit qu'un seul octet, ce qui pose problème avec les caractères multioctets. Passer à%scontourne ce souci. - Limites de PV codées en dur : La structure
Healest 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
.het.cpour une meilleure modularité.
— Editorial Team
Aucun commentaire pour le moment.