Начало
Пошагово рассмотрим написание простенькой игры Хейди, сюжет которой таков:
«Хейди живёт в маленьком домике в лесу. Одним солнечным днём она слышит писк птички — её гнездо упало с ветки на поляну. Хейди кладёт птичку в гнездо, а гнездо обратно на ветку».
Шаблон для игры
Для начала создадим исходный файл-шаблон. Создайте папку 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, то есть, что игра закончена, и выводит на экран «Вы выиграли».
На этом глава закончена, перекомпилируйте игру и проверьте, как работают внесённые изменения. Многое можно сделать получше, и это будет рассмотрено позднее. В следующей главе будет обобщение уже изученных особенностей Информа.