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

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

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

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

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

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

Обратиться за помощью можно на канале #rinform в чате Discord.

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

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

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

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

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

  • Gargoyle — кроссплатформенный плеер, ориентированный на типографику. Поддерживает все форматы. Скачать для Windows
  • 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 для второго такого объекта (если он есть).

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

Ввод игрокадействиеnounsecond
СЛУШАТЬListennothingnothing
СЛУШАТЬ ПТЕНЧИКАListenbirdnothing
ПОДНЯТЬ ПТЕНЧИКАTakebirdnothing
ПОЛОЖИТЬ ПТЕНЧИКА В ГНЕЗДОInsertbirdnest
БРОСИТЬ ГНЕЗДОDropnestnothing
ПОЛОЖИТЬ ГНЕЗДО НА СУКPutOnnestbranch

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

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

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

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

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

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

Справочник

В данном разделе сейчас находятся:

  • описание синтаксиса языка;
  • справка по стандартным свойствам;
  • справка по стандартным атрибутам;
  • справка по стандартным действиям.

Синтаксис Информа

Описание всех конструкций языка Inform 6, с дополнениями, касающихся русской версии.

Значения

Значения в Информе бывают следующих типов:

  • Численные:
    • Десятичные: 451
    • Шестнадцатеричные: $1C3
    • Двоичные: $$111000011
  • Действие: ##Look
  • Символ: 'ъ'
  • Словарное слово: 'хлопушк/а'. Можно использовать как двойные, так и одинарные кавычки.
  • Строка, в двойных кавычках: "Новогодняя хлопушка.". Есть набор специальных символов, в частности:
    • ^ — перенос строки.
    • ~ — типографские кавычки ("). Но в русском языке принято использовать кавычки-ёлочки: « и » (в Z-машине это @<< и @>>).
    • @@64 — «cобачка», @.
    • @@92 — обратный слеш, \.
    • @@94 — карет, ^.
    • @@126 — тильда, ~.

Имена

Имена есть у констант, переменных, массивов, классов, свойств, объектов, атрибутов, функций и меток.

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

Константы

Константа — это значение, которое не изменяется во время игры. По умолчанию константа равна 0.

Constant имя_константы;
Constant имя_константы = выражение;

Есть несколько стандартных констант: true (1), false (0), nothing (0), NULL (-1).

Переменные и массивы

Переменные и массивы могут изменяться в процессе игры, и по умолчанию равны 0.

Глобальные переменные:

Global имя_переменной;
Global имя_переменной = выражение;

Массив машинных слов (word). Слово в Glulx — 4 байта, в Z-машине — 2 байта. Элементы массива длиной N доступны через массив-->0, массив-->1, ... массив-->(N-1).

Array имя_массива ––> N;
Array имя_массива ––> выражение1 выражение2... выражениеN;
Array имя_массива ––> "строка";

Массив-таблица. Элементы таблицы длиной N доступны через массив-->1, массив-->2, ... массив-->N, при этом массив-->0 будет равно N.

Array имя_массива table N;
Array имя_массива table выражение1 выражение2... выражениеN;
Array имя_массива table "строка";

Байтовый массив. Элементы массива длиной N доступны через массив->0, массив->1, ... массив->(N-1).

Array имя_массива –> N;
Array имя_массива –> выражение1 выражение2... выражениеN;
Array имя_массива –> "строка";

Массив-строка. Элементы строки длиной N доступны через массив->1, массив->2, ... массив->N, при этом массив->0 будет равно N.

Array имя_массива string N;
Array имя_массива string выражение1 выражение2... выражениеN;
Array имя_массива string "строка";

Переменные также есть у объектов (как свойства) и у процедур (локальные переменные).

Выражения и операторы

Арифметические и логические операции:

  • p + q сложение,
  • p - q вычитание,
  • p * q умножение,
  • p / q целочисленное деление,
  • p % q остаток от деления,
  • p++ прибавляет единицу к p и возвращает предыдущее значение,
  • ++p прибавляет единицу к p и возвращает новое значение,
  • p-- вычитает единицу от p и возвращает предыдущее значение,
  • --p вычитает единицу от p и возвращает новое значение,
  • p & q побитовое И,
  • p | q побитовое ИЛИ,
  • ~p побитовое НЕ.

Условные выражения возвращают true или false, в зависимости от того, выполняется ли условие. q может быть списком, например q1 or q2 or... qN.

  • p == q p равно q,

  • p ~= q p не равно q,

  • p > q p больше q,

  • p < q p меньше q,

  • p >= q p больше либо равно q,

  • p <= q p меньше либо равно q,

  • p ofclass q объект p является объектом класса q,

  • p in q объект p является непосредственным дочерним для объекта q,

  • p notin q объект p не является непосредственным дочерним для объекта q,

  • p provides q у объекта p есть свойство q,

  • p has q у объекта p есть атрибут q,

  • p hasnt q у объекта p нет атрибута q.

  • p && q возвращает true если оба значения true (ненулевое),

  • p || q возвращает true если хотя бы одно из значений true (ненулевое),

  • ~~p возвращает true если p равно false (нулю).

Чтобы проверить, является ли объект q дочерним для объекта p с рекурсией (чтобы проверялись дети детей и т.д.), используйте IndirectlyContains(p,q).

Чтобы найти ближайшего родителя для двух объектов: CommonAncestor(p,q). Если его нет, вернётся nothing.

Генератор случайных чисел:

  • random(N) вернёт случайное число от 1 до N.
  • random(значение, значение, значение...) вернёт одно из значений случайным образом.

Классы и объекты

Класс — это шаблон для объектов со схожими свойствами.

Class  имя_класса(N)
 class наследуемый_класс наследуемый_класс... наследуемый_класс
 with  свойство,
       ...
       свойство,
 has   атрибут атрибут... атрибут;

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

Объект может быть самостоятельным, или наследован от класса или нескольких классов.

Object [->...] имя_объекта "текстовое имя" объект_родитель
 class наследуемый_класс наследуемый_класс... наследуемый_класс
 with  свойство,
       ...
       свойство,
 has   атрибут атрибут... атрибут;

Вместо Object можно сразу использовать имя класса. Имя объекта, текстовое имя, стрелки и родитель — необязательны. Нельзя использовать одновременно стрелки и объект_родитель.

Каждая из секций — class, with и has (а также редко используемая private) — опциональна, и они могут идти в любом порядке.

Чтобы определить класс объекта (Class, Object, Routine, String или nothing), используйте metaclass(объект).

Секция has

Каждый атрибут в списке:

  • либо выставлен: атрибут,
  • либо снят: ~атрибут.

Чтобы изменить атрибуты в процессе игры: give объект атрибут ... атрибут.

Секция with

Каждое свойство может включать выражение, строку или встроенную процедуру (или несколько).

свойство
свойство значение
свойство значение значение... значение

Доступ к свойству осуществляется через объект.свойство, либо изнутри самого объекта через self.свойство.

Несколько свойств образуют массив свойств (таким обычно является name). В этом случае объект.#свойство возвращает количество байт в массиве, а к записям можно обратиться через объект.&свойство-->0, объект.&свойство-->1 и т.д.

Через объект.класс::свойство можно обратиться к свойству, наследованному из класса. Это вернёт изначальное значение, до того как оно было изменено в объекте.

Работа с деревом объектов

  • move объект to новый_родитель — перемещает объект по дереву;
  • remove объект — удаляет объект;
  • parent(объект) — возвращает родителя объекта (или nothing);
  • child(объект) — возвращает первый дочерний объект (или nothing);
  • sibling(объект) — возвращает соседний дочерний объект родителя (или nothing);
  • children(объект) — возвращает количество непосредственных дочерних объектов.

Обмен сообщениями

  • класс.remaining() — сколько ещё можно создать объектов этого класса;
  • класс.create() — создаёт и возвращает новый объект класса. Возвращает nothing, если создать объект нельзя;
  • класс.destroy(объект) — удаляет динамически созданный объект. Статически созданные объекты так удалить нельзя;
  • класс.recreate(объект) — удаляет динамически созданный объект и создаёт его заново;
  • класс.copy(объект1,объект2) — копирует свойства и атрибуты из первого объекта во второй;
  • объект.property(арг1,aрг2, ... aрг7) — вызывает процедуру-свойство объекта с параметрами;
  • процедура.call(арг1,aрг2, ... aрг7) — вызывает процедуру с параметрами. Это аналогично процедура(арг1,aрг2, ... aрг7);
  • строка.print() — выводит строку;
  • строка.print_to_array(массив) — выводит строку в байтовый массив.

Инструкции

Каждая инструкция должна заканчиваться точкой с запятой: ;.

переменная = выражение;

Блок инструкций — это либо одна инструкция, либо несколько, окружённых фигурными скобками { и }.

Комментарии в коде начинаются с восклицательного знака !.

Множественное присваивание можно делать так:

переменная = переменная = ... = выражение;
переменная = выражение, переменная = выражение, ... ;

Процедуры

У процедуры может быть до 15 локальных переменных. По умолчанию на каждом вызове они изначально равны 0. Рекурсия работает.

Самостоятельная процедура

  • имеет имя, по которому её нужно вызывать, процедура(). Также можно вызвать её не напрямую через indirect(процедура, арг1, арг2, ... арг 7);
  • может принимать аргументы, процедура(арг1, арг2, ... арг 7), которые инициализируют локальные переменные;
  • возвращает true в самом конце при достижении ];.

[ имя_процедуры
    лок_переменная лок_переменная... лок_переменная;
    инструкция;
    инструкция;
    ...
    инструкция;
];

Вложенная процедура

  • не имеет имени, является свойством объекта. Её можно вызвать явно через объект.свойство();
  • принимает аргументы, только если её вызвать явно;
  • возвращает false в самом конце при достижении ];.

свойство [
    лок_переменная лок_переменная... лок_переменная;
    инструкция;
    инструкция;
    ...
    инструкция;
];

Процедуры возвращают одно значение, либо по достижении ];, либо явно:

  • return выражение;
  • return; — возвращает true;
  • rtrue; — возвращает true;
  • rfalse; — возвращает false.

Ветвление

Для проверки условий используются if (если условие выполнено) и else (если не выполнено):

if (выражение)
    блок_инструкций

if (выражение)
    блок_инструкций
else
    блок_инструкций

Если нужно выполнить код в зависимости от значения выражения, используется switch:

switch (выражение) {
    значение: инструкция;... инструкция;
    значение: инструкция;... инструкция;
    ...
    default: инструкция;... инструкция;
}

где каждое значение может быть:

  • константой
  • интервалом констант: 10 to 42;
  • перечислением констант через запятую.

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

jump метка;
...
.метка; инструкция;

Циклы

Цикл с предусловием:

while (выражение)
    блок_инструкций

Цикл с постусловием:

do
    блок_инструкций
until (выражение)

Цикл со счётчиком:

for (установка_переменной : условие : изменение_переменной)
    блок_инструкций

Выполнение кода для всех объектов в игре (объект помещается в переменную):

objectloop (переменная)
    блок_инструкций

Выполнение кода для всех объектов, удовлетворяющих условию:

objectloop (условие)
    блок_инструкций

Чтобы выйти из текущего цикла:

break;

Чтобы начать следующую итерацию цикла:

continue;

Вывод

Вывести список значений:

print значение, значение, ... значение;

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

print_ret значение, значение, ... значение;

Если первое (или единственное) значение это строка, то print_ret можно опустить:

"строка", значение, ... значение;

Каждое значение может быть выражением, строкой или «правилом».

Выражение выводится как целочисленное значение.

Строка выводится как текст.

Среди правил есть следующие:

  • (number) значение — вывести число словами.
  • (char) значение — вывести сиивол с кодом числа.
  • (string) адрес — вывести строку по адресу
  • (name) объект — вывести имя объекта

Падежные формы со строчной и заглавной букв соответственно:

  • (cNom) объект и (CCNom) объект — именительный (Nominative).
  • (cAcc) объект и (CCAcc) объект — винительный (Accusative).
  • (cGen) объект и (CCGen) объект — родительный (Genitive).
  • (cDat) объект и (CCDat) объект — дательный (Dative).
  • (cIns) объект и (CCIns) объект — творительный (Instrumental).
  • (cPre) объект и (CCPre) объект — предложный (Prepositive).

Вывести перевод строки:

new_line;

Несколько способов выделить текст:

style bold;
style underline;
style reverse;
...
style roman;

Глаголы и действия

Для создания нового глагола:

Verb 'глагол' 'глагол'... 'глагол'
  * токен токен... токен –> действие
  * токен токен... токен –> действие
  ...
  * токен токен... токен –> действие;

где вместо Verb может быть Verb meta, вместо действиедействие reverse.

Токены опциональны и бывают следующих видов:

  • 'слово' — конкретное слово;
  • 'слово1'/'слово2'/... — любое из перечисленных слов;
  • атрибут — объект с указанным атрибутом;
  • creature — объект с атрибутом animate;
  • cAcc_creat — объект с атрибутом animate, в винительном падеже;
  • cGen_creat — объект с атрибутом animate, в родительном падеже;
  • cDat_creat — объект с атрибутом animate, в дательном падеже;
  • cIns_held — объект в инвентаре игрока, в творительном падеже;
  • cAcc_held — объект в инвентаре игрока, в винительном падеже;
  • cGen_held — объект в инвентаре игрока, в родительном падеже;
  • cNom_noun — объект в области видимости, в именительном падеже;
  • cAcc_noun — объект в области видимости, в винительном падеже;
  • cGen_noun — объект в области видимости, в родительном падеже;
  • cDat_noun — объект в области видимости, в дательном падеже;
  • cIns_noun — объект в области видимости, в творительном падеже;
  • cPre_noun — объект в области видимости, в предложном падеже;
  • noun=процедура — объект, для которого процедура вернёт true;
  • scope=процедура — объект, который попадёт в сформированную процедурой область видимости;
  • multiheld — один или более объектов в инвентаре игрока;
  • cAcc_multiheld — один или более объектов в инвентаре игрока, в винительном падеже;
  • multi — один или более объектов в области видимости;
  • cAcc_multi — один или более объектов в области видимости, в винительном падеже;
  • multiexcept — то же, что multi, но без указанного объекта;
  • cAcc_multiexcept — то же, что multi, но без указанного объекта, в винительном падеже;
  • multiinside — то же, что multi, но без объектов внутри указанного;
  • cAcc_multiinside — то же, что multi, но без объектов внутри указанного, в винительном падаже;
  • topic — любые слова;
  • number — любое число;
  • функция — отдельная функция для парсинга.

Чтобы создать синоним для имеющегося глагола:

Verb 'глагол' 'глагол'... = 'существующий_глагол';

Чтобы изменить существующий глагол:

Extend 'существующий_глагол' last
  * токен токен... токен –> действие
  * токен токен... токен –> действие
  ...
  * токен токен... токен –> действие;

где вместо Extend может быть Extend only, last можно опустить или указать first или replace.

Чтобы явно вызвать действие в коде (объекты опциональны):

<действие объект второй_объект>;

Чтобы явно вызвать действие и вернуть true:

<<действие объект второй_объект>>;

Полезные директивы

Можно переопределить параметры компиляции в начале файла:

!% список_параметров_компилятора;

Условная компиляция:

Ifdef имя;
Ifndef имя;
Iftrue выражение;
Iffalse выражение;
  ...
Ifnot;
  ...
Endif;

Вывести сообщение при компиляции:

Message "строка";

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

Include "исходный_файл";

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

Include ">исходный_файл";

Заменить библиотечную процедуру:

Replace имя_процедуры;

Указать номер релиза (по умолчанию 1), серийный номер (по умолчанию сегодняшний день в формате ГГММДД), формат статусной строки:

Release выражение;
Serial "ггммдд";
Statusline score;
Statusline time;

Создать новый атрибут:

Attribute имя_атрибута;

Создать новое свойство, которое будет у всех объектов:

Property имя_свойства;
Property имя_свойства выражение;

Список свойств

Ниже идёт список всех свойств (properties) из стандартной библиотеки.

Свойства объекта — это какие-то данные или код, ассоциированный с объектом. Кроме стандартных свойств можно свободно добавлять свои.

n_to, s_to, e_to, w_to

Только для комнат. Также есть ne_to, nw_to, se_to, sw_to, in_to, out_to, u_to и d_to.

Соответственно это направления на север, юг, восток, запад, СВ, СЗ, ЮВ, ЮЗ, внутрь, наружу, вверх и вниз.

Значением свойства может быть:

  • false (по умолчанию), что означает что выхода нет;
  • строка, которая пояснит игроку, почему выхода в этом направлении нет;
  • комната, либо дверь, в которую будет вести выбранное направление из текущей комнаты;
  • процедура, которая вернёт что-либо из выше перечисленного.
Object At_Top_Of_Small_Pit "Над колодцем"
with description
"Прямо из ваших ног находится колодец, буквально дышащий белым туманом.
 В этом месте проход на запад кончается, если не считать очень узкую трещину.^
 Грубые каменные ступени дают возможность спуститься вниз.",
     e_to In_Bird_Chamber,
     w_to "Трещина слишком узка, чтобы в нее протиснуться.",
     d_to
     [; if (large_gold_nugget in player)
        {   deadflag=1;
            "Вы достигли дна колодца быстрее, чем сами хотели,
             и лежите там со сломанной шеей.";
        }
        return In_Hall_Of_Mists;
     ],
...

add_to_scope

Если текущий объект находится в области видимости (scope), то все объекты, перечисленные в этом свойстве, тоже попадут в область видимости. Если делать это через процедуру, то она должна вызвать PlaceInScope(объект), чтобы поместить объект в область видимости.

article и articles

Артикли для объектов. В русской версии не требуются.

after

Сюда попадают все действия после того, как они произошли, но ещё до того, как об этом было сказано игроку.

Для объекта: все действия, происходящие с данным объектом.

Для комнаты: все действия, происходящие в этой комнате.

Если код вернёт false, выполнение продолжится, и выведется текст о том, что произошло. Если true, то выполнение на этом остановится, текста выведено не будет.

Так можно заменить текст стандартных реакций на свой:

Object -> RustyDoor "ржав/ая железн/ая двер/ь"
with name 'больш' 'ржав' 'железн' 'двер' 'петл',
    ...
    after
    [; 
        Open: "Дверь отворилась с жутким скрипом и дождем из осыпающейся ржавчины.";
    ],
has static door locked female;

Или, например, сделать что-то дополнительное к действию:

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

before

Сюда попадают все действия после ввода команды, но ещё до того, как они произошли.

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

Для объекта: все действия, происходящие с данным объектом.

Для комнаты: все действия, происходящие в этой комнате.

Если код вернёт false, то выполнение продолжится. Если true, то выполнение на этом остановится (действие не произойдёт).

Treasure -> "персидск/ий ков/е/р/"
with name 'персидск' 'ковер' 'ковр',
     before
     [; Take: if (Dragon in location)
              "То, что на ковре развалился здоровенный (и весьма свирепый) дракон,
               немного мешает реализации этой привлекательной идеи.";
     ],
...

cant_go

Свойство для комнаты, содержащее строку, либо процедуру, которая выведет строку при попытке пойти в несуществующем направлении. По умолчанию: «Этот путь недоступен».

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

capacity

Для container или supporter-объектов — максимальное количество объектов, которые в или на объект можно поместить. По умолчанию — 100. Для игрока — максимальное количество объектов, которое можно удержать. Для изначального игрока (selfobj) это значение выставляется через MAX_CARRIED.

daemon

После активации через StartDaemon(объект), эта процедура будет вызываться один раз каждый ход до тех пор, пока не будет остановлена через StopDaemon(объект).

Это хорошо подходит для моделирования NPC, автономных систем, механизмов и т.д.

Object -> Bear "огромн/ый пещерн/ый медвед/ь"
with ...,
    daemon [; 
        if (location==thedark) rfalse;
        if (self in location) {
            if (location==At_Breath_Taking_View)
                "^Медведь у Вас за спиной восторженно взревел!";
            rfalse;
        }
        move self to location;
        "^Медведь неотступно следует за Вами.";
    ],
...

describe

Вызывается перед тем, как объект будет выведен в комнате. Если это свойство задано у комнаты, то вызывается перед выводом описания комнаты.

Если describe вернёт:

  • false, то выполнение продолжится, и выведется стандартное описание.
  • true, то на этом выполнение остановится, дальнейшего вывода текста не будет.
Treasure -> "персидск/ий ков/е/р/"
with name 'персидск' 'ковер' 'ковр',
...
describe [;
    if (Dragon in location)
        "Дракон лежит на роскошном персидском ковре!";
    "Пол пещеры покрыт роскошным персидским ковром.";
],
...

description

Для объекта: описание объекта, которое выводится при осмотре объекта игроком.

Для комнаты: описание комнаты.

Описание может быть строкой или может быть сгеренировано процедурой.

Object  hook "маленьк/ий бронзов/ый крюч/о/к/" cloakroom
with  name 'маленьк' 'бронзов' 'крюк' 'крючок' 'крючк' 'вешалк',
...
description [;
    print "Всего лишь маленький бронзовый крючок для одежды, ";
    if (self == parent(cloak)) "с которого свисает черный бархатный плащ.";
    "привинченный к стене.";
],
has scenery supporter male;

door_dir

Это свойство должно возвращать направление, в котором ведёт данная дверь. Например, если мост ведёт на восток, то нужно вернуть e_to.

Object CrystalBridge "хрустальн/ый мост/"
with name 'волшебн' 'хрустальн' 'мост',
    initial "Оба края пропасти теперь соединяет хрустальный мост.",
    ...
    door_dir [; 
        if (location==West_Side_Of_Fissure) return e_to;
        return w_to;
    ],
    ...

door_to

Это свойство должно вернуть, куда (в какую локацию) ведёт дверь.

Можно вернуть:

  • false (по умолчанию), что означает что дверь ведёт никуда;
  • строка, которая пояснит игроку, почему дверь ведёт никуда;
  • объект комнаты, в которую должна вести дверь;
  • процедура, которая вернёт что-либо из выше перечисленного.
Object CrystalBridge "хрустальн/ый мост/"
with name 'волшебн' 'хрустальн' 'мост',
    initial "Оба края пропасти теперь соединяет хрустальный мост.",
    ...
    door_to [;
        if (location==West_Side_Of_Fissure) return On_East_Bank_Of_Fissure;
        return West_Side_Of_Fissure;
    ],
    ...

each_turn

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

Object branch "надежн/ый толст/ый сук/" top_of_tree
  with ...
      each_turn [; if (bird in nest && nest in branch) deadflag = 2; ],
  ...

found_in

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

Так можно удобно моделировать, например, небо, солнце и любые другие объекты, которые должны присутствовать в нескольких местах.

Можно написать процедуру, которая будет это решать. Процедуре нужно оценить переменную location и вернуть true, если объект должен присутствовать, или false в противном случае.

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

Prop "Солдат/ы Гесслера"
    with ...,
    found_in south_square mid_square north_square marketplace,
has animate pluralname;

grammar

Только для объектов animate или talkable. Только для приказов (> Антон, отдай мне бутерброд). Вызывается тогда, когда парсер определил глагол (verb_word) и положение глагола в команде (verb_wordnum) при обращении к кому-то, но ещё не попробовал применить эту грамматику.

Необходимо вернуть один из вариантов:

  • false, чтобы библиотека продолжила выполнение;
  • true, чтобы на этом остановить выполнение и таким образом указать, что ваш код сделал все нужные действия самостоятельно: выставил action, noun, second;
  • словарное слово глагола, например 'бр' («брать»), чтобы библиотека использовала грамматику этого глагола;
  • или минус с глаголом, -'бр', чтобы библиотека использовала грамматику этого глагола, а затем ещё и стандартную.

initial

Описание объекта, который ещё не был подобран — то есть изначальное описание в комнате, где он лежит. Это может быть как строка, так и процедура.

Для комнаты такое свойство вызывается или выводится, когда игрок в неё заходит.

Object -> black_rod "черн/ый жезл/ со ржавой звездой на конце"
with name 'черн' 'ржав' 'жезл' 'звезд',
    initial "Трехфутовый черный жезл, один конец которого украшает 
             ржавая железная звезда, лежит у Ваших ног.",
    description "Черный жезл длиной в три фута, со звездой на конце.",
...

inside_description

Описание объекта, выводящееся, когда игрок находится внутри него. Такой объект должен иметь атрибут enterable.

Object -> cage "железн/ая клетк/а"
with  name 'железн' 'клетк' 'прут' 'решетк' 'знак',
    description
        "Символы на клетке таковы: Птица, Стрела, Вепрь.",
    inside_description [;
        if (self.floor_open)
            "Узкая нора в земляном полу клетки открывает путь вниз,
             в погребальный колодец.";
        "Толстые прутья железной решетки окружают вас со всех сторон.";
        ],
...
has enterable transparent container openable open static female;

invent

Это свойство позволяет изменять то, как выводится объект в инвентаре.

Оно вызывается дважды:

  • сначала никакой текст в инвентаре про этот объект выведен не был, и inventory_stage равно 1.

    Здесь можно удобно и любым способом изменить вывод текста в инвентаре. После чего нужно вернуть false, чтобы продолжить, или true чтобы на этом закончить.

  • затем выведется короткое (обычное) имя объекта, и будет второй вызов, где inventory_stage равно 2.

    Уточняющие детали вида «(открыт)» или «(пуст)» на этот момент ещё не вывелись, и здесь можно вывести свой текст, например «(висит набекрень)».

    Здесь аналогично нужно вернуть false, чтобы продолжить, или true чтобы на этом закончить.

Object  your_candle "свеч/а"
...
invent [;
    if (sin_shadow.evil == true) {
        if (self.lit == true) {
            print "свеча (горит)"; rtrue;
        }
        else
            print "свеча"; rtrue;
    }
    if (self.lit == true) {
        print "твоя свеча (горит)"; rtrue;
    }
    else
        print "твоя свеча"; rtrue;
  ],
...

life

Здесь нужно перечислять правила для animate объектов. Работают они так же, как before или after, но только для следующих действий:

Attack Kiss WakeOther ThrowAt Give Show Ask Tell Answer Order

Если код вернёт false, выполнение продолжится, и выведется текст о том, что произошло. Если true, то выполнение на этом остановится, текста выведено не будет.

Object cecilia1 "девушк/а"
...
    life [;
      WakeOther:
          if (self.woken == false)
              "Ты осторожно толкаешь её, но она спит слишком крепко.";
          else
              "Не нужно.";
    ...

list_together

Объекты с одинаковым свойством list_together будут сгруппированы при выводе в инвентаре или в комнате.

Свойство может быть:

  • числом — все объекты с одинаковым числом группируются;

  • строкой — все объекты с одинаковой строкой группируются;

  • процедурой.

    Такая процедура будет вызвана дважды:

    • первый раз до вывода текста и с inventory_stage равным 1. Здесь нужно вернуть false, чтобы продолжить, или true чтобы на этом закончить.
    • второй раз после вывода группы предметов и с inventory_stage равным 2. Возвращать значения не требуется.
Constant KEYS_GROUP = "ключа";

Object gold_key "золот/ой ключ/"
with name "золот" "ключ",
list_together KEYS_GROUP,
has male;

Object silver_key "серебрян/ый ключ/"
with name "серебрян" "ключ",
list_together KEYS_GROUP,
has male;

Object bronze_key "бронзов/ый ключ/"
with name "бронзов" "ключ",
list_together KEYS_GROUP,
has male;
> ИНВЕНТАРЬ
У тебя с собой есть:
  три ключа:
    бронзовый ключ
    серебряный ключ
    золотой ключ

name

Список словарных слов, относящихся к данному объекту. Это именно те слова, по которым игрок обращается к объектам вокруг.

Если такое свойство есть у комнаты, то при обращении будет выведено «Упоминать это в игре нет необходимости».

Если у объекта есть свойство parse_name, то оно рассматривается в приоритете.

Prop "прилавк/и"
    with name 'прилавк' 'ларьк' 'ларёк' 'ряд',
    description "Еда, инструменты, одежда — обычное барахло.",
    found_in street below_square,
has  pluralname;

number

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

orders

Только для объектов animate или talkable. Обрабатывает приказы от игрока вида > РОБОТ, ПОДМЕТИ КОМНАТУ.

Указанное в приказе действие и объекты, с которым его нужно совершить, будут соответственно в переменных action, noun и second.

Нужно вернуть false, чтобы продолжить, или true чтобы закончить.

Object  priest "мумифицированн/ый жрец/"
...
    orders [;
        Go:
            "~Нет, мне нельзя покидать Гробницу.~";
        NotUnderstood:
            "~Говоришь загадками?~";
        default:
            "~Не твоим приказам я служу, пришелец.~";
    ],
has animate male;

parse_name

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

Процедуре следует пройти по всем словам, вызывая NextWord(). В конце нужно вернуть:

  • 0, если введённые слова никак не относятся к объекту;
  • -1, чтобы парсер продолжил выполнение и просто использовал список из name;
  • или количество слов подряд, которые подошли.
Object  chess_pieces "шахматн/ые фигур/ы" Drawing_Room
 has    scenery pluralname
 with   parse_name [ w colour n;
            w = NextWord();
            if (w == 'белый' or 'черный') {
                n ++;
                colour = w;
                w = NextWord();
            }
            if (w == 'пешка' or 'ладья' or 'тура' ||
                w == 'конь' or 'слон' or 'офицер' ||
                w == 'король' || (w == 'королева' && 
                (colour == 'белая' || rug hasnt general))) return n + 1;
            return 0;
        ], 

plural

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

Class GoldCoin
    with name 'золот' 'монет',
    short_name "золотая монета",
    plural "золотые монеты";

Object bag "сумк/а"
    with name 'сумк',
has container open openable female;

GoldCoin ->;
GoldCoin ->;
GoldCoin ->;
> ИНВЕНТАРЬ
У тебя с собой есть:
  сумка (открыта)
    три золотые монеты

react_after

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

Нужно вернуть true, чтобы на этом закончить, или false, чтобы продолжить.

Object -> psychiatrist "бородат/ый психиатр/"
with name 'бородат' 'борода' 'психиатр' 'психолог',
...
react_after [;
    Insert: print "«Пациент ассоциирует ", (cAcc) noun, " с ",
        (cIns) second, ". Любопытно».^^";
    PutOn: print "«Пациент кладёт ", (cAcc) noun, " на ",
        (cAcc) second, ". Любопытно».^^";
    Look: print "^«Представьте, что меня здесь нет».^^";
],
...
has animate male;

react_before

Работает так же, как before, но перехватывает все действия поблизости, то есть все, где этот объект находится в области видимости. Код выполнится до совершения действия.

Нужно вернуть true, чтобы остановить действие, или false, чтобы продолжить.

Object -> gocage "клетк/а"
...
react_before [;
    Exit:
        if (player in self) {
            if (self hasnt open)
                "Ты не сможешь выйти из клетки, пока крышка люка закрыта.";
            move player to parent(self);
            if (keep_silent == 0)
                print "Ты сходишь с платформы.^";
            rtrue;
        } 
...

short_name

Альтернативное, расширенное название объекта. Может быть строкой или процедурой. Так можно динамически менять выводимое имя объекта.

Если вернуть false, то далее выведется оригинальное название объекта. Если true, то вывод прекратится.

NPC   cellar_figure
with  short_name [;
    if (self.unmasked == true) {
        print "Игнат"; rtrue;
    }
    else
        print "фигура"; rtrue;
    ],
...

short_name_indef

В русской версии не используется.

time_left

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

Если значение сейчас равно 0, то таймер сработает в конце этого хода. Если 1, то в конце следующего, и т.д.

time_out

Процедура, код, который надо выполнить при срабатывании таймера. Таймер нужно установить заранее через StartTimer(obj). Свойство time_left тоже необходимо добавить.

Object clock "будильник"
...
    time_left,
    time_out [;
        "^Раздаётся резкий звон будильника.";
    ],
...

when_closed и when_open

Для дверей и контейнеров (container): строка или процедура, описывающие объект в комнате, когда этот объект закрыт или, соответственно, открыт.

Object girlroom_door "двер/ь" girlroom
   with name 'щеколд' 'двер' 'деревян',
       description "Это простая деревянная дверь, запирающаяся изнутри на щеколду.",
       when_open   "Дверь на востоке - это единственный выход из комнаты. Сейчас она приоткрыта.",
       when_closed "Единственный выход из комнаты - это закрытая дверь на востоке.",
...

when_off и when_on

Для выключаемых объектов (с атрибутом switchable): строка или процедура, описывающие объект в комнате, когда этот объект выключен или, соответственно, включен.

Object -> brass_lantern "латунн/ая ламп/а"
  with name 'латунн' 'ламп' 'фонар' 'светильник',
       when_on  "Ваша лампа поблизости испускает яркий свет.",
       when_off "Рядом тускло поблескивает лампа из латуни.",
...
has switchable female;

with_key

Для запираемых объектов (с атрибутом lockable) здесь нужно указать другой объект, который будет ключом для данного объекта. Если указать 0 или nothing, то подходящего ключа не будет вообще (хотя игроку явно об этом сказано не будет).

Object  hatch_lock "зам/о/к/ люка"
with  pname 'люк' 'замок' 'замк',
    description "Константин хотел убедиться, что братья едят только положенные порции,
    и потому настоял на том, чтобы запереть подвал. Он никогда не давал тебе ключ.",
    ...
    with_key cellar_key,
has scenery lockable locked male;

Список атрибутов

Ниже идёт список всех атрибутов из стандартной библиотеки. Атрибут — это то же самое, что флаг; у каждого объекта атрибут либо выставлен, либо убран.

Можно создавать свои атрибуты:

Attribute hungry;

Чтобы проверить, включен ли атрибут у объекта:

if (vasilisa has hungry) {
    ...
}
if (vasilisa hasnt hungry) {
    ...
}

Атрибуты даются при задании объектов:

Object Vasilisa "Василис/а"
...
has animate female ~hungry;

Флаг можно выставить в процессе игры:

give vasilisa hungry;

Или убрать:

give vasilisa ~hungry;

Стандартные атрибуты

  • absent

    Для объекта в нескольких комнатах (со свойством found_in) обозначает отсутствие объекта.

  • animate

    Обозначает одушевлённый объект — NPC, животное и т.д.

  • clothing

    Обозначает объект, который можно надеть на себя.

  • concealed

    Обозначает объект, скрытый из виду, но присутствующий в комнате.

  • container

    Обозначает, что в объект (но не на объект) можно класть другие объекты.

  • door

    Обозначает, что объект является дверью (или мостом и т.п.).

  • edible

    Обозначает съедобный объект.

  • enterable

    Обозначает объект, в который можно зайти.

  • female

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

  • general

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

  • light

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

  • lockable

    Обозначает, что объект можно запереть «на ключ»; ключ указывается в свойстве with_key.

  • locked

    Обозначает, что объект заперт, и просто так его открыть не получится.

  • male

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

  • moved

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

  • neuter

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

  • on

    Для объекта, который можно включить или выключить, обозначает включённое состояние.

  • open

    Обозначает, что контейнер или дверь открыты.

  • openable

    Обозначает, что контейнер или дверь можно открывать и закрывать.

  • pluralname

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

  • proper

    В русской версии не используется. В английской обозначает вывод объекта без «the».

  • scenery

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

  • scored

    Если впервые подобрать объект с этим атрибутом, игрок зарабатывает OBJECT_SCORE очков. Если впервые зайти в комнату с этим атрибутом, игрок зарабатывает ROOM_SCORE очков.

  • static

    Объект с этим атрибутом нельзя подобрать.

  • supporter

    Обозначает, что на объект (но не в объект) можно класть другие объекты.

  • switchable

    Обозначает, что объект можно включить или выключить.

  • talkable

    Обозначает объект, которому можно давать команды в стиле «объект, сделай что-то». Нужен в тех случаях, где не подходит animate — например, для микрофона.

  • transparent

    Обозначает, что объекты внутри данного контейнера будут видны снаружи.

  • visited

    Означает, что комната была посещена игроком ранее, или игрок находится в ней сейчас.

  • workflag

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

  • worn

    Для предмета одежды означает, что он сейчас надет.

Для комнат (локаций) имеют смысл только light, scored и visited.

Список действий

Любая команда, вводимая игроком, вызывает какое-то действие.

Чаще всего действия используются при обработке в before или after.

Object  rug "каминн/ый коврик/"
 has    concealed static supporter enterable male
 with   name 'каминн' 'ковер' 'коврик' 'мягк' 'красив' 'индийск' 'арабск',
        ...
        before [;
            Take:
                "Но коврик слишком большой и тяжелый!";
            Push,Pull:
                "Но место каминного коврика -- рядом с камином!";
            LookUnder:
                if (player in mantelpiece || player in armchair)
                    "Отсюда вряд ли возможно дотянуться до коврика!";
                if (player in self)
                    "Алиса попыталась приподнять угол коврика, но потерпела неудачу.
                    Причиной оказалось то, что она стояла прямо на нем.
                    Мм-да, мир полон неожиданностей.";
                if (self hasnt general) {
                    give self general;
                    move red_queen to player;
                    "Алиса приподняла угол коврика -- и, заглянув под него,
                    обнаружила там Черную Королеву из шахматного набора!";
                }
        ];

Иногда возникает необходимость добавить собственное действие. Пример для глагола «отдать честь»:

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

Verb 'поклонить'
* cDat_noun -> Salute;

Verb 'отдать'
* 'честь' cDat_noun -> Salute;

Ниже идёт перечисление всех действий из стандартной библиотеки.

Группа 1

Сюда относятся «мета-действия», служебные глаголы.

действиетипичная команда
CommandsOffзапись выкл
CommandsOnзапись, запись вкл
CommandsReadвоспр
FullScoreсчёт полн
LMode1опис норм
LMode2опис длин
LMode3опис крат
NotifyOffизвещ выкл
NotifyOnизвещ вкл
Objectsпредметы
Placesместа
Pronounsимена
Quitконец
Restartначало!, перезапуск
Restoreвосст, загрузить
Saveсохр, сохранить
Scoreсчёт
ScriptOffотчёт выкл
ScriptOnскрипт, отчёт
Verifyпроверка
Versionверсия

Группа 2

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

действиетипичная команда
Lookосмотреться, осм
Examineосм кошку
Searchобыскать шкаф
Invинвентарь, инв
InvTallинв высок
InvWideинв широк
Takeвзять кошку
Dropположить кошку
Removeвзять кошку из шкафа
PutOnположить кошку на стол
Insertположить кошку в шкаф
LetGoкосвенно вызывается действием Remove
Receiveкосвенно вызывается действиями PutOn и Insert
Emptyвысыпать мешок
EmptyTвысыпать мешок в яму
Transferпередвинуть груз в коробку
Go[идти на] север
Enterвойти в клетку
GetOffслезть с кровати
GoInвойти
Exitвыйти
Unlockотпереть дверь
Lockзапереть дверь
SwitchOnвключить радио
SwitchOffвыключить радио
Openоткрыть дверь
Closeзакрыть дверь
Disrobeснять шляпу
Wearнадеть шляпу
Eatсъесть апельсин
Waitждать

Группа 3

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

действиетипичная команда
LookUnderпосмотреть под кровать
Listenпослушать [радио]
Tasteлизнуть мороженое
Touchдотронуться до поверхности
Pullтолкнуть тележку
Pushпотянуть тележку
Waveпомахать флагом
Turnповернуть камеру
PushDirтолкнуть тележку на север
ThrowAtметнуть дротик в плакат
ThrownAtкосвенно вызывается действием ThrowAt
JumpOverперепрыгнуть через забор
Tieпривязать веревку к ветке
Drinkвыпить абсент
Fillнаполнить бутылку
Attackударить хулигана
Swingпокачать берёзу
Blowзадуть свечу
Rubпотереть лампу
Setустановить ловушку
SetToустановить таймер на 5
Buyкупить мороженое
Climbзалезть на скалы
Squeezeсдавить помидор
Burnсжечь улики
Digвыкопать могилу
Cutразрезать красную ленточку
Consultпоискать про ламантинов в энциклопедии
Tellрассказать отелло про дездемону
Answerсказать вас понял капитану
Askспросить кардинала про папу
Giveдать монету бедняку
Showпоказать документы пограничнице
AskForпопросить у официанта счёт
WakeOtherразбудить бабушку
Kissпоцеловать лягушку
Sleepспать
Singспеть
WaveHandsпомахать [руками]
Swimкупаться, плыть
Sorryизвиниться
Strongгрубые слова, мат
Mildменее грубые слова
Jumpподпрыгнуть
Thinkподумать
Smellпонюхать
Prayпомолиться
VagueGoидти
Yesда
Noнет
Wakeпроснуться

FAQ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • index.html
  • lib/glkote.min.css
  • lib/glkote.min.js
  • lib/gnusto.min.js
  • lib/jquery.min.js
  • lib/parchment.min.js
  • lib/parchment.min.css
  • lib/quixe.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 и CCNom — именительный (Nominative).
  • cAcc и CCAcc — винительный (Accusative).
  • cGen и CCGen — родительный (Genitive).
  • cDat и CCDat — дательный (Dative).
  • cIns и CCIns — творительный (Instrumental).
  • cPre и CCPre — предложный (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 'вяз' 'вяж';