О Русском Информе
На Информе можно писать игры с текстовым вводом. Классические примеры таких игр — Adventure и Zork. Здесь на сайте можно поиграть в такие игры на русском языке, а кроме этого скачать инструменты для разработки своих собственных игр.
Русский Информ (RInform) — это перевод стандартной библиотеки популярной на западе системы Inform 6.
Официальный сайт русской версии: https://rinform.org/
В данной книге подробно разбирается разработка простой игры («Хейди»), и есть ответы на часто задаваемые вопросы (FAQ).
Документация на русском языке, увы, не описывает всех тонкостей платформы. На данный момент самая полная документация есть только на английском языке:
- The Inform Beginner's Guide — рекомендуется для новичков.
- The Inform Designer's Manual — строгая и полная инструкция от автора платформы, Грэма Нельсона.
Обратиться за помощью можно на канале #rinform в чате Discord.
Введение и установка
Для того, чтобы написать игру на Информе, нам потребуются:
- текстовый редактор для редактирования исходных файлов игры (официально рекомендуется Sublime Text, но, разумеется, подойдёт любой);
- библиотека Информа, которую нужно будет включить в свою игру, чтобы работала общая модель и стандартное поведение;
- компилятор Информа, с помощью которого из исходного файла мы получим файл игры;
- интерпретатор (плеер), с помощью которого в полученную игру можно будет сыграть.
Библиотеку и компилятор можно скачать с сайта Информа. В архиве помимо прочего есть примеры игр.
Игры можно запускать онлайн, и это самый популярный вариант, но на время разработки обычно пользуются одним из «оффлайн»-плееров:
- Gargoyle — кроссплатформенный плеер, ориентированный на типографику. Поддерживает все форматы. Скачать для Windows
- Lectrote — Windows, Linux, OS X (использует Electron).
- Windows Glulxe для Windows. Там же есть более быстрый Windows Git.
Установка и проверка
Начиная с версии 0.9, версия Glulx считается приоритетной, а версия для Z-машины вторичной. Поэтому в данной книге все примеры подразумевают работу с Glulx. О различиях можно почитать в FAQ.
Для проверки работоспособности и первоначальной настройки:
-
скачайте архив библиотеки с официального сайта и распакуйте его в отдельную, удобно доступную папку, например
c:\rinform\
.В этой папке будет следующее содержимое:
\demos\ демонстрационные игры \demos\demos.bat пакетные файлы для компиляции демонстрационных игр \demos\demos.sh \libext\ расширения для библиотке \library\ русская версия библиотеки inform.exe компилятор Inform (для Windows)
-
В папке demos можно увидеть несколько файлов с расширением
.inf
— это исходные файлы демонстрационных игр. Их можно открыть и просмотреть в текстовом редакторе. -
Файл
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)
Если прочих сообщений нет, то всё прошло без ошибок.
-
В папке
demos
появятся файлы с расширением.ulx
— это готовые файлы игр, которые можно запустить в интерпретаторе. Если установлены Windows Glulxe или Lectrote, то при запуске такого файла он скорее всего автоматически откроется в одном из этих интерпретаторов.
Организация своей игры:
-
создайте папку для игры, например c:\inform\mygame\
-
создайте главный исходный файл игры, 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"; !============================================================================
-
создайте .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;
Рассмотрим эту часть кода по шагам:
-
Для объекта
bird
мы ввели новое свойство,before
. Интерпретатор обращается к свойствуbefore
перед тем, как выполнить конкретное действие с объектом:before [; ... ],
-
Значением свойства является локальная функция, в которой есть метка и две инструкции:
Listen: print "Жалобный писк испуганной птички разрывает тебе сердце. Надо помочь!^"; return true;
-
Метка обозначает тип действия, в данном случае
Listen
(«послушать»). Меткой мы сообщаем интерпретатору следующее: если действие, которое будет совершено над птенчиком это «послушать», то надо выполнить эти инструкции. В противном случае продолжать как обычно. То есть, если игрок введёт ОСМОТРЕТЬ ПТЕНЦА, ВЗЯТЬ ПТЕНЦА, ПОЛОЖИТЬ ПТЕНЦА В ГНЕЗДО, УДАРИТЬ ПТЕНЦА или ПОГЛАДИТЬ ПТЕНЦА, то игрок получит стандартный ответ. Если же игрок введёт ПОСЛУШАТЬ ПТЕНЦА, то действие будет «перехвачено», и выполнятся наши инструкции. -
Выполнятся следующие инструкции:
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;
Рассмотрим этот код по шагам:
-
Для нашей комнаты мы добавили свойство
after
. Интерпретатор обращается к этому свойству после того, как выполнит любое действие в этой комнате:after [; ... ],
-
Значение свойства является локальной функцией, содержащей метку и две инструкции:
Drop: move noun to clearing; return false;
-
Метка обозначает имя действия, в данном случае
Drop
. Мы сообщаем интерпретатору следующее: если только что было совершено действиеDrop
, то выполни эти инструкции перед тем, как сообщить игроку, что действие завершено. Если произошло другое действие, то продолжи как обычно. -
Сначала выполняется инструкция
move noun to clearing;
которая берёт объект, который был перенесён из объекта игрока,
player
, в объектtop_of_tree
(так как выполнилось действиеDrop
), и переносит его ещё раз, в объектclearing
. В инструкцииnoun
является библиотечной переменной, в которой будет храниться идентификатор объекта, к которому применяется действие. То есть если игрок вводит БРОСИТЬ ГНЕЗДО, тоnoun
укажет наnest
, а если БРОСИТЬ ПТЕНЦА, тоnoun
станетbird
. Далее мы исполняемreturn false;
что говорит интерпретатору, что теперь он может вывести игроку свой ответ, то есть, что произошло.
Вот что мы получим в игре:
На верхушке дерева На этой высоте цепляться за ствол уже не так удобно. Здесь имеется надежный толстый сук. > БРОСИТЬ ГНЕЗДО Птичье гнездо положено. > ОСМОТРЕТЬСЯ На верхушке дерева На этой высоте цепляться за ствол уже не так удобно. Здесь имеется надежный толстый сук. > ВНИЗ Полянка Здесь имеется птичье гнездо (где имеется птенчик). >
Конечно, сообщение «Птичье гнездо положено». совсем не информативно в данном случае, поэтому можно сделать так:
Object top_of_tree "На верхушке дерева"
with description "На этой высоте цепляться за ствол уже не так удобно.",
d_to clearing,
after [;
Drop:
move noun to clearing;
print_ret "Предмет упал вниз на землю.";
],
has light;
Здесь print_ret
выводит более корректное сообщение и возвращает true
, что означает что интерпретатор больше не должен ничего выводить на экран.
Птица в гнезде
Игра заканчивается, когда игрок кладёт гнездо на сук. Мы предположили, что птенец уже находится в гнезде, но это может быть не так. Необходимо также проверить, лежит ли птенец в гнезде. Это легко сделать:
Object branch "надежн/ый толст/ый сук/" top_of_tree
with description "Сук достаточно ровный и крепкий, чтобы на нем надежно
держалось что-то не очень большое.",
name 'надежн' 'ровн' 'толст' 'крепк' 'сук' 'ветк',
each_turn [; if (bird in nest && nest in branch) deadflag = 2; ],
has static supporter male;
Мы расширили инструкцию if
:
if (bird in nest && nest in branch) deadflag = 2;
Её можно прочитать так: «Если bird
находится на/в гнезде, и nest
находится на/в branch
, то установить deadflag
равным 2. В противном случае ничего не делать».
Заключение
К этому моменту вы уже поняли, что в игре обязательно надо реализовывать не только свои действия, но и те необязательные, которые могут прийти в голову игроку. Игрок может попробовать вообще что-то глупое, но ему будет интересно получить нестандартный ответ. Непроработка ответов может вообще разрушить эффект погружения.
В этой главе мы рассмотрели:
Свойства объектов
У объектов может быть свойство before
, и если оно есть, то интерпретатор обращается к нему перед выполнением действия над объектом. Аналогично есть свойство after
, куда происходит обращение уже после выполненного действия, но ещё до того, как выведется сообщение игроку. И before
, и after
могут быть как у объектов-предметов (где перехватываются действия применимые к этим объектам), так и у комнат (где перехватываются действия, которые происходят с объектами в комнате).
Значениями этих двух свойств являются локальные функции. Если функция заканчивается return false
, то интерпретатор продолжит выполнение, а если return true
— то интерпретатор больше ничего делать не будет. Так можно либо частично изменить выполнение действия, либо заменить его полностью.
Ранее свойства соединений комнат указывали на объект комнаты, куда произойдёт перемещение. В этой главе также было показано, что это может быть строка (в которой указана причина, почему перемещение невозможно). Также было рассмотрено свойство cant_go
, которое учитывает все неуказанные направления:
e_to forest,
in_to "Такой славный денек... Он слишком хорош, чтобы прятаться внутри.",
cant_go "Единственный путь ведет на восток.",
Функции и параметры
В библиотеке есть много удобных функций, и мы воспользовались функцией PlayerTo
, которая позволяет перенести игрока в другую комнату, не обязательно соседнюю.
При вызове этой функции нам нужно было указать конечную комнату, и мы сделали это в скобках:
PlayerTo(clearing);
Значения в скобках называются параметрами функции. Их может быть несколько, в таком случае они разделяются запятыми. Например можно перенести игрока без вывода описания комнаты, указав второй аргумент:
PlayerTo(clearing, 1);
Здесь 1 подавляет вывод описания новой комнаты.
Инструкции
Нам встретились новые инструкции:
return true;
return false;
Они использовались для управления интерпретатором в локальных функциях.
print "строка";
print_ret "строка";
Инструкция print
выводит указанную строку, а print_ret
выводит строку, делает перенос и выполняет return true
.
if (условие && условие) ...
Мы расширили условие в инструкции if
. &&
— оператор «И», который используется для проверки выполнения нескольких условий сразу. Также существуют оператор ||
«ИЛИ» и оператор ~~
«НЕ».
move объект to родитель;
Инструкция move
изменяет дерево объектов, устанавливая для объекта нового родителя.
Действия
Мы говорили о перехвате таких действий как Listen
, Enter
, Climb
и Drop
. Действие это представление того, что должно быть сделано, в зависимости от глагола, который введёт игрок. Например, ПОСЛУШАТЬ и ПРИСЛУШАТЬСЯ суть одно и то же, поэтому им соответствует действие Listen
. Аналогично ВОЙТИ, ЗАЙТИ, СЕСТЬ НА, ЛЕЧЬ НА приводят к действию Enter
, а ПОЛОЖИТЬ, ВЫБРОСИТЬ означают Drop
. Так облегчается работа писателя-программиста, потому что различных глаголов может быть множество, но количество разных действий гораздо меньше.
В библиотеке каждому действию соответствует номер, и этот номер хранится в переменной action
. Также есть переменная noun
, в которой хранится идентификатор объекта, над которым производится действие, а также second
для второго такого объекта (если он есть).
Вот несколько примеров:
Ввод игрока | действие | noun | second |
---|---|---|---|
СЛУШАТЬ | Listen | nothing | nothing |
СЛУШАТЬ ПТЕНЧИКА | Listen | bird | nothing |
ПОДНЯТЬ ПТЕНЧИКА | Take | bird | nothing |
ПОЛОЖИТЬ ПТЕНЧИКА В ГНЕЗДО | Insert | bird | nest |
БРОСИТЬ ГНЕЗДО | Drop | nest | nothing |
ПОЛОЖИТЬ ГНЕЗДО НА СУК | PutOn | nest | branch |
nothing
— это встроенная константа, обозначающая отсутствие объекта.
В этих главах мы изучили основные принципы, на которых строятся любые игры на Информе, а также средства для создания более интересных реакций.
Пример игры: Вильгельм Телль
В данной главе сейчас нет материалов, но в будущем будет подробно разобрана разработка второй обучающей игры, под названием «Вильгельм Телль».
А пока в «Вильгельма Телля» можно поиграть онлайн: https://iplayif.com/?story=https://rinform.org/demos/WTellR.z5
И исходный код почитать здесь: https://github.com/yandexx/rinform-glulx/blob/master/demos/WTellR.inf
Справочник
В данном разделе сейчас находятся:
- описание синтаксиса языка;
- справка по стандартным свойствам;
- справка по стандартным атрибутам;
- справка по стандартным действиям.
Синтаксис Информа
Описание всех конструкций языка 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.
Какие плееры открывают игры на Информе?
- Glulx (файлы .ulx и .gblorb): Windows Git, Windows Glulxe, Lectrote, а также онлайн.
- Z-machine (файлы .z5, .z8 и .zblorb): Windows Frotz, Lectrote, fizmo, а также онлайн через Parchment.
- На Android плеер Fabularium поддерживает все существующие форматы.
Как опубликовать игру онлайн?
Чтобы запустить произвольную игру в Parchment, нужно указать параметром для сайта iplayif.com путь к файлу игры, уже залитому на какой-нибудь сервер. Например: https://iplayif.com/?story=https://rinform.org/games/photopia/PhotopiaR.z8
Очень просто и разместить игру на своём собственном сайте, даже статическом, т.к. Parchment работает полностью на клиентском JavaScript. Достаточно скачать и разместить у себя на сервере файлы:
- index.html
- lib/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
Как описывать объекты
Нижеперечисленное относится только к объектам. На комнаты (локации) эти правила не распространяются.
-
В имени объекта нужно отделить окончание (или окончания) прямым слешем (/), а точнее — те буквы, которые должны будут изменяться при склонении. Например: латунн/ая ламп/а, надпис/ь на стене, бел/ый туман/. Имя используется при выводе объектов во всевозможных ситуациях, в разных падежах.
-
В свойстве
name
указываются слова, по которым парсер находит объекты. В русской версии нужно перечислять такие слова без окончаний. Здесь нужно не забыть указать и синонимы, по которым объект тоже должен находиться парсером. -
Каждому объекту (кроме комнат) обязательно нужно выдать один из атрибутов, соответствующий роду или числу объекта. Один из четырёх: male — мужской род, female — женский род, neuter — средний род, pluralname — множественное число.
Примеры:
Object -> "друг/ой склон/ холма" with name 'склон' 'сторон' 'холм', description "Кто мешает изучить его самому?", has scenery male;
Object -> "груб/ые каменн/ые ступен/и" with name 'груб' 'каменн' 'ступен', description "Грубые каменные ступени ведут по куполу вверх.", has scenery pluralname;
-
Особый случай — это существительные с беглыми гласными: ковёр (ковром), перекрёсток (перекрёстком) и так далее. Их нужно окружить двумя слешами, а к объекту добавить свойство
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;
-
Есть случаи, когда нужно, чтобы у мужского объекта была женская логика для склонения имени. Например, мужское имя «Слава». В таком случае необходимо дополнительно выдать объекту атрибут fem_grammar.
-
Если возникает случай, где парсер не в состоянии самостоятельно просклонять имя объекта (на вывод), необходимо указать свойство-функцию
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;
-
Если парсер не распознаёт объект автоматически (на ввод), то следует пользоваться свойством-функцией
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
: ним, ней, ними.
Как описывать русские глаголы
-
Глаголы нужно перечислять по корневой части. Парсер распознает приставки («по», «за» и т.д.) и суффиксы-окончания («ся», «ять», «ать» и т.д.) автоматически.
-
Нужно учесть и то, что глаголы могут вводиться и в повелительном наклонении: например, не только «взять» (вз), но и «возьми» (возьм). Это необходимо для приказов, которые игрок может отдавать NPC: «гоблин, отдай мне ключ».
-
Для существительных, с которыми оперирует глагол, нужно указывать токены. Какой токен использовать, определяет падеж существительного в данном контексте. Например, «взять ключ» — винительный падеж (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
.
-
Также, не обязательно, но рекомендуется добавить объект с этими корневыми частями в 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 'вяз' 'вяж';