# О Русском Информе

На Информе можно писать игры с текстовым вводом. Классические примеры таких игр — Adventure и Zork. Здесь на сайте можно поиграть в такие игры на русском языке, а кроме этого скачать инструменты для разработки своих собственных игр.

Русский Информ (RInform) — это перевод стандартной библиотеки популярной на западе системы Inform 6.

Официальный сайт русской версии: https://rinform.org/

В данной книге подробно разбирается разработка простой игры («Хейди»), и есть ответы на часто задаваемые вопросы (FAQ).

# Введение и установка

Для того, чтобы написать игру на Информе, нам потребуются:

- текстовый редактор для редактирования исходных файлов игры (официально рекомендуется Sublime Text, но, разумеется, подойдёт любой);
- библиотека Информа, которую нужно будет включить в свою игру, чтобы работала общая модель и стандартное поведение;
- компилятор Информа, с помощью которого из исходного файла мы получим файл игры;
- интерпретатор (плеер), с помощью которого в полученную игру можно будет сыграть.

Библиотеку и компилятор можно скачать с сайта Информа. В архиве помимо прочего есть примеры игр.

Игры можно запускать онлайн, и это самый популярный вариант, но на время разработки обычно пользуются одним из «оффлайн»-плееров:

- Lectrote — Windows, Linux, OS X (использует Electron).
- Windows Glulxe для Windows. Там же есть более быстрый Windows Git.

## Установка и проверка

Начиная с версии 0.9, версия Glulx считается приоритетной, а версия для Z-машины вторичной. Поэтому в данной книге все примеры подразумевают работу с Glulx. О различиях можно почитать в FAQ.

Для проверки работоспособности и первоначальной настройки:

1. скачайте архив библиотеки с официального сайта и распакуйте его в отдельную, удобно доступную папку, например `c:\rinform\`.

   В этой папке будет следующее содержимое:

       \demos\           демонстрационные игры
       \demos\demos.bat  пакетные файлы для компиляции демонстрационных игр
       \demos\demos.sh
       \libext\          расширения для библиотке
       \library\         русская версия библиотеки
       inform.exe        компилятор Inform (для Windows)

2. В папке demos можно увидеть несколько файлов с расширением `.inf` — это исходные файлы демонстрационных игр. Их можно открыть и просмотреть в текстовом редакторе.

3. Файл `demos.bat` это пакетный файл («батник»), при помощи которого упрощается компилирование файлов игр. Запустите этот файл (если на Linux, то `demos.sh`), на экране появится примерно следующее:

       c:\rinform\demos>..\inform.exe +..\library +language_name=Russian -DG -Cu $DICT_CHAR_SIZE=4 heidi.inf Heidi.ulx
       Inform 6.34 (16th August 2017)

       c:\rinform\demos>..\inform.exe +..\library +language_name=Russian -DG -Cu $DICT_CHAR_SIZE=4 AliceR.inf AliceR.ulx
       Inform 6.34 (16th August 2017)

   Если прочих сообщений нет, то всё прошло без ошибок.

4. В папке `demos` появятся файлы с расширением `.ulx` — это готовые файлы игр, которые можно запустить в интерпретаторе. Если установлены Windows Glulxe или Lectrote, то при запуске такого файла он скорее всего автоматически откроется в одном из этих интерпретаторов.

## Организация своей игры:

1. создайте папку для игры, например c:\inform\mygame\

2. создайте главный исходный файл игры, c:\inform\mygame\mygame.inf. Для удобства воспользуйтесь шаблоном:

       !%
       !=============================================================================
       Constant Story "Новая игра";
       Constant Headline
           "^Шаблон игры на Информе^";

       Include "Parser";
       Include "VerbLib";
       !============================================================================
       ! Описание игровых объектов

       Object first_room "Первая комната"
           with description "Первая комната.",
           has light;

       !============================================================================
       [ Initialise; location = first_room; ];

       !============================================================================
       Include "RussiaG";

       !============================================================================

3. создайте .bat-файл c:\inform\mygame\mygame.bat для удобной компиляции:

       ..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 mygame.inf

## Компиляция игры

Чтобы скомпилировать игру, нужно через командную строку вызвать компилятор Информа inform.exe с верными параметрами.

Рассмотрим на примере демонстрационной игры Heidi, что означают параметры:

    ..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 heidi.inf Heidi.ulx

- `+..\library` — путь к папке, в которой хранится библиотека
- `+language_name=Russian` — параметр языка игры
- `-G` — формат игры (Glulx)
- `-Cu` и `$DICT_CHAR_SIZE=4` — означают использование Юникода (UTF-8) в исходном коде игры
- `heidi.inf` — код игры
- `Heidi.ulx` — выходной файл (файл игры)

Последними параметрами являются входной и выходной файлы. Перед ними также можно указать необязательные свитчи, например `-D` для компиляции дебаг-версии игры.

Форматов игры исторически есть несколько, но ныне используется только Glulx. Ранее для формата Z-машины использовались V5 и V8, у которых максимальный размер игры формата V5 — 256 кб, а игры формата V8 — 512 кб. Игры Glulx могут достигать 4 гигабайт.

# Пример игры: Хейди

В данной главе будет подробно разобрана разработка простой игры под названием «Хейди».

В готовую игру можно поиграть онлайн: https://iplayif.com/?story=https://rinform.org/demos/Heidi.z5

А конечный исходный код почитать здесь: https://github.com/yandexx/rinform-glulx/blob/master/demos/Heidi.inf

# Начало

Пошагово рассмотрим написание простенькой игры Хейди, сюжет которой таков:

> «Хейди живёт в маленьком домике в лесу. Одним солнечным днём она слышит писк птички — её гнездо упало с ветки на поляну. Хейди кладёт птичку в гнездо, а гнездо обратно на ветку».

## Шаблон для игры

Для начала создадим исходный файл-шаблон. Создайте папку `heidi` в папке Информа (например, `c:\rinform\heidi\`), а в ней, при помощи текстового редактора, файл `heidi.inf` со следующим содержимым:

    !% -SD
    !=============================================================================
    Constant Story "Хейди";
    Constant Headline
        "^Пример простой игры на Inform.
         ^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
         ^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";

    Include "Parser";
    Include "VerbLib";
    !============================================================================
    ! Описание игровых объектов

    !============================================================================
    ! Функции инициализации

    [ Initialise; ];

    !============================================================================
    ! Стандартные и расширенные грамматики

    Include "RussiaG";
    !============================================================================

Пока можно просто скопировать и вставить этот текст; каждая строчка будет рассмотрена позднее. И убедитесь, что файл называется именно `heidi.inf`, а не, например, `heidi.txt` (регистр значения не имеет).

В папке игры с помощью текстового редактора создайте следующий файл `heidi.bat`:

    ..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 heidi.inf
    pause

Запустите этот `bat`-файл, в консоли появится примерно следующее:

    c:\rinform\heidi>..\inform.exe +..\library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 heidi.inf
    Inform 6.34 (16th August 2017)

    c:\rinform\heidi>pause
    Для продолжения нажмите любую клавишу . . .

В папке `heidi` появится файл `heidi.ulx`, который можно запустить и поиграть:

    Хейди
    Пример простой игры на Inform.
    Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
    Перевод Юрия Салтыкова a.k.a. G.A. Garinson
    Release 1 / Serial number 180808 / Inform v6.34 Library 6/11 SD

    В темноте
    Кромешная тьма – не видно ни зги!

    >

Теперь разберём каждую строчку исходного файла.

## Разбор исходного файла

Для исходных файлов Информа существует несколько правил:

- Если самая первая (или первые) строчки исходного файла начинаются с `!%`, то компилятор воспринимает их как параметры, а не как часть игры. В данном случае мы включаем два режима для будущей игры, Strict (`-S`) и Debug (`-D`). Strict режим в случае проблем выполнения в игре будет выдавать сообщение об ошибке, а режим Debug добавляет дополнительные команды в игру для облегчения отладки.

  На самом деле режим Strict включён по умолчанию и, например, чтобы его выключить (что не рекомендуется), нужно указать параметр `-~S`.

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

- Компилятор пропускает пустые строки, а также объединяет все пробелы, знаки табуляции и знаки переноса строки в один пробел (кроме пробелов внутри строк).

  Например, можно было записать наш исходник таким образом, и результат остался бы тем же:

      Constant Story "Хейди";
      Constant Headline
      "^Пример простой игры на Inform.^Авторы: Роджер Фирт (Roger Firth) и
      Соня Кессерих (Sonja Kesserich).^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";
      Include "Parser";Include "VerbLib";
      [ Initialise; ];
      Include "RussiaG";

  Но при таком форматировании его гораздо сложнее читать.

- В каждой игре должны быть описаны строковые константы `Story` (название игры) и `Headline` (короткое описание, имя автора). Вместе с датой и номером релиза они выводятся при запуске игры в заголовке.

- В каждой игре должны быть строки `Include` для включения стандартной библиотеки (эти файлы подставляются вместо этих строк при компиляции), и именно в этом порядке:

      Include "Parser";
      Include "VerbLib";
      ...
      Include "RussiaG";

- В каждой игре должна быть функция `Initialise`:

      [ Initialise; ];

  В нашем примере она ничего не выполняет, но тем не менее она должна присутствовать.

- Все команды разделяются символом «точка с запятой» (аналогично C/C++).

Любая новая игра начинается с подобного шаблона, так что можно сохранить его отдельно, чтобы воспользоваться им позднее.

## Задание локаций игры

При написании игры неизбежно задумываются о тех локациях, которые в ней встретятся. В нашей игре они будут следующими:

В Информе локации называются комнатами, пусть у них может и не быть стен. Для начала опишем наши комнаты таким образом:

    Object "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
        has light;

    Object "В лесной чаще"
        with description
                "На западе, сквозь густую листву, можно разглядеть небольшое строение.
                Тропинка ведет на северо-восток.",
        has light;

    Object "Полянка"
        with description
                "Посреди полянки стоит высокий платан.
                 Тропинка вьется меж деревьев, уводя на юго-запад.",
        has light;

    Object "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
        has light;

Рассмотрим общие принципы:

- Задание комнаты начинается со слова `Object` и заканчивается точкой с запятой. Вообще практически всё в игре является объектами — комнаты, предметы, люди, звуки и прочее.

- Строка после слова `Object` — это название, под которым объект появится в игре для игрока.

- Ключевое слово `with` обозначает компилятору, что дальше идёт перечисление свойств.

- `description` содержит в себе подробное описание объекта. В случае комнаты этот текст выводится, когда игрок попадает в эту комнату.

- Ключевое слово `has` обозначает компилятору, что дальше идёт перечисление атрибутов.

- `light` обозначает, что объект является источником света, и что игрок сможет увидеть, что происходит. В каждой комнате должен быть хотя бы один источник света, и обычно это сама комната. Иначе игрок ничего не увидит: «Кромешная тьма – не видно ни зги!»

У свойств есть название и значение (например, `description` и «Ты стоишь около избушки, на восток от которой раскинулся лес»), а у атрибутов есть только название.

Позже, когда игра будет готова, в ней можно будет увидеть следующее:

    Перед домом
    Ты стоишь около избушки, на восток от которой раскинулся лес.

Можно увидеть, как здесь используются название комнаты и её описание (`description`).

## Соединение комнат

Комнаты заданы, и в тексте описано, как локации находится относительно друг друга — например, что из Лесной чащи можно пойти на запад к домику, либо по тропинке на северо-восток. Однако, в коде нужно явно указать, как соединены наши комнаты:

    Object before_cottage "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
            e_to forest,
        has light;

    Object forest "В лесной чаще"
        with description
                "На западе, сквозь густую листву, можно разглядеть небольшое строение.
                Тропинка ведет на северо-восток.",
            w_to before_cottage,
            ne_to clearing,
        has light;

    Object clearing "Полянка"
        with description
                "Посреди полянки стоит высокий платан.
                 Тропинка вьется меж деревьев, уводя на юго-запад.",
            sw_to forest,
            u_to top_of_tree,
        has light;

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
        has light;

Здесь мы сделали два изменения:

- Между ключевым словом `Object` и именем объекта мы ввели внутреннее название, идентификатор этого объекта, которое используется внутри программы. Например, для домика это `before_cottage`, а для лесной чащи — `forest`.

  Идентификатор не может включать в себя пробелы.

- После описания объектов мы ввели строки, которые показывают, как соединены наши комнаты. Например для `before_cottage`:

      e_to forest,

  Так игрок, который находится в первой комнате, сможет ввести в игре ИДТИ НА ВОСТОК (или просто ВОСТОК, или В), и игра перенесёт его в комнату с идентификатором `forest`. Если игрок попробует пойти в другом направлении, то получит в ответ «Этот путь недоступен».

  Таким образом мы добавили односторонний переход на восток, из `before_cottage` в `forest`. В объекте `forest` есть две строки:

      w_to before_cottage,
      ne_to clearing,

  Первая строка вводит путь обратно на запад к объекту `before_cottage` (к домику), а вторая — на северо-восток на Полянку.

  В Информе есть 8 «горизонтальных» направлений:

  `n_to` на север, | `ne_to` на северо-восток,
  `e_to` на восток, | `se_to` на юго-восток,
  `s_to` на юг, | `sw_to` на юго-запад,
  `w_to` на запад, | `nw_to` на северо-запад,

  а также два «вертикальных» направления `u_to` вверх, `d_to` вниз и два дополнительных — `in_to` внутрь и `out_to` наружу.

Последнее что нужно добавить — это начальную локацию. В начале игры Хейди стоит перед своим домом, поэтому укажем, что игра начинается в `before_cottage`. Делается это в функции `Initialise`:

    [ Initialise; location = before_cottage; ];

`location` — это библиотечная переменная, в которой хранится текущее положение игрока. Здесь мы указываем, что в начале игры он должен находиться в комнате `before_cottage`.

Теперь можно внести все эти изменения в наш изначальный шаблон и скомпилировать игру:

    !=============================================================================
    Constant Story "Хейди";
    Constant Headline
        "^Пример простой игры на Inform.
         ^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
         ^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";

    Include "Parser";
    Include "VerbLib";
    !============================================================================
    ! Описание игровых объектов

    Object before_cottage "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
            e_to forest,
        has light;

    Object forest "В лесной чаще"
        with description
                "На западе, сквозь густую листву, можно разглядеть небольшое строение.
                Тропинка ведет на северо-восток.",
            w_to before_cottage,
            ne_to clearing,
        has light;

    Object clearing "Полянка"
        with description
                "Посреди полянки стоит высокий платан.
                 Тропинка вьется меж деревьев, уводя на юго-запад.",
            sw_to forest,
            u_to top_of_tree,
        has light;

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
        has light;

    !============================================================================
    ! Функции инициализации
    [ Initialise; location = before_cottage;];

    !============================================================================
    ! Стандартные и расширенные грамматики
    Include "RussiaG";

    !============================================================================

В получившуюся игру можно поиграть. Конечно тут практически ничего нет, но можно побродить по локациям.

## Добавление птицы и гнезда

Конечно же, птица и её гнездо тоже будут объектами в Информе. Опишем их:

    Object bird "птенчик/"
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
        has ;

    Object nest "птичь/е гнезд/о"
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        has ;

Эти объекты записываются так же, как и комнаты ранее — у них есть идентификатор, название и описание. Описание комнаты выводится при входе в неё игрока, или если игрок пишет ОСМОТРЕТЬСЯ (или ОСМ, или просто О). Описание прочих объектов выводится, когда игрок вводит в игре ОСМОТРЕТЬ ПРЕДМЕТ (или ОСМ, или просто О). У этих объектов нет соединений, например `e_to` или `w_to` (они есть только у комнат) или свойства `light` (оно не нужно, так как освещение предоставляют комнаты).

Слэши в названии объектов необходимы для того, чтобы при упоминании названий объектов в русскоязычной игре они верно склонялись. Отделять стоит окончание, так как оно изменяется при склонении.

Во время игры игрок будет обращаться к этим предметам, например, вводя ОСМОТРЕТЬ ПТЕНЧИКА или ВЗЯТЬ ГНЕЗДО. Чтобы это корректно работало, нужно перечислить слова, которые относятся к данному объекту. Важно указать различные синонимы, чтобы игроку было проще в игре.

    Object bird "птенчик/"
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
        name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
        has ;

    Object nest "птичь/е гнезд/о"
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
        has ;

В секции `name` идёт перечисление так называемых словарных (dictionary) слов в одинарных кавычках. В словарных словах нельзя использовать пробелы, запятые или точки, но сам список разделяется пробелами. Интерпретатор проверяет введённые игроком слова и сверяет их со списками словарных слов. Если игрок упоминает ПТИЧКУ, МАЛЕНЬКОГО ПТЕНЦА или ДЕТЕНЫША, значит он имеет в виду птенца (`bird`), если ПТИЧЬЕ ГНЕЗДО или МОХ, то речь идёт про объект `nest`. Если игрок введёт ГНЕЗДО ПТЕНЕЦ, то интерпретатор выведет сообщение о том, что не понимает, о чём идёт речь.

В списке нужно указывать слова, обрезая окончания, и стараться учесть все возможные уменьшительно-ласкательные формы и прилагательные, которые может ввести игрок.

Для комнат список `name` не нужен, так как взаимодействие с ними происходит по-другому. Например, не нужно вводить ОСМОТРЕТЬ ЛЕС, достаточно ввести ОСМ.

Для гнезда нужно ввести дополнительную особенность — чтобы в неё можно было положить птенца. Для этого мы помечаем его как `container` (контейнер), чтобы игрок мог ввести ПОЛОЖИТЬ ПТИЦУ В ГНЕЗДО. Также мы помечаем его как открытое — `open`, так как по умолчанию контейнеры закрыты.

Кроме того, для верного склонения названий объектов необходимо указать их род. Гнездо — среднего рода, поэтому помечаем его как `neuter`. Птенчик — мужского рода, помечаем как `male`. Объекты с описанием женского рода помечаются как `female`, а объекты множественного числа — `plural`.

    Object nest "птичь/е гнезд/о"
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
        has container open neuter;

Теперь оба объекта готовы, и осталось ввести их в игру. Пусть птенец будет в лесу, а гнездо на поляне:

    Object bird "птенчик/" forest
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
        name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
        has male;

    Object nest "птичь/е гнезд/о" clearing
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
        has container open neuter;

Первую строчку можно прочитать так: «описание объекта `bird` с названием "птенчик", который изначально будет находиться в объекте `forest`».

Размещать объекты в исходном файле игры можно где угодно, но удобнее всего это делать рядом с соответствующими комнатами.

Часть кода с объектами будет выглядеть так:

    !============================================================================
    ! Описание игровых объектов

    Object before_cottage "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
            e_to forest,
        has light;

    Object forest "В лесной чаще"
        with description
                "На западе, сквозь густую листву, можно разглядеть небольшое строение.
                Тропинка ведет на северо-восток.",
            w_to before_cottage,
            ne_to clearing,
        has light;

    Object bird "птенчик/" forest
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
        name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
        has male;

    Object clearing "Полянка"
        with description
                "Посреди полянки стоит высокий платан.
                 Тропинка вьется меж деревьев, уводя на юго-запад.",
            sw_to forest,
            u_to top_of_tree,
        has light;

    Object nest "птичь/е гнезд/о" clearing
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
        has container open neuter;

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
        has light;

Внесите эти изменения, скомпилируйте и запустите. В игре можно будет увидеть следующее:

    В лесной чаще
    На западе, сквозь густую листву, можно разглядеть небольшое
    строение. Тропинка ведет на северо-восток.

    Здесь имеется птенчик.

    >

## Дерево и ветка

В описании полянки есть высокий платан, на который игрок сможет залезть. Опишем его:

    Object tree "высок/ий платан/" clearing
        with description
            "Величавое дерево стоит посреди поляны.
             Кажется, по его стволу будет несложно влезть наверх.",
            name 'высок' 'платан' 'дерев' 'ствол' 'величав',
        has scenery male;

В этом описании нам уже всё знакомо, за исключением `scenery`. Поскольку мы уже написали о дереве в описании локации, нам не нужно, чтобы в игре выводилось «Здесь имеется высокий платан». Для этого мы помечаем его как `scenery`. Кроме того, `scenery` запрещает игроку возможность подобрать объект.

И наконец ветка наверху дерева:

    Object branch "надежн/ый толст/ый сук/" top_of_tree
        with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
                          держалось что-то не очень большое.",
            name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
        has static supporter male;

Здесь встречаются два новых атрибута. `static` аналогичен `scenery`, то есть запрещает подбирать объект, но в отличие от `scenery` всё равно выводит объект отдельно. `supporter` аналогичен `container`, но позволяет класть объекты не в, а на сук. (Позднее будет объяснено, что объект не может быть одновременно и `container`, и `supporter`).

Получаем следующее:

    !============================================================================
    ! Описание игровых объектов

    Object before_cottage "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
            e_to forest,
        has light;

    Object forest "В лесной чаще"
        with description
                "На западе, сквозь густую листву, можно разглядеть небольшое строение.
                Тропинка ведет на северо-восток.",
            w_to before_cottage,
            ne_to clearing,
        has light;

    Object bird "птенчик/" forest
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
        name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
        has male;

    Object clearing "Полянка"
        with description
                "Посреди полянки стоит высокий платан.
                 Тропинка вьется меж деревьев, уводя на юго-запад.",
            sw_to forest,
            u_to top_of_tree,
        has light;

    Object tree "высок/ий платан/" clearing
        with description
            "Величавое дерево стоит посреди поляны.
             Кажется, по его стволу будет несложно влезть наверх.",
            name 'высок' 'платан' 'дерев' 'ствол' 'величав',
        has scenery male;

    Object nest "птичь/е гнезд/о" clearing
        with description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
        name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',
        has container open neuter;

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
        has light;

    Object branch "надежн/ый толст/ый сук/" top_of_tree
        with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
                          держалось что-то не очень большое.",
            name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
        has static supporter male;

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

## Завершение

Первый вариант игры почти готов, осталось внести два изменения. Во-первых, нельзя чтобы Хейди могла забраться на дерево держа в руках и птенца, и гнездо — нужно чтобы игрок сначала положил птенца в гнездо. Есть простой способ ввести это ограничение:

    Constant Story "Хейди";
    Constant Headline
        "^Пример простой игры на Inform.
         ^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
         ^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";

    Constant MAX_CARRIED 1;

Константа `MAX_CARRIED` ограничивает количество предметов, которые могут быть одновременно в руках у игрока. Установив её равной 1, мы указываем что игрок может держать либо гнездо, либо птенца, но не оба одновременно. Однако это ограничение не учитывает содержимое `container`'ов или `supporter`'ов, поэтому птенец в гнезде считается за один объект.

Второе изменение чуть более сложное и более важное — сейчас нет способа «выиграть» игру, то есть пройти её. Цель игры — положить птенца в гнездо, подняться на верхушку дерева и положить гнездо на сук. Если это условие выполнено, то игра должна закончиться. Вот как можно это сделать:

    Object branch "надежн/ый толст/ый сук/" top_of_tree
        with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
                          держалось что-то не очень большое.",
            name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
            each_turn [; if (nest in branch) deadflag = 2; ],
        has static supporter male;

Все нововведения будут рассмотрены подробнее в следующих главах. Рассмотрим их кратко.

Библиотечная переменная `deadflag` обычно равна 0. Если присвоить ей значение 2, то интерпретатор заметит это и выведет сообщение «Вы выиграли». Строку

    if (nest in branch) deadflag = 2;

можно прочитать так: «Проверить, находится ли объект `nest` в объекте `branch` (если `branch` является `container`) или на этом объекте (если `branch` является `supporter`) — если да, то присвоить `deadflag` значение 2». Далее,

    each_turn [; ... ],

стоит понимать как «В конце каждого хода (если игрок находится в той же комнате, где сук), выполнить то, что записано в квадратных скобках». В итоге получаем:

- В конце каждого хода (когда игрок ввёл команду, нажал Enter, и интерпретатор выполнил эту команду), интерпретатор проверяет, находится ли игрок в той же комнате, где находится `branch`. Если нет, то ничего не выполняется. Если да, то интерпретатор проверяет, где находится `nest`. Изначально гнездо на полянке, поэтому ничего не происходит.

- Также, в конце каждого хода интерпретатор проверят значение `deadflag`. Обычно оно равно 0, поэтому ничего не происходит.

- Затем игрок кладёт гнездо на сук. Интерпретатор видит это и устанавливает `deadflag` равным 2.

- Сразу же после этого интерпретатор видит, что `deadflag` равен 2, то есть, что игра закончена, и выводит на экран «Вы выиграли».

На этом глава закончена, перекомпилируйте игру и проверьте, как работают внесённые изменения. Многое можно сделать получше, и это будет рассмотрено позднее. В следующей главе будет обобщение уже изученных особенностей Информа.

# Обобщение

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

## Константы и переменные

Константа это такое имя, значение которого строго задаётся и не может изменяться. Ранее мы встретили строковую константу:

    Constant Story "Хейди";

и числовую константу:

    Constant MAX_CARRIED 1;

Это два наиболее частых способа использования констант в Информе.

Значение переменной может изменяться в ходе игры. Мы ещё не задавали собственных переменных, но уже пользовались двумя такими из библиотеки, где они заданы следующим образом:

    Global location;
    Global deadflag;

Значение глобальной переменной по умолчанию равно 0, но его можно изменить в любой момент, например мы ввели:

    location = before_cottage;

для изменения `location` на `before_cottage`, а также

    if (nest in branch) deadflag = 2;

для изменения `deadflag` на 2.

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

## Описание объектов

Как можно было заметить из предыдущей главы, вся игра описывается в виде объектов. Каждая комната, предмет и даже сам игрок является объектом (объект игрока задаётся библиотекой).

Общий формат задания объекта следующий:

    Object идентификатор "игровое_имя" родительский_объект
    with    свойство значение,
        свойство значение,
        ...
        свойство значение,
    has атрибут атрибут ... атрибут
    ;

Описание начинается с ключевого слова `Object` и заканчивается точкой с запятой, а между ними идёт три основных блока:

- Сразу за словом `Object` идёт заголовочная часть;

- со слова `with` начинается перечисление свойств;

- со слова `has` начинается перечисление атрибутов.

### Заголовок объекта

Заголовок состоит из трёх частей, каждая из которых не обязательна:

- Внутренний идентификатор, по которому другие объекты обращаются к данному объекту. Это должно быть одно слово (можно с цифрами и знаком подчёркивания), до 32 латинских символов, и оно должно быть уникальным в игре. Идентификатор можно опустить, если к объекту не обращаются другие объекты.

  Примеры: `bird`, `tree`, `top_of_tree`.

- Игровое имя в двойных кавычках. Оно может состоять из нескольких слов и не обязательно быть уникальным (например, можно иметь несколько комнат с именем `"Где-то в пустыне"`). Не обязательно, но крайне рекомендуется дать каждому объекту игровое имя.

  Примеры: `"птенчик/"`, `"высок/ий платан/"`, `"На верхушке дерева"`.

- Внутренний идентификатор другого объекта, в котором будет находиться данный в начале игры (такой объект называется «родительским»). Это значение не указывается, если у объекта не будет родительского объекта, а также никогда не указывается для комнат.

  Например, птенчик описывается как:

      Object bird "птенчик/" forest

  что означает что в начале игры он будет находиться в лесной чаще (игрок затем подберёт его с собой). Платан описан следующим образом:

      Object tree "высок/ий платан/" clearing

  Он будет находиться на полянке, но так как он является `scenery`, то игрок не сможет его переместить.

  Есть другой способ описания изначального положения объекта, при помощи стрелочек, например так:

      Object -> bird "птенчик/"
      ...

  Мы не будем использовать его в примерах, но тем не менее это довольно удобный и наглядный способ.

### Свойства объектов

Свойства начинаются с ключевого слова `with`. У объекта может быть любое количество свойств и заданы они могут быть в любом порядке. Сначала идёт имя свойства, затем через пробел его значение, после значения — символ запятой.

Свойства стоит воспринимать как переменные, относящиеся к объекту. Изначально значение равно заданному, но в ходе игры его можно изменять (тем не менее обычно свойства не изменяют). Вот несколько ранее виденных примеров свойств.

    description "Гнездо сплетено из прутиков и аккуратно устлано мхом.",
    e_to forest,
    name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
    each_turn [; if (nest in branch) deadflag = 2; ],

В перечисленных примерах встречаются различные варианты значений свойств: `description` это строка, со свойством `e_to` ассоциирован объект, в свойстве `name` идёт список словарных слов, а `each_turn` содержит в себе локальную функцию. Также значение может быть числовым, например:

    capacity 10,

Существует около 50 стандартных свойств наподобие `name` или `each_turn`. Позже будут рассмотрены самые важные из них, а также то, как задать собственное свойство.

### Атрибуты объектов

Атрибуты начинаются с ключевого слова `has`. Их может быть любое количество и в любом порядке; друг от друга они отделяются пробелом.

Атрибуты проще свойств — у них нет значения, они могут либо присутствовать, либо отсутствовать (быть включены/выключены). Атрибут можно назвать флагом. Изначально если атрибут указан, то он включён (присутствует), если не указан — то выключен (отсутствует).

Ранее мы встретились со следующими атрибутами:

    container light open scenery static supporter

Каждый из них отвечает на вопрос, например, «Является ли объект контейнером?», «Является ли он источником света?» и так далее. Если атрибут указан, то ответом будет «да», если не указан — «нет».

Существует около 30 стандартных атрибутов. Можно также создавать и свои собственные.

## Связи между объектами и дерево объектов

Во время игры Информ следит за связями между объектами — то есть помнит, где находится конкретный объект относительно других объектов. При рассмотрении связей по отношению к объектам используются термины «родительский» и «дочерний».

Когда игрок находится в какой-нибудь комнате, например в лесной чаще, то можно сказать следующее:

- объект лесной чащи является родительским для объекта игрока, или что

- объект игрока является дочерним для объекта лесной чащи.

Также если игрок держит в руках объект, например гнездо, то

- объект игрока — родительский для объекта гнезда, или

- объект гнезда — дочерний для объекта игрока.

У объекта может быть только один «родитель» (или не быть родительских объектов вообще), но может быть сколько угодно «детей» (в том их может и не быть).

Например, рассмотрим следующие объекты:

    Object nest "птичь/е гнезд/о" clearing
    ...
    Object tree "высок/ий платан/" clearing

Здесь для гнезда родителем является объект `clearing`, и также для платана тоже родителем является `clearing`. То есть и гнездо, и платан являются детьми локации Полянка.

> У комнат не бывает родительских объектов, а также одним из их дочерним объектом иногда становится игрок.

Птенчика в лесной чаще мы описали следующим образом:

    Object bird "птенчик/" forest
    ...

В лесной чаще больше ничего нет, поэтому чаща является родителем объекта птенчик, и у чащи есть единственный дочерний объект, птенчик. Когда игрок, который изначально находится в `before_cottage`, переходит на ВОСТОК в чащу, то происходит следующее: родителем игрока становится `forest`, а у `forest` становится два дочерних объекта — птенчик и игрок. В такой манере Информ следит за перемещением объектов и изменением связей.

Далее, пусть игрок подбирает птенца. Происходит изменение связей: птенец теперь — дочерний объект для игрока (уже не для леса), а игрок становится и родительским (для птенца), и дочерним (для леса) объектом.

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

1. В начале игры: |
2. Игрок вводит ИДТИ НА ВОСТОК |
3. Игрок вводит ВЗЯТЬ ПТЕНЦА |
4. Игрок вводит ИДТИ НА СЕВЕРОВОСТОК |
5. Игрок вводит ПОЛОЖИТЬ ПТЕНЦА В ГНЕЗДО |
6. Игрок вводит ВЗЯТЬ ГНЕЗДО |
7. Игрок вводит ИДТИ ВВЕРХ |
8. Игрок вводит ПОЛОЖИТЬ ГНЕЗДО НА СУК |

Такую схему также называют деревом объектов. Её не нужно держать в голове, потому что библиотека выполняет всю работу за нас, и потому что объектов может быть слишком много, чтобы за всеми ними следить. Главное понять общий принцип.

Позже будут рассмотрены команды `parent`, `child` и `children`, при помощи которых можно получить для конкретного объекта его родителя, дочерние объекты и их количество.

## Двойные и одинарные кавычки

### Двойные кавычки

В двойные кавычки заключаются строки — это может быть символ, слово, абзац и вообще текст практически любой длины.

Некоторые примеры специальных символов:

- Для записи двойных кавычек в строке используется тильда: `~`

- Для переноса строки используется символ `^`

Длинные строки можно разбить на несколько строк с переносами, Информ просто склеит их, отбросив лишние пробелы (пробелы между словами остаются нетронутыми). Следующие две строки одинаковы для Информа:

    "Это строка из     разных символов."

    "Это
      строка
        из     разных
                    символов."

При выводе длинного пассажа текста интерпретатор делает автоматический перенос с края экрана. Для собственного переноса используется символ `^`.

В игре мы использовали строковую константу:

    Constant Headline
        "^Пример простой игры на Inform.
         ^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).
         ^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";

которую можно было бы с тем же успехом записать как

    Constant Headline
        "^Пример простой игры на Inform.^Авторы: Роджер Фирт (Roger Firth) и Соня Кессерих (Sonja Kesserich).^Перевод Юрия Салтыкова a.k.a. G.A. Garinson^";

Строки используются, например, в свойстве `description`:

    with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",

Также строки применяются в командах `print`, что можно будет увидеть позже.

### Одинарные кавычки

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

Когда игрок вводит команду, интерпретатор разбивает ввод на отдельные слова и затем ищет их в словаре. Если эти слова образуют некую верную команду, то он пытается её выполнить.

Пример из нашей игры:

    name 'птичь' 'гнезд' 'гнездышк' 'пруть' 'прутик' 'мох',

## Функции и инструкции

Функция представляет из себя набор инструкций, которые выполняются интерпретатором. Есть два вида функций и более 20 видов инструкций.

### Инструкции

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

    location = before_cottage;

что называется присваиванием. Присваивание задаёт новое значение для переменной, в данном случае глобальной библиотечной переменной `location`. Далее,

    if (nest in branch) deadflag = 2;

содержит сразу две инструкции, присваивание, перед которым идёт инструкция `if`:

    if (nest in branch) ...

Инструкция `if` проверяет выполнение какого-либо условия. Если условие истинно, то интерпретатор выполняет инструкцию, которая следует далее. Если условие ложно, то следующая инструкция пропускается. В данном случае проверяется условие, находится ли `nest` на или в объекте `branch` (то есть является ли дочерним). Практически всегда во время игры это будет ложно, поэтому следующая инструкция игнорируется. Когда же условие выполнится, то интерпретатор выполнит присваивание:

    deadflag = 2;

что изменит `deadflag` на 2. Обычно подчинённые инструкции записываются под `if`, с отступом, потому что так их проще читать:

    if (nest in branch)
        deadflag = 2;

### Глобальные функции

Глобальная функция представляет собой серию инструкций, у которой есть своё имя. При вызове функции выполняются эти инструкции. Вот одна из глобальных функций:

    [ Initialise; location = before_cottage; ];

Поскольку размер нашей функции мал, то мы записали её в одну строчку. Её можно отформатировать иначе:

    [ Initialise;
        location = before_cottage;
    ];

Часть `[ Initialise;` обозначает начало функции и включает её имя, по которому её можно вызвать. `];` — это конец функции. Между ними идёт тело функции, в котором содержатся инструкции. Вызвать функцию очень просто:

    Initialise();

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

> Заметьте, что мы описали функцию `Initialise`, но в игре её вызывали. На самом деле, функция вызывается при старте игры, самой библиотекой Информа.

### Локальные функции

Локальные функции похожи на глобальные, но у них нет имени, и они не заканчиваются точкой с запятой. Пример из нашей игры:

    [; if (nest in branch) deadflag = 2; ]

Точнее, мы записали эту функцию как значение свойства:

    each_turn [; if (nest in branch) deadflag = 2; ],

Его можно переписать следующим образом:

    each_turn [;
        if (nest in branch)
        deadflag = 2;
    ],

Любые локальные функции задаются таким образом — как значение свойства объекта. Они привязаны к объекту и находятся в нём.

Локальные функции не вызываются по имени как глобальные функции. Имени у них нет, эти функции вызываются библиотекой автоматически в нужный момент игры. Момент определяется ролью, которую играет свойство. В нашем случае этим моментом будет конец каждого хода, когда игрок находится в той же комнате, где сук. Позже будет показаны другие примеры локальных функций а также способ вручную вызвать локальную функцию, при помощи синтаксиса `идентификатор.свойство()`, в нашем случае это было бы `branch.each_turn()`.

Пробегитесь по имеющемуся исходному коду и убедитесь, что вам всё понятно. В следующей главе мы будем исправлять недостатки нашей первой игры.

# И снова Хейди

Даже в простой игре игрок может попробовать сделать то, о чём заранее не подумал автор. Часто разные действия игрока должны позволить один и тот же результат. Поэтому автору стоит попробовать ввести в игру все разумные варианты, которые могут прийти игроку в голову. Зачастую описания предметов или локаций прямо таки подсказывают, какие здесь есть объекты, и что можно потенциально сделать. Это тоже обязательно нужно учесть. Сделать игру довольно просто, но основное время уйдёт на продумывание различных второстепенных вариантов.

В этой главе мы рассмотрим некоторые из таких вариантов.

## Послушать птенчика

Рассмотрим пример прямо из игры:

    В лесной чаще
    На западе, сквозь густую листву, можно разглядеть небольшое
    строение. Тропинка ведет на северо-восток.

    Здесь имеется птенчик.

    > ОСМОТРЕТЬ ПТЕНЧИКА
    Слишком мал, чтобы летать, птенец беспомощно попискивает.

    > ПОСЛУШАТЬ ПТЕНЧИКА
    Никаких необычных звуков нет.

    >

Видна недоработка. Игра сообщает нам, что птенец беспомощно попискивает, но тут же говорит нам, что никаких необычных звуков нет.

В библиотеке есть обширный набор стандартных сообщений-ответов на стандартные действия. «Никаких необычных звуков нет» является стандартным ответом на команду ПОСЛУШАТЬ. Он подходит для ПОСЛУШАТЬ ГНЕЗДО или ПОСЛУШАТЬ ДЕРЕВО, но в данном случае, с птенцом, он неуместен. Нужно добавить собственную реакцию:

    Object bird "птенчик/" forest
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
            name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
            before [;
                Listen:
                    print "Жалобный писк испуганной птички разрывает тебе сердце.
                        Надо помочь!^";
                    return true;
            ],
        has male;

Рассмотрим эту часть кода по шагам:

1. Для объекта `bird` мы ввели новое свойство, `before`. Интерпретатор обращается к свойству `before` перед тем, как выполнить конкретное действие с объектом:

       before [; ... ],

2. Значением свойства является локальная функция, в которой есть метка и две инструкции:

       Listen:
           print "Жалобный писк испуганной птички разрывает тебе сердце.
               Надо помочь!^";
           return true;

3. Метка обозначает тип действия, в данном случае `Listen` («послушать»). Меткой мы сообщаем интерпретатору следующее: если действие, которое будет совершено над птенчиком это «послушать», то надо выполнить эти инструкции. В противном случае продолжать как обычно. То есть, если игрок введёт ОСМОТРЕТЬ ПТЕНЦА, ВЗЯТЬ ПТЕНЦА, ПОЛОЖИТЬ ПТЕНЦА В ГНЕЗДО, УДАРИТЬ ПТЕНЦА или ПОГЛАДИТЬ ПТЕНЦА, то он получит стандартный ответ. Если же игрок введёт ПОСЛУШАТЬ ПТЕНЦА, то действие будет «перехвачено», и выполнятся наши инструкции.

4. Выполнятся следующие инструкции:

       print "Жалобный писк испуганной птички разрывает тебе сердце.
           Надо помочь!^";

   что выведет на экран указанную строку (^ выполнит перенос на следующую строку). Далее,

       return true;

   что скажет интерпретатору, что ему больше не нужно ничего делать, потому что мы самостоятельно обработали действие `Listen`. Теперь в игре мы получим то, что хотели:

       > ПОСЛУШАТЬ ПТЕНЧИКА
       Жалобный писк испуганной птички разрывает тебе сердце. Надо
       помочь!

       >

Стоит остановиться подробнее на инструкции `return true`. Свойство `before` перехватывает действие над объектом прежде, чем интерпретатор начнёт с ним что-то делать. В этот момент выполняются инструкции из локальной функции. Если последней инструкцией идёт `return true`, то значит что действие обработано и интерпретатору больше ничего не нужно делать, никаких действий или сообщений. Однако если в конце функции стоит `return false`, тогда интерпретатор продолжит выполнять действие так, будто оно не было перехвачено. Иногда это полезно, но не в нашем случае. Если записать эту секцию следующим образом:

    Object bird "птенчик/" forest
        with description "Слишком мал, чтобы летать, птенец беспомощно попискивает.",
            name 'детеныш' 'птиц' 'птичк' 'птенчик' 'птенц' 'маленьк',
            before [;
                Listen:
                    print "Жалобный писк испуганной птички разрывает тебе сердце.
                        Надо помочь!^";
                    return false;
            ],
        has male;

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

    > ПОСЛУШАТЬ ПТЕНЧИКА
    Жалобный писк испуганной птички разрывает тебе сердце. Надо
    помочь!
    Никаких необычных звуков нет.

    >

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

## Вход в домик

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

    Перед домом
    Ты стоишь около избушки, на восток от которой раскинулся лес.

    > ИДТИ ВНУТРЬ
    Этот путь недоступен.

    >

Опять видим не самый лучший ответ. Но это легко исправить:

    Object before_cottage "Перед домом"
        with description
                "Ты стоишь около избушки, на восток от которой раскинулся лес.",
            e_to forest,
            in_to "Такой славный денек... Он слишком хорош, чтобы прятаться внутри.",
            cant_go "Единственный путь ведет на восток.",
        has light;

Обычно свойство `in_to` вело бы в другую комнату, как, например `e_to`, но если указать строку, то интерпретатор выведет эту строку, когда игрок попытается пойти ВНУТРЬ. Если пойти в другом неуказанном направлении, например НАВЕРХ или ИДТИ НА СЕВЕР, то игрок всё равно получит ответ «Этот путь недоступен», но это также легко изменить, добавив свойство `cant_go` с соответствующей строкой. Получим более дружелюбное поведение игры:

    Перед домом
    Ты стоишь около избушки, на восток от которой раскинулся лес.

    > ВНУТРЬ
    Такой славный денек... Он слишком хорош, чтобы прятаться внутри.

    > СЕВЕР
    Единственный путь ведет на восток.

    > ВОСТОК

    В лесной чаще
    ...

Здесь есть и другая проблема — мы не реализовали саму избушку, поэтому ОСМОТРЕТЬ ИЗБУШКУ выдаст игроку «Этого предмета здесь нет». Добавим объект cottage и сделаем его с атрибутом `scenery`, аналогично дереву:

    Object cottage "маленьк/ий домик/" before_cottage
        with description "Домик мал и неказист, но ты очень счастлива, живя здесь.",
            name 'маленьк' 'дом' 'изб' 'терем' 'коттедж' 'хат' 'небольш' 'строен'
                'домик' 'избушк' 'теремок' 'хатк',
            has scenery male;

Это решает проблему, но приводит к ещё одному неподходящему ответу:

    Перед домом
    Ты стоишь около избушки, на восток от которой раскинулся лес.

    > ВОЙТИ В ДОМИК
    Но на/в маленький домик невозможно войти, встать, сесть или лечь.

Это решается аналогично тому, как мы сделали ПОСЛУШАТЬ ПТЕНЧИКА:

    Object cottage "маленьк/ий домик/" before_cottage
        with description "Домик мал и неказист, но ты очень счастлива, живя здесь.",
            name 'маленьк' 'дом' 'изб' 'терем' 'коттедж' 'хат' 'небольш' 'строен'
                'домик' 'избушк' 'теремок' 'хатк',
            before [;
                Enter:
                    print_ret "Такой славный денек...
                        Он слишком хорош, чтобы прятаться внутри.";
                ],
            has scenery male;

При помощи свойства `before` мы перехватываем действие `Enter` (ВОЙТИ), которое применяется к объекту `cottage`. Правда, в этот раз мы воспользовались только одной инструкцией, а не двумя. Просто необходимость «вывести строку, сделать перевод строки, и затем `return true`» встречается так часто, что для этого есть отдельная инструкция, `print_ret`. То есть:

    print_ret "Такой славный денек...
               Он слишком хорош, чтобы прятаться внутри.";

идентично

    print "Такой славный денек...
               Он слишком хорош, чтобы прятаться внутри.^";
    return true;

Заметьте, что в `print_ret` нам не понадобился символ переноса `^`.

## Залезть на дерево

На полянке предполагается, что игрок введёт ВВЕРХ. Но игрок с большой вероятностью попробует ЗАЛЕЗТЬ НА ДЕРЕВО, но получит в ответ лишь «Забираться на высокий платан бессмысленно». Вновь воспользуемся свойством `before`, но чуть по-другому.

    Object tree "высок/ий платан/" clearing
        with description
            "Величавое дерево стоит посреди поляны.
             Кажется, по его стволу будет несложно влезть наверх.",
            name 'высок' 'платан' 'дерев' 'ствол' 'величав',
            before [;
                Climb:
                    PlayerTo(top_of_tree);
                    return true;
            ],
        has scenery male;

Здесь мы перехватываем действие `Climb` (ЗАЛЕЗТЬ), применяемое к объекту `tree`, но не для того, чтобы вывести своё сообщение, а для того, чтобы переместить игрока в другую комнату, так как если бы он ввёл ВВЕРХ. Перемещать игрока вручную довольно сложно, но, к счастью, в библиотеке есть стандартная функция, которая делает всё за нас.

Функция называется `PlayerTo`, и её нужно вызвать с параметром — идентификатором комнаты, куда мы хотим переместить игрока. При вызове параметр указывается внутри скобок: `PlayerTo(top_of_tree)`. Ранее мы встретились с функцией `Initialise`, эта функция не принимает никаких параметров, поэтому мы сказали, что её можно было бы вызвать как `Initialise()`.

Мы переместили игрока, но всё ещё находимся в перехватчике действия `Climb`. И поскольку мы уже обработали действие самостоятельно, нам не нужен стандартный ответ, и мы выполняем `return true`.

## Бросить предмет с дерева

В любой комнате если игрок введёт ПОЛОЖИТЬ (БРОСИТЬ) предмет, который он несёт с собой, то он упадёт в той же локации рядом на землю. Это поведение работает убедительно везде, кроме верхушки дерева — там предмет не должен падать рядом, а должен падать вниз на опушку.

Нам нужно перехватить действие `Drop`, но несколько иначе, чем мы делали раньше. Во-первых, действие должно срабатывать не для конкретных `bird` или `nest`, но в общем, то есть для любых предметов. И во-вторых, нужно учесть, что не все предметы можно бросить: например, нельзя БРОСИТЬ СУК.

Для решения второго пункта нужно перехватить действие `Drop` не до, а после того, как оно произошло. Так мы даём библиотеке разобраться с объектами, которые вообще нельзя бросить, или которых нет в руках у игрока, и вступаем только тогда, когда предмет уже был брошен. А для решения первого пункта мы будем перехватывать `Drop` не для наших объектов, а прямо на локации, где это происходит, то есть на верхушке дерева, `top_of_tree`:

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
            after [;
                Drop:
                    move noun to clearing;
                    return false;
            ],
        has light;

Рассмотрим этот код по шагам:

1. Для нашей комнаты мы добавили свойство `after`. Интерпретатор обращается к этому свойству после того, как выполнит любое действие в этой комнате:

       after [; ... ],

2. Значение свойства является локальной функцией, содержащей метку и две инструкции:

       Drop:
           move noun to clearing;
           return false;

3. Метка обозначает имя действия, в данном случае `Drop`. Мы сообщаем интерпретатору следующее: если только что было совершено действие `Drop`, то выполни эти инструкции перед тем, как сообщить игроку, что действие завершено. Если произошло другое действие, то продолжи как обычно.

4. Сначала выполняется инструкция

       move noun to clearing;

   которая берёт объект, который был перенесён из объекта игрока, `player`, в объект `top_of_tree` (так как выполнилось действие `Drop`), и переносит его ещё раз, в объект `clearing`. В инструкции `noun` является библиотечной переменной, в которой будет храниться идентификатор объекта, к которому применяется действие. То есть если игрок вводит БРОСИТЬ ГНЕЗДО, то `noun` укажет на `nest`, а если БРОСИТЬ ПТЕНЦА, то `noun` станет `bird`. Далее мы исполняем

       return false;

   что говорит интерпретатору, что теперь он может вывести игроку свой ответ, то есть, что произошло.

   Вот что мы получим в игре:

       На верхушке дерева
       На этой высоте цепляться за ствол уже не так удобно.

       Здесь имеется надежный толстый сук.

       > БРОСИТЬ ГНЕЗДО
       Птичье гнездо положено.

       > ОСМОТРЕТЬСЯ

       На верхушке дерева
       На этой высоте цепляться за ствол уже не так удобно.

       Здесь имеется надежный толстый сук.

       > ВНИЗ

       Полянка

       Здесь имеется птичье гнездо (где имеется птенчик).

       >

Конечно, сообщение «Птичье гнездо положено». совсем не информативно в данном случае, поэтому можно сделать так:

    Object top_of_tree "На верхушке дерева"
        with description "На этой высоте цепляться за ствол уже не так удобно.",
            d_to clearing,
            after [;
                Drop:
                    move noun to clearing;
                    print_ret "Предмет упал вниз на землю.";
            ],
        has light;

Здесь `print_ret` выводит более корректное сообщение и возвращает `true`, что означает что интерпретатор больше не должен ничего выводить на экран.

## Птица в гнезде

Игра заканчивается, когда игрок кладёт гнездо на сук. Мы предположили, что птенец уже находится в гнезде, но это может быть не так. Необходимо также проверить, лежит ли птенец в гнезде. Это легко сделать:

    Object branch "надежн/ый толст/ый сук/" top_of_tree
        with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
                          держалось что-то не очень большое.",
            name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
            each_turn [; if (bird in nest && nest in branch) deadflag = 2; ],
        has static supporter male;

Мы расширили инструкцию `if`:

    if (bird in nest && nest in branch) deadflag = 2;

Её можно прочитать так: «Если `bird` находится на/в гнезде, и `nest` находится на/в `branch`, то установить `deadflag` равным 2. В противном случае ничего не делать».

## Заключение

К этому моменту вы уже поняли, что в игре обязательно надо реализовывать не только свои действия, но и те необязательные, которые могут прийти в голову игроку. Игрок может попробовать вообще что-то глупое, но ему будет интересно получить нестандартный ответ. Непроработка ответов может вообще разрушить эффект погружения.

В этой главе мы рассмотрели:

### Свойства объектов

У объектов может быть свойство `before`, и если оно есть, то интерпретатор обращается к нему перед выполнением действия над объектом. Аналогично есть свойство `after`, куда происходит обращение уже после выполненного действия, но ещё до того, как выведется сообщение игроку. И `before`, и `after` могут быть как у объектов-предметов (где перехватываются действия применимые к этим объектам), так и у комнат (где перехватываются действия, которые происходят с объектами в комнате).

Значениями этих двух свойств являются локальные функции. Если функция заканчивается `return false`, то интерпретатор продолжит выполнение, а если `return true` — то интерпретатор больше ничего делать не будет. Так можно либо частично изменить выполнение действия, либо заменить его полностью.

Ранее свойства соединений комнат указывали на объект комнаты, куда произойдёт перемещение. В этой главе также было показано, что это может быть строка (в которой указана причина, почему перемещение невозможно). Также было рассмотрено свойство `cant_go`, которое учитывает все неуказанные направления:

    e_to forest,
    in_to "Такой славный денек... Он слишком хорош, чтобы прятаться внутри.",
    cant_go "Единственный путь ведет на восток.",

### Функции и параметры

В библиотеке есть много удобных функций, и мы воспользовались функцией `PlayerTo`, которая позволяет перенести игрока в другую комнату, не обязательно соседнюю.

При вызове этой функции нам нужно было указать конечную комнату, и мы сделали это в скобках:

    PlayerTo(clearing);

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

    PlayerTo(clearing, 1);

Здесь 1 подавляет вывод описания новой комнаты.

### Инструкции

Нам встретились новые инструкции:

    return true;
    return false;

Они использовались для управления интерпретатором в локальных функциях.

    print "строка";
    print_ret "строка";

Инструкция `print` выводит указанную строку, а `print_ret` выводит строку, делает перенос и выполняет `return true`.

    if (условие && условие) ...

Мы расширили условие в инструкции `if`. `&&` — оператор «И», который используется для проверки выполнения нескольких условий сразу. Также существуют оператор `||` «ИЛИ» и оператор `~~` «НЕ».

    move объект to родитель;

Инструкция `move` изменяет дерево объектов, устанавливая для объекта нового родителя.

### Действия

Мы говорили о перехвате таких действий как `Listen`, `Enter`, `Climb` и `Drop`. Действие это представление того, что должно быть сделано, в зависимости от глагола, который введёт игрок. Например, ПОСЛУШАТЬ и ПРИСЛУШАТЬСЯ суть одно и то же, поэтому им соответствует действие `Listen`. Аналогично ВОЙТИ, ЗАЙТИ, СЕСТЬ НА, ЛЕЧЬ НА приводят к действию `Enter`, а ПОЛОЖИТЬ, ВЫБРОСИТЬ означают `Drop`. Так облегчается работа писателя-программиста, потому что различных глаголов может быть множество, но количество разных действий гораздо меньше.

В библиотеке каждому действию соответствует номер, и этот номер хранится в переменной `action`. Также есть переменная `noun`, в которой хранится идентификатор объекта, над которым производится действие, а также `second` для второго такого объекта (если он есть).

Вот несколько примеров:

Ввод игрока | действие | noun | second
--- | --- | --- | ---
СЛУШАТЬ | `Listen` | `nothing` | `nothing`
СЛУШАТЬ ПТЕНЧИКА | `Listen` | `bird` | `nothing`
ПОДНЯТЬ ПТЕНЧИКА | `Take` | `bird` | `nothing`
ПОЛОЖИТЬ ПТЕНЧИКА В ГНЕЗДО | `Insert` | `bird` | `nest`
БРОСИТЬ ГНЕЗДО | `Drop` | `nest` | `nothing`
ПОЛОЖИТЬ ГНЕЗДО НА СУК | `PutOn` | `nest` | `branch`

`nothing` — это встроенная константа, обозначающая отсутствие объекта.

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

# Пример игры: Вильгельм Телль

В данной главе сейчас нет материалов, но в будущем будет подробно разобрана разработка второй обучающей игры, под названием «Вильгельм Телль».

А пока в «Вильгельма Телля» можно поиграть онлайн: https://iplayif.com/?story=https://rinform.org/demos/WTellR.z5

И исходный код почитать здесь: https://github.com/yandexx/rinform-glulx/blob/master/demos/WTellR.inf

# FAQ

В данной главе покрыты:

- общие вопросы об Информе;
- часто возникающие вопросы о том, как работает русская версия.

# Общие вопросы

## В чём разница между Z-machine и Glulx?

Это два формата файлов, в которые можно компилировать игры на Информе. Более старая, классическая Z-machine поддерживает размер файла игры до 256 Кб (формат .z5) или 512 Кб (формат .z8) и 16 цветов. Glulx — более современная 32-битная система, поддерживающая файлы размером до 4 Гб и расширенные мультимедийные возможности. Компилятор inform умеет компилировать игры в любом из форматов.

В английской версии Информа библиотека единая, но в русской версии пришлось разнести её на два отдельных проекта.

- Glulx: https://github.com/yandexx/rinform-glulx
- Z-machine: https://bitbucket.org/yandexx/rinform/

Начиная с версии 0.9 Русского Информа Glulx считается стабильной и рекомендуемой версией. Разработку стоит вести под неё. Версия для Z-машины считается вторичной.

Glulx имеет следующие преимущества:

- мультимедиа-фичи: картинки, звук, ссылки.
- расширенные возможности типографики.
- возможность разделять окно на произвольные области.
- полная поддержка UTF-8 как в исходниках, так и в готовых играх.
- файлы игр до 4 гигабайт.

Известное ограничение: в онлайн-версии Glulx (Quixe) нельзя в коде игры задавать цвета или размер шрифтов, кроме глобальных настроек через .css.

## Какие плееры открывают игры на Информе?

- Glulx (файлы .ulx и .gblorb): Windows Git, Windows Glulxe, Lectrote, а также онлайн.
- Z-machine (файлы .z5, .z8 и .zblorb): Windows Frotz, Lectrote, fizmo, а также онлайн через Parchment.
- На Android плеер Fabularium поддерживает все существующие форматы.

## Как опубликовать игру онлайн?

Чтобы запустить произвольную игру в Parchment, нужно указать параметром для сайта iplayif.com путь к файлу игры, уже залитому на какой-нибудь сервер. Например: https://iplayif.com/?story=https://rinform.org/games/photopia/PhotopiaR.z8

Очень просто и разместить игру на своём собственном сайте, даже статическом, т.к. Parchment работает полностью на клиентском JavaScript. Достаточно скачать и разместить у себя на сервере файлы:

- index.html,
- lib/parchment.min.js,
- lib/parchment.min.css,
- lib/jquery.min.js
- lib/zvm.min.js

и отредактировать index.html. В .css файле можно поменять шрифты, цвета и прочее, на что хватит вашей фантазии. Несколько примеров: Винтер, Delightful Wallpaper, Dreamhold.

# Особенности русской версии

Для компиляции в Glulx исходники должны быть в UTF-8. Пример командной строки для компиляции игры:

    inform.exe +library +language_name=Russian -G -Cu $DICT_CHAR_SIZE=4 game.inf game.ulx

Для компиляции в Z-машину исходные файлы игры должны быть в кодировке Windows 1251. Пример командной строки для компиляции игры, где указаны все обязательные параметры:

    inform.exe +library +charset_map=library\cyrwin.cm +language_name=Russian -v5 game.inf game.z5

## Как описывать объекты

Нижеперечисленное относится только к объектам. На комнаты (локации) эти правила не распространяются.

1. В имени объекта нужно отделить окончание (или окончания) прямым слешем (/), а точнее — те буквы, которые должны будут изменяться при склонении. Например: латунн/ая ламп/а, надпис/ь на стене, бел/ый туман/. Имя используется при выводе объектов во всевозможных ситуациях, в разных падежах.

2. В свойстве `name` указываются слова, по которым парсер находит объекты. В русской версии нужно перечислять такие слова без окончаний. Здесь нужно не забыть указать и синонимы, по которым объект тоже должен находиться парсером.

3. Каждому объекту (кроме комнат) обязательно нужно выдать один из атрибутов, соответствующий роду или числу объекта. Один из четырёх: male — мужской род, female — женский род, neuter — средний род, pluralname — множественное число.

   Примеры:

       Object -> "друг/ой склон/ холма"
         with name 'склон' 'сторон' 'холм',
         description "Кто мешает изучить его самому?",
         has  scenery male;

       Object -> "груб/ые каменн/ые ступен/и"
         with name 'груб' 'каменн' 'ступен',
         description "Грубые каменные ступени ведут по куполу вверх.",
         has  scenery pluralname;

4. Особый случай — это существительные с беглыми гласными: ковёр (ковром), перекрёсток (перекрёстком) и так далее. Их нужно окружить двумя слешами, а к объекту добавить свойство `casegen`, по примеру ниже. Четвёртым параметром в функции ICVowel должна идти беглая гласная. Есть более сложные случаи, где новые буквы появляются (пятый параметр), см. пример ниже про «ручей».

       Object -> "туманн/ый колод/е/ц/"
         with name 'колодец' 'колодц',
         casegen [ beg end csID;
           return ICVowel (csID, beg, end, 'е', 0);
         ],
         description "Из колодца поднимаются бесформенные клубы белого тумана.",
         has scenery male;

       Treasure -> large_gold_nugget "огромн/ый золот/ой самород/о/к/"
         with name 'золот' 'огромн' 'слиток' 'слитк' 'самородок' 'самородк' 'кусок' 'куск',
         description "Массивный кусок самородного золота!",
         casegen [ beg end csID;
           return ICVowel (csID, beg, end, 'о', 0);
         ],
         initial "На полу поблескивает большой золотой самородок!",
         has male;

       Object Stream "бурн/ый руч/е//й"
         with name 'руче' 'ручь' 'речк' 'поток' 'вод',
         description "Холодный бурный ручей струится вниз по каменистому руслу.",
         casegen [ beg end csID;
           return ICVowel (csID, beg, end, 'е', 'ь');
         ],
         has scenery male;

5. Есть случаи, когда нужно, чтобы у мужского объекта была женская логика для склонения имени. Например, мужское имя «Слава». В таком случае необходимо дополнительно выдать объекту атрибут fem_grammar.

6. Если возникает случай, где парсер не в состоянии самостоятельно просклонять имя объекта (на вывод), необходимо указать свойство-функцию `casegen` и через неё явно указать все нужные склонения. Кроме того, необходимо изменить имя объекта. Например:

       Object Key "/неважно_что_здесь_будет_написано_главное_без_пробелов"
         with name 'ключ',
         description "Твой любимый ключ на тридцать два.",
         casegen [ beg end csID;
           switch (csID) {
             csNom: print "ключ"; rtrue;
             csGen: print "ключа"; rtrue;
             csDat: print "ключу"; rtrue;
             csAcc: print "ключ"; rtrue;
             csIns: print "ключом"; rtrue;
             csPre: print "ключе"; rtrue;
           }
         ],
         has male;

7. Если парсер не распознаёт объект автоматически (на ввод), то следует пользоваться свойством-функцией `parse_name`. Подробное описание находится в DM4 (pdf) на странице 209. В простом примере ниже имя персонажа «Моро» не склоняется.

       parse_name [n;
           while (NextWord() == 'моро') n++;
           return n;
       ],

## Как проверить, что объект верно склоняется по падежам?

Для этого есть удобная команда мета! форм. Чтобы она работала, убедитесь, что игра скомпилирована в режиме Debug (-D).

    >мета! форм самородок
    Объект «огромн/ый золот/ой самород/о/к/» (Ед.ч./М.р.):

    И.п.: Огромный золотой самородок
    Р.п.: Огромного золотого самородка
    Д.п.: Огромному золотому самородку
    В.п.: Огромный золотой самородок
    Т.п.: Огромным золотым самородком
    П.п.: Огромном золотом самородке

    >

## Как вывести объект в каком-либо падеже

Для этого есть следующие функции. Вторая функция нужна для вывода с большой буквы (обычно, в начале предложений).

- `cNom` и `ССNom` — именительный (Nominative).
- `cAcc` и `ССAcc` — винительный (Accusative).
- `cGen` и `ССGen` — родительный (Genitive).
- `cDat` и `ССDat` — дательный (Dative).
- `cIns` и `ССIns` — творительный (Instrumental).
- `cPre` и `ССPre` — предложный (Prepositive).

    "Вы тщательно установили неуклюжую монстроподобную фотокамеру,
     направили свет лампы на цель и терпеливо дождались,
     пока экспонирование ", (cGen) noun, " не завершится.";

    print_ret "Вам нет нужды беспокоиться о ", (cPre) self, ".";

    [ SaluteSub;
        if (noun has animate)
            print_ret (CCNom) noun, " приветствует тебя.";
        print_ret (CCNom) noun, " не замечает этого.";
    ];

## Вспомогательные функции

Все эти функции принимают объект, и вызывать их в коде нужно как `(function) noun`.

- `PronounS` выводит местоимение, подходящее объекту («ты», «он», «она», «оно», «они»).
- `Pronoun` работает как `PronounS`, но выводит с заглавной буквы.
- `SAEnd` выводит окончание краткой формы прилагательных или причастий («открыта», «открыто», «открыты»).
- `V1aEnd` выводит окончания глаголов -ет или -ут.
- `V1bEnd` выводит окончания глаголов -ет или -ют.
- `V2aEnd` выводит окончания глаголов -ит или -ат.
- `V2bEnd` выводит окончания глаголов -ит или -ят.
- `VPEnd` выводит окончание для глаголов в прошедшем времени («пропал», «пропала», «пропало», «пропали»).
- `AEnd`: окончания прилагательных -ый, -ая, -ое, -ые.
- `AEnd2`: окончания прилагательных -ий, -ая, -ое, -ие.
- `AEnd3`: окончания прилагательных -ой, -ая, -ое, -ые.
- `PEnding1`: -им, -ой, -ими.
- `PEnding2`: -ым, -ой, -ыми.
- `GenIt`: его, её, их.
- `GenIt2`: него, неё, них.
- `DatIt`: ему, ей, им.
- `DatIt2`: нему, ней, ним.
- `InsIt`: им, ей, ими
- `InsIt2`: ним, ней, ними.

## Как описывать русские глаголы

1. Глаголы нужно перечислять по корневой части. Парсер распознает приставки («по», «за» и т.д.) и суффиксы-окончания («ся», «ять», «ать» и т.д.) автоматически.

2. Нужно учесть и то, что глаголы могут вводиться и в повелительном наклонении: например, не только «взять» (вз), но и «возьми» (возьм). Это необходимо для приказов, которые игрок может отдавать NPC: «гоблин, отдай мне ключ».

3. Для существительных, с которыми оперирует глагол, нужно указывать токены. Какой токен использовать, определяет падеж существительного в данном контексте. Например, «взять ключ» — винительный падеж (Accusative), «дотронуться до двери» — дверь в родительном падеже (Genitive).

   Есть следующие токены:

   - `cNom_noun` — именительный (Nominative).
   - `cAcc_noun` — винительный (Accusative).
   - `cGen_noun` — родительный (Genitive).
   - `cDat_noun` — дательный (Dative).
   - `cIns_noun` — творительный (Instrumental).
   - `cPre_noun` — предложный (Prepositive).

   Кроме этого, можно использовать более строгие токены, соответствующие категориям объектов:

   - `cIns_held`, `cAcc_held`, `cGen_held` — для объектов, которые есть в инвентаре игрока (у которых есть атрибут held).
   - `cAcc_creat`, `cGen_creat`, `cDat_creat` — для живых объектов (NPC с атрибутом animate).
   - `cAcc_multi` — для группы объектов (например, команда «взять» позволяет брать более одного объекта).
   - `cAcc_multiheld`, `cAcc_multiexcept`, `cAcc_multiinside`.

4. Также, не обязательно, но рекомендуется добавить объект с этими корневыми частями в VerbDepot, как указано в примере ниже. В нём нужно указать полную форму глагола, которая иногда выводится в игре.

       ! "вязать"
       Verb    'вяз' 'вяж'
           * cAcc_noun         -> Tie
           * cAcc_noun 'к' cDat_noun   -> Tie      ! "привязать"
           * 'к' cDat_noun cAcc_noun   -> Tie reverse
           * cAcc_noun 'с'/'со' cIns_noun  -> Tie      ! "связать"
           * 'с'/'со' cIns_noun cAcc_noun  -> Tie reverse;

       Object "вязать" VerbDepot
           with name 'вяз' 'вяж';