Данный файл является частью Руководства по TADS для авторов игр.
Copyright © 1987, 1996 Майкл Дж. Робертс (Michael J. Roberts). Все права защищены.

Руководство было преобразовано в формат HTML Н. К. Гайем (N. K. Guy), компания tela design.

Перевод руководства на русский язык - Валентин Коптельцев


Глава пятая


Справочник по языку программирования

В данной главе подробно описаны все ключевые слова, операторы, синтаксические элементы, встроенные функции и специализированные средства языка программирования (ЯП) TADS.


Условные обозначения

В данной главе используются следующие условные обозначения, позволяющие исключить неоднозначности при описании языка TADS:

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


Информация о версии

За годы разработки язык TADS обрастал новыми функциями и возможностями, но отследить, какие возможности были доступны в той или иной версии, в прошлом было проблематично. Однако начиная с выхода релиза 2.2.4, в TADS была добавлена возможность проверки, какая именно версия программы-интерпретатора используется. В связи с этим для всех функций и возможностей, добавленных в версии 2.2.4 и позже, указано, начиная с какой версии они появились. Если такой информации не приведено, то соответствующий элемент был доступен в TADS до релиза 2.2.4.


Компоненты дистрибутива TADS

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

При написании игры на TADS автор использует текстовый редактор (например, BBEdit, UltraEdit, emacs и т. д. - не входит в дистрибутив TADS) для создания и редактирования файлов исходников, а затем компилирует их, используя компилятор TADS. Компилятор проверяет весь исходный код на наличие синтаксических ошибок, после чего создает новый файл, представляющий собой двоичное представление исходников. Эта форма представления (называемая файлом игры) обеспечивает более высокую эффективность исполнения инструкций программы, за счет этого ваша игра будет требовать меньше памяти и работать быстрее.

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


Формат входного файла

В качестве исходных файлов компилятор TADS использует файлы в простом текстовом формате (ASCII). С точки зрения компилятора цепочка из пробелов любой длины эквивалентна одному пробелу, при этом пробелами считаются также символы табуляции и перевода строки. Пробелы могут располагаться между любыми двумя синтаксическими элементами, но необходимы только для разделения идентификаторов или ключевых слов, не отделенных друг от друга никакими иными знаками препинания. Компилятор "знает", что знаки препинания не являются частью ключевого слова или идентификатора, и способен разделить знаки пунктуации даже в том случае, если они следуют друг за другом без пробелов. Однако использование пробелов рекомендуется для лучшей читаемости кода.


Включение дополнительных файлов в текст игры

Директива #include позволяет включить содержимое одного файла в другой:

#include "file.t"
#include <file.t>

В данном примере file.t - это имя файла, который включается в код. Включаемый файл может в свою очередь включать в себя текст другого файла и т. д. - всего до десяти уровней вложения. Обратите внимание, что значок решетки # должен размещаться с начала строки; ведущие пробелы не допускаются. Как правило, файлы advr.tstdr.t) включаются в начале файла с исходным кодом игры, что позволяет получить доступ к многочисленным определениям функций/объектов, необходимым для подавляющего большинства текстовых квестов. Эти определения носят довольно общий характер; при необходимости они могут модифицироваться в исходном коде самой игры.

Обратите внимание, что, когда имя файла заключено в кавычки, компилятор вначале ищет файл в текущем каталоге, а затем в специальном каталоге для "включаемых" файлов (который задается опцией -i для версий компилятора, работающего из командной строки (для большинства операционных систем), либо специальным пунктом меню в интегрированной среде разработки TADS). Если же имя файла заключено в угловые скобки, то компилятор не будет просматривать текущий каталог, а сразу перейдет к поиску в каталоге включаемых файлов. Как правило, имена файлов системных библиотек (например, того же advr.t) указываются в угловых скобках, а все остальные размещаются в текущем каталоге и указываются в кавычках. Это может быть особенно удобно, когда автор ведет работу одновременно над несколькими играми, используя для них общие файлы системных библиотек.

Еще раз обратите внимание, что перед директивой #include не должно быть пробела, строка должна начинаться со значка #. Об этой особенности необходимо помнить, когда вы вставляете в свою игру готовый код из другого источника - например, из Интернет-браузера. В противном случае ваш код просто не будет компилироваться.


Множественное включение одного и того же файла

Компилятор TADS отслеживает названия файлов, включаемых в исходник игры, и игнорирует избыточные директивы #include. Однако обратите внимание, что работа данной функции основана исключительно на имени файла. Если вы обращаетесь к одному и тому же файлу под разными именами (например, указывая при первом включении полный путь к файлу, а во втором используя угловые скобки), компилятор окажется не в состоянии отследить такое "дублирование", и текст файла будет включен в исходный код игры дважды, что приведет к выдаче огромного множества сообщений об ошибках. Вследствие этого предпочтительным является использование директивы #include с угловыми скобками, без указания путей к файлам. Помимо всего прочего, это обеспечит лучшую переносимость исходных файлов игры на другие компьютеры (в том числе кроссплатформенную, так как не требуется учитывать специфические соглашения относительно правил задания путей, действующие в разных операционных системах).

Обратите внимание, что имена включаемых файлов, уже предварительно скомпилированные в бинарный файл, подгружаемый при указании опции -l компилятора, также записываются в бинарный файл. Таким образом, вам не потребуется удалять директиву #include из вашего исходного кода только потому, что вы уже используете предварительно скомпилированную версию этого файла. Более подробную информацию об использовании прекомпилированных исходных файлов можно найти в разделе, посвященном компилятору.


Комментарии

Две косые черты-слэша (//), расположенные вне закавыченного строкового значения, указывают на то, что остаток строки - это комментарий, пропускаемый компилятором.

Кроме того, возможно использование комментариев в стиле языка Си; они начинаются /*, заканчиваются */ и могут охватывать сразу несколько строк.

Примеры:

  // Эта строка - просто комментарий.
  /*
  Это комментарий,
  занимающий
  несколько строк.
  */


Идентификаторы

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


Область действия идентификаторов

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

Наименование свойств - это также глобальные идентификаторы. Название, используемое для свойства, не может служить именем объекта или функции; обратное также верно. В то же время одно и то же название свойства может использоваться в нескольких разных объектах. Поскольку имя свойства никогда не используется само по себе, а только совместно с именем объекта, которому это свойство принадлежит, компилятор TADS в состоянии определить, к какому именно свойству обращается игра, даже если объектов с одинаковыми названиями свойств в игре несколько.

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


Определения объектов

В общем виде определение объекта выглядит так:

  идентификатор: object [список-свойств] ;

Это - определение объекта, не имеющего родительского класса. Альтернативно, объект можно определить и как имеющий родительский класс:

  идентификатор: имя-класса [, имя-класса [...]] [список-свойств] ;

Здесь имя-класса - это идентификатор, определенный где-либо в ином месте программы в качестве объекта или класса (без разницы, с родительским классом или без оного), и задает родительский класс определяемого объекта; если определение объекта содержит несколько родительских классов, их идентификаторы отделяются друг от друга запятыми. Новый объект, имя которого определяется идентификатором, наследует все свойства родительского класса/классов. Если свойство включено в (опциональный) список-свойств и одновременно содержится в списке свойств родительского класса (или родительского класса более высокого порядка), новое свойство заменяет наследуемое.

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

  vase: container, fixeditem
  ;

Если оба класса, container и fixeditem, определяют некое свойство m1, а объект vase его не определяет, то будет унаследовано свойство m1 класса container, так как название этого класса находится в списке раньше, чем fixeditem.

Возможен и более сложный случай, хотя на практике он почти не встречается. Скорее всего, вы с ним никогда не столкнетесь, поэтому просто пропустите этот абзац и не заморачивайтесь по его поводу, если он покажется вам слишком запутанным. Предположим, в вышеприведенном примере оба родительских класса, container и fixeditem, в свою очередь, яляются потомками класса item, при этом для классов item и fixeditem определено свойство m2, а для класса container и объекта vase свойства с таким названием не определено. На первый взгляд, кажется, что объект vase унаследует свойство m2 класса container (а следовательно, и класса item), поскольку этот класс стоит в списке первым. На самом деле это не так; поскольку класс fixeditem также наследует свойство m2 от класса item и затем переопределяет его, объект vase унаследует именно этот переопределенный метод. Таким образом, полная версия правила наследования звучит так: в случае множественного наследования наследуется свойство родительского класса, стоящего в списке ближе всего к началу (левее), при условии, что это свойство не переопределяется одним из следующих стоящих в списке классов.


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

Список свойств имеет следующую форму:

  определение-свойства [список-свойств]

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

Определение свойства может выглядеть так:

  идентификатор = элемент-данных

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


Типы данных, определяемых в свойствах

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

Свойство может определять данные следующих типов: число, строка в двойных кавычках, строка в одинарных кавычках, список, объект, а также два специальных значения - nil и true.


Числа

Число представляет собой простую последовательность цифр. По умолчанию они записываются в десятичном виде, однако возможно также представление чисел в восьмеричной и шестнадцатиричной транскрипции. Восьмеричные числа начинаются с ведущего нуля; таким образом, например, 035 - это восьмеричная запись числа 29. Шестнадцатиричные числа начинаются с 0x, например, 0x3a9.

Допустимы числовые значения в (десятичном) диапазоне с -2147483647 по 2147483647 включительно. Возможно использование только целых чисел, т. е. числа с плавающей точкой не допускаются.


Строки в двойных кавычках

Строка в двойных кавычках - это произвольная последовательность символов, обрамленных двойными кавычками, например:

  "Пример строки в двойных кавычках. "

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

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

Последовательности из нескольких пробелов подряд в таких строках сжимаются до одиночных пробелов. Это правило распространяется также на переводы строки; строка в двойных кавычках может занимать несколько строк. Например, следующая форма записи совершенно корректна:

  "Это - строковое значение, 
  занимающее

  несколько строк. "

Обратите внимание, что, как уже было сказано выше, TADS сжимает все "пустое пространство", включая пустые строки, до одного пробела между словами. Таким образом, вы можете набивать строку текста, не беспокоясь о том, как она будет выведена на экран; TADS выполнит всю работу по сокращению лишних пробелов и переносу строк самостоятельно.

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

\t Знак табуляции, соответствует четырем пробелам. Бывает полезным, если вам необходимо вывести последовательность из нескольких пробелов. Эквивалентом данной последовательности в HTML-TADS является тэг <TAB MULTIPLE=4>.
\n

Переход на новую строку (перевод каретки). Заканчивает текущую строку и переходит на следующую. Обратите внимание, что повторение этой последовательности несколько раз подряд не будет иметь эффекта, так как блок, отвечающий за форматирование выводимого текста, попросту игнорирует "лишние" переводы строк. Обычно это удобно, поскольку вам не требуется отслеживать количество переводов строк, например, в случае, если текст на экран выводится при обращении к разным свойствам нескольких объектов. Если вам необходимо принудительно задать одну или несколько пустых строк, используйте последовательность \b. Эквивалентный тэг в HTML-TADS - <BR HEIGHT=0>.

\b

Заканчивает текущую строку, вставляя за ней пустую. При использовании цепочки таких последовательностей будет выведено соответствующее число пустых строк. Практически полным эквивалентом данной последовательности в HTML-TADS является тэг <P> (за исключением того, что при использовании нескольких таких тэгов подряд не произойдет вставки нескольких пустых строк).

\" Знак двойных кавычек. Обратите внимание, что это "простые" кавычки; если требуется вывести "типографские" кавычки, необходимо использовать возможности HTML-TADS.
\' Одинарные кавычки (апостроф). Обратите внимание, что это "простой" апостроф; если требуется использовать его "типографский" эквивалент, необходимо задействовать возможности HTML-TADS.
\\ Обратная косая черта (обратный слэш).
\< Левая угловая скобка (знак "меньше"). Для вывода одиночного значка эта последовательность в принципе не требуется, однако если необходимо вывести цепочку таких значков, то всем символам в цепочке, начиная со второго, должны предшествовать обратные слэши, поскольку в противном случае TADS воспримет это как попытку включения в строку вычисляемого выражения (см. далее). Таким образом, если вам требуется вывести строку вида <<<<<, то в коде ее надо будет задать следующим образом: "<\<\<\<\<". Также обратите внимание, что в HTML-TADS эта последовательность выводится как левая угловая скобка и не воспринимается как начало тэга.
\^ (Т. е. обратный слэш, за которым следует "крышечка" или значок "стрелка вверх"; на большинстве клавиатур с англоязычной раскладкой этот символ выводится нажатием SHIFT и клавиши с цифрой 6). Данная последовательность переводит следующий выводимый на экран символ в верхний регистр. Как правило, эта последовательность используется непосредственно перед обращением к функции или методу, которые должны вывести текст, располагающийся в начале предложения. Действие данной последовательности полностью аналогично действию встроенной функции caps().
Примечание переводчика: к сожалению, как и функция caps(), данная последовательность преобразует регистр только латинских букв, в связи с чем ее применение в русскоязычной игре будет весьма ограниченным. Для символов кириллицы рекомендуется использовать определенные в файле advr.t функции ZA и ZAG.
\v Действие этой последовательности противоположно "\^": в то время как "\^" преобразует следующий за ней символ в верхний регистр, "\v" преобразует его в нижний регистр. Действие этой последовательности полностью эквивалентно действию встроенной функции nocaps().
Примечание переводчика: как и предыдущая последовательность, "\v" преобразует регистр только для символов латиницы. Аналога для кириллицы пока не существует - видимо, в связи с тем, что необходимость в таком преобразовании встречается гораздо реже, чем для обратной задачи. В то же время в advr.t определена функция loweru, выполняющая преобразование в нижний регистр для всей строки.
\пробел (Т. е. обратный слэш, за которым следует пробел). "Фиксированный" пробел. Бывает полезным для достижения некоторых спецэффектов, так как позволяет отменить некоторые умолчальные опции форматирования, действующие при выводе текста на экран: в частности, обходит "сжатие" цепочки пробелов, а также вывод двух пробелов после точки в некоторых версиях интерпретатора TADS. Например, если необходимо вывести чье-либо имя с сокращенными инициалами, лишний пробел после инициалов, как правило, нежелателен. В этом случае вы можете гарантировать вывод только одного пробела независимо от версии интерпретатора, например, так: "Майкл Дж.\ Робертс". (Имейте также в виду, что некоторые версии интерпретатора могут выводить как один, так и два пробела после точки - в зависимости от пользовательских настроек).
\( Начало выделения текста. Текст после последовательности "\(" будет выделяться либо цветом, либо полужирным шрифтом - в зависимости от версии интерпретатора. Для некоторых систем эта последовательность вообще не окажет никакого действия. Обратите внимание, что данная последовательность не может иметь несколько уровней вложения, т. е. все последующие последовательности "\(" в тексте, для которого уже включено выделение, будут просто игнорироваться. Данная опция форматирования является предтечей мультимедийных средств HTML-TADS, которые обеспечивают намного более широкие и гибкие возможности по форматированию текста.
\) Окончание выделения текста. Текст после данной последовательности не будет выделяться. Обратите внимание, что одна-единственная последовательность "\)" отключает выделение текста вне зависимости от того, сколько последовательностей "\(" ей предшествовало.
\- Передать два байта. Данная последовательность представляет собой знак дефиса, следующий за обратным слэшем, и сообщает блоку вывода текста, что следующие два байта необходимо передать "как есть", не пытаясь их интерпретировать. Эта опция может быть полезной для многобайтных наборов символов (например, компьютера Macintosh с японской локалью), где каждый символ представляется двумя байтами. По большей части вы можете использовать одно- и двухбайтовые символы в тексте в любых комбинациях без каких-либо дополнительных усилий. Однко некоторые двухбайтовые символы содержат в качестве кода одного из байтов ASCII-код обратного слэша; в таких случаях блок вывода текста некорректно воспринимает байт, код которого соответствует "\", как начало специальной последовательности форматирования. Размещая перед такими символами "\-", вы препятствуете такой некорректной интерпретации, и символ отображается правильно.
\H+ Включить HTML-возможности интерпретатора. HTML-TADS предоставляет значительно более богатые возможности по форматированию текста по сравнению со стандартным TADS, однако для этого используются не специальные последовательности, а теги. Более подробную информацию вы найдете в соответствующем разделе документации.
\H- Отключить HTML-возможности интерпретатора.

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


Включение выражений в строки

TADS предоставляет удобное средство, позволяющее включать в строки выражения, не прерывая эту строку. Всякий раз, когда при выводе строки интерпретатору TADS встречаются две левых угловых скобки (знака "больше"), интерпретатор приостанавливает вывод и считает все, что идет после угловых скобок, выражением, пытаясь вычислить его значение. Конец выражения обозначается двумя правыми угловыми скобками (знаками "меньше"), после которых вывод строки продолжается как обычно. Это позволяет значительно упростить определение свойств, которые должны ссылаться на другие свойства; например, определение

  itsHere = "<< self.sdesc >> находится здесь."

эквивалентно следующему более подробному определению:

  itsHere =
  {
    self.sdesc; " находится здесь.";
  }

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

Для этого средства ЯП существует ряд ограничений. Во-первых, недопустимо включать в строку в качестве выражения другую строку, которая, в свою очередь, также содержит вложенное выражение. Во-вторых, включение выражений можно использовать только для строк в двойных кавычках (а не в одинарных).

Поскольку некоторым авторам игр может потребоваться вывод в строке нескольких левых угловых скобок подряд, в TADS имеется служебная последовательность "\<", описанная выше.

Обратите внимание, что использование включаемых в строку выражений полностью совместимо с HTML-TADS и не вызывает проблем, несмотря на использование символов < и >. Это возможно благодаря тому, что соответствующие символы интерпретируются компилятором в ходе компиляции игры и уже не появляются в конечном тексте, выводимом игрой.


Строки в одинарных кавычках

Строки в одинарных кавычках по своему внешнему виду (и применяемым служебным последовательностям) во многом аналогичны рассмотренным выше строкам в двойных кавычках, только, конечно, заключены в апострофы. Чтобы включить апостроф в "тело" строки, используется последовательность \'.

Строки в одинарных кавычках не выводятся автоматически на экран при обращении к ним, а используются как строковые значения (аналогично таковым в других ЯП). Для работы с ними существует ряд встроенных функций, одна из которых, say, как раз служит для вывода их на экран. Вообще, выражение say('Строка для вывода') практически полностью эквивалентно записи "Строка для вывода" (за исключением использования включенных выражений).

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


Списки

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

  [ список-значений ]

Список-значений может не содержать ничего; в этом случае список будет пустым, например:

  [ ]

Он также может содержать набор значений (числовых, строковых, логических, объектов или списков). В большинстве случаев списки состоят из элементов с одинаковым типом данных, но это не является обязательным требованием языка (например, такие "разношерстные" списки активно используются в парсерных функциях - примечание переводчика).

Примеры списков:

  [ 1 2 3 ]
  [ 'привет' 'пока' ]
  [ [1 2 3] [4 5 6] [7 8 9] ]
  [ vase goldSkull pedestal ]

Элементы списка необязательно должны быть константами. Единственное исключение - если весь список целиком должен быть константой (например, при определении значения свойства); в этом случае и все его элементы тоже должны быть константами (т. е. в нем нельзя будет использовать локальные переменные и выражения с переменным значением). Однако список, включаемый напрямую в код (например, при присвоении списочного типа значения локальной переменной) может содержать среди своих элементов в том числе и переменные значения. Например, следующая функция создает список из трех выражений в динамическом режиме:

  f: function(x, y, z)
  {
    return([x+1 y+1 z+1]);
  }

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

  f: function(x, y, z)
  {
    return((([ ] + (x+1)) + (y+1)) + (z+1));
  }

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


nil и true

В TADS предопределены две специальные константы, nil и true. В основном они используются в качестве логических (или булевских) значений, при этом nil соответствует значению "ложь", а true - "истина". Например, выражение 1 > 3 имеет значение nil, а 1 < 3 - true. Кроме того, nil используется в качестве признака отсутствия значения; например, nil возвращается при попытке получить первый элемент пустого списка, а также при обращении к свойству объекта, которое для него не определено.

Рекомендуется использовать в качестве логических значений именно nil и true, а не их числовые эквиваленты (например, 0 и 1), поскольку это сделает код вашей программы более наглядным.


Выражения

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

  exprObj: object
    x = 1
    y = 2
    z = (self.x + self.y)
  ;

При каждом обращении к exprObj.z будет вычисляться и возвращаться сумма текущих значений exprObj.y и exprObj.z.

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

  exprObj2: object
    w(a, b) = (a * b)
  ;

При обращении к exprObj2.w ему требуется передать два параметра, которые будут перемножены, а полученное выражение - возвращено в вызывающий код. Например, при обращении exprObj2.w(3, 4) будет возвращено значение 12.

Обратите также внимание, что свойство z из предыдущего примера можно было бы определить без использования префикса self. перед x и y:

  z = (x + y)

Связано это с тем, что при использовании имен свойств без ссылки на объект по умолчанию считается, что имеется в виду текущий объект self.


Лексические свойства

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

Всего существует пять лексических свойств (по частям речи): noun - существительное, adjective - прилагательное, verb - глагол, preposition - предлог и article - артикль (в русском языке, понятное дело, не используется). Лексические свойства должны иметь строковое значение (в одинарных кавычках) или представлять собой список таких строковых значений, записываемых через пробелы без дополнительных разделителей. Использование других типов может привести к непредсказуемым эффектам.

Как правило, область применения лексических свойств, соответствующих разным частям речи, выглядит так: noun и adjective используются для большинства объектов (предметов) в игровом мире; verb - для глаголов, с помощью которых игрок манипулирует предметами; preposition - для всевозможных предлогов-связок. Определение для одного объекта лексических свойств из разных категорий (скажем, noun и verb) в принципе возможно, но не рекомендуется, особенно если у вас нет большого опыта работы с (R)TADS. Такое объединение может иметь смысл, если автор хочет добиться некого спецэффекта и при этом очень четко представляет себе, какого именно.

Одной из особенностей лексических свойств является их механизм наследования: они наследуются только в том случае, если родительский класс не является объектом в игре, а определен с помощью директивы class. В принципе, такой подход понятен: иметь в игре два или несколько предметов с идентичным набором лексических свойств (как это происходило бы, если бы TADS поддерживал их наследование от экземпляров объекта) в подавляющем большинстве случаев нежелательно.

Чаще всего набор лексических свойств задается статически на этапе компиляции, однако в TADS есть средства для изменения словаря в ходе самой игры. Подробнее об этом см. здесь.


Методы

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

Код метода заключается в фигурные скобки {}. Любые конструкции, допустимые для функций, можно использовать и в определениях методов. Методы могут определять локальные переменные, им также могут передаваться аргументы. Если метод содержит аргументы, их список (который в принципе аналогичен списку аргументов функции) перечисляется в скобках после названия метода, например:

  obj1: object
    f(x) =
    {
      return(x + 1);
    }
  ;

При обращении к такому методу список аргументов также указывается после названия метода:

  f1: function
  {
    say(obj1.f(123));
  }


Функции

Функция - это структурно законченный фрагмент кода, который, в отличие от метода, не является частью объекта. Определение функции в обобщенном виде выглядит так:

  идентификатор: function [ ( список-аргументов ) ]
  {
    тело-функции
  }

Список-аргументов может отсутствовать, т. е. наличие аргументов у функции необязательно. Однако, если аргументы все-таки имеются, их список выглядит следующим образом:

  идентификатор [, список-аргументов ]

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

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

  addlist: function(list)          // сложить номера в списке
  {
    local sum, count, i;
    i := 1;                        // устанавливаем счетчик на первый элемент списка
    sum := 0;                      // сбрасываем сумму
    count := length(list);         // получаем количество элементов в списке
    while (i < count)              // пока список не кончился...
    {
      sum := sum + list[i];        // прибавляем текущий элемент
      i := i + 1;                  // переходим к следующему элементу списка
    }
    return(sum);
  }


Функции с переменным количеством аргументов

В TADS имеется возможность определить функцию, которой можно будет передавать переменное количество аргументов. В определении такой функции вы можете задать обязательный минимум аргументов, передающихся функции в любом случае (этим минимумом может быть и отсутствие аргументов), а затем указать, что опционально могут быть переданы и другие аргументы. Для этих целей используется многоточие ("..."), которое ставится в качестве последнего элемента в списке аргументов функции. Например, определение функции, которая может вызываться с любым количеством аргументов - от нуля до бесконечности - будет выглядеть так:

  f: function(...)
  {
  }

А это - определение функции, которой всегда передается как минимум один аргумент, но могут передаваться и дополнительные аргументы:

  g: function(fmt, ...)
  {
  }

Если функции может передаваться переменное количество аргументов, вы можете определить, сколько именно аргументов было ей передано при текущем вызове, используя псевдо-переменную argcount. Значение этой переменной соответствует числу аргументов, переданных текущей функции.

Чтобы получить аргумент, используйте встроенную функцию getarg(argnum). Параметр argnum - это номер аргумента, который вы хотите получить; getarg(1) возвращает первый переданный аргумент, getarg(2) - второй и т. д. Обратите внимание, что если для функции перед знаком многоточия определены обязательные аргументы, getarg будет возвращать значения и для них. Например, в вышеприведенном примере для функции g, getarg(1) вернет значение аргумента fmt.

Еще одна встроенная функция, datatype(значение), может использоваться для определения типа данных переданного аргумента.

Следующая функция выводит на экран любое количество переданных ей значений:

  displist: function(...)
  {
    local i;
    for (i := 1 ; i <= argcount ; i++)
    {
      say(getarg(i));
      " ";
    }
    "\n";
  }


Предварительное объявление функций

Альтернативная форма записи инструкции function позволяет заранее объявить имя функции, не определяя саму функцию. Формат такого предварительного объявления выглядит так:

  идентификатор: function;

Обратите внимание, что при этом функция не определяется; таким образом мы просто сообщаем компилятору, что где-то дальше будет объявлена функция с именем, соответствующим объявленному идентификатору. Это позволяет зарезервировать идентификатор в качестве имени функции; если этого не сделать, компилятор может "подумать", что идентификатор относится к объекту.

В большинстве случаев предварительное объявление не требуется, за исключением ситуаций, когда используется setdaemon() и подобные ей встроенные функции. В принципе, когда в коде определен вызов функции, компилятору "понятно" из самого синтаксиса этого вызова, идет ли речь о функции или об объекте. Однако при использовании setdaemon() и других функций той же группы из синтаксиса невозможно определить, происходит ли обращение к объекту или к функции; в этом случае (при отсутствии предварительно объявленной функции) по умолчанию считается, что происходит обращение именно к объекту.


Написание кода

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

Каждая инструкция в (R)TADS завершается точкой с запятой.


local

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

  local список-идентификаторов ;

Список-идентификаторов, в свою очередь, должен иметь следующий вид:

  идентификатор [ инициализация ] [, список-идентификаторов ]

Инициализация является опциональной и в общем виде выглядит так:

  := выражение

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

Еще раз отметим, что определяемые таким образом переменные "видны" только внутри тела функции/метода, где они объявлены. Кроме того, переменные, объявленные при помощи инструкции local, замещают в том блоке кода, где они объявлены, любые глобальные переменные с теми же именами.

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

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

  f: function(a, b)
  {
    local i, j;                       /* переменные не инициализируются */
    local k := 1, m, n := 2;          /* некоторые переменные инициализируются, некоторые нет */
    local q := 5*k, r := m + q;       /* q можно использовать сразу после того, как она инициализирована */
    for (i := 1 ; i < q ; i++)
    {
      local x, y;                     /* локальные переменные могут объявляться в начале любого блока */
      say(i);
     }
   }


Выражения

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

&

Возвращает "адрес" функции или свойства. Другими словами, этот оператор позволяет обращаться к функции/свойству без ее/его вызова.

Имя функции/свойства должно следовать за оператором & без пробела. Возвращаемое значение может быть присвоено локальной переменной либо передано в качестве аргумента другой функции/методу. Впоследствии этот "адрес" может использоваться для вызова функции/свойства. См. также раздел "Непрямой вызов функций и методов" далее в этой главе. (Примечание: в более старых версиях TADS для получения адреса функции использовался оператор #. Этот оператор и сейчас понимается системой, однако в современных версиях рекомендуется использовать &).


.

(Точка). Обращается к свойству объекта. С левой стороны точки записывается выражение, значением которого является объект, а справа - имя свойства. Если свойство является методом, требующим передачи аргументов, эти аргументы записываются в круглых скобках после названия свойства (точно так же, как при вызове функции). С правой стороны можно использовать также имя локальной переменной или выражение, заключив их в круглые скобки; в этом случае они должны ссылаться на свойство объекта.

[]

Индексация списка; ставится справа от выражения, значение которого - список. Между квадратными скобками должно размещаться выражение со значением числового типа. Если индекс (то самое значение в квадратных скобках) равен n, то данный оператор возвращает n-ный элемент списка. Первый элемент списка имеет индекс 1 (т. е. индексация списка ведется с единицы, а не с нуля). Например, выражение ['один' 'два' 'три'][1+1] вернет в качестве значения строку 'два'. Обратите внимание, что выход числа n за границы списка (т. е. если n окажется меньше единицы или больше числа элементов в списке, возвращаемого функцией length, приведет к возникновению ошибки исполнения.

++

Инкремент; увеличение на единицу числовой переменной или свойства. Выражение, к которому применяется оператор ++, должно допускать присваивание ему значения (иначе говоря, это должно быть такое выражение, которое допустимо использовать с левой стороны оператора присваивания, :=. Оператор ++ может либо предшествовать выражению-операнду, либо следовать за ним. Если оператор предшествует операнду (например, ++i), то операнд увеличивается на единицу, и значением выражения будет значение операнда после этого увеличения. Если же оператор следует за операндом (например, i++), значением выражения будет значение операнда до увеличения. Так, если в наших примерах i имеет значение 3, то ++i будет иметь значение 4, в то время как i++ будет иметь значение 3. Иначе говоря, представьте, что вы читаете выражение слева направо, и при этом учтите, что значение выражения соответствует значению переменной i в тот момент, когда вы ее прочитываете, а операция инкремента производится, когда вы прочитываете оператор ++. Тогда для выражения ++i вы сначала считываете оператор инкремента, который прибавляет единицу к i, а затем получаете значение i, которое в этот момент уже равно 4. Для записи i++ вы сначала считываете переменную i, которая на тот момент еще равна 3, и уже после этого вы видите оператор ++, который присваивает i значение 4.

Примечание переводчика: очень тонкое различие;). Честно говоря, на практике я не сталкивался с ситуациями, где бы использовались особенности расположения оператора ++ относительно операнда.


--

Декремент; уменьшение на единицу числовой переменной или свойства. В целом полностью аналогичен оператору ++, за исключением того, что уменьшает операнд, а не увеличивает его.


not Логическое "не". Если операнд равен true, выражение примет значение nil и наоборот. Данный оператор можно применять только к логическим значениям (true и nil).
- Операция обращения знака. При применении в качестве унарного префиксного оператора (т. е., проще говоря, оператора, записываемого перед единственным операндом) минус изменяет знак следующего за ним числа на противоположный.

* Арифметическое перемножение. Вычисляет произведение операндов, размещенных по обе стороны звездочки.
/ Операция деления. Вычисляет частное делимого, размещаемого слева от косой черты, и делителя, размещаемого справа от нее. Обратите внимание, что в (R)TADS используются только целые числа, поэтому остаток просто отбрасывается. Например, при вычислении выражения 7/2 результатом будет 3.

+ Выполняет арифметическое сложение чисел, объединение списков, "сцепление" строк. Если справа и слева от знака плюс стоят числа, результатом будет их сумма. Если один из операндов - список (но не оба), то операнд, не являющийся списком, будет добавлен в список в качестве последнего элемента. Если оба операнда являются списками, элементы списка - правого операнда присоединяются к концу левого операнда. Например, результатом выражения [1 2 3] + [4 5] будет список [1 2 3 4 5]. Если оба операнда - строки, то левая строка будет продолжена правой. Другие комбинации типов данных не допускаются (вызывают ошибку исполнения).
- При наличии операндов справа и слева данный оператор выполняет арифметическое вычитание либо исключает элементы из списка. Если оба операнда являются числами, то вычисляется разность левого операнда (уменьшаемого) и правого (вычитаемого). Если левый операнд является списком, а правый - нет, то правый операнд исключается из списка, если он там встречается (если нет, то оператор не выполняет никаких действий). Если оба операнда являются списками, то все элементы "правого" списка, встречающиеся в "левом", будут из этого "левого" списка удалены.

=

Равенство. Операнды по обеим сторонам должны принадлежать одному и тому же типу данных, но не могут быть списками. Единственное исключение: на равенство nil можно проверять данные любого типа. Если оба операнда равны, выражение равно true, в противном случае оно равно nil.

<>

Неравенство. Равно true, если операнды с правой и левой стороны неравны (при этом оба операнда должны иметь один и тот же тип данных).

>

Операция "больше". Как и другие операторы сравнения, может применяться только к данным числовых и строковых типов. Возвращает true или nil. Обратите внимание, что при применении к строковым данным сравнение основывается на принятом порядке таблицы символов, используемой в вашем компьютере (например, ASCII).

< Операция "меньше".
>= Операция "больше или равно".
<=

Операция "меньше или равно".


and Операция логического умножения. Если оба операнда справа и слева равны true, то и все выражение равно true. Если хотя бы один из них равен nil, то все выражение имеет значение nil. Обратите внимание, что если операнд слева равен nil, то значение операнда справа даже не будет вычисляться, так как значение результирующего выражения в любом случае будет nil.

or Операция логического сложения. Если хотя бы один из операторов слева или справа равен true, то и все выражение имеет значение true; если оба равны nil, то выражение примет то же значение. Обратите внимание, что если операнд слева равен true, то значение операнда справа даже не будет вычисляться, так как значение результирующего выражения в любом случае будет true.

? : Оператор "если". Это троичный оператор, т. е. он использует три операнда: условие ? выраж-да : выраж-нет. Вначале проверяется значение выражения условие; если оно равно true или ненулевому числу, вычисляется выраж-да, и его значение будет значением всего выражения в целом. В противном случае вычисляется выраж-нет, и все выражение в целом примет его значение. Обратите внимание, что вычисляться будет или выраж-да, или выраж-нет, но не оба сразу. Это позволяет использовать данный оператор, если выражения, помимо возврата значения, выполняют какие-либо побочные действия - например, выводят на экран текст.

:= Присваивание значения. Операнд слева должнен быть переменной или свойством, которому будет присвоено значение правого операнда. Этот оператор имеет самый низкий приоритет, и, в отличие от остальных, читается справа налево. Таким образом, выражение a := b := 3 означает, что значение 3 будет вначале присвоено переменной b, а затем - a. Обратите внимание, что операция присваивания одновременно является "полноценным" выражением, возвращающим присвоенное значение; следовательно, например, значением выражения (a := 3) + 4 будет 7, при этом в качестве "побочного эффекта" переменной a будет присвоено значение 3.
+= Вычисление суммы с присваиванием значения. На самом деле, это не "настоящий" оператор, а сокращенная форма записи. Скажем, выражение a += b полностью аналогично выражению a := a + b, но записывается короче. Значением выражения будет a + b, оно же будет присвоено и переменной a после выполнения операции.
-= Вычитание с присваиванием значения. Сокращенная форма записи операции вычитания; выражение a -= b эквивалентно a := a - b.
*= Перемножение с присваиванием значения. Сокращенная форма записи операции умножения; выражение a *= b эквивалентно a := a * b.
/= Деление с присваиванием значения. Сокращенная форма записи операции деления; выражение a /= b эквивалентно a := a / b.

, Оператор-запятая просто позволяет вам начать новое выражение внутри другого выражения. С левой и с правой стороны от запятой ставятся выражения. Эти выражения полностью независимы; запятая между ними не выполняет никаких вычислительных действий над ними. Возвращаемым значением пары выражений, разделенных запятыми, будет значение второго (правого) выражения. Оператор-запятая полезен обычно в случаях, когда необходимо только одно выражение, но вы хотите вычислить их несколько ради каких-либо побочных эффектов. Например, при инициализации инструкции for вам может потребоваться инициировать несколько переменных; вы можете сделать это, разделив их запятыми. Примечание: оператор-запятая отличается от запятой, используемой для разделения аргументов при вызове функции/метода. В функции или методе разделяются разные выражения, являющиеся аргументами; в обычном же выражении оператор-запятая отделяет части одного и того же выражения.

Как уже было сказано, операторы приведены в порядке убывания приоритета. Таким образом, операторы-запятые выполняются в последнюю очередь, после того, как будут выполнены операторы с более высоким приоритетом. Обратите внимание, что некоторые операторы в списке разбиты на группы; например, сгруппированы все операторы сравнения. Внутри каждой группы операторы имеют равный приоритет. Операторы с равным приоритетом выполняются слева направо; например, выражение 3-4+5 эквивалентно (3-4)+5. Единственным исключением является оператор присваивания, для которого действует обратный порядок выполнения; например, в выражении a := b := 2 значение 2 сначала присваивается переменной b, а затем переменной a присваивается значение b.

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


Присваивание значения

Операция присваивания не является специальной инструкцией; по сути, это выражение, использующее оператор присваивания:

  сущность := выражение;

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

  a: function(b, c)
  {
    local d, e, lst;
    d := b + c;                   // присваивание значения локальной переменной
    obj3.prop1 := 20;             // присваивание значения свойству prop1 объекта obj3
    e := obj3;                    // присваивание локальной переменной значения объектного типа
    e.prop1 := obj2;              // присваивание свойству prop1 объекта obj3 значения объектного типа obj2
    e.prop1.prop2 := 20;          // присваивание свойству prop2 объекта obj2 числового значения 20
    fun1(3).prop3 := 1 + d;       // присваивание значения свойству prop3 объекта, возвращаемого выражением fun1(3)
    lst := [1 2 3 4 5];           // присваивание значения списочного типа локальной переменной 
    lst[3] := 9;                  // теперь lst имеет значение [1 2 9 4 5]...
    lst[5] := 10;                 // ...а теперь - [1 2 9 4 10]
    /* lst[6] := 7 - такая операция будет некорректной, так как в списке всего пять элементов */
  }


Запись операторов в стиле языка C

Пользователи (R)TADS, которые "по совместительству" являются программистами на C, часто находят удобным общее сходство между TADS и C, однако те незначительные отличия, которые все-таки существуют между языками, служат для таких пользователей постоянным источником ошибок и недоразумений при переходе с одного языка на другой. В связи с этим в (R)TADS имеется возможность использовать операторы в стиле C. Обратите внимание, что, если вы не являетесь опытным программистом на C, вам скорее всего не потребуется читать этот раздел.

Прежде всего, (R)TADS поддерживает весь набор операторов C.

a % b Возвращает остаток от деления a на b
a %= b Присваивает значение (a % b) переменной a
a != b

Эквивалентно (a <> b)

!a

Эквивалентно (not a)

a & b Побитное И
a &= b Устанавливает a равным (a & b) (результату операции побитного И для a и b)
a | b Побитное ИЛИ
a |= b Устанавливает a равным (a | b) (результату операции побитного ИЛИ для a и b)
a && b Эквивалентно (a and b)
a || b Эквивалентно (a or b)
a ^ b побитное XOR (исключающее ИЛИ, сложение по модулю 2) a и b
a ^= b Устанавливает a равным (a ^ b) (результату операции побитного XOR для a и b)
~a побитное отрицание (инверсия) a
a << b Сдвинуть a влево на b битов
a <<= b Устанавливает значение a равным (a << b) (результату операции сдвига a на b битов влево)
a >> b Сдвинуть a вправо на b битов
a >>= b Устанавливает значение a равным (a >> b) (результату операции сдвига a на b битов вправо)

Некоторые из этих операторов, такие как !, &&, and ||, являются просто альтернативной формой записи операторов (R)TADS. "Побитные" операторы работают не с логическими, а с числовыми значениями; они "рассматривают" операнды как битовые векторы (массивы, состоящие из отдельных бит), и осуществляют соответствующую операцию к каждому биту двоичного представления чисел. Например, выражение 3 & 2 вернет значение 2, поскольку в двоичном представлении операнды будут записываться как "011" и "010", соответственно. Операции побитного смещения по своему действию аналогичны делению или умножению на степень двойки; 1 << 5 вернет значение 32, поскольку оно эквивалентно умножению 1 на 2 в пятой степени.

Во-вторых, в (R)TADS возможен режим работы, в котором используется оператор присваивания языка C. Обычный оператор присваивания (R)TADS имеет вид :=, а оператор равенства =. В C эти операторы выглядят, соответственно, как = и ==. Если вам удобнее работать с записью операторов, принятой в C, вы можете указать (R)TADS, что будете использовать именно такие операторы вместо обычных. По умолчанию (R)TADS использует "родные" версии операторов. Перейти на запись, принятую в C, можно двумя способами: используя ключ командной строки при запуске либо вставив директиву компилятора #pragma в исходный текст программы.

Чтобы откомпилировать всю игру целиком "в стиле C", используйте ключ командной строки -C+ (пользователи "Макинтошей" могут воспользоваться соответствующим пунктом меню "Options"; при выборе этого пункта компиляция будет происходить по правилам записи, принятым в C, а при сбросе - по стандартным правилам (R)TADS). Использование ключа -C+ означает применение правил записи языка C для всего исходного кода компилируемой игры. (Ключ -C- явным образом отключает этот режим; именно так происходит компиляция по умолчанию).

Чтобы указать компилятору, что определенный файл необходимо компилировать с применением правил записи C, вы можете использовать директиву #pragma C+. "Парная" ей директива #pragma C- указывает на необходимость применения обычных операторов (R)TADS. Эти директивы могут располагаться где угодно в файле исходного кода; они должны быть единственными директивами в строке и должны начинаться с первого символа строки (т. е. перед ними не должно быть пробелов).

Директива #pragma оказывает влияние только на текущий файл с исходным кодом, а также на файлы, которые включены в него директивой #include. Оба стандартных библиотечных файла, входящих в комплект поставки RTADS, advr.t и stdr.t, используют стандартную форму записи операторов TADS, и для них в явном вмде задана директива #pragma C-. Однако в связи с тем, что эта директива распространяется только на сами эти файлы, вы спокойно можете включать их в состав файла игры, использующего запись операторов в стиле C. Просто используйте, как обычно, директиву #include adv.t - даже если ваш файл использует режим C, файл advr.t будет откомпилирован правильно, поскольку для него режим записи по правилам, принятым в C, будет отключен, а по окончании обработки advr.t компилятор (R)TADS автоматически восстановит режим компиляции исходного файла.

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

Если компилятор работает в режиме совместимости с C, то он выдает сообщение "possibly incorrect assignment" ("возможно, некорректная операция присваивания") во всех случаях, когда встречает выражение следующего вида:

  if ( a = 1 ) ...

Синтаксически такая инструкция совершенно корректна, однако при использовании записи операторов в стиле C она означает, что переменной a присваивается значение 1; поскольку возвращаемым значением операции присваивания всегда будет то значение, которое присваивается, то услоие данного оператора if всегда будет возвращать true. Написание подобного оператора присваивания в случаях, когда на самом деле требуется сравнить два значения - это очень распространенная ошибка среди программистов на C (независимо от их опыта). Более того, изначально я специально выбрал ":=" в качестве оператора присваивания в TADS, чтобы избежать таких ошибок. Впоследствии же, когда в (R)TADS появилась возможность переключаться с использования "родных" операторов на операторы языка C, я ввел вышеописанное предупреждающее сообщение о некорректной операции присваивания, чтобы оперативно отлавливать такие ошибки. Компилятор будет отслеживать операции присваивания, встречающиеся в условиях операторов if, while и do, а также оператора for. Чтобы избежать вывода этого сообщения, вы можете явным образом выполнить сравнение результата оператора присваивания, например:

  if ( ( a = 1 ) != 0 ) ...

Отдельные C-подобные операторы могут вызвать некоторые проблемы.

Прежде всего, оператор >> нельзя использовать в строках с конструкциями вида << >>, поскольку в этом случае он будет восприниматься как завершение вычисляемого в строке выражения. Даже использование скобок в этом случае не поможет, поскольку компилятор распознает конструкции << >> до того, как проверяет включенные в них выражения. Таким образом, код следующего вида не будет работать:

  myprop = "x divided by 128 is << (x >> 7) >>! "     // неправильный код

Вместо этого следует использовать следующую форму записи:

  myprop = { "x divided by 128 is "; x >> 7; "! "; }  // правильный код

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

  mylist = [ &prop1 &prop2 &prop3 ]

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

В целях совместимости с предыдущими версиями (R)TADS в вышеназванном примере воспримет операторы & как унарные. Однако, встретив такую конструкцию, он выдаст предупреждающее сообщение о том, что она неоднозначна (новое сообщение TADS-357, "operator "&" interpreted as unary in list." - "оператор "&" интерпретирован в списке как унарный"). Вы можете избежать вывода этого сообщения двумя способами. Во-первых, можно исключить неоднозначность списка. Для этого между его элементами надо вставить запятые:

  mylist = [ &prop1, &prop2, &prop3 ]

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

  mylist = [ ( 2 & 3 & 4 ) ]

Другой способ избавиться от данного предупреждающего сообщения - это новая опция компилятора -v-abin, которая отключает вывод этого предупреждения. При указании этой опции командной строки компилятор по-прежнему интерпретирует оператор & как унарный, но не сообщает вам об этом.

Обратите внимание, что (R)TADS будет воспринимать любой оператор, имеющий как унарную, так и бинарную версию, как унарный, если встретит его в списке. Для оператора - это соглашение является новым по сравнению с более старыми версиями TADS, где минус в списках всегда интерпретировался как бинарный (операция вычитания). Я не думаю, что это будет серьезной проблемой совместимости, поскольку старая бинарная интерпретация всегда была скорее нежелательной, и, по-моему, пользователи ее избегали. Однако, если у вас имеется древняя игра, то, возможно, стоит скомпилировать ее хотя бы один раз без указания опции -v-abin и проверить все строки, для которых выдается предупреждение TADS-357 (для операторов + и -) - это позволит вам убедится, что поведение игры не изменится при компиляции в новой версии (R)TADS. Все предупреждения TADS-357 для оператора & для игры, созданной с использованием старой версии TADS, можно спокойно игнорировать.

Примечание переводчика: плюсы и минусы в списках, увы - это проблема не только древних версий, как описано выше. Даже во вполне современной игре запись вида

[i-1]
   
всегда будет интерпретироваться как
[i, -1].
   
Если имеется в виду именно список, состоящий из единственного элемента со значением, равным переменной i, уменьшенной на единицу, запись должна выглядеть так:
[(i-1)].
   


Вызовы функций

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

  имя-функции( [ список-аргументов ] );

Здесь имя-функции - это название функции, определенное где-то в коде программы. Список аргументов (если он задан) передается функции при вызове, значения этих аргументов используются при выполнении функции. (Разумеется, количество передаваемых при вызове аргументов в общем случае должно совпадать с тем количеством, которое задано в определении функции). Каждый из аргументов может быть произвольным выражением; аргументы должны отделяться друг от друга запятыми.

Скобки после названия функции требуются вне зависимости от того, передаются ли ей аргументы или нет; собственно, скобки необходимы для того, чтобы сообщить компилятору, что в этой строке происходит вызов функции. (Может возникнуть резонный вопрос - а зачем в принципе использовать имя функции, если не происходит ее вызова? Ответ таков: некоторые специализированные встроенные функции, такие как setdaemon, которая описывается ниже, позволяют указать функцию, которая, возможно, будет вызываться впоследствии, однако в момент указания ее имени обращения к ней не проиходит. В подобных случаях имя функции используется без скобок).

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

Пример:

  showlist(a+100, 'Список: ', list1);


Непрямой вызов функций и методов

Функции и методы можно также вызвать "непрямым образом" - иначе говоря, вы можете определить указатель/ссылку для вызова функции или метода. Данную ситуацию можно также рассматривать следующим образом: вы обращаетесь к функции, не выполняя ее. Это бывает полезным, когда необходимо определить некую общую функцию, которая, в свою очередь, вызывает разные функции в зависимости от переданных ей параметров.

Чтобы вызвать функцию с использованием ссылки на нее, просто присвойте переменной значение, соответствующее адресу функции, а впоследствии используйте при вызове функции имя этой переменной, заключенное в скобки, например:

  g: function(a, b, c)
  {
    return(a + b + c);
  }

  f: function
  {
    local fptr, x;
    fptr := &g;                /* получить адрес функции g */
    x := (fptr)(1, 2, 3);      /* вызвать функцию с использованием указателя */
  }

Указатель на свойство используется точно так же.

  f: function(actor, obj)
  {
    local propPtr := &doTake;          /* получить указатель на свойство doTake  */
    obj.(propPtr)(actor);              /* обратиться к свойству при помощи указателя */
  }

Пусть, например, вам необходимо реализовать способ задавания вопросов персонажам в игре относительно разнообразных предметов. Одно из возможных решений - создать обобщенный обработчик "ask" ("спросить"), которому будут передаваться в качестве аргументов персонаж и предмет. Для каждого объекта определим свойство, которое будет выводить информацию, соответствующую знаниям того или иного персонажа о данном объекте. Далее, для персонажей определим при помощи указателя, к какому свойству предмета необходимо будет обратиться, чтобы получить ответ персонажа про этот предмет. Если для объекта такое свойство не определено, это будет означать, что персонаж ничего про этот предмет не знает.

Наш обобщенный обработчик получится довольно простым (точнее, весьма компактным):

  ask: function(actor, obj)
  {
    local propPtr;

    /* определить, к какому свойству предмета надо будет обращаться */
    propPtr := actor.askPropPtr;

    /* проверить, определено ли это свойство для предмета */
    if (defined(obj, propPtr))
    {
      /* оно определено - осуществляем его непрямой вызов */
      obj.(propPtr);
    }
    else
    {
      /* оно не определено - используем ответ по умолчанию */
      actor.dontKnow;
    }
  }

Таким образом, для каждого объекта-персонажа необходимо просто определить свойство, "отвечающее" за объем знаний персонажа о том или ином предмете, и поместить ссылку на это свойство в свойство персонажа под названием askPropPtr. Кроме того, для персонажа должен быть определен также ответ по умолчанию в свойстве dontKnow. Вот пример:

  joe: Actor
    sdesc = "Джо"
    noun = 'джо'
    dontKnow = "Джо лишь пожимает в ответ плечами, скребя в затылке. "
    askJoe = "Пожалуй, выслушивать историю жизни Джо - это чересчур. "
    askPropPtr = askJoe
  ;

Наконец, для каждого предмета необходимо определить соответствующее свойство askJoe, если Джо должен что-либо знать об этом предмете. Аналогичным образом можно определить необходимый набор свойств и для других персонажей. Достоинство такого подхода в том, что при этом удается локализовать всю информацию, относящуюся к объекту (и в том числе знания о нем персонажей), внутри этого объекта. Кроме того, обобщенный обработчик ask имеет на редкость несложную структуру. Безусловно, сам описанный здесь механизм может с первого взгляда показаться запутанным, однако конечное решение получается простым, удобным в работе и легко масштабируется.


return

Функция может вернуть значение в ту часть кода, откуда осуществлялся ее вызов, при помощи следующей конструкции:

  return [ выражение ];

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

Ниже приведен пример функции, которая вычисляет сумму элементов списка и возвращает эту сумму в качестве своего значения.

  listsum: function(lst)
  {
    local i, sum, len := length(lst);
    for (i := 1, sum := 0 ; i <= len ; i++)
      sum += lst[i];
    return(sum);
  }

Обратите внимание, что скобки указывать необязательно. Последняя строка функции могла бы выглядеть и так:

  return sum;

В действительности последние версии стандартного файла-библиотеки advr.t в основном используют форму записи без скобок.


if

Обобщенная форма записи условного оператора в (R)TADS выглядит так:

  if ( выражение ) инструкция
  [ else инструкция ]

Здесь и далее инструкция может толковаться и как одиночная инструкция, и как набор (последовательность) инструкций, заключенных в фигурные скобки. Значение выражения должно быть либо числовым (в этом случае нулевое значение считается эквивалентом "нет", а любое другое - эквивалентом "да"), либо логическим - true или nil.

Обратите внимание, что опциональный подоператор else считается относящимся к последнему предшествующему ему оператору if в том случае, если используются вложенные условные операторы. Рассмотрим следующий пример:

  if (self.islit)	        // В комнате горит свет
    if (film.location = Me)	// Игрок несет пленку
      "Опа! Вы засветили пленку! ";
    else
      "В этой комнате темно. ";

Очевидно, что в этом примере автор хотел, чтобы подоператор else относился к первому оператору if, однако на самом деле компилятор отнесет его ко второму (как к последнему предшествующему else). Чтобы избежать таких ситуаций, следует применять фигурные скобки, явно задавая с их помощью группировку операторов:

  if (self.islit)	          // В комнате горит свет
   {
     if (film.location = Me)
      {
       "Опа! Вы засветили пленку! ";
      }
   }
  else
   {
     "В этой комнате темно. ";   // Игрок несет пленку
   }


switch

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

Форма записи инструкции switch имеет следующий вид:

  switch ( выражение )
  {
    [ список-подоператоров-case ]
    [ подоператор-default ]
  }

Форма записи списка-подоператоров-case:

  case выражение-константа :
    [ инструкции ]
    [ список-подоператоров-case ]

Форма записи подоператора-default is:

  default:
    [ инструкции ]

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

Выражение может быть числового, строкового, объектного, списочного типа либо true/nil. Значение выражения сравнивается с со значениями, следующими за каждым подоператором case. Если одно из сравнений даст положительный результат, будут выполнены все инструкции, следующие за соответствующим подоператором case. Если ни одно из case-сравнений не даст положительного результата и при этом определен подоператор default, будут выполнены инструкции после default. Если же нет ни "удачных" сравнений, ни default, то вся инструкция switch будет пропущена и выполнение программы возобновится после закрывающей фигурной скобки switch.

Обратите внимание, что выполнение инструкции switch не прерывается, когда выполнение доходит до следующего подоператора case. Вместо этого продолжается выполнение инструкций, следующих за этим case. Если вы хотите прервать выполнение инструкций внутри конструкции switch после выполнения группы инструкций, относящихся к одному подоператору case, вам необходимо использовать инструкцию break.

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

Ниже приведен пример конструкции switch.

  f: function(x)
  {
    switch(x)
    {
      case 1:
        "x равен одному";
        break;
      case 2:
      case 3:
        "x равен 2 или 3";
        break;
      case 4:
        "x равен 4";
      case 5:
        "x равен 4 или 5";
      case 6:
        "x равен 4, 5, или 6";
        break;
      case 7:
        "x равен 7";
        break;
      default:
        "x находится вне интервала от 1 до 7";
      }
    }


while

Инструкция while определяет цикл, т. е. набор инструкций, которые выполняются снова и снова до тех пор, пока выполняется некое условие.

  while ( выражение ) инструкция

Как и для инструкции if, инструкция может быть как отдельной инструкцией, так и набором инструкций, объединенным при помощи фигурных скобок. Выражение должно быть либо числовым (в этом случае нулевое значение рассматривается как "ложь", а любое другое - как "истина"), либо логическим (true/nil).

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


do-while

Инструкция do-while определяет цикл несколько иной конструкции, чем while. Этот тип цикла также выполняется до тех пор, пока некое контрольное выражение не примет значение "ложь" (точнее, 0 или nil), однако вычисление значения выражения здесь производится в конце цикла. За счет этого цикл в любом случае будет выполнен хотя бы один раз, поскольку первая проверка значения контрольного выражения произойдет после первого выполнения цикла.

Обобщенная форма записи этой инструкции:

  do инструкция while ( выражение );

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


for

Инструкция for определяет наиболее общий и наиболее мощный тип цикла. Любой цикл for всегда можно заменить циклом на основе инструкции while, однако цикл for при аналогичной работе зачастую позволяет получить намного более компактный и читабельный код.

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

  for ( нач-выраж ; усл-выраж ; повт-выраж ) инструкция

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

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

Второе выражение, усл-выраж, носит название условия цикла. Оно имеет точно такое же назначение, что и выражение условия цикла while. Это выражение вычисляется перед каждым проходом цикла. Если его значение соответствует логическому "да" (ненулевое числовое или true), то выполнение цикла продолжается; в противном случае цикл прерывается, и выполнение программы продолжается инструкцией, следующей сразу за циклом. Обратите внимание, что, как и условие цикла while, усл-выраж вычисляется в том числе и перед первым проходом цикла (однако после вычисления выражения нач-выраж); следовательно, цикл for не будет выполнен ни разу, если усл-выраж при начале цикла будет иметь значение, соответствующее логическому "нет".

Третье выражение, повт-выраж - это "выражение повторной инициализации". Это выражение вычисляется после каждого прохода цикла. Если оно возвращает некое значение, это значение отбрасывается; единственное назначение повторной инициализации - изменить необходимым образом переменные, задействованные в цикле. Как правило, выражение повторной инициализации увеличивает переменную-счетчик цикла или выполняет какое-либо аналогичное действие.

Любые из вышеназванных выражений (и даже все они вместе) могут отсутствовать. Отсутствие усл-выраж эквивалентно использованию в качестве условия цикла константы true; таким образом, цикл с заголовком for ( ;; ) будет выполняться вечно либо до тех пор, пока в теле цикла не встретиться инструкция break. Цикл типа for, не определяющий выражения начальной и повторной инициализации, полностью идентичен циклу while.

Ниже приведен пример использования инструкции for. Эта функция реализует простой цикл, выполняющий суммирование элементов списка.

  sumlist: function(lst)
  {
    local len := length(lst), sum, i;
    for (sum := 0, i := 1 ; i <= len ; i++)
      sum += lst[i];
  }

Обратите внимание, что цикл, выполняющий абсолютно те же действия, можно записать с пустым телом, возложив суммирование на выражение повторной инициализации; присваивание начального значения переменной len также может осуществляться в заголовке цикла (выражением начальной инициализации).

  sumlist: function(lst)
  {
    local len, sum, i;
    for (len := length(lst), sum := 0, i := 1 ; i <= len ;
      sum += lst[i], i++);
  }


break

Используя инструкцию break, можно добиться, чтобы программа завершила выполнение цикла досрочно:

  break;

Это бывает полезно, когда требуется выйти из цикла до его "нормального" завершения. Выполнение программы возобновляется с инструкции, следующей непосредственно за тем циклом, в котором определена инструкция break.

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


continue

Инструкция continue передает управление к началу (заголовку) того цикла, в котором определена. Ее можно использовать в цикловых конструкциях for, while и do-while.

В цикле for инструкция continue передает управление выражению повторной инициализации. Иначе говоря, вначале вычисляется значение третьего выражения в заголовке цикла (если оно присутствует), а затем - второго (условного) выражения (опять-таки, если оно имеется в наличии). Если значение второго выражения соответствует логическому "да" либо это выражение отсутствует, выполнение программы продолжается первой инструкцией тела цикла; в противном случае начинают выполняться инструкции, следующие непосредственно за цикловой конструкцией.


goto

Инструкция goto используется для безусловного перехода внутри функции/метода, где она определена. Для того, чтобы переход осуществился, необходимо задать для "целевой" инструкции метку. Метку определяют, указывая перед инструкцией имя метки с двоеточием на конце.

Обратите внимание, что действие метки всегда распространяется на всю функцию/метод, где она определена. В этом состоит отличие от локальных переменных, которые действуют в пределах блока (группы инструкций, ограниченных фигурными скобками), в котором они определены. Обратиться к метке из-за пределов определяющей ее функции/метода нельзя.

Пример использования инструкции goto:

  f: function
  {
    while (true)
    {
      for (x := 1 ; x < 5 ; x++)
      {
        /* выполнение неких действий */
        if (myfunc(3) < 0)              /* возникла ошибка? */
          goto exitfunc;                /* да, ошибка - покидаем функцию */
        /* выполнение еще каких-то действий */
      }
    }
    /* сюда мы попадаем, если что-то пошло не так */
    exitfunc: ;
  }

Подобное использование goto избавляет от необходимости вводить дополнительную проверку условия во внешнем цикле while, что позволяет несколько упростить код и сделать его более наглядным.

Среди цивилизованных специалистов по вычислительной технике инструкция goto считается вредным и порочным пережитком древних, вышедших из употребления языков, и репутации TADS в кругах серьезных разработчиков языков программирования, несомненно, был нанесен непоправимый ущерб тем, что эта инструкция все же была в него включена. Таким образом, вам следует использовать ее как можно реже, а еще лучше вообще не использовать, в особенности если вы хотите произвести впечатление своими программистскими опытами на какого-нибудь цивилизованного специалиста по вычислительной технике. Однако многие программисты-практики рассматривают goto как очень полезный инструмент в руках опытного профессионала, и посмеиваются над категоричностью обвинительного суждения всезнающих академиков, большинство из которых, скорее всего, не написало ни строчки кода с тех пор, как их собственные преподаватели пинали их за использование goto в программах на Паскале, за исключением, может быть, отдельных алгоритмов на псевдо-коде, которые, впрочем, все равно обычно заканчиваются словами "написание оставшейся части предоставляется читателю в качестве упражнения". Автор TADS не собирается принимать ничью сторону в этом ожесточенном противостоянии (да, это чувствуется - примечание переводчика), однако надеется, что сможет удовлетворить оба воинствующих лагеря, дав одним возможность использовать goto где можно и где нельзя, а другим - насладиться собственной добродетельностью оттого, что они могли бы применить goto, но перебороли этот грязный соблазн. При работе в (R)TADS выбор остается за вами.


pass

Метод может с какого-то момента унаследовать поведение одноименного метода родительского класса, для этого используется инструкция pass:

  pass имя-метода;

Имя-метода должно совпадать с именем того метода, где определена инструкция pass. При выполнении инструкции pass вызывается тот метод родительского класса, который бы вызывался в том случае, если бы текущий объект его не переопределил. При вызове используются те же аргументы, что и при вызове метода, в котором определена инструкция pass. При этом объект self не изменяется (т. е. ссылается на тот объект, метод которого вызывался изначально, до обращения к pass).


abort, exit and exitobj

В ходе обработки команды игрока игра может прервать нормальный ход событий, вернувшись к запросу/получению следующей команды у игрока, тремя разными способами. Инструкция abort прерывает обработку полностью и запрашивает следующую команду; обычно она используется в системных командах (например, сохранение/восстановление игры), которые не должны считаться за ход, в связи с чем они не вызывают демоны/запалы. С другой стороны, инструкция exit пропускает всю дальнейшую обработку команды, сразу переходя к выполнению демонов и запалов; обычно она используется, когда автору требуется прервать обработку (как вариант - команда обработана "досрочно"). Наконец, exitobj по своему действию полностью аналогична exit, за исключением того, что пропускает обработку только для одного объекта в команде, переходя к следующему.

Обратите внимание, что все сказанное про демоны/запалы относится как к демонам, запускаемым при помощи функции notify, так и к демонам и запалам, запускаемым при помощи setdaemon и setfuse.

Вы можете использовать инструкцию abort внутри демона/запала, при этом она будет работать "как задумано" (прервет текущую обработку и пропустит выполнение всех оставшихся демонов/запалов после данного хода). В прежних версиях TADS этого делать было нельзя.

Примечание переводчика: честно говоря, не рекомендовал бы размещать abort в демонах/запалах, и вот почему. Порядок инициации и выполнения демонов и запалов в TADS в общем случае непредсказуем (т. е. никто, в том числе и сам автор TADS, не может дать твердых гарантий, что (а) порядок выполнения демонов всегда соответствует порядку их инициации и (б) остается неизменным при каждом прохождении игры). Поскольку за увеличение счетчика ходов в игре обычно также отвечает демон, то, возможно, демон с abort будет иногда выполняться раньше него, а иногда - позже. "Нерегулярное", а то и непредсказуемое изменение числа ходов в игре вполне может вызвать у игрока недоумение. Кроме того, выполнение запалов также может меняться от одной игровой сессии к другой непредсказуемым образом.


askdo и askio

Эти инструкции прерывают текущую обработку наподобие abort, пропуская также выполнение демонов и запалов, однако позволяют игроку вводить дополнительную информацию. Инструкция askdo просит у игрока ввести "прямой" объект; система при этом выводит соответствующий запрос. Игрок при этом может ввести либо запрашиваемый объект, либо новую команду. Например, для глагола "взять" при выполнении askdo будет выведен запрос "Что вы хотите взять?", после чего система будет ожидать ответа игрока. Инструкция askio действует аналогично, но ей в качестве аргумента передается предлог; этим предлогом дополняется запрос "косвенного" объекта. Например, если используется глагол "отпереть" и выполняется следующая команда:

  askio(withPrep);

, то система выведет запрос "При помощи чего вы хотите отпереть это?", при этом игрок может ввести "косвенный" объект или новую команду. Инструкция askio может использоваться только в том случае, если "прямой" объект уже определен.

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

О принципах формирования запросов на ввод объектов и функциях-"точках входа", которые за это отвечают, можно почитать здесь.

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


self

При обращении к свойствам объектов система определяет специальный объект под названием self. Этот специальный объект ссылается на тот объект, которому принадлежит данное свойство. С первого взгляда это кажется довольно бессмысленным, однако представьте себе ситуацию, когда родительский класс объекта определяет свойство, обращающееся к другим свойствам объекта:

  class book: object
    description =
    {
      "Эта книга << self.color >>.";
    }
  ;

  redbook: book
    color = "красная"
  ;

  bluebook: book
    color = "синяя"
  ;

В этом примере обобщенный объект, book, "знает", каким образом описать книгу в зависимости от ее цвета. Определяемые объекты-наследники, красная книга (redbook) и синяя книга (bluebook) просто определяют свой цвет в предназначенном для этого свойстве вместо того, чтобы заново переопределять свойство-описание. Таким образом, при обращении к свойству-описанию красной книги (redbook.description) мы получим следующее сообщение:

  Эта книга красная.


inherited

Специальный псевдо-объект под названием inherited позволяет обращаться к свойствам родительского класса текущего объекта self. Этот объект по своему действию несколько напоминает инструкцию pass, но в ряде случаев предоставляет гораздо больше возможностей. Прежде всего, inherited позволяет просто вызвать метод родительского класса и вернуть управление текущему свойству после того, как выполнение вызываемого метода закончилось; инструкция pass же не позволяет вернуться обратно в вызывающий метод. Во-вторых, вы можете обращаться к inherited из выражения благодаря тому, что возвращаемое вызываемым методом значение не теряется и не отбрасывается, а может быть использовано при дальнейших вычислениях. В-третьих, вызываемому при помощи inherited методу можно передавать аргументы, отличные от таковых текущего метода.

Вы можете использовать inherited во всех тех же случаях, что и self.

Ниже приведен пример использования псевдо-объекта inherited.

  myclass: object
    sdesc = "myclass"
    prop1(a, b) =
    {
       "Это свойство prop1 класса myclass. self = << self.sdesc >>,
        a = << a >>, b = << b >>.\n";
        return(123);
    }
  ;

  myobj: myclass
    sdesc = "myobj"
    prop1(d, e, f) =
    {
        local x;
        "Это свойство prop1 объекта myobj.  self = << self.sdesc >>,
        d = << d >>, e = << e >>, f = << f >>.\n";
        x := inherited.prop1(d, f) * 2;
        "Возвращаемся в свойство prop1 объекта myobj.  x = << x >>\n";
    }
  ;

При вызове myobj.prop1(1, 2, 3) будет выведен следующий текст:

  Это свойство prop1 объекта myobj. self = myobj, d = 1, e = 2, f = 3.
  Это свойство prop1 класса myclass. self = myobj, a = 1, b = 3.
  Возвращаемся в свойство prop1 объекта myobj. x = 246.

Обратите внимание на одну особенность inherited, объединяющую этот псевдообъект с инструкцией pass: в обоих случаях объект self остается тем же и в вызывающем (методе-наследнике), и в вызываемом методе. В этом заключается принципиальная разница между использованием inherited и прямым вызовом родительского метода (при котором в качестве имени объекта вместо inherited используется имя родительского объекта).

Начиная с версии TADS 2.2.4 синтаксис обращения к inherited был расширен - теперь можно указывать также родительский класс, чей метод надо наследовать, однако в остальном правила использования данного псевдо-объекта не изменились:

  inherited fixeditem.doTake(actor);

В приведенном выше примере мы указываем, что хотим унаследовать поведение метода doTake родительского класса fixeditem, хотя бы даже стандартные правила наследования определяли бы в качестве родительского другой класс. Это бывает полезным в случаях множественного наследования, когда может понадобиться более полный контроль над тем, от каких именно родителей должен наследовать свои реакции объект-потомок.


argcount

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


replace и modify

Большинство авторов игр сталкиваются с тем, что при написании игры более-менее приличного размера им так или иначе не избежать редактирования стандартной библиотеки advr.t. Хотя ничего принципиально порочного в такой практике нет, при выходе новой версии TADS могут возникать проблемы: в таком случае вам придется либо по-прежнему пользоваться вашей старой, но измененной вами копией advr.t, что автоматически означает добровольный отказ от всех улучшений и исправлений ошибок, выполненных в новой версии TADS; либо провести анализ и синхронизировать изменения обеих версий библиотеки, что потребует значительных затрат труда и времени. Использование инструкций replace и modify может помочь вам в решении этой проблемы.

Эти инструкции позволяют вносить изменения в ранее определенные объекты. Другими словами, вы можете использовать стандартную библиотеку advr.t при помощи инструкции #include, а затем указать изменения для объектов, которые компилятор уже обработал. При помощи этих инструкций можно задавать изменения трех типов: полную замену функции, полную замену объекта, а также добавление новых или замену существующих свойств объекта.

Для замены ранее определенной функции вам достаточно поставить перед новым определением инструкцию replace. Само определение ничем не отличается от обычного. В следующем примере показано альтернативное определение функции scoreStatus из библиотеки advr.t, выполняющей нестандартное форматирование строки статуса:

       #include <adv.t>

       replace scoreStatus: function( points, turns )
       {
          setscore( cvtstr( pts ) + ' очков/' + cvtstr( turns ) + ' ходов' );
       }

Вы можете выполнить точно такие же действия и для объектов. Например, вы можете полностью заменить глагол пристегнуть ("пристегнуть"), определенный в advr.t:

       #include <adv.t>

       /* Отменяем глагол "пристегнуться" */
       replace fastenVerb: deepverb
          verb = 'пристегнуть'
	  sdesc = "пристегнуть"
	  prepDefault = toPrep
	  ioAction( toPrep ) = 'FastenTo'
       ;

При полной замене объекта предыдущее определение удаляется, включая всю информацию о наследовании свойств и записи в словаре. Остаются только те свойства объекта, которые указаны в его новом определении; первоначальное определение отбрасывается целиком.

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

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

       modify pushVerb
          verb = 'нажимай'
          ioAction( withPrep ) = 'PushWith'
       ;

Обратите внимание на ряд особенностей, которые иллюстрирует данный пример. Во-первых, в инструкции modify вы не можете указывать родительские классы - они сохраняются теми же, что и для исходного определения объекта. Далее, обратите внимание на указанное дополнительно лексическое свойство. Оно не заменяет исходные лексические определения, а добавляется к ним. Наконец, обратите внимание, что при модификации определения объекта допустимо указывать новые псевдосвойства-обработчики глаголов (таких как doДействие и ioДействие). Все вновь определяемые обработчики трактуются так, как если бы они были сразу указаны в исходном определении объекта.

В методе, который вы переопределяете с помощью инструкции modify, вы можете использовать инструкцию pass для обращения к исходной версии метода в оригинальном определении объекта. Резюмируя, можно сказать, что механизм действия modify следующий: оригинальный объект переименовывается, а вместо него создается новый объект под тем же именем, который является потомком исходного (который становится безымянным). После применения modify вы уже не сможете напрямую обратиться к исходному объекту; единственный способ задействовать его - косвенный, через заменивший его объект. Ниже приведен пример использования pass совместно с modify.

     class testClass: object
	    sdesc = "testClass"
	    ;

     testObj: testClass
        sdesc =
	    {
	        "testObj...";
		pass sdesc;
	    }
        ;

     modify testObj
        sdesc =
	    {
	        "модифицированный testObj...";
		pass sdesc;
	    }
	    ;

При обращении к testObj.sdesc на экран будет выведено:

  модифицированный testObj...testObj...testClass

Вы также можете заменить какое-либо свойство целиком, удалив все следы его первоначального определения. При этом при использовании pass или inherited произойдет обращение к методу, унаследованному объектом от родительского класса. Для такой операции необходимо разместить инструкцию replace непосредственно перед названием заменяемого свойства. Рассмотрим вариацию на тему предыдущего примера:

      modify testObj
        replace sdesc =
	    {
	        "модифицированный testObj...";
		pass sdesc;
	    }
	    ;

При этом выводимый при обращении к testObj.sdesc текст изменится следующим образом:

  модифицированный testObj...testClass

Итак, повторим еще раз - инструкция replace, размещенная перед определением свойства, сообщает компилятору о том, что предыдущее определение этого свойства должно быть удалено полностью (вместо "скрытого наследования", которое произойдет, если не указать replace). Разница проявится в том, что pass и inherited будут обращаться не к исходному определению свойства, а к соответствующему свойству родительского класса.


Встроенные функции

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


addword

Вызов: addword(объект, &часть_речи, слово)

Ставит в соответствие объекту слово (которое должно быть строковым значением в одинарных кавычках) в качестве указанной части_речи. Часть_речи может принимать значение noun (существительное), adjective (прилагательное), plural (множественное число), verb (глагол), article (артикль - в русском языке, по понятным причинам, вряд ли будет использоваться), либо preposition (предлог). Вы можете использовать эту функцию как применительно к объектам, определенным статически в коде игры, так и к объектам, динамически созданным в ходе ее выполнения.

Примеры использования данной функции можно найти в разделе "Динамический словарный запас" главы, посвященной синтаксическому анализатору.


askfile

Вызов: askfile(текст_запроса, код_типа_запроса, код_типа_файла, флаг)

История: аргументы с кодами типов появились, начиная с TADS 2.3.0. Аргумент флаг появился в TADS 2.5.0.

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

Эта функция наиболее полезна для таких операций, как сохранение/восстановление игры, где требуется, чтобы игрок ввел имя файла. Строковые выражения, передаваемые через этот аргумент, могут содержать последовательности \n и \t, которые будут правильно преобразованы при отображении текста запроса.

В TADS 2.3 появились два дополнительных, опциональных аргумента, которые позволяют автору игры точнее определить, какой именно тип запроса следует отображать и какой тип файла запрашивать. Эти аргументы являются своего рода "подсказками" для кода операционной системы, отвечающего за вывод диалогового окна "Открыть"; указывая эти аргументы, вы позволяете системе более правильно настроить вид диалога.

Аргумент код_типа_запроса указывает, происходит ли открывание существующего файла или сохранение файла. В некоторых операционных системах (включая Windows и MacOS) для открытия существующего файла используется один тип диалога, а для сохранения - другой; вы можете использовать данный аргумент в качестве "селектора" для выбора нужного типа диалога. Этот аргумент может иметь одно из следующих значений (определены в advr.t):

ASKFILE_PROMPT_OPEN - открыть существующий файл для чтения;
ASKFILE_PROMPT_SAVE - открыть файл для записи.

Для некоторых систем диалог открытия файла автоматически будет фильтровать список файлов таким образом, чтобы игрок мог видеть файлы только того типа, который ему нужен (например, только файлы сохраненных игр). Аргумент код_типа_файла позволяет указать, по какому именно типу файлов требуется фильтровать список. Этот аргумент может принимать одно из следующих значений (определенных в advr.t):

FILE_TYPE_GAME - собственно файл игры (.gam)
FILE_TYPE_SAVE - сохраненная игра (.sav)
FILE_TYPE_LOG - транскрипт игры (log) file
FILE_TYPE_DATA - обобщенный файл с данными (используется для функции fopen())
FILE_TYPE_CMD - файл ввода команд
FILE_TYPE_TEXT - текстовый файл
FILE_TYPE_BIN - бинарный файл с данными
FILE_TYPE_UNKNOWN - неизвестный тип файла

Если вы опустите аргументы с кодами типов при обращении к askfile, функция поступит так, как она вела себя в более старых версиях TADS: аргумент с текстом запроса должен содержать слово "save" или "write" для того, чтобы было отображено именно диалоговое окно "Сохранить файл", а не "Открыть файл" (разумеется, для операционных систем, которые определяют оба типа этих диалоговых окон).

Примечание переводчика: экспериментально проверено, что система реагирует именно на английские слова "save" и "write", их русские эквиваленты "сохранить"/"записать" не воспринимаются. В системе Windows, к примеру, это приводит к тому, что открывающееся окно содержит в заголовке тот текст, который был передан функции askfile, однако соответствующая реинкарнация подтверждающей кнопки формы подписана "Открыть", а не "Сохранить". Таким образом, для русскоязычной игры/локализованной ОС этот прием не срабатывает, и предпочтительнее пользоваться более новым форматом вызова askfile.

Опциональный четвертый аргумент появился в версии TADS 2.5.0 и позволяет вам задавать дополнительные флаги для функции askfile. Возможна передача следующих значений флагов (определены в advr.t):

ASKFILE_EXT_RESULT   Использование расширенных кодов возврата (описаны далее). Если указан данный флаг, функция будет использовать расширенные коды возврата; без указания этого флага будут использоваться обычные коды возврата.

Если вы хотите работать с новым аргументом-флагом, вам необходимо будет также указать и аргументы код_типа_запроса и код_типа_файла. Если будет пропущен хотя бы один из них, функция askfile не сможет распознать последний аргумент в качестве аргумента-флага.

Если пропустить аргумент-флаг, askfile по умолчанию будет считать его равным нулю, что, в свою очередь, означает, что она будет работать точно так же, как и в более версиях TADS (не поддерживавших этот флаг). Поскольку в старых играх этот аргумент никогда не определялся, то таким образом будет обеспечена стопроцентная совместимость askfile при компиляции/проигрывании старых игр в новой версии TADS.

До выхода версии 2.5.0 askfile возвращала строковое значение при удачном завершении либо nil при неудаче. Однако при этом вызывающей функции/методу невозможно было определить, по какой именно причине произошла неудача (и, в частности, не делалось различия между случаями, когда файл действительно не открывался/не сохранялся из-за сбоя и когда пользователь просто закрывал соответствующее диалоговое окно, отказываясь от выбора файла). Когда функции передается в качестве флага значение ASKFILE_EXT_RESULT, функция возвращает дополнительную информацию, позволяющую распознавать эти случаи.

При указании флага ASKFILE_EXT_RESULT функция askfile возвращает список, состоящий из двух элементов. Первый элемент представляет собой число, отражающее результат операции выбора файла; второй элемент является строковым значением, если операция завершилась успешно, либо nil, если нет. Возможными значениями первого элемента возвращаемого списка являются (все значения определены в advr.t):

ASKFILE_SUCCESS   Файл успешно выбран. Вторым элементом списка будет строковое значение, содержащее имя выбранного файла.
ASKFILE_FAILURE   Возникла ошибка запроса имени файла. Такая ошибка обычно означает, что системе не удалось отобразить диалоговое окно выбора файла (например, вследствие нехватки памяти).
ASKFILE_CANCEL   Пользователь отказался от выбора файла (например, нажатием кнопки "Отмена" в соответствующем диалоговом окне). Поскольку пользователь не желает продолжать операцию, она должна быть прервана, однако сообщение об ошибке в этом случае отображать не требуется.

Ниже приведен пример (позаимствованный из advr.t - реализация команды "восстановить") использования расширенной информации о результатах операции выбора файла.

 {
        local savefile;
        
        savefile := askfile('Файл сохраненной игры',
                            ASKFILE_PROMPT_OPEN, FILE_TYPE_SAVE,
                            ASKFILE_EXT_RESULT);
        switch(savefile[1])
        {
        case ASKFILE_SUCCESS:
            return mainRestore(savefile[2]);

        case ASKFILE_CANCEL:
            "Отменено. ";
            return nil;

        case ASKFILE_FAILURE:
        default:
            "Неудача. ";
            return nil;
        }


caps

Вызов: caps()

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

Вызов caps() абсолютно аналогичен действию последовательности "\^".

Не путайте данную функцию со встроенной функцией upper(строка), которая преобразует в верхний регистр весь аргумент целиком. В отличие от нее, caps() не требует аргументов и воздействует только на первый символ выводимого вслед за ней текста.

Примечание переводчика: данная функция преобразует регистр только латинских букв, в связи с чем ее применение в русскоязычной игре будет весьма ограниченным. Для символов кириллицы рекомендуется использовать определенные в файле advr.t функции ZA и ZAG.


car

Вызов: car(список)

Возвращает первый элемент списка либо nil, если список пустой.

Обратите внимание, что то же значение можно получить при помощи выражения список[1], использующего оператор индексации списка. Основное различие между использованием функций car и cdr и индексацией списка - в общем-то чисто стилистическое; используя car и cdr для "прочесывания" списка, вы можете использовать рекурсию до тех пор, пока список не кончится (при этом эти функции вернут nil). При использовании же индекса списка вам потребуется заранее определить количество элементов в списке; эту информацию можно получить, используя встроенную функцию length. Выбор того или иного способа - исключительно вопрос личных предпочтений.


cdr

Вызов: cdr(список)

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


clearscreen

Вызов: clearscreen()

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

В версиях интерпретатора TADS с мультимедийными возможностями данная функция имеет свои особенности: она "честно" очищает экран, однако сохраняет его содержимое в качестве некоего блока или страницы, которые игрок затем может пролистывать, пользуясь меню интерпретатора. (В частности, для HTML-TADS такие переходы осуществляются с помощью команд "Go/Previous Page" и "Go/Next Page" - примечание переводчика).


cvtnum

Вызов: cvtnum(строка)

Преобразует строку, содержащую символьное представление числа, в число. Например, cvtnum('1234') вернет число 1234. Обратите внимание, что специальные строки (такие, как 'true' и 'nil') также воспринимаются функцией и преобразуются, соответственно, в булевские значения true и nil.

Примечание переводчика: можно добавить к данному описанию следующее. Принцип работы функции таков: она просматривает строковый аргумент до тех пор, пока не встретит символ, не являющийся цифрой, и преобразует в число именно "цифровую" часть строки. Если таковая отсутствует, возвращается значение 0. Таким образом, cvtnum('12AB') вернет число 12, а cvtnum('AB12') - ноль. Кроме того, для корректного преобразования булевских значений необходимо, чтобы их строковые представления были целиком в нижнем регистре. Все следующие вызовы - cvtnum('True'), cvtnum('TRUE') и cvtnum('TrUe') - вернут ноль.


cvtstr

Вызов: cvtstr(значение)

Данная функция преобразует числовое или булевское значение в соответствующее строковое представление. Например, cvtstr(1234) вернет строку '1234', cvtstr(true) - строку 'true', а cvtstr(nil) - 'nil'. При вызове cvtstr со строковым аргументом будет возвращен этот аргумент.


datatype

Вызов: datatype(значение)

Данная функция возвращает тип значения. Основная область применения - функции с переменным количеством аргументов, однако ее также можно использовать, например, для анализа списков с разнотипными элементами.

В зависимости от типа данных значения функция возвращает один из следующих кодов:

1 - число
2 - объект
3 - строка
5 - nil
7 - список
8 - true
10 - ссылка на функцию
13 - ссылка на свойство объекта


debugTrace

Вызов: debugTrace(1, флаг)

Данная форма функции debugTrace позволяет вам включать или отключать режим отладки/трассировки синтаксического анализатора. Если флаг равен true, режим отладки включается, а если nil, то отключается.

В режиме отладки синтаксический анализатор генерирует отладочные сообщения по мере процесса анализа команды игрока. Эти сообщения позволяют понять, каким образом СА воспринимает то или иное слово в команде. При первом "проходе" предложения СА выводит все возможные части речи для каждого из слов. В ходе дальнейшего анализа СА выводит информацию по каждому из словосочетаний: слова, из которого они состоят, части речи, которые СА использует для каждого из слов в словосочетании (если можно использовать несколько частей речи, СА все равно выбирает только одну), а также объекты, которым можно поставить в соответствие словосочетание.

Режим отладки СА может помочь вам найти корень проблемы в ситуации, когда парсер отказывается понимать отдельные словосочетания, которые, на ваш взгляд, вполне корректны. Поскольку СА выбирает из нескольких неоднозначных интерпретаций слов, то часто бывает полезным знать, каким именно образом СА воспринимает команды; новый режим отладки предназначен для того, чтобы облегчить это понимание.

Данный режим доступен как в стандартном интерпретаторе, так и в отладчике TADS, поэтому вызов debugTrace(1, flag) всегда будет успешным (правда, ничего не сказано об альтернативных интерпретаторах типа WinTADS - примечание переводчика). Функция не возвращает никакого значения.


defined

Вызов: defined(объект, ссылкаНаСвойство, флаг)

История: аргумент флаг добавлен в версии TADS 2.5.1.

Эта функция позволяет вам определить, определено/унаследовано ли для объекта то или иное свойство, или нет. Функция возвращает true, если оно определено для него (непосредственно в нем или в одном из его родительских классов), и nil в противном случае.

Обратите внимание, что аргумент ссылкаНаСвойство должен быть именно ссылкой. Получить ссылку можно, используя оператор & и имя нужного свойства. Например, чтобы проверить, определено ли для текущей локации главного персонажа свойство ldesc, можно использовать следующее выражение:

  x := defined(Me.location, &ldesc);

В этом примере переменная x примет значение true, если для локации текущего главного персонажа определено свойство ldesc, и nil в противном случае.

Третий аргумент является опциональным. При его указании функция вернет более детальную информацию относительно определения свойства. Этот аргумент может принимать одно из следующих значений (определенных в advr.t):

DEFINED_ANY   Это - значение по умолчанию; оно имеет тот же эффект, что и вызов функции без третьего аргумента-флага. Функция вернет true, если объект определяет или наследует свойство с указанным названием, и nil в противном случае.
DEFINED_DIRECTLY   Функция вернет true только в том случае, если свойство определено непосредственно объектом. Если для объекта данное свойство не определено вообще или просто унаследовано от родительского класса, функция вернет nil.
DEFINED_INHERITS   Функция вернет true только в том случае, если объект наследует свойство. Если свойство не определено для объекта вообще или определено непосредственно в объекте, а не унаследовано, функция вернет nil.
DEFINED_GET_CLASS   Функция возвращает класс, в котором определено данное свойство. Если объект непосредственно определяет свойство, функция вернет сам этот объект. Если объект наследует свойство от родительского класса, функция возвращает этот родительский класс. Если объект не определяет и не наследует свойство, функция вернет nil.

Например, чтобы проверить, определено ли свойство verDoTake непосредственно в объекте redBook, можно использовать следующее выражение:

   if (defined(redBook, &verDoTake, DEFINED_DIRECTLY))
      "Свойство verDoTake непосредственно определено в объекте redBook. ";


delword

Вызов: delword(объект , &часть_речи, слово)

Удаляет свойство (которое должно быть строковым значением в одинарных кавычках) из лексических свойств объекта. Аргумент часть_речи указывает, из какого именно списка лексических свойств следует удалить слово, и может принимать значения noun (для существительных), adjective (для прилагательных), plural (для существительных во множественном числе), verb (для глаголов), article (для артиклей) и preposition (для предлогов). Вы можете удалять слова для любого объекта - как определенного статически в коде игры, так и созданного динамически в ходе выполнения игровой программы. Кроме того, вы можете удалять как слова, определенные статически, так и добавленные в ходе самой игры (функцией addword).

Примеры использования данной функции можно найти в разделе "Динамический словарный запас" главы, посвященной синтаксическому анализатору.


endCommand

Вызов: endCommand(персонаж, глагол, список_ПО, предлог, КО, статус)

Появилась начиная с версии TADS 2.5.0

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

СА вызывает endCommand один раз в конце хода. Обратите внимание, что функция вызывается не для каждого "прямого" объекта, а для всей команды в целом; даже если список_ПО содержит более одного "прямого" объекта, функция будет вызываться только один раз (точно так же, как демоны и запалы вызываются для каждой команды только один раз).

Аргумент статус имеет тот же смысл, что аналогичный аргумент функции postAction. Остальные аргументы полностью аналогичны аргументам, используемым при вызове функции preCommand.

endCommand вызывается в конце хода в любом случае, даже если команда прервана по инструкции abort. При таком прерывании демоны/запалы будут пропускаться, и сразу будет выполняться endCommand.

Данная функция не возвращает никакого значения.

Подробнее о данной функции можно почитать здесь.


execCommand

Вызов: execCommand(персонаж, глагол, ПО, предлог, ИО, флаги)

Появилась начиная с версии TADS 2.4.0

Данная функция предоставляет игровой программе прямой доступ к подсистеме выполнения команд синтаксического анализатора. Подчеркнем еше раз - доступ предоставляется не к подсистеме разбора введенной команды, а к подсистеме выполнения, которая, используя объекты, задействованные в команде, исполняет ее, осуществляя при этом валидацию объектов (validDo, validIo), анализ реакции комнаты (roomAction), анализ реакции актера (actorAction), вызов обобщенных методов объекта (dobjCheck, iobjCheck, dobjGen, iobjGen), верификацию действия и собственно действие (verIoVerb, verDoVerb, ioVerb, doVerb либо глагол.action).

Более подробное описание данной функции можно посмотреть здесь.


exitobj

Вызов: exitobj()

Появилась начиная с версии TADS 2.4.0

Данная функция предоставляет дополнительный способ завершения обработки текущей команды в произвольный момент времени. Она во многом напоминает exit, но отличается от нее в одном-единственном отношении: в то время как exit прерывает обработку для всей текущей команды целиком, сразу переходя к выполнению демонов и запалов, exitobj прерывает обработку только для текущего "прямого" объекта, переходя к обработке следующего.

Это различие проявляется в том случае, если игрок вводит команду, указав в ней несколько "прямых" объектов. Предположим, вы определяете для какой-либо локации метод roomAction следующим образом:

      roomAction(actor, verb, dobj, prep, iobj) =
      {
        /*
         *  Когда игрок прикасается к чему-то, что еще не в его инвентаре, то объект, 
         *  к которому он прикоснулся, должен пропадать
         */
        if (dobj != nil && !dobj.isIn(actor))
        {
          "caps();<<ZAG(dobj, &sdesc)>> исчезает в яркой вспышке света!\n";
          dobj.moveInto(nil);
          exit;
        }
      }

Теперь предположим, что игрок вводит следующие команды:

>взять мяч
Мяч исчезает в яркой вспышке света!
>взять молоток и долото
Молоток исчезает в яркой вспышке света!

В первом случае реакция игры в принципе нормальная, а вот во втором - не очень. Причина - в том, что инструкция exit ведет к прерыванию команды и отбрасыванию всех оставшихся необработанными "прямых" объектов.

Чтобы справиться с этой проблемой, достаточно будет заменить в вышеописанном примере exit на exitobj. Результат будет более-менее удовлетворительным:

>взять молоток и долото
Молоток исчезает в яркой вспышке света!
Долото исчезает в яркой вспышке света!

Как вы могли убедиться, функция exitobj бывает полезной, если необходимо прервать команду только для текущего объекта, но при этом обработать все оставшиеся объекты списка. Функция же exit скорее предназначена для случаев, когда введенная игроком команда в принципе неприменима в текущей игровой ситуации (например, попытка взять что-либо, когда игрок лежит на полу связанным по рукам и ногам), поэтому обрабатывать оставшиеся объекты нет необходимости. Использование exitobj особенно эффективно в сочетании с функцией execCommand, поскольку позволяет универсальным образом "перенаправить" выполнение команды, пропустив (полностью или частично) стандартный алгоритм обработки, и при этом не сообщая синтаксическому анализатору, что выполнение команды окончилось неудачно.

Правила использования функции exitobj полностью совпадают с таковыми для инструкции exit.


fclose

Вызов: fclose(ссылка_на_файл)

Закрывает файл, к которому программа обращается, используя параметр ссылка_на_файл. После закрытия файла любые файловые операции с использованием этой ссылки будут недействительными (и вызовут ошибку). Более подробно см. в разделе, посвященном работе с файлами.


find

Вызов: find(где_ищем, что_ищем)

Если аргумент где_ищем - это список, функция вернет порядковый номер элемента со значением что_ищем в списке где_ищем (отсчет начинается с 1 для первого элемента списка). Если значение что_ищем в списке отсутствует, функция вернет nil. Например, find([4 5 6], 5) вернет 2.

Если аргумент где_ищем - это строка (в одинарных кавычках), то аргумент что_ищем тоже должен быть строкой; функция при этом вернет порядковый номер начального символа подстроки что_ищем в строке где_ищем, если найдет эту подстроку, и nil, если не найдет. Нумерация символов в строке начинается с 1. Например, find('abcdefghij', 'cde') вернет 3.


firstobj

Вызов: firstobj()

Альтернативный формат вызова: firstobj(класс)

Данная функция используется совместно с функцией nextobj(объект) для перебора всех объектов в игре, не являющихся классами. Функция firstobj возвращает первый объект-наследник указанного класса, который при этом сам классом не является, а последовательные вызовы nextobj(объект) используются, чтобы получить следующие аналогичные объекты. Порядок, в котором объекты возвращаются функциями, произволен и непредсказуем, но первоначальный однократный вызов firstobj, а затем последовательные вызовы nextobj до тех пор, пока она не вернет nil, гарантируют, каждый из имеющихся в игре объектов-наследников заданного класса будет возвращен ровно один раз.

Эти функции наиболее полезны при инициализации игры - для составления списков объектов, удовлетворяющих определенным критериям; впоследствии эти списки могут использоваться для быстрого доступа к этим объектам. Почти классический пример: если в игре имеются темные комнаты, то для них программе потребуется каждый раз проверять, имеется ли в этих комнатах источник света (свеча, лампа и т. д.). Для ускорения работы интерпретатора в процессе игры можно составить список всех источников света в игре заранее, а затем проверять объекты только из этого списка (а не все объекты в игре). Например, мы могли бы определить для объектов-источников света свойство islamp, равное true, а затем составить список таких объектов в начале игры, используя следующий код:

  getlamps: function
  {
    local obj, l;
    l := [];
    obj := firstobj();
    while(obj <> nil)
    {
      if (obj.islamp) l := l + obj;
      obj := nextobj(obj);
    }
    global.lamplist := l;
  }

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

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

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

  getlamps: function
  {
    local obj, l;
    l := [];
    obj := firstobj(lampitem);  // lampitem - класс объектов-источников света
    while(obj <> nil)
    {
      l := l + obj;
      obj := nextobj(obj, lampitem);
    }
    global.lamplist := l;
  }

Обратите внимание, что при этом дополнительно экономится время, которое затрачивалось на проверку свойства obj.islamp, поскольку это свойство становится ненужным (его роль принимает на себя принадлежность объекта соответствующему классу). За счет этого, а также благодаря тому, что набор объектов изначально ограничен, цикл будет выполняться значительно быстрее.


firstsc

Вызов: firstsc(объект)

Возвращает первый родительский класс объекта. Возвращает nil, если таковой отсутствует (это возможно только в том единственном случае, если объект был определен с типом object).

Данная функция применяется в основном для работы с эквивалентными (неразличимыми) объектами. В сочетании со свойством isEquivalent данная функция позволяет определить, являются ли объекты неразличимыми. Если у обоих объектов один и тот же родительский класс первого уровня и свойство isEquivalent имеет значение true, то эти объекты неразличимы.


fopen

Вызов: fopen(имя_файла, режим_доступа)

История: режим текстового доступа появился, начиная с версии TADS 2.2.4

Открывает файл с именем имя_файла как текстовый или бинарный (в зависимости от значения аргумента режим_доступа). Файлы, записанные в текстовом режиме, могут использоваться другими приложениями (текстовыми редакторами, браузерами и т. д.), а бинарные файлы будут в общем случае "понятны" только в (R)TADS. Аргумент режим_доступа (тип - строка в одинарных кавычках) может принимать следующие значения:

"b" - открыть файл как бинарный. Этот режим используется по умолчанию, если режим доступа не задан (для совместимости с предыдущими версиями).

"r" - открывает файл для чтения; на момент открывания файл должен существовать.

"r+" - открывает файл для чтения и записи; если файл не существует, он создается заново. В режиме текстового доступа этот режим запрещен.

"t" - открыть файл в качестве текстового. Вы можете использовать режим текстового доступа в сочетании с "r" (чтение) и "w" (запись); использование совместно с режимами "r+" и "w+" в настоящее время запрещено. Текстовый режим появился, начиная с версии TADS 2.2.4.

"w" - создает новый файл и открывает его для записи; если файл уже существует, он затирается.

"w+" - создает новый файл и открывает его для чтения и записи; если файл уже существует, он затирается. В режиме текстового доступа этот режим запрещен.

Функция возвращает ссылку на файл, которая используется в последующих файловых операциях (fwrite(), fread(), fclose() и т. д.) в качестве "идентификатора" открываемого файла.

При неудачном завершении операции функция возвращает nil. Открытие файла может завершиться неудачно по целому ряду причин: например, при попытке открытия несуществующего файла в режиме "r" (на чтение).

Более подробно см. в разделе, посвященном работе с файлами.


fread

Вызов: fread(ссылка_на_файл)

История: режим текстового доступа появился, начиная с версии TADS 2.2.4

Читает очередную запись из текстового или двоичного файла и возвращает считанное значение. Тип значения будет соответствовать тому типу, который изначально был записан в файл на данной позиции с использованием fwrite(). При возникновении ошибки функция возвращает nil; чаще всего это означает, что вы достигли конца файла.

При работе в текстовом режиме (см. fopen()) fread в любом случае считывает из файла строку. Если конец файла не достигнут, возвращенная строка заканчивается последовательностью "\n" (так же, как и функция fwrite(), fread всегда преобразует символ перевода строки в соответствии с соглашениями, принятыми для конкретного компьютера, и заменяет его на TADS-овскую последовательность "\n" в соответствующих местах). Если fread при считывании строки встретит конец файла, то вернет считанную строку до конца файла без перевода строки на конце. При последующих вызовах fread будет возвращать nil.


fseek

Вызов: fseek(ссылка_на_файл, позиция_байта)

Переводит маркер файла на позицию_байта (иначе говоря, смещение относительно начала файла). В качестве позиции_байта, как правило, должно использоваться значение, ранее возвращенное функцией ftell(), поскольку другие значения могут не соответствовать записи в файле.


fseekeof

Вызов: fseekeof(ссылка_на_файл)

Переводит маркер на конец файла.


ftell

Вызов: ftell(ссылка_на_файл)

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


fwrite

Вызов: fwrite(ссылка_на_файл, значение)

История: режим текстового доступа появился, начиная с версии TADS 2.2.4

Записывает значение в текстовый или двоичный файл. Значение должно быть цифрового, строкового (в одинарных кавычках) или булевского типа. Функция возвращает nil при успешном завершении операции, true - при неудачном; наиболее частая причина последнего - переполнение диска.

Когда файл открывается в режиме текстового доступа (см. fopen()), fwrite может записывать только строковые значения. Передаваемые для записи строки могут содержать специальные управляющие последовательности "\t", "\n" и "\\". Первая из них преобразуется в символ табуляции, вторая - в перевод строки (с учетом соглашений, принятых на данном конкретном компьютере), а третья - в простой обратный слэш. Использование других управляющих последовательностей не допускается. Функция fwrite не добавляет автоматически символ перевода строки к передаваемому ей значению, поэтому для включения переводов строк в файл вы должны указать соответствующую последовательность в передаваемой строке в явном виде.

Поскольку TADS всегда следует соглашениям о символе перевода строки, принятым в конкретной системе, fwrite() в любом случае корректным образом преобразует управляющую последовательность "\n", включенную в строку-аргумент, в символ перевода строки. Автору игры об этом задумываться не требуется.

Информацию о режимах безопасности при записи файлов в TADS см. в разделе, посвященном работе с файлами.


getarg

Вызов: getarg(номер_аргумента)

Функция возвращает аргумент, соответствующий номеру_аргумента, чье значение должно быть числом в интервале от единицы до количества переданных аргументов (последнее можно определить, используя псевдопеременную argcount). getarg(1) вернет первый аргумент, переданный текущей функции, getarg(2) - второй и т. д., вплоть до getarg(argcount). Функция может быть использована для получения аргументов функций, допускающих переменное их количество.


getwords

Вызов: getwords(объект, &свойство)

Функция возвращает список строковых значений, который определяет лексические свойства, определенные для объекта для данной части речи. Аргумент свойство может иметь значения noun для существительных, adjective для прилагательных, plural для множественного числа, verb для глаголов, article для артиклей или preposition для предлогов.

Примеры использования данной функции можно найти в разделе "Динамический словарный запас" главы, посвященной синтаксическому анализатору.


getfuse

Вызов: getfuse(ссылка_на_запал, параметр)

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

Альтернативный формат вызова: getfuse(объект, &запал)

Такой формат вызова getfuse позволяет получить информацию по запалу, активированному при помощи функции notify. Если запал уже сработал или был удален при помощи unnotify, функция возвращает nil; в противном случае она возвращает число ходов, оставшихся до срабатывания запала.


gettime

Вызов: gettime(опциональный_аргумент)

История: поддержка расширенной информации появилась, начиная с версии TADS 2.3.0

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

год календарный год (например, 1992).
месяц номер месяца (январь = 1, февраль = 2 и т. д.)
день число текущего месяца
день_недели день недели (1 = воскресенье, 2 = понедельник и т. д.)
день_года номер дня в году (1 = 1 января)
час час суток (по 24-часовой шкале) (полночь = 0, полдень = 12, 3 часа дня = 15 и т. д.)
минута минута часа (от 0 до 59)
секунда секунда текущей минуты (от 0 до 59)
прошло_секунд число секунд, начиная с 1 января 1970 г., 00:00:00 по Гринвичу. Последнее значение удобно для вычисления разницы между двумя моментами времени.

В дополнение к вышеописанному, начиная с TADS 2.3 введена поддержка получения расширенной информации от системных часов реального времени. Функции можно передать опциональный аргумент, определяющий, какую именно информацию она должна вернуть. Константы для значений аргумента определены в advr.t:

GETTIME_DATE_AND_TIME - при этом будет возвращена информация о дате и времени в обычном формате (как описано выше; точно та же информация будет возвращена и при вызове без аргументов). Таким образом достигается обратная совместимость со старыми версиями TADS.

GETTIME_TICKS - возвращает количество миллисекунд относительно некой произвольной точки отсчета. Этой точкой отсчета может быть какое-либо системное событие, например, запуск текущего сеанса интерпретатора TADS или включение компьютера. Точка отсчета берется произвольно, однако остается неизменной в течение всего текущего сеанса. За счет этого такую форму вызова gettime удобно использовать для вычисления временных периодов между двумя событиями в рамках небольшого промежутка времени. Например, при считывании событий с использованием функции inputevent вы можете использовать это временное значение в качестве предела ожидания события. Это может выглядеть так:

local max_time, cur_time, evt;

   /* На ожидание событий тратим не более 5 секунд (5000 миллисекунд) */
   max_time := gettime(GETTIME_TICKS) + 5000;
   for (;;)
   {
      /* Проверяем, достигли ли мы нашего временного предела */
      cur_time := gettime(GETTIME_TICKS);
      if (cur_time >= max_time)
          break;

      /* Считываем события, но прерываемся, если вышли за пределы наших временных ограничений */
      evt := inputevent(max_time - cur_time);

      /* обрабатываем событие */
      switch(evt[1])
         // и т. д.
   }


incturn

Вызов: incturn()

Увеличивает счетчик ходов (при вызове без аргументов - на единицу). Как правило, вызов этой функции осуществляется специальным демоном при каждом вводе команды игроком. Счетчик ходов используется для запуска запалов, поэтому, если они используются, необходимо увеличивать его на единицу после каждого хода.

Еще раз обратите внимание: вызов данной функции не выполняется системой автоматически, поскольку, во-первых, счетчик ходов не должен увеличиваться после определенных команд (в частности, системных), а во-вторых, автор игры может применять в своей игре особый счет ходов. Именно поэтому за вызов данной функции "отвечает" сама игра.

Альтернативный формат вызова: incturn(число)

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

Если указан аргумент, больший единицы, функция выполняет все запалы, которые должны сработать до истечения указанного числа ходов, но не после. Обратите внимание, что стандартный вызов incturn() без аргументов не приведет к выполнению каких-либо запалов, а просто сократит на единицу счетчики для них.

Например, при вызове incturn(2) система сначала выполнит все запалы, счетчики которых равны 1, после чего сократит на единицу счетчики всех остальных запалов. Аналогично, incturn(3) сначала выполнит запалы с счетчиками, равными 1, затем запалы, чьи счетчики равны 2, после чего уменьшит счетчики всех оставшихся запалов на 2. (Примечание переводчика: "недостающая" единица при уменьшении счетчиков запалов, очевидно, связана с тем, что в соответствии с настройками стандартных библиотек в конце каждого хода все равно будет вызываться incturn без аргументов.)

Функция incturn оказывает влияние на запалы, определенные как с использованием setfuse(), так и notify(). При этом ее работа никак не сказывается на демонах.


input

Вызов: input()

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

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

die: function
  {
    "*** Вы погибли ***
    \bВыберите: начать ЗАНОВО, ВОССТАНОВИТЬ игру или ВЫЙТИ. >";
    while (true)
    {
      local response;
      response := upper(input());
      if (response = 'ЗАНОВО') restart();
      else if (response = 'ВОССТАНОВИТЬ')
      {
        response := askfile();
        if (restore(response)) "Failed. ";
        else abort;
      }
      else if (response = 'ВЫЙТИ')
      {
        quit();
        abort;
      }
      else "Пожалуйста, введите один из следующих вариантов - ЗАНОВО, ВОССТАНОВИТЬ, ВЫЙТИ: >";
    }
  }


inputdialog

Вызов: inputdialog(пиктограмма, текст_запроса, список_ответов, индекс_по_умолчанию, индекс_отмены)

История: добавлена, начиная с TADS 2.5.0

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

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

Аргумент пиктограмма указывает, какая иконка будет отображена в окне и будет ли вообще. Пиктограммы отображаются только на графических системах; текстовые системы данный аргумент игнорируют. Возможные значения данного аргумента определены в advr.t:

INDLG_ICON_NONE   Не выводить пиктограмму.
INDLG_ICON_WARNING   Вывести пиктограмму-предупреждение. Принято считать, что такая пиктограмма указывает на потенциальную или незначительную проблему. (В операционной системе Windows будет выведена пиктограмма-восклицательный знак.)
INDLG_ICON_INFO   Выводит пиктограмму-информационное сообщение. Подобная питограмма означает, что диалоговое окно должно информировать пользователя о ходе выполнения операции. (В ОС Windows это будет пиктограмма с буквой "i", обведенной кружком.)
INDLG_ICON_QUESTION   Выводит пиктограмму-вопрос, означающую, что программе требуется дополнительная информация от пользователя. (В ОС Windows это будет иконка-знак вопроса.)
INDLG_ICON_ERROR   Выводит пиктограмму-сообщение о возникшей ошибке. (В ОС Windows этому значению аргумента соответствует изображение знака "Стоп".)

Аргумент текст_запроса - это строковое значение, содержащее выводимое в диалоговом окне (или перед приглашением на ввод - для систем без поддержки графики) сообщение.

Аргумент список_ответов представляет собой список строковых значений, соответствующих возможным ответам пользователя. Каждое строковое значение - это один допустимый вариант ответа. В графических системах для каждого такого значения в диалоговом окне будет отображена отдельная кнопка; строка будет соответствовать подписи на кнопке. В текстовых системах строки будут перечислены через запятую после текста_запроса.

Каждая строка из строк в списке_ответов может опционально содержать символ амперсанда ("&"); символ, следующий за амперсандом, будет соответствовать "горячей клавише" на клавиатуре, нажатие которой будет интерпретироваться системой как выбор данного ответа. Значок амперсанда не отображается в подписи кнопки или в списке ответов, которые увидит игрок. Например, строка-ответ "&Yes" сделает клавишу "Y" "горячей" для кнопки диалога с подписью "Yes". Для некоторых ОС "горячая" клавиша будет выделена визуально; например, в Windows для вышеприведенного примера буква "Y" в подписи на кнопке будет подчеркнута. Если в строке-ответе нет амперсанда, то соответствующей кнопке не будет соответствовать "горячая клавиша".

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

Примечание переводчика: глюк номер 1: в версиях TADS до 2.5.11 при задании списка_ответов таким способом вместо символов кириллицы в тексте_запроса и подписях на кнопках будут выводиться вопросительные знаки.

Вместо строковых элементов списка_ответов можно использовать числовые значения (а также любые комбинации строковых и числовых значений). Если элемент списка имеет числовое значение, это указывает на то, что для кнопки должна использоваться стандартная системная подпись. По возможности рекомендуется использовать именно стандартные подписи, поскольку при этом будет учтен язык системы. Стандартные подписи считываются из внешних источников (конечно, для ОС, поддерживающих эту функцию), что упрощает локализацию. (Примечание переводчика: Увы, у меня это опять-таки не сработало - на моей локализованной Windows XP диалоговое окно упорно использовало английские подписи для кнопок.)

В advr.t определены следующие значения для стандартных подписей:

INDLG_LBL_OK   "OK"
INDLG_LBL_CANCEL   "Cancel" (отмена)
INDLG_LBL_YES   "Yes" (да)
INDLG_LBL_NO   "No" (нет)

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

Примечание переводчика: глюк номер 2: отличается от глюка № 1 только тем, что для кнопок, описанных вышеназванными константами, подписи всегда будут английскими. Начиная с версии TADS 2.5.11 он также исправлен.

Кроме того, вы можете выбрать стандартный набор кнопок, а не указывать каждую кнопку отдельно. Если аргумент список_ответов будет не списком, а числом, то система "понимает", что необходимо использовать один из определенных в ОС стандартных наборов кнопок. Преимуществом использования таких стандартных наборов является то, что кнопки автоматически будут поименованы в соответствии с языком конкретной ОС (разумеется, если только сама ОС поддерживает такую возможность). Чтобы выбрать стандартный набор кнопок, используйте в качестве аргумента список_ответов одну из следующих констант (определены в файле advr.t):

INDLG_OK   В диалоговом окне будет присутствовать единственная (и при этом правильно локализованная) кнопка "ОК".
INDLG_OKCANCEL   В диалоговом окне будут присутствовать кнопки "OK" и "Cancel" (отмена).
INDLG_YESNO   Диалог будет иметь две кнопки - "Yes" (да) и "No" (нет).
INDLG_YESNOCANCEL   В диалоге будут отображаться три кнопки - "Yes" (да), "No" (нет) и "Cancel" (отмена).

Аргумент индекс_по_умолчанию указывает порядковый номер ответа в списке, который будет выбран по умолчанию. Если пользователь нажмет клавишу Enter (или выполнит иное действие, которое, в соответствии с соглашениями, принятыми в конкретной системе, будет означать выбор ответа по умолчанию), будет использован ответ именно с этим номером. Нумерация списка ответов начинается с единицы. Если приравнять этот аргумент nil, то ответа по умолчанию выбрано не будет, и (R)TADS потребует от пользователя выбрать какую-либо кнопку. (Обратите внимание, что для некоторых систем передача nil через аргумент индекс_по_умолчанию не будет иметь заметного эффекта; например, для Windows фокус клавиатуры по-любому будет установлен в одной из кнопок диалогового окна, и нажатие клавиши Enter приведет к выбору соответствующего этой кнопке ответа.)

Аргумент индекс_отмены определяет порядковый номер ответа в списке, соответствующего отмене. Большинство ОС с графическим интерфейсом пользователя предусматривают стандартный способ закрытия диалогового окна с отменой операции - для Windows, например, это нажатие клавиши Escape. Индекс_отмены и определяет тот ответ, который будет использован при таком стандартном закрытии окна. Если такого ответа не предусмотрено, необходимо передать в этом аргументе значение nil - в этом случае (R)TADS не позволит игроку закрыть диалоговое окно подобным образом.

Функция возвращает порядковый номер ответа, выбранного пользователем, в списке: 1 - для первого ответа, 2 - для второго и т. д. Для стандартных наборов ответов (INDLG_YESNO и т. д.) ответы отсчитываются в соответствии с именем константы (а также с порядком, в каком название кнопок приведено в их описании): для INDLG_YESNO кнопке "Yes" (да) соответстует 1, а кнопке "No" (нет) - 2.

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

    ret := inputdialog(INDLG_ICON_WARNING, 'What would you like to do next?', 
                       ['&Restore', 'Re&start', '&Quit'],
                       nil, 3);
	     /* Запрашиваем пользователя, что он хочет сделать:
              восстановить игру, перезапустить ее или выйти из программы
           */
    switch(ret)
    {
    case 1:
      /* Восстанавливаем игру */
      break;

    case 2:
      /* Перезапускаем */
      restart();
      break;

    case 3:
      /* Выходим из программы */
      quit();
      break;
    }

В системе с графическим интерфейсом пользователя при этом будет выведено диалоговое окно с текстом сообщения "What would you like to do next?" ("Что вы хотите делать далее?") и тремя кнопками: одна с подписью "Restore" ("Восстановить <игру>"), одна - "Restart" ("Перезапустить") и одна - "Quit" ("Выйти"). Если игрок нажмет кнопку "R", будет выбрана первая кнопка, если "S", то вторая, а если "Q" - третья. Если игрок закроет диалог, нажав кнопку Escape (для ОС Windows), то реакция функции будет аналогичной тому, как если бы он выбрал кнопку "Quit".

Для системы без графического интерфейса TADS выведет следующий текст, начав его с новой строки:

    What would you like to do next? (R)estore/Re(s)tart/(Q)uit >

Затем TADS будет ждать, пока игрок введет некую последовательность символов (аналогично встроенной функции input()). Если игрок введет один символ, TADS сверит его с сокращением (буква в скобках) для каждого из возможных ответов и, если найдет совпадение, то вернет соответствующее этому ответу значение. Если игрок введет более одного символа, TADS сверит введенную последовательность с начальными символами каждого из возможных вариантов. Если это последовательность будет совпадать с одним-единственным из ответов, TADS будет считать, что именно этот вариант пользователь и выбрал. Если же введенная пользователем строка допускает неоднозначное толкование либо не совпадает ни с одним из допустимых вариантов, TADS повторит запрос и вновь будет ожидать ответа игрока.

Для полноты картины привожу также пример, который будет работать в русскоязычной системе (только с графическим интерфейсом). Текст запроса - (c) Астрид Линдгрен.

    otvet:=3;
	     while(otvet=3)
             {otvet := inputdialog(INDLG_ICON_QUESTION, 'Бросил ли ты пить коньяк по утрам?',
                       INDLG_YESNOCANCEL, nil, 3);
              switch(otvet)
                {
                 case 1:
                         /* Ответ утвердительный */
                         "\bЗначит, на одеколон перешел? Деградируешь на глазах...";
                         break;
                 case 2:
                         /* Ответ отрицательный */
                         "\bЗря, зря. Пьянство до добра не доводит.";
                         break;
                 case 3:
                         /* Ответ уклончивый */
                         "\bНе пытайся вилять! Прямо и предельно честно 
                          отвечай на поставленный вопрос!";
                         break;
                }
             )

У функции inputdialog() есть свои ограничения (помимо описанных выше глюков при использовании в русскоязычной системе - примечание переводчика). Текст запроса не может быть длиннее 256 символов. Максимальное количество вариантов ответа - 10, причем общая длина всех ответов в сумме не должна превышать 256 символов. Кроме того, для обеспечения переносимости желательно выбирать как можно более короткие подписи для кнопок - в некоторых ОС в диалоговых окнах используются кнопки фиксированной ширины, и длинные подписи на них просто не поместятся. По возможности используйте подписи из одного слова.


inputevent

Вызов: inputevent(тайм-аут)

Альтернативный формат вызова: inputevent()

История: добавлена, начиная с TADS 2.3.0

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

Функция inputevent может вызваться с одним аргументом или без аргументов. Когда она вызывается без аргументов, она просто ожидает, пока произойдет какое-либо системное событие. При передаче ей аргумента данная функция ждет, пока произойдет событие, либо когда истечет период времени, заданный аргументом (в миллисекундах) - так называемое "холостое срабатывание".

Обратите внимание, что передаваемый тайм-аут далеко не всегда будет в точности соответствовать реально прошедшему до "холостого срабатывания" функции времени. Связано это с тем, что в разных компьютерах используются счетчики времени с разной дискретностью, кроме того, многозадачные и многопользовательские системы зачастую вносят дополнительные (и при этом совершенно непредсказуемые) задержки в процесс обработки событий. Поэтому время, реально прошедшее с момента вызова и до "холостого срабатывания", может оказаться несколько больше, чем заданный тайм-аут. Однако, поскольку дополнительная задержка в подавляющем большинстве случаев не превысит нескольких сотен миллисекунд, пользователь, скорее всего, этой задержки не заметит.

Функция возвращает значение списочного типа, описывающее происшедшее событие. Первый элемент списка - это число, определяющее тип события. Состав остальных элементов списка меняется в зависимости от типа события. Константы типов событий определены в advr.t. Возможны следующие коды:

INPUT_EVENT_KEY Пользователь нажал клавишу. Вторым элементом списка, возвращаемым в этом случае inputevent, будет строковое значение, соответствующее нажатой клавише. Это значение совпадает с тем, которое возвратила бы функция inputkey при нажатии той же клавиши.
INPUT_EVENT_HREF Пользователь щелкнул по гиперссылке. Данное событие возвращается только при работе в мультимедийном интерпертаторе (R)TADS. Вторым элементом списка будет строка с текстом тэга HREF для выбранной ссылки.
INPUT_EVENT_TIMEOUT До истечения тайм-аута не происходило никаких событий. Возвращенный список будет состоять из одного-единственного элемента.
INPUT_EVENT_EOF Данный код указывает на то, что интерпретатор (R)TADS прекращает свою работу либо произошла ошибка считывания события.
INPUT_EVENT_NOTIMEOUT Это - не "настоящее" событие, а просто ошибка, указывающая на то, что данная система не поддерживает возможность указания тайм-аута для функции inputevent. Если такая ошибка возникает, вы по-прежнему можете использовать эту функцию, но не сможете указывать тайм-аут, по истечении которого функция автоматически завершит работу. Все интерпретаторы TADS под DOS (TR, TRX, TR32), а также интерпретатор HTML TADS для Windows поддерживают эту функцию. То же самое справедливо для большинства интерпретаторов под другие операционные системы, однако, возможно, существуют отдельные сторонние интерпретаторы, лишенные такой функциональности.


inputkey

Вызов: inputkey()

История: Распознавание функциональных клавиш добавлено, начиная с версии TADS 2.3.0, остальных - 2.5.0

Считывает нажатие клавиши на клавиатуре, возвращает строку, соответствующую нажатой клавише. Данная функция не требует аргументов. При вызове она сначала осуществляет вывод всего текста, находящегося в буфере вывода, затем останавливает игру до тех пор, пока игрок не нажмет клавишу. При нажатии клавиши выполнение программы возобновляется, функция при этом возвращает соответствующее нажатой клавише строковое значение. Функция inputkey() считывает клавишу непосредственно с клавиатуры, а не из командной строки.

Кроме того, функция возвращает универсальное представление определенных функциональных клавиш. До версии TADS 2.3 использование функции inputkey в сочетании с клавишами вне стандартного набора ASCII было почти невозможным, так как для таких клавиш возвращался некий код в "низкоуровневом" формате, который был довольно-таки бесполезен, поскольку еще и менялся от системы к системе. В настоящее время для представления подобных клавиш (R)TADS использует переносимый строковый формат.

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

[up] Стрелка вверх
[down] Стрелка вниз
[right] Стрелка вправо
[left] Стрелка влево
[end] Переход к концу строки
[home] Переход к началу строки
[del-eol] Удаление текста до конца строки
[del-line] Удаление строки
[del] Del (удаление символа)
[page up] Страница вверх
[page down] Страница вниз
[top] Переход к началу файла/документа
[bottom] Переход к концу файла/документа
[fN] Функциональная клавиша № N (N заменяется числом от 1 до 10)
[word-left] Переход на одно слово влево
[word-right] Переход на одно слово вправо
[del-word] Удаление слова
[bksp] Забой
[esc] Escape
[ctrl-X] Control-X (X заменяется на букву в нижнем регистре: нажатие Control-C будет представлено как [ctrl-c])
[alt-X] Alt-X или Meta-X (X заменяется на букву в нижнем регистре: нажатие Alt-F будет представлено как [alt-f])

Кроме того, для клавиши "Return" (или "Enter") возвращается "\n", а для табулятора - "\t".

Хотя эти названия клавиш переносимы, следует помнить, что не все компьютеры имеют полный набор клавиш, поэтому далеко не всегда можно рассчитывать на то, что игрок сможет нажимать любые из этих клавиш. Единственные клавиши, которые имеются наверняка, это обычные клавиши ASCII, клавиша ввода (Enter), табулятор и забой. (Примечание переводчика: кстати, было бы интересно потестировать работу этой функции на мобильном телефоне - сам я, к сожалению, не имею такой возможности.) Поэтому, когда вы используете клавишу из расширенного набора, всегда необходимо предоставлять альтернативный ввод с использованием обычных клавиш. Например, если вы хотите реализовать систему меню с навигацией при помощи клавиш перемещения курсора вверх и вниз, следует предусмотреть альтернативную навигацию - например, с использованием клавиши N (от слова "Next" - следующий) как синонима клавиши "вниз", и клавиши P (от слова "Previous" - предыдущий) в качестве синонима клавиши "вверх".

Из всего расширенного набора клавиш лучше всего обстоит дело с переносимостью, пожалуй, у клавиш перемещения курсора (значения [up], [down], [left] и [right]), поскольку они в том или ином виде присутствуют на клавиатурах подавляющего большинства компьютеров и терминалов. Функциональные клавиши ([f1] - [f10]) также имеются во многих системах, хотя в некоторых ОС некоторые из них применяются в специальных целях; например, в Windows клавиша F10 используется для входа в меню, поэтому при выполнении под Windows игровая программа никогда не получит соответствующего значения при использовании функции inputkey(). Комбинации клавиш с использованием ALT и CONTROL также сильно различаются для разных систем.

Ниже приведен демонстрационный пример с использованием клавиш перемещения курсора.

numberVerb: deepverb
  verb = 'число'
  action(actor) =
  {
      local num;
      local done;
      local changed;

      "Для изменения числа используйте клавиши \"Вверх\" и \"Вниз\" 
      либо \"+\" и \"-\". Для выхода нажмите клавишу ввода.\b";
      num := 5;
      changed := true;
      for (done = nil ; !done ; )
      {
          if (changed)
          {
              "\nТекущее значение = <<num>>";
              changed := nil;
          }

          switch(inputkey())
          {
            case '\n':
              done := true;
              break;

            case '+':
            case '[up]':
              ++num;
              changed := true;
              break;

            case '-':
            case '[down]':
              --num;
              changed := true;
              break;
          }
      }

    "\bОкончательное выбранное значение: <<num>>. ";
  }
  ;


intersect

Вызов: intersect( список1, список2 )

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

  intersect( [ 1 2 3 4 5 6 ], [ 2 4 6 8 10 ] )

возвращает список [ 2 4 6 ].

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


isclass

Вызов: isclass(объект, класс)

Данная функция определяет, является ли объект потомком класса. Возвращает true, если это так, или nil в противном случае. Функция работает со всем деревом наследования, поэтому объект необязательно должен быть прямым потомком класса - функция вернет true и в том случае, если класс окажется "дедовским", "прадедовским" и т. д. для объекта.

Рекомендуется при прочих равных условиях использовать именно данную функцию для определения "наследственности" объекта. Такой способ имеет следующие преимущества по сравнению с альтернативным (использование специальных свойств, наследуемых объектом от класса): не требуется выделять память под хранение свойства; сканирование дерева наследования происходит намного быстрее, чем проверка каждого объекта в иерархии на значение свойства.

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


length

Вызов: length(переменная)

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


logging

Вызов: logging(значение)

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

Если значение равно nil, это означает, что файл-журнал должен быть закрыт, а запись транскрипта - прекращена.

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

Функция не возвращает никаких значений.


lower

Вызов: lower(строка)

Данная функция возвращает строку, в которой все символы преобразованы в нижний регистр (увы, для кириллицы не работает - примечание переводчика). См. также функцию upper.


morePrompt

Вызов: morePrompt()

Добавлена: TADS 2.2.4

Данная функция позволяет принудительно вывести системный запрос ДАЛЬШЕ (обычно выводится системой, когда выводимый текст выходит за пределы экрана). Может использоваться, например, для создания драатической паузы перед началом новой главы истории. Функция вызвается без аргументов и не возвращает никакого значения.


nextobj

Вызов: nextobj(объект)

Альтернативный формат вызова: nextobj(объект, класс)

Данная функция используется для получения следующего объекта в процессе поиска, инициированного при помощи firstobj(). Объект - это значение, возвращенное в результате предыдущего вызова firstobj() или nextobj(объект). Функция возвращает следующий (не являющийся классом) объект; если этот список исчерпан, возвращается nil.

Альтернативная форма вызова позволяет ограничить поиск объектов заданным классом - будут возвращаться только объекты-потомки этого класса.

Пример использования данной функции приведен в описании firstobj().


nocaps

Вызов: nocaps()

Данная функция оказывает противоположное действие по сравнению с caps() - она указывает, что следующий выводимый символ необходимо преобразовать в нижний регистр (и, опять-таки, она работает только с латинскими буквами). Того же эффекта можно добиться, используя управляющую последовательность "\v".

Обратите внимание, что функции caps() и nocaps() отменяют друг друга; вызов

caps(); nocaps();
  

эквивалентен вызову nocaps();.


notify

Вызов: notify(объект, &метод, число_ходов)

Данная функция позволяет организовывать работу "демонов" и "запалов". Демоном называется некая функция, автоматически запускающаяся по окончании каждого хода. Запал - это функция с "отложенным запуском"; она выполняется один раз через заданное число ходов после инициализации.

Функция notify позволяет использовать в качестве демона/запала указываемый при вызове метод объекта (амперсанд перед названием метода обязательно должен присутствовать). Если аргумент число_ходов равен нулю, метод будет инициализирован в качестве демона (система будет вызывать его автоматически после каждого хода). Если число_ходов представляет собой ненулевое значение, произойдет инициализация запала - метод будет вызван один раз через количество ходов, равное числу_ходов. В обоих случаях метод вызывается без аргументов.

Одновременно могут быть активны не более 200 демонов и запалов.

При ненулевом значении аргумента число_ходов действие notify аналогично действию функции setfuse, а при нулевом - setdaemon. Основное преимущество notify состоит в том, что она позволяет локализовать код демонов/запалов в объектах, к которым эти демоны/запалы относятся, вместо использования сторонних, "отдельностоящих" функций. (Примечание переводчика: я сам всегда пользуюсь функцией notify. Справедливости ради необходимо отметить, что ее объективным недостатком является невозможность передать аргумент вызываемому методу.)

См. также описание функции unnotify(), отменяющей действие notify.

Пример использования функции notify приведен ниже. В этом примере мы определяем два объекта: бомбу и кнопку на бомбе. При нажатии кнопки соответствующий обработчик вызывает notify, которая, в свою очередь, инициирует запуск метода explode через три хода. Обратите внимание на использование амперсанда перед названием метода explode.

  bomb: item	// Сама бомба
    location = bombroom
    sdesc = "бомба"
    noun = 'бомба'
    isActive=nil	// Флаг, показывающий, запущен ли детонатор
    ldesc =
    {
      "У бомбы имеется небольшая кнопка с надписью
      \"детонатор\". ";
      if (self.isActive) "Бомба громко тикает. ";
    }
    explode =
    {
      "Бомба взрывается! ";
      self.moveInto(nil);
    }
  ;

  bombButton: buttonItem
    location = bomb
    sdesc = "кнопка детонатора"
    adjective = 'детонатора' 'детонатора#r'
    // Существительные (noun) уже определены в advr.t
    verDoPush(actor) =
    {if(bomb.isActive)
       {// Детонатор уже запущен
        "Ты вновь нажимаешь кнопку на бомбе, 
        но ничего не происходит."
    }
    doPush(actor) =
    {
      "Бомба начинает громко тикать. ";
      notify(bomb, &explode, 3);              // Бомба взорвется через три хода
      bomb.isActive := true;
    }
  ;


objwords

Вызов: objwords( число )

Возвращает список слов, которые игрок использовал в текущей команде для того, чтобы обратиться к тому или иному объекту. Аргумент число определяет, какой именно объект имеется в виду: если этот аргумент равен 1, будут возвращены слова для "прямого" объекта, а если 2 - для "косвенного". Возвращаемое значение представляет собой список строковых значений; строки соответствуют словам, использовавшимся в текущей команде для выбранного объекта (при этом буквы будут переведены в нижний регистр, а пробелы и знаки препинания - удалены). При использовании специальных слов-местоимений, таких как "все", "это", список будет содержать один-единственный элемент, соответствующий использованному специальному слову.

Например, если игрок введет "взять все", то objwords(1) вернет [ 'все' ], а objwords(2) - []. Обратите внимание, что objwords(1) вернет [ 'все' ] и в том случае, если игрок введет какую-либо вариацию на тему данной команды, например, "взять всех" или "взять все кроме книги".

Если игрок введет команду "положить все в красную коробку", objwords(1) вернет [ 'все' ], а objwords(2) - [ 'красную' 'коробку' ].

Если игрок использует в команде несколько "прямых" объектов, функция вернет слова только для текущего объекта. Например, если игрок введет "положить синюю папку и зеленую книгу в красную коробку", objwords( 1 ) вернет [ 'голубую' 'папку' ] в ходе обработки первого "прямого" объекта и [ 'зеленую' 'книгу' ] - для второго. Подробнее об обработке команд с нескольких "прямыми" объектами см. здесь.

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

Примечание переводчика: собственно, сейчас соответствующий обработчик в advr.t именно по такой схеме и реализован. При том, что для английского языка эта схема действительно удобнее в большинстве случаев, с русским языком немного сложнее: во-первых, спрашивать можно не только о чем-то, но и про что-то - соответственно, это как минимум в два раза увеличивает число необходимых проверок. Во-вторых, если закладываться на опечатки игрока (фразы типа "спросить о корабль"), то распознавать потребуется уже все падежи. В общем-то ничего страшного в этом нет, но рутинной работы добавится, да и операторы будут выглядеть более громоздко. Упомяну и вариант, когда такая схема трудноприменима в том числе и в английском языке: если необходимо распознавать объект и по существительному, и по прилагательному (например, "спросить библиотекаря про черную книгу" и "спросить библиотекаря про желтую газету"). Как действовать в этом случае - тема отдельного разговора;).

Вы можете вызвать objwords() из метода-верификатора или метода-действия (verDoГлагол, verIoГлагол, doГлагол, ioГлагол), а также из метода doDefault. Обратите внимание, что в последнем варианте возвращаемое значение в некоторых случаях будет отличаться: например, при использовании слова "все" функция вернет не [ 'все' ], а [ 'A' ] (в связи с особенностями внутреннего алгоритма обработки слов). Возможность вызова objwords из doDefault появилась, начиная с версии TADS 2.2.


outcapture

Вызов: outcapture( состояние )

(R)TADS позоляет осуществлять перехват текста, выводимого на экран при помощи выражений в двойных кавычках и встроенной функции say. Это позволяет автору игры, например, получать доступ к тексту, выводимому краткими и подробными описаниями объектов (sdesc/ldesc).

Для доступа к этой возможности программа сначала должна сообщить (R)TADS, что необходимо начать перехват выводимого текста. Для этого и вызывается outcapture:

  stat := outcapture( true );

При таком вызове начинается перехват выводимого на экран текста. Возвращаемое значение является кодом состояния, которое передается при последующем вызове для того, чтобы отключить режим перехвата. Использование данного кода позволяет использовать "вложенные" вызовы этой функции, поскольку появляется возможность восстановить состояние перехвата на момент инциирующего вызова outcapture при повторном (завершающем) вызове outcapture. Описанный код состояния служит исключительно для внутреннего использования (R)TADS - от автора игры требуется только не забыть передать его при завершающем режим перехвата текста вызове.

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

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

  str := outcapture( stat );

Этот повторный вызов сообщает (R)TADS, что перехват следует прекратить. Возвращаемое значение этой функции - строковое значение (в одинарных кавычках), содержащее весь текст, который был выведен, пока режим перехвата был активен (т. е. с момента "парного" вызова outcapture( true )).

При перехвате текста (R)TADS преобразует все форматные строки (такие как "%you%") и обрабатывает управляющие последовательности "\^" и "\v" (преобразующие регистр следующего за ними символа в верхний или нижний, соответственно). При этом все остальные управляющие последовательности (такие, как "\n" и "\b") сохраняются в строке в первозданном виде.

Отобразить текст, перехваченный в результате вызова outcapture(stat), можно, используя встроенную функцию say: say(str). По идее, текст при этом будет выведен так же, как если бы перехват не был включен вообще.

Обращения к outcapture могут быть вложенными. Как уже отмечалось выше, возвращаемый код состояния после первого вызова служит для того, чтобы восстановить режим, который действовал до предыдущего вызова. Строка, возвращаемая после вызова, завершающего режим перехвата, содержит только тот текст, который выводился с момента предыдущего вызова outcapture, включившего этот режим. Так как вызовы к этой функции могут быть вложенными, вам не требуется беспокоится о том, что вызываемые вами методы также могут использовать эту функцию.

Обратите внимание, что система самостоятельно отключает перехват текста в случаях, когда она выводит запрос пользователю (будь то начало новой команды, уточнение объекта и т. д.). Когда система отключает перехват текста, она очищает и соответствующий буфер, поэтому при всех последующих соответствующих обращениях к этой функции будут возвращаться пустые строки. Таким образом, перехватывать текст можно только внутри одной команды.


outhide

Вызов: outhide( флаг )

Переключает режим скрытия выводимого текста (точно таким же образом, как это делает синтаксический анализатор при устранении неопределенности объектов). Параметр flag может принимать значения true или nil. При вызове outhide(true) система включает режим скрытия выводимого текста. При этом синтаксический анализатор этот текст "не увидит". При вызове outhide(nil) этот режим отключается, и текст начинает отображаться, как обычно. Вызов outhide(nil) также возвращает значение, указывающее, происходил ли вывод текста в скрытом режиме (с момента вызова outhide(true)).

Повторим еще раз - этот механизм в точности повторяет тот, который используется синтаксическим анализатором в процессе устранения неопределенности, поэтому, как правило, данную функцию не следует использовать в методах-обработчиках (verDoГлагол или verIoГлагол). Основное назначение данной функции - вызов методов-верификаторов verDoГлагол и verIoГлагол для проверки, не препятствуют ли они выполнению определенного действия над определенным объектом.

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

Альтернативный формат вызова: outhide(флаг) - вложенные вызовы

При вызове outhide(true) функция возвращает флаг состояния, представляющий собой значение, которое можно использовать при повторном вызове outhide для восстановления режима, действовавшего до первого вызова outhide. Это позволяет использовать вложенные вызовы - режим скрытия текста можно включать из функции или метода, не беспокоясь о том, осуществлялось ли обращение к outhide из других функций.

Для использования вложенных вызовов необходимо сохранить значение, возвращаемое outhide(true), и затем использовать его в качестве аргумента при повторном вызове (вместо nil). При этом возвращенное при повторном вызове outhide() значение будет являться флагом, показывающим, происходил ли вывод текста в промежутке между двумя вызовами. Пример:

  local old_stat1, old_stat2;
  local new_stat1, new_stat2;
  old_stat1 := outhide(true);
  "Этот текст не будет виден.";
  old_stat2 := outhide(true);
  // Здесь мы не выводим текста
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

Поскольку outhide(old_stat2) показывает, выводился ли текст в период вложенного вызова outhide(true), new_stat2 будет равен nil. В то же время new_stat1 будет равен true, так как с момента первого вызова outhide(true) текст выводился. Теперь рассмотрим другую последовательность вызова:

  old_stat1 := outhide(true);
  // Здесь текст не выводится
  old_stat2 := outhide(true);
  "Этот текст не будет виден.";
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

В этом случае значение true примут и new_stat1, и new_stat2, поскольку текст выводился между обеими парами вызовов (хотя и было выведено всего одно сообщение).

Итак, обобщенная форма использования outhide() имеет следующий вид:

{
   local old_stat;
   local new_stat;

   old_stat := outhide(true);
   // Здесь можно делать что угодно -
   // выводимый текст отображаться не будет
   new_stat := outhide(old_stat);
}

Переменную new_stat можно будет использовать для проверки того, осуществлялся ли вывод текста между вызовами outhide(true) и outhide(old_stat). Кроме того, режим вывода текста будет возвращен в то состояние, которое действовало до вызова outhide(true).


parseAskobjIndirect

Вызов: parseAskobjIndirect(актер, глагол, предлог, информация_о_существительном)

История: введена, начиная с TADS 2.5.1

Данная функция позволяет более точно настроить текст уточняющего запроса игроку, выводимого синтаксическим анализатором в случае, если в команде не указан "косвенный" объект, но этот объект требуется. Эта функция введена в дополнение к существовавшим ранее parseAskobj() и parseAskobjActor(). Более подробное описание см. здесь.


parseNounList

Вызов: parseNounList(список_слов, список_типов, начальный_индекс, сообщать_если_нет_объекта, несколько_словосочетаний, проверить_актера)

История: введена, начиная с TADS 2.4.0

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


parseNounPhrase

Вызов: parseNounPhrase(список_слов, список_типов, текущий_индекс, сообщать_если_нет_объекта, проверить_актера)

История: введена, начиная с TADS 2.4.0

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


parserDictLookup

Вызов: parserDictLookup(список_лексем, список_типов)

История: введена, начиная с TADS 2.4.0

Данная функция позволяет просматривать словарь игры с целью получения списка объектов, для которых определен заданный набор лексических свойств. Функция полезна при создании собственного алгоритма разбора команд; подробное ее описание см. в разделе Обработка введенной строки непосредственно из игры.


parserGetMe

Вызов: parserGetMe()

История: введена, начиная с TADS 2.2.4

В настоящее время синтаксический анализатор предоставляет средства для динамической смены главного персонажа в ходе игры. Более ранние версии TADS всегда использовали в качестве главного персонажа объект Me, и возможности сменить его не было. Это затрудняло написание игр, в которых на разных отрезках действовали разные главные персонажи.

Впоследствии данная возможность была добавлена в TADS; для получения текущего объекта - главного персонажа используется функция parserGetMe(). Эта функция не требует аргументов.

Обратите внимание, что в файлах advr.t и stdr.t более не используются прямые ссылки на объект Me для кода, обраающегося к свойствам объекта-ГП (например, при проверке инвентаря или выводе описаний локаций). Вместо этого для получения ГП используется parserGetMe(). Соответственно, если вы используете в вашей игре смену главного персонажа (при помощи функции parserSetMe()), имеет смысл использовать такой же подход.


parserGetObj

Вызов: parserGetObj(аргумент)

История: введена, начиная с TADS 2.4.0

Данная функция позволяет игровой программе получать информацию об объектах, задействованных в текущей команде. Функция вызывается с одним аргументом, который определяет, какой именно объект необходимо получить, и может принимать следующие значения (константы определены в advr.t):

PO_ACTOR - актер (персонаж) текущей команды;

PO_VERB - объект класса deepverb, соответствующий глаголу текущей команды;

PO_DOBJ - "прямой" объект;

PO_PREP - объект-предлог, служащий связкой с "косвенным" объектом;

PO_IOBJ - "косвенный" объект;

PO_IT - объект, на который ссылается местоимение "это" (или "оно");

PO_HER - объект, на который ссылается местоимение "она";

PO_HIM - объект, на который ссылается местоимение "он";

PO_THEM - объект, на который ссылается местоимение "они".

Функция возвращает требуемый объект (или nil, если такого объекта в команде нет).

Более подробно функция описана в разделе Получение информации о текущей команде главы, посвященной синтаксическому анализатору.


parserGetTokTypes

Вызов: parserGetTokTypes(список_лексем)

История: введена, начиная с TADS 2.4.0

Данная функция позволяет получить перечень типов лексем для заданного списка_лексем. Вы можете использовать данную функцию, к примеру, для получения типов лексем, возвращаемых функцией parserTokenize. Полное описание данной функции можно найти в разделе Обработка введенной строки непосредственно из игры главы, посвященной синтаксическому анализатору.


parserResolveObjects

Вызов: parserResolveObjects(актер, глагол, предлог, другой_объект, способ_использования, имя_верификатора, список_лексем, список_объектов, тихо)

История: введена, начиная с TADS 2.4.0

Функция позволяет игровой программе получить доступ к встроенным средствам подбора объектов и устранения неопределенности синтаксического анализатора. Ее можно использовать совместно с parseNounList или собственной функцией разбора словосочетаний. Полное описание данной функции можно найти в разделе Обработка введенной строки непосредственно из игры главы, посвященной синтаксическому анализатору.


parserSetMe

Вызов: parserSetMe(новый_ГП)

История: введена, начиная с TADS 2.2.4

Как было сказано выше, вы можете менять объект, являющийся главным персонажем, по своему желанию. По умолчанию таковым по-прежнему будет объект Me, однако его всегда можно заменить, используя встроенную функцию parserSetMe. В качестве единственного аргумента этой функции передается объект, который должен будет выступать в роли нового главного героя.

Примечание переводчика: напомню, что вместо прямого обращения к данной функции рекомендуется использовать определенную в advr.t функцию switchPlayer, поскольку последняя выполняет ряд необходимых низкоуровневых действий, сопутствующих смене главного героя.


parserTokenize

Вызов: parserTokenize(команда)

История: введена, начиная с TADS 2.4.0

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


parseUnknownVerb

Вызов: parseUnknownVerb(актер, список_слов, список_типов, код_ошибки)

История: введена, начиная с TADS 2.4.0

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


postAction

Вызов: postAction(актер, глагол, п_объект, предлог, к_объект, статус)

История: введена, начиная с TADS 2.5.0

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


preCommand

Вызов: preCommand(актер, глагол, список_п_объектов, предлог, к_объект)

История: введена, начиная с TADS 2.5.0

Данная функция является "точкой входа" - синтаксический анализатор обращается к ней (если она определена) в самом начале фазы исполнения - перед вызовом первого метода-обработчика для первого "прямого" объекта в команде.

Подробное описание функции содержится в разделе Последовательность синтаксического анализа.


proptype

Вызов: proptype(объект, ссылка_на_свойство)

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

Функция возвращает одно из следующих числовых значений (данный список по существу явлется несколько расширенным набором кодов, возвращаемым функцией datatype):

1 - Число
2 - Объект
3 - Строка
5 - nil
6 - Код (исполняемые инструкции, заключенные в фигурные скобки)
7 - Список
8 - true
9 - Строка в двойных кавычках
10 - Ссылка на функцию
13 - Ссылка на свойство объекта

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

  x := proptype(Me.location, &north);

Обратите внимание, что использование proptype сильно отличается от использования datatype в связи с тем, что proptype не обращается напрямую к свойству (иначе говоря, не выполняет его) для определения его типа. Этот факт имеет два важных следствия. Первое (и наиболее важное) - это то, что, как уже было сказано выше, использование proptype позволяет обойтись без нежелательных побочных эффектов при определении типа данных свойства (имеются в виду такие вещи, как, например, вывод строки текста при определении типа свойства - строки в двойных кавычках). Второе следствие - при помощи этой функции вы можете определить только тип свойства (в частности, установить, что свойство является кодом), но ни в коем случае не возвращаемое свойством значение.


quit

Вызов: quit()

Завершает игру. На самом деле функция просто устанавливает флаг, указывающий, что игра должна быть прервана перед началом разбора следующей команды, поэтому все остальные функции/методы, следующие для текущей команды следом за quit, будут выполняться обычным образом. В связи с этим непосредственно следом за quit обычно вставляют инструкцию abort. Пример использования функции quit см. в описании функции input.


rand

Вызов: rand(верхний_предел)

Данная функция возвращает случайное число в диапазоне от 1 до верхнего_предела. (Аргумент верхний_предел должен быть положительным целым числом). См. также функцию randomize.


randomize

Вызов: randomize()

Функция запускает генератор псевдослучайных чисел, задавая ему некое начальное значение. Это значение зависит от конкретной системы и использует системные часы. До вызова randomize генератор (через функцию rand) всегда будет выдавать одну и ту же последовательность чисел; после новой инициализации последовательность чисел становится непредсказуемой. При тестировании бывают ситуации, когда вызов randomize лучше опустить (в этом случае все события, основанные на генераторе случайных чисел, будут происходить одинаковым образом при каждом прохождении). При релизе достаточно будет добавить вызов randomize в функцию init.


reGetGroup

Вызов: reGetGroup(порядковый_номер)

История: введена, начиная с TADS 2.3.0

Позволяет получить доступ к данным, возвращаемым функцией поиска по заданному шаблону reSearch. reSearch объединяет данные о найденных по шаблону подстроках в группы заданной структуры. Наиболее удобный доступ к этим данным можно организовать при помощи reGetGroup. Аргумент порядковый_номер - это номер группы, возвращенной функцией reSearch.

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


remdaemon

Вызов: remdaemon(функция, контекст)

Отключает демон, инициированный ранее функцией setdaemon().

Обратите внимание, что если одна и та же функция была запущена в качестве демона несколько раз, то вызов remdaemon() отключит только первый демон; иначе говоря, в подобном случае для полного отключения функции-демона количество вызовов remdaemon должно быть равно количеству вызовов setdaemon для этой функции. Кроме того, аргумент контекст должен быть равен значению аналогичного аргумента, передававшемуся при вызове setdaemon. Использование контекста позволяет запускать одну и ту же функцию в качестве демона несколько раз и затем выборочно отключать именно те инкарнации демона, которые требуется.

Наконец, вызов remdaemon в случае, когда отключаемый демон не инициирован, попросту игнорируется.

См. также функцию unnotify.


remfuse

Вызов: remfuse(функция, контекст)

(См. также встроенную функцию setfuse().)

Данная функция отключает установленный ранее запал, либо не выполняет никаких действий (если указанная функция не инициировалась в качестве запала). Если запал удаляется до момента срабатывания, он не будет выполняться. Если функция устанавливалась в качестве запала несколько раз, то remfuse отключит только первую из установленных копий запалов; иначе говоря, для полного отключения функции-запала количество вызовов remfuse должно быть равно количеству вызовов setfuse для этой функции. Кроме того, аргумент контекст должен быть равен значению аналогичного аргумента, передававшемуся при вызове setfuse. Использование контекста позволяет запускать одну и ту же функцию в качестве запала несколько раз и затем выборочно отключать именно те экземпляры запала, которые требуется.

Наконец, вызов remfuse в случае, когда отключаемый запал не инициирован, попросту игнорируется.


reSearch

Вызов: reSearch(шаблон, строка_для_поиска)

История: введена, начиная с TADS 2.3.0

Функция reSearch осуществляет поиск первого вхождения шаблона в строке_для_поиска. Возвращает nil, если шаблон не найден. Если поиск был успешным, функция возвращает список, первым элементом которого будет число, соответствующее позиции первого вхождения шаблона (нумерация позиций ведется, начиная с единицы), второй элемент укажет длину (количество символов) подстроки, соответствующей шаблону, а третьим элементом, собственно, и будет сама найденная подстрока.

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


resourceExists

Вызов: resourceExists(имя_ресурса)

История: введена, начиная с TADS 2.5.1

Данная функция позволяет определить, может ли быть подключен некий мультимедиа-ресурс (как правило, рисунок или аудиотрек) с указанным именем_ресурса. Аргумент имя_ресурса - это строковое значение, соответствующее уникальному идентификатору ресурса; функция возвращает true, если ресурс может быть подключен, и nil, если нет. nil возвращается как в случае, если интерпретатор не поддерживает мультимедийных возможностей, так и тогда, когда ресурс не может быть найден. Соответственно, true возвращается только при выполнении обоих вышеназванных условий (т. е. и ресурс найден, и интерпретатор поддерживает мультимедийные возможности). Таким образом, при вызове из чисто текстового интерпретатора (что, впрочем, для русскозычных игр малоактуально) данная функция всегда будет возвращать nil.

Область применения функции - например, случай, когда вы в принципе пишите игру с мультимедийными возможностями, но не "вживляете" их в саму игру, а выкладываете в виде отдельного файла (проявляя заботу об игроках с медленным или дорогим доступом к Интернету). В этом случае вам может потребоваться, чтобы вместо нескачанных рисунков игра выводила какой-то дополнительный описательный текст. Для определения таких ситуаций в ходе выполнения игровой программы resourceExists подходит наилучшим образом.

Вот пример проверки существования рисунка в формате JPEG при помощи данной функции:

    if (!resourceExists('images/title.jpg'))
    {
       // Графическая заставка отсутствует - выводим ее текстовую версию
       ...
    }


restart

Вызов: restart()

Данная функция перезапускает игру с самого начала. Она не возвращает никакого значения.

Альтернативный формат вызова: restart( ссылка_на_функцию, параметр )

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

Итак, альтернативный формат вызова restart может быть полезен, если требуется добиться от игры иного поведения при перезагрузке, чем при первоначальном запуске. Обратите внимание, что в advr.t (см. глагол restartVerb) при перезапуске по умолчанию вызывается функция initRestart, определенная в этой же библиотеке. Вариант этой функции, определенный в advr.t, просто устанавливает флаг global.restarting равным true.

Аргумент параметр будет просто передан вызываемой функции, что позволит сохранять определенный объем информации между перезагрузками. Например, если ваша игра начинается с того, что у персонажа спрашивают его пол, имя и возраст, то можно при перезапуске передавать содержащий эти данные список, избавив таким образом игрока от необходимости вводить эти данные снова и снова. В "умолчальной" реализации библиотеки advr.t в качестве такого передаваемого параметра фигурирует свойство global.initRestartParam.


restore

Вызов: restore(имя_файла)

История: возвращаемые значения были изменены, начиная с версии TADS 2.5.0

Имя_файла является строковым значением, содержащим имя файла, который был создан функцией save. Функция восстанавливает ранее сохраненное игроком состояние игры.

В старых версиях при успешном завершении функции она возвращала nil; в противном случае возвращалось true. Обратите внимание, что восстанавливаемое состояние игры должно быть сохранено в текущей (используемый для восстановления) версии интерпретатора и в текущей версии игры (иначе говоря, при перекомпиляции игровой программы она не сможет обратиться к ранее сохраненным игроком файлам при помощи данной функции). При несовпадении одной из этих версий система откажется восстанавливать файл, выдав соответствующее сообщение.

Числовые коды, возвращаемые функцией в случае ошибки, введены, начиная с версии TADS 2.5.0. Для разных случаев возвращаются разные коды, что позволяет дать игроку более полную информацию о характере проблемы, препятстсвующей успешному восстановлению игры. Используются следующие значения (константы определены в advr.t):

RESTORE_SUCCESS   Успешное завершение
RESTORE_FILE_NOT_FOUND   Файл, из которого восстанавливается состояние игры, не существует (либо его не удалось открыть по какой-либо другой причине).
RESTORE_NOT_SAVE_FILE   Файл не является сохраненным состоянием игры.
RESTORE_BAD_FMT_VSN   Файл был сохранен несовместимой версией интерпретатора TADS.
RESTORE_BAD_GAME_VSN   Файл был сохранен другой игрой, или другой версией текущей игры.
RESTORE_READ_ERROR   Возникла ошибка чтения из файла. Это может означать, что поврежден либо сам файл, либо физический носитель, на котором этот файл расположен.
RESTORE_NO_PARAM_FILE   В параметре не указан файл. Это значение возвращается только в случае, если используется вызов restore(nil) для восстановления состояния игры из файла, указанного игроком при запуске (см. ниже). Такой код означает, что игрок не указал файл для восстановления игры.

Для совместимости с более старыми версиями TADS константа RESTORE_SUCCESS, соответствующая успешному завершению, определена равной нулю, а все остальные - как ненулевые значения. В большинстве случаев это позволяет обойтись без модификации старого кода, использовавшего проверку на true/nil (т. е., например, работа оператора if (restore(fname)) не будет меняться при такой модификации). Потребуется изменение лишь в тех случаях, когда в коде явно использовано сравнение со значениями true или nil. Впрочем, применительно к RTADS все это имеет чисто теоретический интерес.

Альтернативный формат вызова: restore( nil )

В такой форме функция restore() позволяет автору выбрать момент времени для восстановления ранее сохраненной игры из файла в случае, если игрок при запуске указал игре такой файл. Иначе говоря, при вызове restore(nil) программа сначала проверяет, указал ли игрок при запуске файл для восстановления; если это так, эта игра немедленно восстанавливается, после чего функция возвращает nil. Если такой файл не был указан, функция возвращает true.

Таким способом игру можно запустить лишь на интерпретаторах TADS с графическим интерфейсом пользователя (говоря конкретнее, HTML-интерпретаторы для Windows и MacOS); в то же время применение этого сравнительно нового варианта вызова функции restore не вызовет ошибок и на других платформах. На "Маках" (собственно, как и в "Виндах") существует возможность запустить приложение, открыв документ, ассоциированный с этим приложением. При этом файл сохраненной игры будет ассоциироваться именно с той игровой программой, в которой он был создан. Это просто удобная особенность "Макинтошей", позволяющая игроку объединять операции запуска игровой программы и восстановления игры.

(Примечание переводчика: на самом деле, в Windows это не работает - но может быть, все еще впереди;).

Вы можете использовать вызов restore(nil) в своей функции init для того, чтобы определить момент восстановления игры. Например, если ваша игра снабжена длинным вступительным текстом, вы могли бы при помощи этого вызова проверить до вывода этого текста, не восстанавливается ли игра; в этом случае можно с большой долей достоверности предположить, что игрок уже читал введение, и не выводить его повторно.

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

Если вы не включаете вызов restore(nil) в свою функцию init, система автоматически восстановит заданное состояние игры после того, как init завершит работу.

Стоит дополнительно подчеркнуть отличие данного случая восстановления состояния игры при запуске от другой ситуации, когда, например, игрок указывает файл с игрой для восстановления в командной строке. В последнем случае вместо init будет вызываться функция initRestore.


rundaemons

Вызов: rundaemons()

Выполняет все демоны, инициированные как функцией setdaemon, так и notify. Возвращаемое значение отсутствует.


runfuses

Вызов: runfuses()

Выполняет все запалы с закончившимся сроком исполнения, если таковые есть. При наличии таких запалов возвращает true, в противном случае - nil. Выполняющиеся запалы могут быть инициированы как функцией setfuse, так и notify.


save

Вызов: save(имя_файла)

Сохраняет текущее состояние игры в файл, имя которого задается строковым аргументом имя_файла. Восстановление состояния игры осуществляется функцией restore.

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


say

Вызов: say(значение)

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

Функция не возвращает никакого значения.


setdaemon

Вызов: setdaemon(функция, значение)

Устанавливает функцию-демон. Значение передается в качестве единственного аргумента функции при каждом ее вызове в конце очередного хода.

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

  test: function;

При этом собственно определение функции test будет определено в файле позднее; вышеприведенная инструкция просто сообщает компилятору, что такая функция существует и может использоваться в вызовах setdaemon() и подобных им.

Максимальное число одновременно запущенных демонов не может превышать 100; обращение к setdaemon() при 100 активных демонах вызовет ошибку исполнения.

Функция не возвращает никакого значения.

Схожий принцип работы имеет функция notify().


setfuse

Вызов: setfuse(функция, тайм-аут, значение)

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

Примечание: функция должна быть известна компилятору до того, как он начнет обработку соответствующего вызова setfuse. В связи с этим функция (или хотя бы обращение к ней) должна быть определена в исходном коде раньше, чем этот вызов. Более подробно см. в описании функции setdaemon.

Максимальное число одновременно ожидающих выполнения запалов не может превышать 100; обращение к setfuse() при 100 активных запалах вызовет ошибку исполнения.

Функция не возвращает никакого значения.


setit

Вызов: setit(объект)

Устанавливает объект, которому будет поставлено в соответствие местоимение "это" в следующей команде игрока.

По умолчанию синтаксический анализатор (R)TADS, встретив в команде слово "это" или его синоним, пытается применить его к последнему "прямому" объекту, к которому обращался игрок, например:

  >осмотреть яблоко
  Это просто яблоко.
  
  >съесть его
  Яблоко было очень вкусным!
  

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

  >вниз
  Металлический люк закрыт.

  >открой его
  Этому препятствует стоящий на люке тяжелый сундук.

  >сдвинь его
  При помощи чего ты хочешь сдвинуть это?

  >лома
  Поддевая сундук снизу и наваливаясь всем своим весом на свободный конец лома, тебе через 
  некоторое время удается сдвинуть сундук так, чтобы доступ к люку был открыт.

В этом случае после каждой из первых двух команд выполнялась функция setit(). При использовании механизма установки местоимения по умолчанию игроку выдавались бы раздражающие его сообщения - после первой команды, к примеру, "Я не знаю на что Вы ссылаетесь словом "его"" (если до этого не было команд с "прямыми" объектами), а после второй - что-то вроде "Ты не можешь сдвинуть люк."

Функция не возвращает никакого значения.

Альтернативный формат вызова: setit( nil )

При использовании nil в качестве аргумента при вызове setit() объект, на который ссылается местоимение "это" в данный момент, аннулируется (при использовании игроком подобных местоимений в следующей команде будет выдано сообщение "Я не знаю на что Вы ссылаетесь словом "это"").

Альтернативный формат вызова: setit( объект, код )

При такой форме вызова вы можете определить, на какой объект должно ссылаться местоимение "он" или "она". Необязательный аргумент код задает местоимение (1 для "он", 2 для "она"). Если требуется аннулировать "ссылочный" объект, в качестве аргумента объект указывается nil.

Альтернативный формат вызова: setit( список_объектов )

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


setscore

Вызов: setscore(количество_очков, число_ходов)

Альтернативный формат вызова: setscore(строковое_значение)

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

Вызов этой функции целесообразно осуществлять из игровой программы каждый раз при изменении числа ходов (иначе говоря, после каждого хода) либо при изменении счета, обеспечивая тем самым отображение наиболее свежей информации в строке состояния. Это, в частности, означает, что такой вызов должен выполняться также после восстановления состояния игры при помощи функции restore, а также перед перезапуском игры посредством restart. Впрочем, если вы используете умолчальные механизмы подсчета ходов (функция-демон turncount, оперделенная в advr.t) и начисления очков (функция incscore, определенная там же), то о прямом вызове setscore, как правило, можно не беспокоиться.

Альтернативный формат вызова setscore был недоступен в ранних версиях TADS. Этот формат позволяет игре выводить в строке состояния любое строковое_значение, которое будет выравниваться по правому краю строки состояния. Например, можно вывести время суток (в мире игры), здоровье персонажа и т. д.

Обратите внимание, что ряд предопределенных функций в advr.t и stdr.t используют стандартный вызов setscore; если вы хотите использовать нестандартный формат строки состояния, потребуется соответствующим образом модифицировать эти вызовы.


setversion

Вызов: setversion(версия)

Примечание: данная функция более не используется, хотя (R)TADS по-прежнему поддерживает ее в целях обратной совместимости с играми, написанными с использованием совсем уж древних версий системы.

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


skipturn

Вызов: skipturn(число_ходов)

Данная функция аналогична incturn и обеспечивает пропуск заданного числа_ходов (которое должно быть не меньше 1). Единственное отличие этой функции от incturn состоит в том, что она не выполняет запалы, счетчики которых должны истечь в выбранном интервале ходов. Вместо этого такие запалы просто "втихую" удаляются. Это относитcя как к запалам, установленным при помощи setfuse, так и при помощи notify. На демоны данная функция не оказывает никакого эффекта.


substr

Вызов: substr(строка, начало, длина)

Функция возвращает подстроку строки, начинающуюся с символа с позицией начало и с количеством символов, соответствующим заданной длине. Например, при вызове substr('abcdefghi', 4, 3) будет возвращена строка 'def'. Если заданное начало окажется больше длины строки, будет возвращена пустая строка (обратите внимание, что пустая строка ('') и nil - это разные вещи). Если длина строки меньше, чем начало+длина-1, будут возвращены символы от позиции начало до конца строки; например, при вызове substr('abcdefg', 4, 20) будет возвращена строка 'defg'.


systemInfo

Вызов: systemInfo( __SYSINFO_xxx )

История: введена, начиная с версии TADS 2.2.4. Поддержка __SYSINFO_HTML_MODE и __SYSINFO_MPEG_AUDIO добавлена в версии 2.3.0.

Эта встроенная функция позволяет вашей игре определить на уровне программы, какие возможности поддерживает версия интерпретатора (R)TADS, в которой эта игра запущена в данный момент. Вызов функции имеет примерно следующий вид:

result := systemInfo( __SYSINFO_xxx );

где __SYSINFO_xxx - это одна из набора предопределенных компилятором констант (см. далее). Анализируя переменную result, вы сможете получить информацию о поддержке текущим интерпретатором той или иной (в основном мультимедийной) возможности (R)TADS, и в зависимости от этого модифицировать поведение игры. Например, вы можете захотеть слегка изменить текст выводимых игрой описаний в зависимости от того, может ли интерпретатор воспроизводить звуковые эффекты или нет. Другое применение - проверка поддержки тех или иных системных функций (например, поиска по шаблонам).

Однако прежде, чем вызывать systemInfo с теми или иными кодами-константами, необходимо убедиться, что поддерживается сама функция systemInfo. Более ранние версии интерпретатора TADS, чем 2.2.4, эту функцию не поддерживают, соответственно, и возвращаемые коды анализировать не имеет смысла. К счастью, наличие поддержки данной функции можно проверить, используя следующий фрагмент:

        if (systemInfo(__SYSINFO_SYSINFO) = true)
        {
            /*
             *  Функция systemInfo ПОДДЕРЖИВАЕТСЯ этим интерпретатором -
             *  обращение к ней позволит получить необходимые результаты
             */
        }
        else
        {
            /*
             *  systemInfo НЕ ПОДДЕРЖИВАЕТСЯ данным интерпретатором
             */
        }

В реальности была выпущена всего одна версия TADS с поддержкой мультимедийных возможностей, но без поддержки systemInfo: это была версия 2.2.3, которая имела к тому же статус первой публичной бета-версии. В связи с этим в подавляющем большинстве случаев (а для русскоязычных игр - вообще всегда) будет верным утверждение, что интерпретатор, не поддерживающий systemInfo, не будет поддерживать и мультимедийные возможности.

Для функции предопределены следующие коды-константы вида __SYSINFO_xxx:

__SYSINFO_VERSION Возвращает строку, содержащую номер версии интерпретатора (например, '2.5.10').
__SYSINFO_HTML

Возвращает 1, если интерпретатор поддерживает тэги HTML, и 0 в противном случае. Если возвращается 0, то можно исходить из того, что интерпретатор не поддерживает графику, звуковые эффекты и т. д.

Обратите внимание, что __SYSINFO_HTML возвращает 0 в том числе и для современных текстовых версий интерпретатора, имеющих ограниченную поддержку HTML, поскольку при вызове с этим кодом systemInfo проверяет наличие именно полноценной поддержки HTML.

__SYSINFO_HTML_MODE Возвращает true, если в интерпретаторе в данный момент включена поддержка тэгов, и nil в противном случае. При этом не проверяется, поддерживает ли текущая версия интерпретатора HTML; единственное назначение такого вызова systemInfo - определить, активна ли в данный момент последовательность "\H+" или нет.
__SYSINFO_OS_NAME Возвращает строку, содержащую название операционной системы, в которой запущен интерпретатор. (Название совпадает со строковым значением, используемым компилятором при определении символа препроцессинга __TADS_SYSTEM_NAME, однако позволяет определить операционную систему не на этапе компиляции, а во время выполнения игровой программы).
__SYSINFO_JPEG Возвращает 1, если интерпретатор поддерживает графику JPEG, и 0 в противном случае.
__SYSINFO_PNG Возвращает 1, если интерпретатор поддерживает графику PNG, и 0 в противном случае.
__SYSINFO_PNG_TRANS Проверяет поддержку прозрачных изображений PNG. Некоторые из более свежих версий интерпретатора HTML-TADS поддерживают прозрачные рисунки PNG - в этом случае отдельные области рисунка могут быть назначены прозрачными, и через них будет просвечивать фон. systemInfo(__SYSINFO_PNG_TRANS) вернет true, если эта возможность поддерживается, и nil, если нет (а также в случае, если интерпретатор вообще не поддерживает графику PNG). Речь идет о поддержке только базовой функции прозрачности (пиксель либо прозрачен, либо нет), без учета таких спецэффектов, как alpha blending. Возможность проверки поддержки прозрачности PNG была добавлена, начиная с версии TADS 2.5.4, и не поддерживается более ранними версиями интерпретатора.
__SYSINFO_PNG_ALPHA Проверяет, поддерживается ли спецэффект "alpha blending" для прозрачных рисунков PNG. В настоящее время ни один интерпретатор TADS этот эффект не поддерживает; проверка введена для возможного использования в будущем. Сама возможность такой проверки введена, начиная с версии TADS 2.5.4.
__SYSINFO_WAV Возвращает 1, если интерпретатор поддерживает звуковые файлы WAV, 0 в противном случае.
__SYSINFO_MIDI Возвращает 1, если интерпретатор поддерживает музыку MIDI, 0 в противном случае.
__SYSINFO_MIDI_WAV_OVL Возвращает 1, если эффекты MIDI и WAV могут проигрываться одновременно, и 0, если нет. В последнем случае при проигрывании файлов WAV воспроизведение файлов MIDI будет приостанавливаться.
__SYSINFO_WAV_OVL Возвращает 1, если интерпретатор позволяет одновременно проигрывать несколько звуковых файлов WAV, и 0, если нет. В последнем случае воспроизведение фоновых файлов WAV приостанавливается на время проигрывания файла "переднего плана".
__SYSINFO_MPEG_AUDIO Возвращает 1, если интерпретатор хоть как-то поддерживает формат MPEG 2.0, и 0, если нет. Если данная функция возвращает 1, то поддержку каждого конкретного субформата MPEG 2.0 необходимо проверять дополнительно (см. ниже).
__SYSINFO_MPEG_AUDIO_1 Возвращает 1, если поддерживается MPEG 2.0 layer I, и 0 в противном случае.
__SYSINFO_MPEG_AUDIO_2 Возвращает 1, если поддерживается MPEG 2.0 layer II, и 0 в противном случае.
__SYSINFO_MPEG_AUDIO_3 Возвращает 1, если поддерживается MPEG 2.0 layer III ("обиходное" название - MP3), и 0 в противном случае.
__SYSINFO_PREF_IMAGES Возвращает 1, если в пользовательских настройках включено отображение графики, и 0 в противном случае. Обратите внимание, что результаты проверки поддержки тех или иных форматов рисунков (см. коды __SYSINFO_JPEG и __SYSINFO_PNG) не зависят от пользовательских настроек.
__SYSINFO_PREF_SOUNDS Возвращает 1, если в пользовательских настройках включено воспроизведение звуковых эффектов WAV, и 0 в противном случае.
__SYSINFO_PREF_MUSIC Возвращает 1, если в пользовательских настройках включено воспроизведение фоновой музыки MIDI, и 0 в противном случае.
__SYSINFO_PREF_LINKS Возвращает 0, если в пользовательских настройках интерпретатора отображение гиперссылок отключено (в этом случае ссылки отображаются как текст и неактивны); 1, если гиперссылки включены; и 2, если включен режим "горячей клавиши" - в обычных условиях ссылки отключены, а при нажатии некой клавиши (для интерпретатора HTML-TADS под Windows это будет "Control") выделяются и могут быть использованы для перехода.
__SYSINFO_AUDIO_FADE Возвращает nil или 0, если система не поддерживает плавное изменение громкости звука (в HTML TADS, при помощи тега SOUND), либо комбинацию битовых флагов в противном случае. Битовые флаги носят названия __SYSINFO_AUDIOFADE_WAV, __SYSINFO_AUDIOFADE_MPEG, __SYSINFO_AUDIOFADE_OGG и __SYSINFO_AUDIOFADE_MIDI и означают поддержку данного функционала для соответствующих аудиоформатов (WAV, MP3, Ogg Vorbis и MIDI).
__SYSINFO_AUDIO_CROSSFADE Возвращает nil или 0, если система не поддерживает перекрестное плавное изменение громкости звука (в HTML TADS, при помощи тега SOUND), либо комбинацию битовых флагов в противном случае. Названия битовых флагов совпадают с таковыми для __SYSINFO_AUDIO_FADE и означают поддержку указанного функционала для соответствующих форматов аудиофайлов.

Начиная с версии 2.5.2, в интерпретаторе HTML-TADS появилась возможность указывать гиперссылки на страницы в сети Интернет (до этого гиперссылки могли использоваться только внутри игры). В связи с этим для функции systemInfo были введен ряд дополнительных кодов, служащих для проверки возможностей системы по переходу по URL-ссылкам вида <A HREF>:

__SYSINFO_LINKS_HTTP Проверяет, может ли система переходить по ссылкам HTTP (такие ссылки начинаются с "http:").
__SYSINFO_LINKS_FTP Проверяет, может ли система переходить по ссылкам FTP (такие ссылки начинаются с "ftp:").
__SYSINFO_LINKS_NEWS Проверяет, может ли система переходить по ссылкам на новостные группы USENET (такие ссылки начинаются с "news:").
__SYSINFO_LINKS_MAILTO Проверяет, может ли система создавать новое письмо адресату, чей адрес прописан в ссылке, при щелчке на последней (такие ссылки начинаются с "mailto:").
__SYSINFO_LINKS_TELNET Проверяет, может ли система переходить по ссылкам TELNET (такие ссылки начинаются с "telnet:").

Пусть, например, вы хотите дать из своей игры прямую гиперссылку на страничку, которую вы создали для этой игры в Интернете. Вы можете использовать вызов systemInfo(__SYSINFO_LINKS_HTTP) для того, чтобы проверить, поддерживает ли интерпретатор переход по таким ссылкам, и в зависимости от этого либо отобразить ссылку с использованием тэгов HTTP, либо просто выделить ее в тексте:

   "Последняя версия данной игры доступна ";
   if (systemInfo(__SYSINFO_LINKS_HTTP))
       // Система поддерживает переходы по HTTP-ссылкам
       "<A HREF='http://www.mysite.com/mygame.htm'>на моем сайте</A>";
   else
       // Прямой переход по гиперссылке невозможен
       "на моем сайте (http://www.mysite.com/mygame.htm)"
   ". Там же можно найти подсказки к игре, а также историю ее создания.";


timeDelay

Вызов: timeDelay(интервал)

История: введена, начиная с TADS 2.3.0

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

timeDelay( 5000 );


undo

Вызов: undo()

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

Однократный вызов undo() целесообразен в случае, если вам нужно отменить только события, произошедшие в течение текущего хода. Например, в обработчике, который предложит игроку в качестве опции отмену последнего действия после того, как тот оказался убит, достаточно будет вызвать undo() один раз.

При реализации в игре команды отмены последнего хода undo() следует вызывать дважды: первый раз отменяются результаты того хода, когда игрок ввел команду отмены, а при повторном вызове - предыдущего хода. Именно таким образом реализован стандартный глагол undoVerb в advr.t.

Функция undo() возвращает true, если операция отмены хода завершилась успешно, и nil, если не нашлось информации для отмены. Возникновение последней ошибки означает либо то, что игрок последовательными отменами вернулся к состоянию, соответствующему началу игры (или, как вариант, только что восстановленному из файла), либо что информация в буфере сделанных ходов исчерпана. Точный размер буфера ходов зависит от конкретной игры, однако типичное значение составляет около сотни ходов. При заполнении буфера из него удаляются наиболее ранние ходы.


unnotify

Вызов: unnotify((объект, &метод)

Данная функция отменяет действие предшествующего вызова notify() с теми же аргументами объект и метод (амперсанд перед аргументом метод указывать обязательно). Если было несколько вызовов notify() с одинаковыми аргументами, unnotify() отменяет только последний из них. При отсутствии "парного" вызова notify() unnotify() игнорируется.


upper

Вызов: upper(строка)

Функция возвращает строку, все латинские символы которой преобразованы к верхнему регистру. Не стоит путать ее с функцией caps(), которая переводит в верхний регистр только следующий выводимый символ.


verbinfo

Вызов: verbinfo(объект-глагол)

Альтернативный формат вызова: verbinfo(объект-глагол, предлог)

Функция позволяет получить информацию о методах-верификаторах и обработчиках doAction и ioAction для заданного объекта-глагола. Более подробную информацию см. в разделе Получение информации о глаголе главы, посвященной синтаксическому анализатору.


yorn

Вызов: yorn()

Ждет, пока игрок введет символ, и возвращает 1, если игрок ввел "Y", 0, если "N", и -1, если какой-либо другой символ. Понятно, что данная функция будет иметь в лучшем случае ограниченное применение для русскоязычных игр. При ее использовании автору необходимо позаботиться о выводе соответствующего запроса.


Динамическое создание объектов

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

  x := new bookItem;

При выполнении этой инструкции будет динамически создан новый объект, являющийся потомком класса bookItem. Если говорить более подробно, то выполняются следующие действия: создается новый объект, в качестве родительского класса ему назначается bookItem, после чего выполняется метод construct вновь созданного объекта; этот метод выполняет все необходимые (с точки зрения автора игры) действия на этапе создания объекта. Например, "умолчальный" метод construct класса thing в advr.t попросту перемещает вновь созданный объект в положенную ему локацию - это необходимо для того, чтобы в список содержимого (contents) этой локации был включен этот объект.

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

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

  x := new object;

Если вы знакомы с языком программирования C++, вы заметите важное отличие метода construct в (R)TADS от "сишных" конструкторов. В C++ конструктор класса-наследника автоматически вызывает конструктор родительского класса (за исключением виртуальных родительских классов). В (R)TADS этого не происходит; метод construct в этом отношении ничем не отличается от обычного метода (R)TADS. Конечно, ничто не мешает вам осуществить вызов метода construct родительского класса в явном виде, используя псевдообъект inherited. То же самое верно и для метода destruct (см. ниже).

Новый объект наследует все лексические свойства своего родителя.

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

  delete x;

При этом вначале вызывается метод destruct объекта, чтобы "сообщить" этому объекту, что он будет удален, а затем выполняет собственно удаление объекта. Дальнейшие обращения к этому объекту будут некорректны, поскольку занятая под него память уже освобождена системой (а может быть, уже выделена под другой объект). "Умолчальный" метод destruct класса thing в advr.t перемещает объект в nil, удаляя его тем самым из списка contents содержащего его объекта/локации.

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

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

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

Обратите внимание, что (R)TADS не выполняет "уборку мусора" после удаления динамически созданных объектов. В частности, система не в состоянии самостоятельно отследить, был ли тот или иной объект удален или нет. Поэтому, если вы "потеряете" объект, созданный при помощи new, он останется в составе игры до ее конца. В связи с этим необходимо с известной долей осторожности подходить к использованию динамических объектов, и принимать меры по недопущению переполнения памяти и файла подкачки объектами, с которыми вы все равно не можете взаимодействовать. Даже с учетом того, что (R)TADS в состоянии сам выгружать неиспользуемые объекты их памяти, вы все равно используете идентификаторы объекта, пул которых является ограниченным: в целом в игре можно создавать не более 65535 (статических и динамических) объектов.


Неразличимые объекты

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

Подробное описание соответствующего функционала приведено в главе Более сложные технические приемы синтаксического анализа.


Директивы препроцессинга

(R)TADS позволяет автору игры определять символы препроцессинга. Директива #define создает определение символа препроцессинга:

  #define TEST  "Это тест! "

При этом будет определен символ препроцессинга TEST; кроме того, эта же директива указывает, что данный символ необходимо заменять на текст "Это тест! " везде, где он встречается (за исключением комментариев и строковых значений). При этом символ необязательно должен быть определен в качестве строкового значения - в качестве значения символа используется весь текст после идентификатора символа до конца строки.

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

  sdesc =
  {
    TEST;
  }

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

  sdesc =
  {
    "Это тест! "
  }

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

  #undef TEST

Компилятор автоматически определяет ряд символов препроцессинга (обратите внимание, что их названия начинаются с двух значков подчеркивания):

__TADS_VERSION_MAJOR Определение символа содержит номер основной версии текущего компилятора (для TADS 2.x это всегда будет 2).
__TADS_VERSION_MINOR Номер подверсии TADS; в настоящее время равен 5.
__TADS_SYSTEM_NAME Строковое значение (в одинарных кавычках), определяющее название текущей операционной системы. Для DOS оно будет равно 'MSDOS'; для MacOS - 'Macintosh'. Кроме того, TADS определяет и отдельный символ, имя которого совпадает с текущим значением символа __TADS_SYSTEM_NAME (например, для DOS-систем будет определен еще и символ MSDOS). Значение этого символа всегда будет равно 1, но на самом деле оно не играет никакой роли - важен сам факт того, что символ определен (поскольку основное его назначение - использование при условной компиляции).
__DEBUG Этот символ определяется, если игра компилируется для отладки (задан ключ -ds командной строки или выбрана соответствующая команда в TADS Workbench). Проверяя, определен ли этот символ (при помощи директивы #ifdef), вы сможете включать на этапе компиляции куски кода, предназначенные специально для отладки (например, "магические" глаголы, позволяющие перемещаться в определенные комнаты), при этом не озабочиваясь необходимостью удалять этот код вручную при финальной компиляции. Если символ определен, его значение равно 1; поскольку он предназначен для использования при условной компиляции, конкретное его значение неважно.
__DATE__ Определяется как строковое значение (в одинарных кавычках), содержащее дату начала текущей компиляции (на английском языке), например: 'Jan 01 1994'.
__TIME__ Определяется как строковое значение (в одинарных кавычках), содержащее время начала текущей компиляции в следующем формате: '01:23:45'. Используется 24-часовой отсчет. В ходе компиляции это значение не обновляется - оно всегда соответствует именно началу процесса.
__FILE__ Определяется как строковое значение (в одинарных кавычках), содержащее имя файла исходного кода программы, компилируемого в настоящий момент.
__LINE__ Определяется как число, соответствующее номеру строки файла исходного кода, компилируемой в настоящий момент.

Символы препроцессинга можно определять также из командной строки компилятора. Опция -D позволяет задать идентификатор и значение определяемого символа. Для этого необходимо указать идентификатор символа, знак "равно" и значение символа. Если знак равенства не указывать, символ по умолчанию примет значение 1. Например, чтобы определить в программе символ с идентификатором DEMO, можно использовать следующий формат вызова программы-компилятора:

  tc32 -DDEMO mygame

Вы также можете удалить большую часть символов препроцессинга, которые компилятор определяет автоматически (за исключением символов __FILE__ и __LINE__). Можно удалить и символы, определенные ранее в командной строке при помощи опции -D, как описано выше; эта возможность может быть полезной, если для компиляции используется конфигурационный файл, содержащий те или иные символы. Для отмены определения символа следует использовать опцию -U:

  tc32 -UDEMO mygame

Если символ с идентификатором DEMO был ранее определен в командной строке, это определение будет аннулировано.

Вы можете проверить, существует ли определение того или иного символа препроцессинга, используя директиву #ifdef ("if defined" - "если определен"). Проверка на несуществование определения символа осуществляется посредством директивы #ifndef ("if not defined" - "если не определен").Эти директивы позволяют включать или не включать код в вашу программу в зависимости от того, определен тот или иной символ или нет, например:

  #ifdef TEST
  sdesc = { TEST; }
  #endif

Код между директивами #ifdef и #endif будет компилироваться только в том случае, если определен символ препроцессинга TEST. Кроме того, существует директива #else, позволяющая включать в компиляцию код в том случае, если предшествующая ей проверка при помощи #ifdef или #ifndef дала отрицательный результат.

Условная компиляция особенно эффективна в сочетании с символами, определяемыми вами из командной строки при помощи опции -D, поскольку позволяет отключать/задействовать те или иные возможности игры в зависимости от набора символов препроцессинга, определенных в командной строке. Это позволяет автору, имея один и тот же набор файлов исходного кода, создавать разные варианты своей игры. Например, если вы хотите выпустить урезанную демо-версию, вы можете использовать уcловную компиляцию для того, чтобы включать в игровой мир или исключать из него те или иные локации. Также можно использовать условную компиляцию для создания отладочных версий игры, включая в них, например, "магические" глаголы для мгновенного перемещения в тестируемую локацию. В последнем случае компилятор даже облегчает задачу, автоматически определяя символ __DEBUG, если игрок выбрал отладочный режим компиляции. (Подробнее об опциях компилятора можно прочитать в Приложении D).

Была введена также директива #error, позволяющая генерировать ошибку из игровой программы. Любой текст, следующий за этой директивой, выводится на экран в качестве сообщения об ошибке, например:

  #ifndef TEST
  #error Переменная TEST не определена!
  #endif


Работа с файлами

(R)TADS позволяет игровой программе считывать данные из внешних файлов и осуществлять запись в них. Данная возможность предназначена для того, чтобы обеспечить независимый от стандартных средств сохранения/записи игры способ передачи информации между сеансами игры (например, для сохранения настроек конкретного игрока). (R)TADS работает как с двоичными файлами внутреннего формата, так и с простыми текстовыми файлами.

Все операции над файлами в (R)TADS осуществляются при помощи так называемого дескриптора файла (ДФ). ДФ - это генерируемое системой TADS значение (номер), которое можно впоследствии использовать для обращения к файлу. ДФ создается автоматически при открытии файла; после получения дескриптора вы можете записывать данные в файл и считывать их. После завершения работы с файлом он закрывается, что приводит к аннулированию его дескриптора.

Для открытия файла используется функция fopen(). Этой функции передаются два аргумента: строковое значение (в одинарных кавычках), определяющее имя открываемого файла (с учетом правил его формирования, принятых в данной системе), и флаг режима. Флаг режима может принимать одно из следующих значений (или быть их комбинацией):

"b" - открыть файл как двоичный. Если вид файла не указан, по умолчанию принимается двоичный (в целях совместимости с предыдущими версиями).

"r" - открыть файл для чтения (файл должен существовать).

"r+" - открыть файл для чтения и записи; если на момент открытия файл не существует, он создается заново. В настоящее время этот режим не поддерживается для текстовых файлов.

"t" - открыть файл как текстовый. К текстовым файлам можно обращаться в режимах "r" (только чтение) и "w" (только запись). Режимы "r+" и "w+" для текстовых файлов не поддерживаются. Обратите также внимание, что поддержка работы с текстовыми файлами появилась, начиная с версии TADS 2.2.4.

"w" - создание нового файла для записи; если файл существует на момент открытия, он перезаписывается.

"w+" - создание нового файла для чтения и записи; если файл существует на момент открытия, он перезаписывается. В настоящее время этот режим не поддерживается для текстовых файлов.

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

Возвращаемым значением fopen() является дескриптор файла, который впоследствии можно использовать для обращений к файлу. Если выполнение функции окончилось неудачно и открыть файл в заданном режиме не удалось, fopen() возвращает nil. Попытка открытия файла может завершиться неудачей по целому ряду причин. Например, если попытаться открыть несуществующий файл в режиме чтения ("r"), то fopen() вернет nil, поскольку такой режим открытия предполагает, что файл уже существует.

В нижеприведенном примере файл с именем TEST.OUT открывается для записи:

  fnum := fopen( 'test.out', 'w' );

Для закрытия файла используйте функцию fclose():

  fclose( fnum );

После того, как вы закрыли файл, его дескриптор более недействителен. В то же время (R)TADS может повторно использовать одни и те же дескрипторы при повторных обращениях к fopen(). Обратите внимание, что в (R)TADS возможно одновременное открытие только ограниченного числа файлов (в настоящее время не более десяти), поэтому, завершив работу с файлом, желательно его сразу же закрыть. (На практике вам вряд ли понадобится работать более чем с одним файлом одновременно - примечание переводчика.)

Для записи данных в файл используется функция fwrite(). В качестве аргументов ей передаются дескриптор файла и значение, которое необходимо записать; это значение может быть строковым, числовым или равным true. Обратите внимание, что nil в файл записать нельзя. Это связано с тем, что функция чтения из файла fread() возвращает nil в случае неудачи. Таким образом, если разрешить запись nil в файл, то у системы не будет возможности отличить "легитимное" считанное значение nil от состояния ошибки. Функция fwrite() записывает в файл само значение и информацию о его типе. (Примечание переводчика: последняя фраза относится, конечно, к случаю записи в двоичный файл.)

Функция fwrite() возвращает nil при успешном завершении и true при сбое. В последнем случае причиной сбоя скорее всего (хотя и не всегда) является переполнение диска.

  if ( fwrite( fnum, 'строковое значение' ) or fwrite( fnum, 123 ) )
   "Ошибка записи в файл!";

Если файл открыт для чтения, вы можете читать из него данные при помощи функции fread(). В качестве аргумента ей передается дескриптор файла; в качестве возвращаемого значения используется то значение, которое будет считано. Тип этого значения будет совпадать с тем, который оно имело, когда функция fwrite() записывала его в файл. Если fread() вернет nil, это означает, что произошла ошибка чтения; как правило, это означает, что достигнут конец файла.

  res := fread( fnum );
  say( res );

Вы можете получить текущую позицию маркера в файле при помощи функции ftell():

  "Текущее положение маркера чтения в файле - "; ftell( fnum ); ". ";

Функция ftell() возвращает позицию от начала файла (в байтах) записи, которая будет считана/записана при следующем обращении к файлу.

Вы можете спозиционировать маркер в файле при помощи функций fseek() и fseekeof(). Первая из них перемещает маркер на определенную позицию, используя в качестве точки отсчета начало файла. Например, следующая команда установит маркер в самое начало файла:

  fseek( fnum, 0 );

Функция fseekeof() перемещает маркер в конец файла (EOF означает "End Of File" - конец файла):

  fseekeof( fnum );

Обратите внимание, что при использовании fseek() следует соблюдать определенную осторожность. В качестве позиции ей следует передавать только значения, полученные ранее при помощи функции ftell(); в противном случае маркер может быть спозиционирован, например, посреди строкового или числового значения. Попытка записи в случайную позицию файла может привести к его порче, так как существующие данные будут частично затерты.

Двоичные файлы (R)TADS не предназначены для обмена данными с другими программами - иначе говоря, если не пускаться на ухищрения, они могут использоваться только играми, написанными в (R)TADS. В то же время эти файлы будут "понятны" интерпертатору (R)TADS вне зависимости от операционной системы (например, файлы, созданные в Windows, будут читаться в MacOS). Если же вам требуется обмениваться данными с другими программами, используйте текстовый формат.

Некоторые игроки весьма болезненно относятся к возможности того, что злонамеренный автор игры, используя функциональность работы с файлами, потенциально может нанести ущерб операционной системе. В связи с этим TADS, начиная с версии 2.2.4, позволяет установить так называемый уровень безопасности файловых операций. Эта дополнительная настройка позволяет ограничить доступ к файлам из игры.

Уровень безопасности файловых операций задается для DOS-интерпретатора из командной строки, а в HTML-TADS доступен из меню настроек пользователя. Всего существует пять уровней безопасности, перечисленные ниже (с указанием соответствующей опции командной строки):

-s0 - (по умолчанию) минимальная безопасность - возможность записи и чтения файлов в любом каталоге
-s1 - чтение из любого каталога, запись только в текущий каталог
-s2 - только чтение из любого каталога
-s3 - только чтение, только из текущего каталога
-s4 - максимальная безопасность - файловые операции запрещены

Если игра пытается выполнить файловую операцию, запрещенную для текущего уровня безопасности, функция fopen() завершается с ошибкой, возвращая nil.

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




Нашу жизнь рвут в клочья детали Упрощай, упрощай.
ГЕНРИ ДЭВИД ТОРО (HENRY DAVID THOREAU), Где я жил и зачем я жил (1854)

Написано было интересно, только не очень понятно.
МАРК ТВЕН (MARK TWAIN), Приключения Гекльберри Финна (1884)


Глава четвертая Содержание Глава шестая