Идея выучить C появилась у меня довольно давно. Я пробовал писать в Arduino IDE, но мне не хватало чего-то более масштабного — такого проекта, где можно наделать кучу ошибок, но при этом видеть результат и двигаться дальше.

Я наткнулся в Play Market на ASCII-RPG под названием Stone Story. Сам формат меня зацепил: минимализм, но при этом ощущение полноценной игры. Поэтому я решил сделать нечто похожее, но со своими механиками.

Мне показалось, что сочетание моего ника и RPG звучит вполне нормально. Так и появилось название MerRPG.

Структуры

Первым делом я сразу решил использовать структуры, а именно:

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;
  • Структура игрока

  • Структура монстров

  • Структура лечения

Функции

С этими аспектами мне понравилось рабоать больше всего. Я использовал void функции (за исключением главной функции main)

Разделю на несколько аспектов:

  1. Инициализация игрока и монстров:

void init_player(Player* player) {
    printf("Send Nickname: ");
    scanf("%14s", player->Name);
    player->Hp = 20;
}
  • Сделал ограничение для имени персонажа

  • Сделал ограничение хп игрока

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

}
  • Добавил несколько монстров

  • Сделал случайный выбор одного из трех

  1. 2. Вывод модельки игрока и монстра:

    void print_status(Player player,Monster monster){
        printf("\n--- MerRPG ---\n");
        if (monster.type == 0) {
        printf(" 0             (AMOGUS)\n");
        printf("/|\\             (00)\n");
        printf("/ \\             /__\\\n");
        } else if (monster.type == 1) {
            printf(" 0            (SLIME)\n");
            printf("/|\\            (00)\n");
            printf("/ \\           /~~~\\\n");
        } else if (monster.type == 2) {
            printf(" 0             (SPIDER)\n");
            printf("/|\\           //\\(oo)/\\\\\n");
            printf("/ \\          //        \\\\\n");
        }
    
        printf("%s HP: %d\n",player.Name, player.Hp);
        printf("%s HP: %d\n", monster.Name,monster.Hp);
    
    }

Почему здесь я не использовал указатель? Т.к программа лишь выводит значения, но не изменяет их.

  1. 3 Атака игрока и монстра:

    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("Emeny caused %d damage\n",damage_monster);
        if (player ->Hp < 0) {
            player ->Hp =0;
    }
}

По сути две эти функции не отличаются функционалом, однако специально для монстров я сделал и использовал понятия минимального и максимального урона.

  1. 4 Функция лечения игрока

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

Здесь я добавил ограничение, чтобы лечение не превышало максимальное HP.

  1. 5 Самая большая функция - функция старта игры:

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);  
    }
  • Основной цикл боя

  • Обработка действий игрока

  • Проверка победы или поражения

  1. 6 Уже в отдельном файле была перенесена функция начального меню игры:

void menu_game(void) {
      while (1) {
printf(" __  __           ____  ____   ____ \n");
printf("|  \\/  | ___ _ __|  _ \\|  _ \\ / ___| \n");
printf("| |\\/| |/ _ \\ '__| |_) | |_) | |  _ \n");
printf("| |  | |  __/ |  |  _ <|  __/| |_| |\n");
printf("|_|  |_|\\___|_|  |_| \\_\\_|    \\____|\n");
    printf("=== MerRPG ===\n");
    printf("1 - Start game\n");
    printf("2 - Exit\n");

    scanf("%d", &choice);

    if (choice == 1) {
        start_game();
    }
    else if (choice == 2) {
        break;
    }
}
}

В будущем планирую сюда добавить настройки.

  1. 7 Ну и последняя, но немало важная функция - главная функция main:

    int main() {
    srand(time(NULL));
    menu_game();
    
    return 0;
    }

srand() в данном случае делает так, что бы при каждом запуске игры:

  • Монстры

  • Урон

  • Лечение

    Были случайными.

Костыли:

Хочу вынести парочку интересных для меня костлкй.. А именно:

printf("Press any button to return to menu\n");
char tmp;
getchar();
scanf("%s",&tmp);  
    }

Я хотел, чтобы игрок мог нажать любую кнопку для возврата в меню. Но при использовании кириллицы программа начинала вести себя странно и могла зациклиться. Насколько я понял, это связано с тем, что %c читает один байт, а кириллица занимает больше. В итоге я заменил %c на %s.

Еще один костыль в этом же куске... Без getchar() игра просто не даст пользователю нажать на любую кнопку и сразу же перекинет его в главное меню. Поэтому пришлось вручную очищать буфер ;)

Костыль с ограничением количество хп, которое дает лечение:

void start_game() {    
Player player;
Monster monster;
Heal heal = {1,6};

Мне не удалось сделать это по аналогии с минимальным и максимальным уроном монстров. Поэтому я нашел более "легкий" способ.

Файлы с расширением .h

Как только мне понадобилось вынести меню в отдельный файл, так сразу этот файл мне помог это сделать.. А точнее 2 файла. Т.к в меню используется данный кусок, вызывающий функцию start_game():

    if (choice == 1) {
        start_game();
    }

Нужно было создать два файла .h - game.h и menu.h

#ifndef GAME_H
#define GAME_H

void start_game(void);

#endif
#ifndef MENU_H
#define MENU_H

void menu_game(void);

#endif

В каждом из которых нужно было вызывать требуемые функции.

В целом мне нравится заниматься данными вещами. Игра будет развиваться, будут появляться новые фичи ( и костыли).

Я буду вам очень признателен за помощь в исправлении моих ошибок. Выслушаю любую критику.