Volver al inicio

Aprendiendo C a través de ASCII-RPG: código y estructuras

El artículo desglosa el código de un simple ASCII-RPG en C para aprender el lenguaje. Describe estructuras de datos, funciones de combate, menú y errores típicos de entrada. Adecuado para desarrolladores intermedios/senior.

ASCII-RPG en C: desglose completo del código del juego
Advertisement 728x90

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.

Google AdInline article slot

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.

Google AdInline article slot

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:

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

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 siguiente scanf("%s", &tmp).
  • Entrada cirílica/multibyte: %c solo lee un único byte, lo que falla con caracteres multibyte como los cirílicos. Cambiar a %s evita el problema.
  • Límites de vida fijos: La estructura Heal se 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() con srand(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 .h y .c para mejorar la modularidad.

— Editorial Team

Advertisement 728x90

Leer después