Crear un RPG en ASCII con C: De las estructuras al bucle del juego
Muchos desarrolladores aprenden C sumergiéndose en proyectos prácticos. Crear un RPG sencillo basado en ASCII es una excelente manera de familiarizarse con estructuras de datos, punteros, funciones y manejo de E/S. En esta guía, analizaremos el código de un juego de combate para terminal que incluye encuentros aleatorios con monstruos, destacando al mismo tiempo las trampas más comunes para principiantes.
Estructuras de datos principales
El proyecto utiliza typedef para definir las entidades centrales del juego:
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: Nombre (limitado a 14 caracteres), HP y poder de ataque.
- Monster: Nombre, HP, rango de daño y un identificador de tipo para el arte ASCII.
- Heal: Rango de restauración de HP.
Limitar la entrada del nombre con %14s en scanf evita desbordamientos de búfer. La vida máxima del jugador está fijada en 20.
Inicialización de entidades
Las funciones de inicialización rellenan las estructuras con los valores iniciales:
void init_player(Player* player) {
printf("Send Nickname: ");
scanf("%14s", player->Name);
player->Hp = 20;
}
Los monstruos se seleccionan de un array de plantillas usando 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];
}
Llamar a srand(time(NULL)) en main() garantiza una semilla nueva cada vez que se ejecuta el programa.
Representación del estado de combate
La función print_status dibuja el arte ASCII del monstruo según su tipo y muestra la vida actual:
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);
}
Aquí los valores se pasan por copia, ya que la función solo lee los datos.
Mecánicas de combate
El ataque del jugador inflige entre 1 y 5 puntos de daño y utiliza punteros para modificar la vida del monstruo:
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;
}
}
El ataque del monstruo se calcula dentro de su rango de daño definido:
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;
}
}
La curación limita la vida del jugador a un máximo 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;
}
}
El bucle principal del juego
La función start_game() controla la secuencia de combate dentro de un bucle 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);
}
El bucle verifica continuamente la vida de ambas entidades. Tras la acción del jugador, el monstruo contraataca si sigue con vida.
Menú y modularidad
El menú principal reside en un archivo independiente junto a 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 igual forma, start_game() se declara en game.h. La función main() inicializa el generador de números aleatorios y lanza el menú.
Errores comunes y soluciones
- Limpieza del búfer de entrada: Tras
scanf("%d"), un carácter de nueva línea (\n) permanece en el búfer.getchar()lo consume antes del siguientescanf("%s", &tmp). - Entrada cirílica/multibyte:
%csolo lee un único byte, lo que falla con caracteres multibyte como los cirílicos. Cambiar a%sevita el problema. - Límites de vida fijos: La estructura
Healse inicializa localmente como{1,6}sin pasarse a través del bucle.
Las futuras iteraciones deberían introducir arrays dinámicos de monstruos, un sistema de guardado y mecánicas de juego ampliadas.
Conclusiones clave
- Utiliza punteros para modificar estructuras dentro de funciones.
- Combina siempre
rand()consrand(time(NULL))para una aleatorización correcta. - Vacía el búfer de entrada tras leer enteros con
scanf. - Establece límites de longitud para cadenas en
scanf(p. ej.,%14s) para evitar desbordamientos de búfer. - Divide tu código en archivos
.hy.cpara mejorar la modularidad.
— Editorial Team
Aún no hay comentarios.