Конечные автоматы в менюшном движке. Реализуем доисторическое приключение на ink. Часть 2

Продолжаем первую часть. Пишем вступление к игре и вход в основной параграф:

Доисторическая схватка
Если вы готовы, то выберите
*   [Начать]
    Лёгкий ветерок над пустынной равниной дарит небольшую прохладу после знойного дня. Охотник прислонился к стволу дерева и спокойно наблюдает за магическим пламенем. В этот год ему удасться сохранить его, если неподалёку отыщется еще несколько деревьев. Верное копьё, с каменным наконечником лежит возле левой ноги. Небольшой шорох из ближайших кустов и уже его руки крепко сжимают древко. Только не это, кажется на этой небогатой земле появился еще один охотник - не знающий пощады. Зубастая морда высунулась из-за кустов и начался древнейший танец смерти.
    -> init_and_start_knot
-> END

Для удобства сначала пройдём через узел инициализации, а затем уже войдём в главный узел игры, в котором будем крутиться до самого конца. В него добавим реакции на основные глаголы игры.

//Узел для инициализации значений переменных
=== init_and_start_knot ===
VAR is_end = false //признак окончания игры
...
->game_knot

//Узел игры
=== game_knot ===
{is_end == true: -> END}
+ [Ждать]
    ->game_knot
+ [Метнуть копьё]
    ->game_knot
+ [Лезть на дерево]
    ->game_knot
+ [Ударить ветку]
    ->game_knot
-> END


Ну вот, некий каркас из узлов есть, теперь добавим функции, которые будут отражать различные состояния объектов игры. И добавим их на печать в главный узел.

//Узел игры
=== game_knot ===
{is_end == true: -> END}
{print_hero_state(hero_state)} <>
{print_dino_state(dino_state)} <>
{print_pike_state(pike_state)}

+ [Ждать]
    ->game_knot
+ [Метнуть копьё]
    ->game_knot
+ [Лезть на дерево]
    ->game_knot
+ [Ударить ветку]
    ->game_knot
-> END

//Состояния героя
=== function print_hero_state(state) ===
{ 
    - state == far_tree: 
        ~ return "Охотник сквозь пламя костра видит раскидистое дерево."
    - state == near_tree: 
        ~ return "Охотник стоит под деревом."
    - state == up_tree: 
        ~ return "Охотник на дереве, крепко держится за ствол."
}

//Состояния динозавра
=== function print_dino_state(state) ===
{ 
    ...
}

//Состояния копья
=== function print_pike_state(state) ===
{ 
    ...
}

Теперь осталось только добавить «мозг» всей игры — функцию обработки конечного автомата. На вход её будет поступать тип команды игрока, в качестве результата она будет менять состояния героя, динозавра и копья, а также соответствующую реакцию на событие. Приведу пример пока только для изменения состояния динозавра.

//Узел игры
=== game_knot ===
{is_end == true: -> END}
{print_hero_state(hero_state)} <>
{print_dino_state(dino_state)} <>
{print_pike_state(pike_state)}

+ [Ждать]
    {update_states(cmd_wait)}
    ->game_knot
+ [Метнуть копьё]
    {update_states(cmd_throw)}
    ->game_knot
+ [Лезть на дерево]
    {update_states(cmd_up)}
    ->game_knot
+ [Ударить ветку]
    {update_states(cmd_hit)}
    ->game_knot
-> END

//Функция обработчик состояний
=== function update_states(cmd_type)
//По умолчанию выдаём отклик, похожий на инстед-реализацию
~ temp action_result = "Не сработало. "
//Запоминаем текущие состояния, чтобы автоматы работали параллельно, при изменении в ходе обработки
~ temp curr_dino_state = dino_state
~ temp curr_pike_state = pike_state
~ temp curr_hero_state = hero_state

//Обновляем динозавра
{
-dino_state == dino_visible:
    //Если бросаем копье, когда динозавр видим, то он убегает, иначе он просто скрывается
    {curr_hero_state == near_tree && cmd_type == cmd_throw:
        ~dino_state = dino_run
    -else: 
        ~dino_state = dino_hidden
    }
	{curr_hero_state != near_tree && cmd_type == cmd_throw:
		~action_result = "Охотник не может подобрать удобную позицию чтобы метнуть копьё, зверюга очень изворотливая."
	}
-else:
	{cmd_type == cmd_throw:
		~action_result = "Присмотревшись к кустам, охотник понимает, что вслепую атаковать бессмысленно."
	}
    //динозавр снова на виду
    ~dino_state = dino_visible
}

...

~return action_result


В этой части рассмотрен простой способ реализации автомата на ink. Полный исходный код и особенности оформления будут в следующей части.

2 комментария

techniX
Несколько уточнений по коду.

1. По умолчанию переменные используются в булевом контексте, поэтому сравнение с is_end можно записать вот так:
{is_end: -> END}

2. Если единственная задача функции — просто выводить игровой текст, то это можно сделать чуть проще:
=== function print_hero_state(state) ===
{ 
    - state == far_tree: 
        Охотник сквозь пламя костра видит раскидистое дерево.
    - state == near_tree:
        Охотник стоит под деревом.
    - state == up_tree: 
        Охотник на дереве, крепко держится за ствол.
}
alastochkin
Спасибо за рекомендации, буду учитывать в дальнейшем. Правда второй совет, наверное не применю. В финальной версии тексты вынес в переменные для более удобной вычитки.