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

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

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

Система Разработки Текстовых Прикюченческих Игр (Text Adventure Development System, TADS)


Обзорная документация

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

Данный раздел - это свод основной информации в полном Руководстве по TADS для авторов игр, которое также распространяется свободно. Этот обзор был подготовлен в целях быстрого введения в TADS.

Комментарий tela design от октября 1996 г.: настоящий файл является результатом преобразования оригинальной shareware-документации в формат HTML. Пока TADS не стал программой, распространяемой свободно, этот файл был единственной частью документации, доступной для незарегистрированных пользователей. Примечание переводчика: русская версия TADS - RTADS имеет ряд особенностей, обусловленных отличиями структуры русского и английского языков. В переводе по мере возможности учтены эти отличия.


"Блуждания в Окопный День"

Вместе с TADS поставляется большая демонстрационная игра, Ditch Day Drifter (в русском варианте - "Блуждания в Окопный День"). Исходный текст игры помещен в файле DITCHR.T (русская версия). Эта игра была включена в комплект поставки как в целях демонстрации возможностей TADS, так и для того, чтобы на наглядном примере помочь вам в написании собственных игр на TADS. После того, как вы прочтете описание TADS, вам может показаться полезным просмотреть файлы DITCH.T и DITCHR.T для ознакомления с дополнительными примерами написания программ на TADS. Русская версия

Чтобы поиграть в "Блуждания...", под Windows нужно запустить программу TADS Workbench, после чего выбрать пункт Load Game (загрузить игру) в меню File (файл). В открывшемся диалоге следует выбрать файл ditchr.t. Система запросит дополнительную информацию - всего будет три всплывающих окна. В первых двух нажимаем кнопку Далее, в третьем - Готово. Затем выбираем пункт Compile and Run (скомпилировать и запустить игру) в меню Build (компилирование).

На системах с Macintosh запустите компилятор двойным щелчком мыши по приложению "TADS Compiler". Компилятор откроет диалоговое окно с рядом параметров. Щелкните по кнопке "Select" рядом с полем "Filename", затем выберите файл DITCHR.T, используя стандартный диалог выбора файлов. После этого щелкните по кнопке "Compile". После того, как компилятор закончит работу, щелкните по кнопке "Quit". Теперь вы можете запустить игру либо двойным щелчком по новому документу DITCHR.GAM, либо дважды щелкнув по приложению "TADS Run-Time" и затем выбрав DITCHR.GAM в диалоге выбора файлов.


Что такое TADS?

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

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


Написание игр в TADS

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


Базовые навыки - демонстрационная игра

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

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

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

  /* Это просто комментарий, такой же, как, например, в языке Си */
#define USE_HTML_STATUS             /* Определение, необходимое для правильного форматирования переноса строк в HTML-TADS */
#include <advr.t>             /* включить файл с основными определениями объектов для текстовых квестов (русская версия)*/
#include <generator.t>              /* включить файл с генератором падежных форм - доступен, начиная с версии 26 библиотек RTADS */
#include <stdr.t>                  /* включить файл определений стандартных функций, вызываемых при старте (русская версия) */
#include <errorru.t>              /* включить файл с русскими сообщениями об ошибках */

  startroom: room                     /* по умолчанию, игра начинается в комнате с названием startroom */
    sdesc = "У пещеры"              /* краткое опиание комнаты (от английского Short DESCription) */
    ldesc = "Ты стоишь на ярко освещенном месте прямо рядом с большой, темной и мрачной
            пещерой, лежащей к северу. "
    north = cave                 /* Комната под названием "cave" находится к северу */
  ;

  cave: room
    sdesc = "Пещера"
    ldesc = "Ты находишься внутри темной и затхлой пещеры. Солнечный свет прорывается
            через проход, расположенный к югу. "
    south = startroom
  ;

Для запуска примера используйте команду Compile and Run (скомпилировать и запустить игру) в меню Build (компилирование) программы TADS Workbench. Если по каким-то причинам вы в TADS Workbench работать не хотите или не можете, скомпилируйте текстовый файл с использованием программы TC32 - консольного компилятора TADS, и запустите получившийся файл при помощи плеера HTML-TADS либо консольного проигрывателя TR32 из командной строки. Если вы назвали ваш файл с примером игры "mygame.t", то в большинстве операционных систем вы сможете скомпилировать его командой

  tc32 mygame

и запустить игру при помощи команды

  tr32 mygame

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

Рассмотрим данный пример построчно.

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

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

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

Третья строка включает в текст игры файл generator.t из состава дистрибутива RTADS. Это - встроенный в систему генератор падежных форм, о его назначении будет рассказано далее. Данный функционал доступен, начиная с 26 версии библиотек RTADS. Если вы по какой-либо причине работаете с более старой версией библиотек, данную строку необходимо исключить.

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

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

Следующей строкой - "startroom: room" - вы сообщаете компилятору, что вами определена комната (объект room) с названием "startroom". С точки зрения языка TADS, само по себе слово "room" ничего не значит, однако в ранее включенном нами файле advr.t содержится определение объекта "room". Комната (room) - это один из упомянутых нами ранее объектных классов.

Далее определяется атрибут "sdesc" для этой комнаты. Атрибут sdesc представляет собой краткое описание (от английского Short DESCription); для комнаты оно отображается всякий раз, когда игрок заходит в комнату. Следующая строка определяет атрибут "ldesc" - полное (или "длинное") описание (от английского Long DESCription); оно отображается, когда игрок заходит в комнату впервые либо запрашивает полное описание комнаты командой "осмотреться". Наконец, атрибут "north" указывает, что игрок, находясь в комнате startroom, может перейти в другую комнату, под названием "cave", набрав команду "север".

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

pedestal: surface, fixeditem
   sdesc = "пьедестал"
   rdesc = "пьедестала"
   ddesc = "пьедесталу"
   vdesc = "пьедестал"
   tdesc = "пьедесталом"
   pdesc = "пьедестале"
   noun = 'пьедестал' 'пьедестала' 'пьедесталу' 'пьедесталом' 'пьедестале'
          'пьедесталу#d' 'пьедесталом#t' 'постамент' 'постамента' 'постаменту' 
          'постаментом' 'постаменте' 'постаменту#d' 'постаментом#t'
   isHim = true
   location = cave
;

goldSkull: item
   sdesc = "золотой череп"
   rdesc = "золотого черепа"
   ddesc = "золотому черепу"
   vdesc = "золотой череп"
   tdesc = "золотым черепом"
   pdesc = "золотом черепе"
   noun = 'череп' 'черепа' 'черепу' 'черепом' 'черепу#d' 'черепом#t' 'черепе' 
   adjective = 'золотой' 'золотого' 'золотому' 'золотым' 'золотом' 'золотому#d' 
               'золотым#t'
   isHim = true
   location = pedestal
;

Как и для объектного класса "room", классы "surface", "fixeditem", and "item" также не являются встроенными в TADS, а определены в файле advr.t. Обратите внимание, что объект можно определять, используя не один, а несколько классов; в данном примере объект "pedestal" (пьедестал) одновременно относится к классам "surface" (поверхность) и "fixeditem" ("неберущийся" предмет). Поверхность (surface) - это объект, на который можно помещать (ставить, класть) другие объекты; "неберущийся" предмет (fixeditem) - это объект, который игрок не может взять и носить с собой. Класс объекта "goldSkull" - это так называемый предмет (item), который представляет собой объект, который игрок может поднять/взять и носить с собой.

Как и для комнаты (room), атрибут sdesc содержит краткое описание объекта или, иначе говоря, имя/название объекта. Для этих объектов не определен атрибут ldesc, поэтому для них по умолчанию будет отображаться подробное описание, определенное соответствующим классом. Например, для предмета (item) подробное описание будет звучать так: "Это кажется тебе обычным золотым черепом.". Для поверхности подробное описание содержит перечисление объектов, находящихся на ней.

Специально для русского языка дополнительно определяются атрибуты rdesc, ddesc, vdesc, tdesc и pdesc, которые просто представляют собой краткое описание объекта соответственно в родительном, дательном, винительном, творительном и предложном падежах.

Поскольку игрок может взаимодействовать с этими объектами, им необходимо поставить в соответствие некие слова, т. е. фактически "научить" игру "понимать" названия объектов. Атрибуты "noun" (существительное) и "adjective" (прилагательное) определяют слова, при помощи которых игрок может указать интерпретатору, какой именно объект он имеет в виду (или, проще говоря, назвать объект). Обратите внимание, что значения атрибутов sdesc и ldesc заключены в двойные кавычки, а значения атрибутов с названиями - в одинарные. Также обратите внимание, что атрибуты noun и adjective могут иметь несколько значений. Причем для русского языка их, как правило, будет несколько, поскольку потребуется определить все падежные формы для каждого слова. Кроме того, помимо обычных шести падежных форм, для каждого из основных синонимов прилагательных и существительных необходимо определить специальные формы, совпадающие с дательным и творительным падежом, но имеющие на конце суффиксы #d и #t, соответственно. Это требуется в связи с некоторыми особенностями работы TADS с падежами (а точнее, для обхода отсутствия падежей в стандартном TADS). Подробнее см. далее в документации.

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

pedestal: surface, fixeditem
   desc = 'пьедестал/1м'
   noun = 'пьедестал/1м' 'постамент/1м'
   isHim = true
   location = cave
;

goldSkull: item
   desc = 'золотой/1мп череп/1м'
   noun = 'череп/1м'
   adjective = 'золотой/1мп'
   isHim = true
   location = pedestal
;

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

Атрибут isHim, равный true, означает, что соответствующий предмет имеет мужской род. Для предметов женского рода необходимо определять атрибут isHer, равный true, а для среднего рода ни один из этих атрибутов определять не нужно.

Объекты имеют и еще один дополнительный атрибут: "location" (местоположение). Этот атрибут попросту указывает на объект, содержащий определяемый объект. Для объекта pedestal местоположением является комната "cave", а поскольку goldSkull находится на пьедестале, то его местоположение - объект "pedestal".

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

  goldSkull: item
   sdesc = "золотой череп"
   rdesc = "золотого черепа"
   ddesc = "золотому черепу"
   vdesc = "золотой череп"
   tdesc = "золотым черепом"
   pdesc = "золотом черепе"
   noun = 'череп' 'черепа' 'черепу' 'черепом' 'черепу#d' 'черепом#t' 'черепе' 
   adjective = 'золотой' 'золотого' 'золотому' 'золотым' 'золотом' 'золотому#d' 
               'золотым#t'
   isHim = true
   location = pedestal
   doTake(actor) =
   {
     "Едва ты успеваешь поднять череп, как из стен вылетает целая туча
      отравленных стрел! Ты пытаешься увернуться от них, но их слишком много!";
      die();
   }
;

Или, для библиотек начиная с 26-ой версии:

  goldSkull: item
   desc = 'золотой/1мп череп/1м'
   noun = 'череп/1м'
   adjective = 'золотой/1мп'
   isHim = true
   location = pedestal
   doTake(actor) =
   {
     "Едва ты успеваешь поднять череп, как из стен вылетает целая туча
      отравленных стрел! Ты пытаешься увернуться от них, но их слишком много!";
      die();
   }
;

Определение атрибута doTake (что означает Direct Object Take - обработка глагола взять для "прямого" объекта (объекта непосредственной манипуляции)) использует в качестве аргумента персонаж (actor), который пытается взять объект (поскольку в данном случае у нас нет других персонажей, кроме игрока, здесь будет фигурировать главный персонаж (ГП), которому соответствует объект "Me"("Я")). Система вызывает doTake каждый раз, когда игрок пытается взять объект. Обратите внимание, что данное конкретное определение атрибута doTake связано именно с самим этим объектом; другой объект может иметь свое собственное определение атрибута doTake, который будет выполнять совершенно другие действия. В данном случае мы просто выводим сообщение (текст, заключенный в двойные кавычки, просто выводится на экран в процессе выполнения), а затем вызывает функцию "die", определенную в stdr.t.

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

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

smallRock: item
   sdesc = "мелкий камень"
   rdesc = "мелкого камня"
   ddesc = "мелкому камню"
   vdesc = "мелкий камень"
   tdesc = "мелким камнем"
   pdesc = "мелком камне"
   noun = 'камень' 'камня' 'камню' 'камнем' 'камне' 'камню#d' 'камнем#t'
   adjective = 'мелкий' 'мелкого' 'мелкому' 'мелким' 'мелком' 'мелкому#d' 'мелким#t'
   isHim = true
   location = cave
;

Либо для новых версий стандартных библиотек:

smallRock: item
   desc = 'мелкий/1мп камень/1м'
   noun = 'камень/1м'
   adjective = 'мелкий/1мп'
   isHim = true
   location = cave
;

Теперь поменяем определение атрибута doTake объекта goldSkull. Замените старое определение doTake приведенным ниже кодом.

doTake(actor) =
   {
      if (self.location <> pedestal or smallRock.location = pedestal)
      {
         pass doTake;
      }
      else
      {
         "Едва ты успеваешь поднять череп, как из стен вылетает целая туча
         отравленных стрел! Ты пытаешься увернуться от них, но их слишком много!";
         die();
      }
   }

В этом новом виде атрибут doTake сначала проверяет, находится ли объект, который берут (специальный объект "self" (сам), который является объектом, чей doTake в данный момент выполняется), в данном случае золотой череп, уже НЕ на пьедестале. Если это так, ничего происходить не должно, и мы просто "передаем" (pass) doTake. Мы также передаем doTake, если мелкий камень находится на пьедестале. Когда мы передаем doTake, запускается атрибут doTake, унаследованный объектом от его родительского класса (в данном случае item). Это позволяет нам заменять определение атрибута только в специальных случаях, а в остальных использовать его поведение по умолчанию. Если камень не положили на пьедестал и череп пытаются снять с пьедестала, происходит запуск отравленных стрел, как и в предыдущем случае.

В дистрибутив входит файл под названием GOLDSKUL.T (в английском варианте; русский вариант (файл GOLDSKLR.T - версия для старых версий системных библиотек; GSKLRU26.T - для библиотек со встроенным генератором) можно найти на http://www.rtads.org), включающий данную демо-игру, в том числе и всю задачку с золотым черепом и пьедесталом в законченном виде.

Если вы компилируете файл из интегрированной среды разработки TADS (TADS Workbench) командами Compile and Run (скомпировать и запустить) или Compile for Debugging (компилировать для отладки) из меню Built (компиляция), то при запуске игры будет выводиться перечень сгенерированных словоформ. Это делается для того, чтобы автор имел возможность просмотреть этот перечень и выявить ошибки, допущенные генератором падежей (они нечасто, но встречаются). Если такие ошибки обнаружены, то для соответствующего слова падежные формы надо будет прописать вручную. Однако, если вы отправите информацию об этой ошибке автору русифицированных библиотек, в следующей их версии она будет устранена. Для того, чтобы перечень словоформ не выводился, используйте для компиляции команду Compile for Release (компилировать для релиза).

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


Язык TADS

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

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

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

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

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

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

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


Создание комнаты

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

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

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

Вот пример определения комнаты.

  nsCrawl:  room
    sdesc = "Узкий лаз, ведущий с севера на юг"
    ldesc = "Вы находитесь в узком проходе, соединяющем пещеры, расположенные к северу и югу. 
        С севера тянет холодом и сыростью."
    north = iceRoom
    south = roundRoom
  ;

Первая строка определения объекта указывает имя объекта - в данном случае "nsCrawl", а также тип объекта. Обратите внимание, что регистр букв в имени имеет значение - "nsCrawl", "NSCrawl" и "nscrawl" будут восприниматься системой как разные имена, а следовательно, и объекты.

Следующая строка определяет одно из свойств/атрибутов объекта - sdesc, или краткое описание. Строка означает, что свойству sdesc присвоено значение "Узкий лаз, ведущий с севера на юг". Значение sdesc отображается всегда, когда пользователь заходит в комнату, а также используется в строке состояния.

В TADS заключенные в двойные кавычки строки обрабатываются специальным образом: двойные кавычки означают, что заключенная в них строка должна отображаться всякий раз, когда происходит выполнение соответствующего свойства. Использование кавычек аналогично инструкции PRINT в других языках программирования. Таким образом, каждый раз при обращении к свойству sdesc объекта nsCrawl на экране будет отображаться строка "Узкий лаз, ведущий с севера на юг".

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

Следующие две строки определяют свойства-направления - north (север) и south (юг). Этим свойствам в качестве значений присвоены ссылки на другие объекты-комнаты. Они определяют, что игрок, выходя из данной комнаты на север, попадет в комнату, определяемую объектом iceRoom, а выходя на юг - в комнату roundRoom. Обратите внимание, что другие направления не указаны - это просто означает, что игрок не сможет перемещаться в других направлениях. Другие возможные свойства-направления: east (восток), west(запад), up (вверх), down (вниз), in (войти), out (выйти), ne (СВ - северо-восток), se (ЮВ - юго-восток), nw (СЗ - северо-запад) и sw (ЮЗ - юго-запад).

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


Создание предметов

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

   dollar: item
      location = nsCrawl
      sdesc = "один доллар"
      rdesc = "одного доллара"
      ddesc = "одному доллару"
      vdesc = "один доллар"
      tdesc = "одним долларом"
      pdesc = "одном долларе"
      noun = 'доллар' 'доллара' 'доллару' 'долларом' 'долларе' 'доллару#d' 'долларом#t'
             'банкнота' 'банкноты' 'банкноте' 'банкноту' 'банкнотой' ''банкноте#d' ''банкнотой#t'
      adjective = 'один' 'одного' 'одному' 'одним' 'одном' 'одному#d' 'одним#t' '1'
      isHim = true
   ;

   rope: item
      noun = 'веревка' 'веревки' 'веревке' 'веревку' 'веревкой' 'веревке#d' 'веревкой#t'
             'шнур' 'шнура' 'шнуру' 'шнуром' 'шнуре' 'шнуру#d' 'шнуром#t'
      sdesc = "веревка"
      rdesc = "веревки"
      ddesc = "веревке"
      vdesc = "веревку"
      tdesc = "веревкой"
      pdesc = "веревке"
      ldesc = "Эта веревка длиной 50 футов выглядит довольно прочной; 
              похоже, она может выдержать груз в несколько сот фунтов."
      isHer = true
      location = backpack
   ;

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

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

Для всех предметов необходимо определять сравными true войства isHim и isHer, определяющие, соответственно, мужской или женский род предмета, для которого они определены. Если их не определить, то возникнут проблемы с согласованием падежей для определенных по умолчанию реакций TADS, а также при обращении к предметам при помощи местоимений - например, в следующей ситуации:

> осмотреть камень

Это выглядит как обычный камень.

> взять его
Для объектов среднего рода эти свойства определять не нужно. Для объектов во множественном числе необходимо определять равным true свойство isThem (при этом свойства isHim и isHer определять необязательно).

В русском языке в лексических свойствах для каждого синонима необходимо дополнительно указывать все его падежные формы (конечно, если они различны - например, для слова "веревка" дательный падеж совпадает с предложным ("веревке"), и эту форму достаточно определить один раз). Кроме того, для дательного, творительного, а в некоторых случаях - для родительного падежей каждого из синонимов требуется указывать слова-двойники соответствующих форм, дополненных соответственно суффиксами #d, #t и #r. Это требуется в связи с необходимостью обеспечить распознавание падежей русского языка в TADS - подробнее об этом см. далее в документации. Наконец, помимо краткого описания предмета, необходимо определить его краткие описания во всех других падежных формах, что требуется для правильного согласования падежей при выводе реакций на попытки манипуляций над объектом со стороны игрока; эти описания содержатся в свойствах rdesc, ddesc, vdesc, tdesc и pdesc.

Начиная с 26-й версии системных библиотек в составе RTADS появился генератор падежных форм, позволяющий сделать определения лексических свойств объектов гораздо менее громоздкими и сократить объем ручного ввода текста. Вместо свойств sdesc, rdesc и т. д. достаточно будет определить свойство desc, а для лексических свойств noun и adjective - всего по одной форме для каждого используемого синонима. Например, вышеуказанное определение для веревки примет следующий вид:

    rope: item
      desc = 'веревка/1ж'
      noun = 'веревка/1ж' 'шнур/1м'
      ldesc = "Эта веревка длиной 50 футов выглядит довольно прочной; 
              похоже, она может выдержать груз в несколько сот фунтов."
      isHer = true
      location = backpack
   ;

Попутно отметим, что для одного доллара настолько сократить определение не удастся, так как генератор пока не умеет работать с числительными.

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

Флаги, указывающие на число:
"1" - единственное число;
"2" - множественное число (нельзя использовать вместе с предыдущим флагом).
Флаги, указывающие на род:
"m" (латинская) или "м" (русская) - мужской род;
"f" или "ж" - женский род;
"n" или "с" (русская) - средний род. Вместо этого флага можно указать одновременно флаги "м" и "ж".
Флаги, указывающие на часть речи:
"a" (латинская) или "п" - указывает на то, что данное слово является прилагательным (adjective);
"у" (русская) - указывает на то, что данное слово является существительным (если вообще не указывать флаг, указывающий на часть речи, слово в любом случае будет трактоваться как существительное, поэтому данный флаг практически не используется).
Дополнительные флаги:
"i" или "о" (русская) - указывает на то, что слово обозначает одушевленный (animated) объект;
"u" или "д" - указывает на ударное (accentuated) окончание слова. Вместо указания данного флага можно написать в окончании прописную букву;
"-" - указывает, что слово не должно склоняться; как правило, задается для существительных-определений в сочетаниях вида "голова жирафа".

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

флаги "1" и "2";

флаги "м" и "с", а также "ж" и "с";

флаги "п" и "у";

флаг "-" и флаги числа, рода и дополнительные.

В качестве примера приведем определение тестового объекта из файла generator.t.

testobj: item
desc='странный/1мп предмет/1м из иного/п- мира/п-'
adjective = 'плоский/1м'  'акульи/2'
noun='воблы/2ж' 'крюки/м2' 'рука/1ж' 'вилы/2' 'щепочка/1ж' 'быстрая/п1ж' 'блоха/ж' 'муравьИ/2ом'
;

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

Обратите внимание, что свойства могут быть определены в любом порядке. Также нет необходимости обязательно определять все свойства; например, если для предмета не задано свойство ldesc, будет отображаться сообщение по умолчанию ("Это кажется тебе обычным одним долларом"). Однако практически для любого предмета потребуется определить свойства desc (при использовании генератора) либо sdesc, rdesc, ddesc, vdesc, tdesc, pdesc (если генератор не используется), а также location и noun.

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


Контейнеры

Вам часто понадобится создавать предметы, которые могли бы содержать другие предметы. Для этого просто определите объект класса "container" (контейнер), а не "item". Во всех остальных отношениях объект класса "container" ничем не отличается от объекта класса "item", в частности, у них совпадает набор свойств. Пример определения такого объекта:

   backpack: container
      desc = 'рюкзак/1м'
      location = maintenanceRoom
      noun = 'рюкзак/1м'
      isHim = true
   ;

Помимо объектов-контейнеров вы можете создавать объекты класса "openable" (открываемые контейнеры). Это те же контейнеры, только их можно открывать и закрывать. При создании такого контейнера для него определяется дополнительное свойство - "isopen", которое является флагом, указывающим, открыт контейнер или закрыт. Если вы хотите, чтобы изначально контейнер был закрыт, установите isopen = nil. Пример:

   toolbox: openable
      desc = 'инструментальный/1мп ящик/1м'
      location = maintenanceRoom
      noun = 'ящик/1м'
      adjective = 'инструментальный/1мп'
      isHim = true
      isopen = nil
   ;


"Неберущиеся" предметы

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

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

Пример:

   wardrobe: fixeditem
      location = bedroom
      noun = 'шкаф/1м'
      sdesc = 'шкаф/1м'
      isHim = true
   ;


Наследование и ADV.T (ADVR.T)

До сих пор мы просто описывали различные классы объектов, которые вы можете создавать, и свойства, которые вам необходимо определять при создании этих объектов. Может сложиться впечатление, что эти объектные классы каким-либо образом встроены в TADS. На самом деле ни один из этих классов не является чем-то особенным; все они определены с использованием языка TADS.

Объекты, с которыми мы встречались - комната (room), предмет (item), контейнер (container), открываемый контейнер (openable), "неберущийся" предмет (fixeditem) - определены в файле ADV.T (в русской версии - ADVR.T; далее речь пойдет только о ADVR.T, но все то же самое справедливо и для ADV.T). Этот файл представляет собой набор определений объектных классов, которые пригодны для большинства игр, но ничего особенного в них нет, т. е. они ничем принципиально не отличаются от других объектов, определяемых пользователем. В терминах программирования этот набор определений представляет собой не встроенные средства языка со скрытой от пользователя реализацией, а библиотеку подпрограмм с открытым кодом. Вы даже можете полностью переписать файл ADVR.T, чтобы повысить своеобразие вашей игры.

Обратите внимание, что объекты тех классов, которые определены в ADVR.T (или в вашем собственном наборе определений) создаются чрезвычайно легко - как если бы эти классы были встроены в TADS. Метод, который обеспечивает такую легкость, называется "наследование". Язык TADS позволяет определять объект таким образом, чтобы он наследовал (перенимал) все свойства другого объекта. Когда, например, вы определяете объект класса room, вы делаете именно это: говорите системе TADS, чтобы она создала новый объект по образу и подобию объекта "room" (который система уже "видела"), за исключением ряда свойств по вашему желанию, если вы решите их добавить или изменить. Если вы посмотрите файл ADVR.T, вы увидите, что определение объекта room очень сложное; однако вы можете создавать объекты класса room, ничего не зная о том, как этот класс устроен - все, что вам нужно знать - это набор свойств, которые вы хотите определить/добавить для нового объекта.

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

nestedroom - "встроенная" комната - комната, расположенная внутри другой комнаты
chairitem - стул (что-то, на чем игрок может сидеть)
beditem - кровать (что-то, на чем игрок может лежать)
thing - вещь - низкоуровневый класс предметов (как правило, напрямую не используется)
item - простой предмет, который игрок может брать и носить с собой
lightsource- источник света
hiddenItem - объект, который на момент начала игры спрятан в другом объекте
hider - низкоуровневый класс объекта-укрытия для hiddenItem (напрямую не используется)
underHider - объект, под которым могут быть спрятаны другие объекты
behindHider- объект, позади которого могут быть спрятаны другие объекты
searchHider- объект, внутри которого могут быть спрятаны другие объекты
fixeditem - недвижимый предмет (который игрок не может взять и носить с собой)
readable - предмет, который игрок может читать
fooditem - предмет, который игрок может съесть
dialItem - предмет, для которого игрок может установить разные значения (типа шкалы или диска с цифрами)
switchItem - предмет, который игрок может включить/выключить
room - комната - местоположение в игре
darkroom - неосвещенная комната (не содержит источников света, если игрок не принес их с собой)
Actor - "актер" - персонаж в игре
moveableActor - персонаж, которого игрок может брать и носить с собой
follower - псевдо-объект, используемый для организации следования игрока за другими персонажами
basicMe - набор определений, подходящих для главного персонажа (ГП) в большинстве игр
decoration - декорация - предмет, не несущий никакой другой смысловой нагрузки, кроме как быть фоном, декорацией
buttonItem - предмет, который игрок может нажать
clothingItem - предмет, который игрок может одеть
obstacle - препятствие - предмет, препятствующий движению игрока
doorway - одна сторона двери
lockableDoorway - дверь, которую можно запереть
vehicle - транспортное средство - предмет, который можно использовать для перемещений по игровому миру
surface - поверхность - предмет, на который можно класть другие предметы
container - контейнер - предмет, внутрь которого можно класть другие предметы
openable - контейнер, который можно открывать и закрывать
qcontainer - контейнер, который не перечисляет свое содержимое автоматически (при испоьзовании команды "осмотреть")
lockable - контейнер класса openable, который можно запирать и отпирать
keyedLockable - контейнер класса lockable, для запирания/отпирания которого требуется ключ
keyItem - предмет, который можно использовать для запирания/отпирания объекта класса keyedLockable
transparentItem - объект, чье содержимое видно
basicNumObj - базовое определение для объекта numObj (числительное), пригодное для большинства игр
basicStrObj - базовое определение для объекта strObj (строка текста), пригодное для большинства игр
deepverb - глагол
travelVerb - глагольный объект для глаголов, определяющих перемещение персонажа
sysverb - "системный" глагол
Prep - предлог

Каждый объект в ADVR.T полностью описан в комментарии, предшествующем определению. В этом комментарии указано, какие свойства необходимо определить при создании объекта данного класса, а также описано примерное поведение объекта.


Методы

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

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

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

   attic: room
      sdesc = "Чердак"
      ldesc =
      {
         "Ты находишься на обширном пыльном чердаке. Со стропил повсюду свисает паутина. 
         В северной части комнаты находится окно, которое в данный момент ";
         if ( atticWindow.isopen )
            "открыто";
         else
            "закрыто";
         ". Вниз спускается лестница. ";
      }
      down = hallway
   ;

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

Ты находишься на обширном пыльном чердаке. Со стропил повсюду свисает паутина. В северной части комнаты находится окно, которое в данный момент открыто. Вниз спускается лестница.


Выражения

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

  3 + 4

Для изменения порядка действий над числами можно использовать скобки. Например, поскольку умножение имеет более высокий приоритет по сравнению со сложением, выражение 3 + 4 * 5 дает в результате 23; если же записать его как (3 + 4) * 5, результат будет 35, поскольку в этом случае сложение выполняется до умножения.

Если вы хотите присвоить значение, используйте оператор ":=" (знак равенства следует за двоеточием сразу же, без пробелов). Например, чтобы присвоить свойству isopen объекта atticWindow значение nil, вы бы использовали следующее выражение:

  atticWindow.isopen := nil;

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

Далее приведен список операторов TADS в порядке убывания приоритета.


&
Обращается к "адресу" функции или свойства.

.
Обращается к свойству объекта: obj.prop производит обращение к свойству prop объекта obj.

[]
Обращение к элементу списка. Если переменная var содержит список [ 5 4 3 2 1 ], то var[ 2 ] равно 4, var[ 4 ] равно 2, var[ 5 ] равно 1 и т. д.

++
Увеличивает на единицу значение числовой переменной, с присвоением полученного значения той же переменной. Если переменная var равна 5, то после выполнения оператора var++ она будет равна 6.

--
Аналогично предыдущему оператору, но значение уменьшается на единицу.

not
Логическое отрицание. not true равно nil, а not nil равно true.

-
Арифметическое обращение знака (при использовании в качестве унарного минуса, предшествующего переменной или числу).

*
Умножение чисел: 5 * 6 равно 30.

/
Деление чисел с отбрасыванием дробной части: 30 / 5 равно 6. Поскольку дробная часть отбрсывается, то 5 / 2 равно 2.

+
Сложение чисел: 3 + 4 равно 7.
Объединение строк: 'hello' + 'goodbye' равно 'hellogoodbye'.
Объединение списков: [ 1 2 ] + [ 3 4 ] равно [ 1 2 3 4 ].
Добавление элемента в список: [ 1 2 ] + 3 равно [ 1 2 3 ].

-
Вычитание чисел: 10 - 3 равно 7.
Удаление элемента из списка: [ 1 2 3 ] - 2 равно [ 1 3 ].

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

<>
Проверка на неравенство; a <> b возвращает true, если a не равно b, в противном случае возвращает nil.

>
Оператор "больше". Можно сравнивать величины двух чисел или двух строк.

<
Оператор "меньше".

>=
Оператор "больше или равно".

<=
Оператор "меньше или равно".

and
Логическое умножение (логическое "и"): a and b равно true, если и a, и b равны true, в противном случае выражение возвращает nil. Обратите внимание, что, если a равно nil, то вычисление b не производится, поскольку выражение в целом все равно даст в результате nil вне зависимости от значения b.

or
Логическое сложение (логическое "или"): a or b равно true, если хотя бы одно из выражений a и b равно true, или если оба выражения равны true. Обратите внимание, что, если a равно true, вычисление b не производится.

? :
Условный оператор: выражение cond ? a : b возвращает значение a, если cond равно true, и b, если cond равно nil. Обратите внимание, что будет выполняться только одно из выражений a и b.

:=
Оператор присваивания.

+=
Оператор сложения и присваивания: переменной или свойству в левой части выражения присваивается значение правой части выражения, прибавленное к первоначальному значению этой переменной/свойства. a += b эквивалентно a := a + b.

-=
Оператор вычитания и присваивания.

*=
Оператор умножения и присваивания.

/=
Оператор целочисленного деления и присваивания.

,
Конъюнкция: сперва вычисляется выражение по левую сторону запятой, затем выражение с правой стороны от запятой, результат которого и возвращается в качестве результата всего выражения конъюнкции. Например, результатом (a, b) будет b.


Объект "self"

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

   book: item
      ldesc =
      {
        "Это "; self.color; " книга. ";
      }
   ;

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

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

В данном случае объект - родоначальник класса, book, может определять общий метод sdesc, который автоматически изменяется в зависимости от свойств отдельных дочерних объектов. Когда происходит обращение к redbook.sdesc, то фактически выполняется код метода sdesc, унаследованный от родительского объекта book; однако, поскольку выполняется метод redbook.sdesc, объект self указывает на redbook, и благодаря этому метод book.sdesc "знает", свойства какого именно объекта надо использовать; в частности, при обращении к self.color на экран будет выведено "красная" (для объекта redbook). Точно так же, когда выполняется bluebook.sdesc, self указывает на bluebook, и self.color выводит на экран "синяя". Определенные в ADVR.T классы широко используют этот механизм для того, чтобы создавать общие атрибуты классов, которые автоматически приспосабливались бы к объектам, создаваемым вами для вашей игры, притом, что авторы ADVR.T, разумеется, не могут ничего знать заранее о ваших объектах.


Когда выполняются методы?

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

Когда игрок вводит команду в игре, СА разбивает ее на отдельные слова. После этого он просматривает свои лексические таблицы в поисках объектов, соответствующих словам команды; если помните, специальные свойства verb, noun (сущесвительное), plural (множественное число), adjective (прилагательное), article (артикль) и preposition (предлог) ставят объекту в соответствие набор лексических слов. Когда СА определит, к каким объектам относится команда игрока, он вызывает определенные методы этих объектов; эти методы выполняют всю работу по обработке команды. Поскольку методы определены внутри самих объектов, вы можете поменять практически любые аспекты поведения игры в TADS; однако благодаря механизму наследования вам не потребуется что-либо менять, если вы этого сами не захотите - вы можете использовать определения объектов из ADVR.T в том виде, в каком они есть, заполняя только необходимые описания и свойства.

Команде соответствует набор объектов; каждый объект классифицируется в соответствии со своей функцией в команде. Объектами являются: персонаж (actor), глагол (verb), набор так называемых прямых объектов (direct objects) (опционально), предлог (preposition) (опционально) и косвенный объект (indirect object) (опционально). Если игрок не дал команду другому персонажу (например: "робот, положи деталь в коробку"), СА предполагает, что персонажем является главный персонаж (ГП) (по умолчанию - Me).

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

В приведенном ниже списке вы также увидите параметр <префикс глагола>. Это - специальный строковый параметр, определяемый в объектах класса deepverb; мы рассмотрим его ниже. Если параметр <префикс глагола> равен, например, 'Take', то запись do<префикс глагола> обозначает метод doTake.

   <actor> . roomCheck( <verb> )
   <actor> . actorAction( <verb>, <dobj>, <prep>, <iobj> )
   <actor> . location . roomAction( <actor>, <verb>, <dobj>, <prep>, <iobj> )

   Если был указан косвенный объект (iobj):
      <dobj> . verDo<префикс глагола>( <actor>, <iobj> )
      Если метод verDo<префикс глагола> ничего не вывел на экран:
         <iobj> . verIo<префикс глагола>( <actor> )
         Если метод verIo<префикс глагола> ничего не вывел на экран:
            <iobj> . io<префикс глагола>( <actor>, <dobj> )
   Если был указан прямой объект (dobj):
      <dobj> . verDo<префикс глагола>( <actor> )
      Если метод verDo<префикс глагола> ничего не вывел на экран:
         <dobj> . do<префикс глагола>( <actor> )
   Иначе:
      <verb> . action( <actor> )

   Выполнить все демоны (daemon), активные на данный момент
   Выполнить и деактивировать все запалы (fuse), счетчик ходов для которых дошел до нуля

СА получает параметр <префикс глагола> из глагольного объекта, если указан прямой или косвенный объект. Параметр <префикс глагола> - это строковое значение, определяемое свойством doAction глагольного объекта, если в команде задействован только прямой объект, или свойством ioAction того же глагольного объекта, если в команде присутствуют как прямой, так и косвенный объект. Например, рассмотрим следующий глагольный объект:

   takeVerb: deepverb
      verb = 'взять'
      sdesc = "взять"
      doAction = 'Take'
      ioAction(outofPrep) = 'TakeOut'
      ioAction(awayPrep) = 'TakeAway'
   ;

Если игрок ввел команду "взять мяч", то имеется только прямой объект, и используется свойство doAction, в связи с чем параметр <префикс глагола> принимает значение 'Take'. Это означает, что для прямого объекта будут вызываться методы verDoTake и doTake. В этом случае порядок вызова методов СА примет следующий вид (считаем, что прямому объекту соответствует объект ball (мяч)):

   Me.roomCheck( takeVerb )
   Me.actorAction( takeVerb, ball, nil, nil )
   Me.location.roomAction( Me, takeVerb, ball, nil, nil) 
   ball.verDoTake( Me )
   Если метод verDoTake ничего не вывел на экран:
      ball.doTake( Me )

Если введена команда "взять мяч из коробки", СА выбирает свойство ioAction в зависимости от присутствующего в команде предлога; в данном случае предлог "из" соответствует объекту-предлогу outofPrep, в связи с чем используется глагольный префикс 'TakeOut'. Таким образом, для объектов будут вызываться свойства verDoTakeOut, verIoTakeOut и ioTakeOut. В этом случае порядок вызов методов будет выглядеть так (косвенному объекту соответсвтует объект box (коробка)):

   Me.roomCheck( takeVerb )
   Me.actorAction( takeVerb, ball, box, outofPrep )
   Me.location.roomAction( Me, takeVerb, ball, box, outofPrep )
   ball.verDoTakeOut( Me, box )
   Если метод verDoTakeOut ничего не вывел на экран:
      box.verIoTakeOut( Me )
      Если метод verIoTakeOut ничего не вывел на экран:
         box.ioTakeOut( Me, ball )

Эта последовательность может показаться ужасно запутанной, но, как правило, вам не придется вникать в ее работу, да и вообще особо думать о ней. Почти во всех случаях вы сможете достичь требуемого эффекта, работая с методами the verDo<префикс>, do<префикс>, verIo<префикс> и io<префикс>.

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

Обратите внимание, что методы verDo<префикс> и verIo<префикс> предназначены для проверки, может ли объект в принципе использоваться с данной командой, а непосредственно выполняют команду методы do<префикс> и io<префикс>. Принцип действия тут таков: если объект не может использоваться в данной команде, методы verDo<префикс> и verIo<префикс> должны выводить сообщение об ошибке, а если может, то эти методы ничего не должны делать. Это может показаться странным способом определения допустимости объекта для команды, но этот принцип делает необычайно удобным написание этих методов - все, что вам надо сделать в ходе проверки - это вывести сообщение об ошибке, если требуется.


Программные инструкции

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


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

Пример:

   	ldesc =
	{
	   local cnt, loc;

           cnt := 0;
           loc := nil;
	}

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

return ;
Возврат в точку вызова без передачи значения.

if ( <выражение> ) <инструкция1> else <инструкция2>
Вычисляет <expression>; если его значение не равно nil или 0, выполняется <инструкция1>. В противном случае выполняется <statement2>. Обратите внимание, что вся подконструкция "else" является опциональной; если она отсутствует, а <выражение> равно nil или 0, выполнение программы продолжается инструкцией, следующей за <инструкцией1>.

Обратите внимание, что <инструкция1> и <инструкция2> могут быть как одиночными инструкциями, так и последовательностями инструкций, заключенными в фигурные скобки.

Пример:

   	if ( torch.islit )
        {
           "Внезапно ты понимаешь, что здесь пахнет газом, и что в руках у тебя горящий факел. 
            Пытаясь спастись, ты делаешь рывок в сторону выхода, но уже поздно: пламя факела поджигает
            газ, и раздается оглушительный взрыв. ";
            die();
        }
        else
            "Внезапно ты понимаешь, что здесь пахнет газом.
             К счастью, здесь нет открытого пламени. ";

switch ( <выражение> )
{
case <константа1>: <инструкции1>;
case <константа2>: <инструкции2>;
default: <инструкции3>;
}
Эта инструкция позволяет вам выбрать одну из нескольких ветвей программы в зависимости от значения некоего выражения. Производится вычисление <выражения>, затем полученное значение сравнивается с константами в строках с метками case. Если значение выражения совпадает с одной из этих констант, начинают выполняться инструкции, следующие сразу за соответствующей меткой case. Если значение выражения не совпадает ни с одной из констант и имеется метка default, управление передается на инструкции, следующие за меткой default. Метка default является опциональной.

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

Пример:

   	switch( x )
        {
	case 1:
	case 2:
	   "x равен 1 или 2";
	   break;
	case 3:
	   "x равен 3";
           /* Поскольку инструкции break здесь нет, мы продолжаем выполнять следующий case */
	case 4:
	   "x равен 3 или 4";
	   break;
	default:
	   "x больше или равен 5";
        }

while ( <выражение> ) <инструкция>
Выполняет <инструкцию> в цикле до тех пор, пока значение <выражения> не станет равным nil или 0. <Выражение> вычисляется перед каждым выполнением <инструкции>, поэтому, если значение <выражения> окажется равным nil или 0 до первого выполнения цикла, <инструкция> не будет выполняться ни разу. Как и в случае с инструкцией if, <инструкция> может быть как одиночной, так и последовательностью инструкций, заключенных в фигурные скобки.

Пример:

   	while ( cnt < length( lst ) )
        {
	   "Следующий элемент списка: "; say( lst[ cnt ] ); "\n";
           ++cnt;
        }

do <инструкция> while ( <выражение> );
Эта инструкция аналогична инструкции while, но <выражение> вычисляется в конце каждого цикла. Таким образом, <инструкция> будет выполнена по крайней мере один раз, поскольку значение <выражения> не проверяется при первом прогоне цикла.

Пример:

   	do
        {
	   "cnt = "; say( cnt ); "\n";
           --cnt;
	} while ( cnt > 0 );

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

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

Пример:

   	for ( cnt := 1 ; cnt < length( lst ) ; ++cnt )
        {
           "следующий элемент списка: "; say( lst[ cnt ] ); "\n";
        }

break;
Прерывает цикл while, do-while или for, либо выполнение инструкции switch. Управление немедленно передается на инструкцию, следующую за циклом или конструкцией switch. Инструкция break полезна, если условие выхода из цикла удобней всего проверить где-нибудь в середине цикла.

continue;
Возврат к началу цикла while, do-while или for. Инструкции, следующие за continue, пропускаются для данного прогона цикла. Для цикла типа for после инструкции continue проверяется значение выражения <повтор-выр>.

pass <имя-свойства>;
При выполнении метода, который заменяет одноименный метод родительского объекта, инструкция pass передает управление методу родительского объекта, который был заменен текущим методом. Обратите внимание, что после инструкции pass управление никогда не возвращается в текущий метод. Также обратите внимание, что <имя свойства> должно совпадать с именем текущего свойства.

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

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

askdo;
Прерывает обработку текущей команды и просит игрока указать прямой объект.

askio( <объект-предлог> );
Прерывает обработку текущей команды, устанавливает <объект-предлог> в качестве предлога для этой команды, и просит игрока указать косвенный объект.

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

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


askfile( prompt )
Запрашивает у игрока имя файла; prompt - это строковый параметр (в одинарных кавычках), определяющий текст, который выводится на экран при запросе. Во всех случаях, когда это возможно, для запроса файла выводится соответствующиее стандартное диалоговое окно операционной системы.

caps()
Следующий выводящийся на экран символ после вызова этой функции заменяется на соответствующий заглавный. См. также nocaps().

Эта функция работает только для латинских букв; аналог для кириллицы рассмотрен далее в документации.


car( list )
Возвращает первый элемент списка "list". Например, car( [ 1 2 3 ] ) возвращает 1.

cdr( list )
Возвращает список list без его первого элемента. Например, cdr( [ 1 2 3 ] ) возвращает [ 2 3 ].

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

cvtnum( str )
Преобразует строку str (заключенную в одинарные кавычки), содержащую текстовое представление числа, в соответствующее числовое значение. Например, cvtnum('1234') возвращает 1234.

cvtstr( num )
Преобразует число num в строку, содержащую текстовое представление данного числа. Например, cvtstr( 100 ) возвращает '100'.

datatype( val )
Возвращает тип данных для значения переменной val (после вычисления этого значения). Возвращает следующие коды:

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


defined( obj, &prop )
Возвращает значение "истина" (true), если объект obj определяет или наследует свойство prop, в противном случае возвращает nil.

find( value, target )
Возвращает смещение target внутри value. Если value - это список, то функция find возвращает порядковый номер (индекс) элемента target в списке value, или nil, если target в списке value отсутствует. Например, find([5 4 3], 4) возвращает 2. Если value - это строковая переменная, то данная функция возвращает смещение подстроки target или nil, если такой подстроки нет. Например, find('abcdef', 'cde') возвращает 3.

firstobj()
Инициирует цикл по всем объектам в игре, не являющимися классами. Возвращает объект. См. также nextobj( obj ).

firstobj( cls )
Инициирует цикл по всем объектам - наследникам родительского класса cls в игре, которые сами не являются классами. Возвращает объект. См. также nextobj( obj, cls ).

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

getarg( num )
Возвращает текущему методу или функции значение аргумента с номером num.

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

getfuse(obj, &msg)
Определяет, активен ли еще запал funcptr, и если да, то сколько осталось ходов до того, как он сработает. Если запал неактивен (он уже сработал, или был отключен функцией remfuse, или никогда не был активирован), функция возвращает nil. В противном случае она возвращает число ходов, оставшихся до срабатывания запала. В этой форме функция getfuse() позволяет вам проверить запал, инициированный функцией notify(). Если запал неактивен, функция возвращает nil, в противном случае она возвращает число ходов, оставшихся до срабатывания запала.

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

year (год) - календарный год (напр, 1992).
month (месяц) - номер месяца (январь = 1, февраль = 2 и т. д.)
day (день) - чиисло текущего месяца
weekday (день недели) - номер дня недели (1 = воскресенье, 2 = понедельник и т. д.)
yearday (день в году) - порядковый номер дня в году (1 = 1 января)
hour (час) - час дня в 24-часовом формате (полночь = 0, полдень = 12)
minute (минута) - минута текущего часа (от 0 до 59)
second (секунда) - секунда текущей минуты (от 0 до 59)
elapsed (прошло секунд) - количество секунд, прошедших с 1 января 1970 г., 00:00:00 по Гринвичу. Это значение полезно при вычислении разницы между двумя моментами времени.


incturn(num)
Увеличивает счетчик ходов на величину num. Вызов функции без аргументов (incturn()) эквивалентен вызову incturn(1). Как правило, вызов incturn() без аргументов выполняется специальным демоном после каждого хода в игре. При передаче аргумента больше 1 функция выполняет все запалы, которые должны были сработать в течение числа ходов, меньшего или равного num, и уменьшает число ходов до срабатывания остальных запалов на величину num. (При вызове без аргументов функция не выполняет запалы, а просто уменьшает число ходов, оставшееся до их срабатывания, на 1). Функция работает с запалами, инициированными командами setfuse() и notify(). Учтите, что она не оказывает влияния на демонов.

input()
Позволяет пользователю ввести строку текста и возвращает этот текст в виде строкового значения.

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

Учтите, что inputkey() считывает входные данные непосредственно с клавиатуры, не обращая внимания на командную строку.


intersect( list1, list2 )
Возвращает пересечение двух списков (множеств) list1 и list2 - т. е. список (множество) элементов, присутствующих как в list1, так и в list2. Пример:
  intersect( [ 1 2 3 4 5 6 ], [ 2 4 6 8 10 ] )
возвращает список [ 2 4 6 ]. Учтите, что поведение для списков с повторяющимися элементами не до конца определено в отношении числа повторяющихся элементов, включаемых в результирующий список. В текущей реализации количество повторяющихся элементов в результирующем списке соответствует тому количеству этих элементов, которые присутствуют в более коротком из исходных списков, однако в будущем этот принцип может быть изменен, поэтому полагаться на него не рекомендуется.

isclass( obj, cls )
Возвращает true, если объект obj является наследником класса cls, в противном случае возвращает nil.

length( val )
Возвращает количество символов в строке или число элементов в списке (в зависимости от типа аргумента val).

logging( val )
Если аргумент val - это строковое значение, создает файл с именем, соответствующим val, и начинает запись (протоколирование) всего текста, выводимого на экран, в этот файл. Если val равен nil, закрывает текущий файл протокола и прекращает протоколирование.

lower( str )
Возвращает строку, состоящую из символов строки str, преобразованных в нижний регистр. Работает только с латинскими буквами. См. также upper().

nextobj( obj )
Возвращает объект, следующий за obj, в произвольном порядке, или nil, если список объектов исчерпан. Ниже приведен пример организации цикла, в котором каждый объект в игре, не являющийся классом, будет возвращен ровно один раз:
   local obj;
   for ( obj := firstobj() ; obj ; obj := nextobj( obj ) ) /* ... */;

См. также функцию firstobj().


nextobj( obj, cls )
Возвращает объект-наследник родительского класса cls, следующий за obj, или nil, если список объектов исчерпан. Ниже приведен пример организации цикла, в котором каждый объект в игре, сам не являющийся классом, но наследующий свойства от родительского класса cls, будет возвращен ровно один раз:
   local obj;
   for ( obj := firstobj( cls ) ; obj ; obj := nextobj( obj, cls ) ) /* ... */;

См. также функцию firstobj( cls ).


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

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


notify( obj, &prop, turns )
Осуществляет вызов указанного метода объекта через определенное количество ходов. Свойство prop объекта obj (например, obj.prop) вызывается один раз по истечении числа ходов, равного параметру turns. Если этот параметр равен нулю, свойство prop вызывается после каждого хода. См. также unnotify().

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

Перехват организуется следующим образом. Сначала осуществляется вызов

  stat := outcapture( true );

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

  str := outcapture( stat );

При этом перехват текста отключается, а в строковую переменную str возвращается перехваченный текст.


outhide(flag)
Включает или отключает вывод текста на экран, в зависимости от значения параметра flag. Если flag равен true, вывод на экран отключается, а если nil - включается снова. Функция outhide(nil) также возвращает значение, указывающее, осуществлялся ли скрытый вывод с момента вызова outhide(true).

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


proptype(obj, &prop)
Возвращает тип свойства prop объекта obj, не выполняя этого свойства. Если свойство является методом, этот метод не вызывается функцией proptype, и поэтому тип возвращаемого методом значения определить нельзя. Вместо этого функция proptype просто возвращает 6, что означает, что свойство является методом. Возвращаемые значения:

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


quit()
Завершает игру.

rand( lim )
Возвращает случайное значение в пределах от 0 до lim включительно.

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

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

remfuse( function, value )
Отключает запал, который был до этого активирован встроенной функцией setfuse().

restart()
Запускает игру заново с самого начала.

restore( filename )
Восстанавливает игровую сессию, прдварительно сохраненную в файле с именем, определенным строковым параметром filename.

rundaemons()
Запуск всех демонов. Эта функция запускает все демоны, активируемые функциями setdaemon() и notify(). Функция не возвращает никакого значения.

runfuses()
Запускает все "просроченные" запалы. Возвращает true, если такие запалы имелись, или nil, если нет. Этой функцией запускаются запалы, активируемые как функцией setdaemon(), так и notify().

save( filename )
Сохраняет текущую игру в файл с именем, соответствующим строковому параметру filename.

say( value )
Выводит на экран значение параметра value, которое может быть числовым или строковым (в одинарных кавычках).

setdaemon( function, value )
Инициирует демон. Функция function будет вызываться один раз после каждого хода, при этом в качестве аргумента ей будет передаваться параметр value. Параметр value может быть произвольным; он реализован для того, чтобы передать демону любую информацию, которая может ему понадобиться. Функция-демон должна быть определена до ее использования в setdaemon().

setfuse( function, time, value )
Инициирует запал. Функция function будет вызвана через число ходов, определенное параметром time. Параметр value - это произвольное значение, передающееся функции function в качестве аргумента; он предназначен для того, чтобы передавать функции любую необходимую информацию.

setit( obj )
Устанавливает местоимение "it" (это) - специальное слово, используемое в игре для обращения к последнему использовавшемуся объекту - таким образом, чтобы оно ссылалось на объект obj. Этот объект станет тем объектом, к которому обращается игрок при использовании слова "это".

setit( nil )
При использовании nil в качестве аргумента функции setit() сбрасывает местоимение "it" (это). Игрок не сможет использовать его в следующей команде.

setit( obj, num )
При таком вызове setit() можно указать, какое местоимение использовать. Параметр num может быть равен 1 - в этом случае используется местоимение "him" (он, его), или 2 - "her" (она, ей). Если параметр не указан, то используется местоимение "it" (это). Обратите внимание, что передачей соответствующих значений num и значения nil в качестве параметра obj можно добиться, чтобы очищалось не только местоимение "it", но и "him", и "her".

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

setscore( score, turns )
Устанавливает значение для очков, отображаемого в строке статуса. Числовые параметры score и turns означают соответственно количество набранных очков и число сделанных ходов с начала игры.

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

skipturn( num )
Эта функция аналогична функции incturn( num ); она пропускает определенное количество (num) ходов, которое должно быть не менее 1. Разница между skipturn() и incturn() в том, что skipturn() не выполняет запалы, которые должны сработать в течение пропускаемых ходов - она просто отключает их, не вызывая. Это относится к запалам, активированным как командой setfuse(), так и notify(). Обратите внимание, что на демоны эта функция не оказывает действия.

substr( str, ofs, len )
Возвращает подстроку строки str, начинающуюся с символа ofs от начала строки и имеющую длину len символов. Например, substr('abcdef', 3, 2) возвращает 'cd'.

undo()
Отменяет один (последний) сделанный ход.

unnotify( obj, &prop )
Отменяет вызов свойства obj.prop, инициированный ранее соответствующей командой notify(). Если такого вызова не было, выводится сообщение об ошибке исполнения. См. также notify().

upper( str )
Возвращает строку str, преобразованную в верхний регистр. Работает только для латинских букв. См. также lower().

yorn()
Ждет, пока игрок введет "YES" (да) или "NO" (нет). Возвращает 1, если игрок ввел YES, 0, если игрок ввел NO, и -1, если игрок ввел какой-либо другой текст. Обратите внимание, что проверяется только первая буква текста, введенного игроком, и что ввод может осуществляться как строчными, так и заглавными буквами. Кроме того, ответ надо вводить только на английском, поэтому для русских игр полезность этой функции сомнительна.


Что дальше?

Возможности TADS далеко не исчерпываются описанными в данном обзоре. Чтобы узнать больше, вы можете ознакомиться с демонстрационной игрой "Блуждания в Окопный День" (в английском варианте - Ditch Day Drifter). Эта игра также не использует всех возможностей TADS (а если точнее, то в связи с гибкостью инструментов TADS она не затрагивает даже десятой их части), но, тем не менее, содержит немало примеров типичных ситуаций, встречающихся в текстовых играх, которые могут помочь вам в написании собственных игр.

Для получения полной информации по TADS вам следует обратиться к другим главам Руководства по TADS для авторов игр. Данное руководство включает в себя:

Кроме того, Руководство по TADS содержит значительное количество подробных примеров написания игр. В главе "Основы работы в TADS" рассматривается процесс разработки игры и ее воплощения в коде TADS. Глава "Более сложные технические приемы" содержит множество примеров с подробными объяснениями, показывающих способы решения широкого спектра задач, характерных для игр, например:


О компании High Energy Software

Компания High Energy Software раньше распространяла TADS на условиях shareware, но теперь отошла от дел. Майкл Робертс, создатель TADS, в настоящее время поддерживает и распространяет эту систему на условиях freeware. Из данной документации были по возможности удалены ссылки на High Energy Software и контактную информацию по этой компании, поскольку все ее телефоны и адреса более недействительны. Если вы, тем не менее, наткнетесь на такую информацию, просто не обращайте на нее внимания - она сохранилась по недосмотру.


Содержание Информация по лицензии и авторским правам