返回首页

通过 ASCII-RPG 学习 C:代码和结构

本文详解了一个简单的 C ASCII-RPG 代码,用于学习该语言。描述了数据结构、战斗函数、菜单和典型的输入错误。适合中高级开发者。

C 中的 ASCII-RPG:游戏完整代码详解
Advertisement 728x90

用 C 语言打造 ASCII RPG:从结构体到游戏主循环

许多开发者通过动手实践来学习 C 语言。开发一款简单的 ASCII 字符 RPG 游戏,是熟悉数据结构、指针、函数和 I/O 处理的绝佳途径。在本指南中,我们将逐步拆解一款基于终端的回合制战斗游戏的代码,该游戏包含随机怪物遭遇战,同时也会重点提示初学者常踩的坑。

核心数据结构

项目使用 typedef 定义游戏核心实体:

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:名称(限制 14 个字符)、生命值(HP)和攻击力。
  • Monster:名称、生命值、伤害范围以及用于 ASCII 绘图的类型标识符。
  • Heal:生命恢复范围。

scanf 中使用 %14s 限制名称输入可有效防止缓冲区溢出。玩家的最大生命值硬编码为 20。

Google AdInline article slot

初始化实体

初始化函数为结构体填充初始值:

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

怪物通过 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];
}

main() 中调用 srand(time(NULL)) 可确保每次运行程序时生成不同的随机种子。

Google AdInline article slot

渲染战斗状态

print_status 函数根据怪物类型绘制 ASCII 艺术图,并显示当前生命值:

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

此处采用值传递,因为该函数仅读取数据而不修改。

战斗机制

玩家攻击造成 1-5 点伤害,并使用指针修改怪物生命值:

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

怪物攻击则从其定义的伤害范围内取值:

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

治疗功能会将玩家生命值上限锁定为 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;
    }
}

游戏主循环

start_game() 函数通过 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);  
}

循环持续检查双方生命值。玩家行动后,若怪物仍存活,则会进行反击。

菜单与模块化

主菜单位于独立文件中,并配合 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;
        }
    }
}

同理,start_game()game.h 中声明。main() 函数负责初始化随机数种子并启动菜单。

常见陷阱与解决方案

  • 清空输入缓冲区:执行 scanf("%d") 后,换行符(\n)会残留在缓冲区中。使用 getchar() 可在下一次 scanf("%s", &tmp) 前将其消耗掉。
  • 中文/多字节字符输入%c 仅读取单字节,遇到中文等多字节字符时会出错。改用 %s 可规避此问题。
  • 硬编码的生命值上限Heal 结构体在局部初始化为 {1,6},并未在循环中动态传递。

未来版本可引入动态怪物数组、存档系统及更丰富的玩法机制。

核心要点总结

  • 使用指针在函数内部修改结构体。
  • 始终将 rand()srand(time(NULL)) 搭配使用,以确保真正的随机性。
  • 使用 scanf 读取整数后,务必刷新输入缓冲区。
  • scanf 中强制限制字符串长度(如 %14s)以防缓冲区溢出。
  • 将代码拆分为 .h.c 文件,提升模块化程度。

— Editorial Team

Advertisement 728x90

继续阅读