главная  |   статьи  |   гостевая книга |    перекресток миров  |  



Рассказы с фронта кодирования

Ножницы, брюки, очки и орангутанги

© Andrew Pontious
original from: http://www.xyzzynews.com



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

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


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

Сначала я создам один из тех множественных-единственных объектов. Я должен заметить, это довольно тяжело для не-пользователя TADS:

scissors: item            // "item" является классом TADS для
                          // чего-то, что вы можете поднять
    sdesc = "ножницы"     // "краткое" описание объекта
    adesc = "ножницы"     // описание, использующееся, когда
                          // игра хочет назвать предмет 
                          // с использованием артикля "a"
    noun = 'ножницы'      // термин, которым игрок может
                          // воспользоваться, чтобы назвать предмет
    location = startroom  //  где объект расположен вначале игры
;
Комментарий Г.А. №1

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

>посмотреть
Вы в стартовой комнате вашей тестовой игры.
Здесь вы видите ящик и ножницы.


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

Игра разумно отвечает:
Ножницы не находится ящике.


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

  verDoTakeOut( actor, io ) =
    {
        if ( io <> nil and not self.isIn( io ))
        {
            caps(); self.thedesc; "не находится в"; io.thedesc; ". ";
        }
        self.verDoTake(actor);       /* убедиться, что объект
                                      вообще можно брать */
    }
Комментарий Г.А. №2

Атрибут "actor" относится к тому персонажу, который выполняет действие (обычно "Я", игрок), и "io" - косвенный объект, в этом случае то, из чего вы берете прямой объект. "Self" здесь - ножницы. Обратите внимание, как код сообщает, есть ли косвенный объект ("io <> nil"), и если "self" (ножниц) в нем нет ("isIn" - специальный метод, который проверяет это), выдает вам сообщение, с которым мы уже столкнулись.


То, что я должен сделать - найти способ "убедить" эту программу распознавать этот объект как множественный и выдавать верный отклик на это. Моя первая попытка в решении включала создание нового свойства в нашем объекте "ножницы", названное "plural", путем добавления этой строки где-нибудь в определении ножниц:
plural = true

Затем я использую команду "modify", которая позволяет мне переопределить отдельные методы или свойства объекта после того, как объект уже был объявлен, в этом случае - в ADV.T -- однако, вы не можете изменить отдельные строки метода или свойства, и должны повторить их все. Как, например:

 modify thing
    verDoTakeOut( actor, io ) =
    {
        if ( io <> nil and not self.isIn( io ))
        {
           caps(); self.thedesc;
           // НОВЫЙ КОД!
           if (self.plural) " не находятся в"; else " не находится в";
           // КОНЦОВКА СТАРОГО КОДА
           " в "; io.thedesc; ". ";
        }
        self.verDoTake(actor);       /* убедиться, что объект
                                      вообще можно брать */
    }
;
Комментарий Г.А. №3

Итак, я пытаюсь откомпилировать это и получаю сообщение "error TADS-335: invalid vocabulary property value" (неверное значение свойства). Что это означает? "Plural = true" - вполне правильно! После недолгого поиска, я обнаружил, что ADV.T уже определяет свойство, названное "plural", но как строку. Мой последующий код пытался переопределить переменную после того, как она была объявлена, причем со строкового типа в логический. Ни-ни.

Тогда взамен я меняю "plural" на менее изящный "pluralflag" (все же я бы мог быть злонамеренным и использовать "Plural", так как TADS чувствителен к регистру (Комментарий Г.А. №4) ) и перекомпилирую. Теперь программа выполняется следующим образом:

>взять ножницы из ящика.
Ножницы - не находятся ящике.

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

Из предметов:

verDoPutOn: "{прямой объект} уже лежит/лежат на {косвенный объект}!"
verDoTellAbout: "Кажется, {прямой объект} вас не интересует/интересуют".


И из персонажа, другого прототипа-объекта, который имеет методы, подходящие для ваших NPC:
verDoWalkon: "{Прямой объект} возражает/возражают против этого"


Итак, теперь я обнаружил все места, где сообщения должны быть изменены, и для этого модифицировал все объекты и методы из ADV.T в своем собственном коде. И даже лучше для вас, я сохраняю свои изменения в отдельном файле, который другие люди могли бы включать в свои игры для того, чтобы использовать его преимущества, исправляя "из ящика", как оно было, просто включая "pluralflag = true" (или новое имя - см. ниже) в нужные объекты. Все же запомните - не модифицируйте те же методы снова в вашем коде после того, как вы включили мою утилиту, или вы уничтожите мои изменения в ADV.T! Лучшим способом, если вам нужно изменить те же методы, будет не вызов файла утилиты отдельно из вашей игры (технически: #include), а полная вставка моего кода в вашу игру (и там с ним возиться). Вскоре вы найдете его на IF архиве под именем plurals.t в Programming/TADS/Examples, или на прилагаемом диске XYZZYnews, если вы подписались на него, вместе с небольшой тестовой игрой, названной pluralstest.t, показывающей много примеров этих объектов.


Итак, мы это сделали, не так ли? Я подумал так же, пока не попытался:
>поместить ножницы
Во что вы хотите его поместить?


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

Шаг первый: "The New Book of the Parser" в Upgrade Manual TADS версии 2.2, страница 90: "Если глагол требует прямой или косвенный объект, но команда не находит ни то, ни другое, и синтаксический анализатор не может подставить подходящий по умолчанию... синтаксический анализатор проверяет, определена ли в вашей игровой программе функция, названная parseAskobjActor [перед тем, как продолжать свой алгоритм]". Окей, круто, я определю эту функцию, но потом я нахожу что, даже если это тот косвенный объект, который вы ищете, метод не дает никаких параметров прямого объекта - только персонаж, глагол, и предлог, если такой есть. Следовательно, у вас нет способа передать вашему новому методу parseAskobjActor определения, какой же именно прямой объект и единственный или множественный он, до того, как вы зададите вопрос, поставленный выше (Прим. переводчика: "Во что вы хотите его поместить?"). Итак, мы не можем изменить вопрос с точки зрения единственного-множественного тем способом, которым хотели. Тупик. Я смотрю на другой, более общий метод, который TADS обеспечивает, чтобы изменять сообщения ошибки, функцию parseError. Она позволяет вам заменять любое стандартное сообщение ошибки, которое генерируется, новым, например, сообщением №6, "Я ожидал существительное после 'of'.". И здесь мы очень ограничены; мы просто получаем номер сообщения, которое синтаксический анализатор должен выдать, как параметр (есть их целая таблица), и можем также заменять сообщение в каждом случае, когда синтаксический анализатор использует его, или оставить его без изменений.
Сообщения №140-149 имеют дело с частями сообщений, с которыми мы могли бы целиком провозиться, используя parseAskobjActor, например, 140: "что вы хотите" или 143: "?". Почему это вы должны хотеть заменить, скажем, знак вопроса, я не уверен, но вы можете это сделать. Синтаксический анализатор собирает эти части вокруг того, что набрал игрок, например, Во+ "что вы хотите " + поместить + "это" + "?".


Номер 141 - то самое надоедливое "это", но постойте, есть же и №147, "их". Когда оно используется? Страница 91: "Если нужно местоимение для прямого объекта, синтаксический анализатор проверяет каждый объект в списке прямых объектов. Если игрок явно задал множественные прямые объекты (используя "все" или список фраз существительных), синтаксический анализатор отображает сообщение 144 ("их")." Текст продолжает объяснять, как используются свойства isHim и isHer объектов, определенных игроком ("поместить тетушку Хелен", например), чтобы было возможно заменять местоимения "его" или "ее".


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


Мой способ, по общему признанию грубый,- заменить сообщение 141 "этим". Но, это имеет смысл. Поход Дэвида Бэджетта при написании эквивалента advr.t, - названного WorldClass, решает проблему, полностью исключая местоимение; его parseAskobjActor задаёт вопросы типа "Какими?" или "В котором?", что, я должен признать, весьма умно, пусть и звучит резко.

Лучшее решение - иметь синтаксический анализатор, проверяющий мое свойство, которое ради логического соответствия я теперь назвал "isThem", прежде чем решить, какое местоимение использовать. Это будет даже более идеальным, если в следующей версии TADS (если есть следующая версия TADS) синтаксический анализатор позволит вам заменять всю работу по определению местоимений на свой собственный метод, на этот раз с верными параметрами. Гибкость является королем. Это то, чем по-настоящему блистает Inform, с тех пор как все подобные внутренние обработки синтаксического анализатора доступны для правки со стороны игрока (вот почему они уже получили полную утилиту обработки множественно-единственных конструкций).
Комментарий Г.А. №5

И это все изменения, необходимые для того, чтобы обеспечивать множественно-единственные конструкции! Может быть, есть другие, на которые я не обратил внимания, но во время детального тестирования своей собственной игры я не обнаружил ни одного (знаменитые последние слова...). Моя игра имеет другие серьезные изменения в ADV.T, вносящие новые возможности и заставляющие многие вещи работать более гладко и логично, из которых я хочу составить утилиты, как только выйдет моя игра. У вас будет возможность использовать всё это, если вы не хотите массово переходить на WorldClass. Есть также возможность, что сам TADS включит аналогичные улучшения в следующую версию. Майкл Робертс, вы слушаете? ( Комментарий Г.А. №6)


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



Комментарии



Комментарий от ГрАнда №1:

В RTADS это выглядит так (использовал генератор, что ускорило процедуру до 7 секунд)
nozhnitsy : item
    location = startroom
    sdesc = "ножницы"     // именительный падеж
    rdesc = "ножниц"      // родительный падеж
    ddesc = "ножницам"    // дательный падеж
    vdesc = "ножницы"     // винительный падеж
    tdesc = "ножницами"   // творительный падеж
    pdesc = "ножницах"    // предложный падеж
    noun = 'ножницы' 'ножниц' 'ножницам' 'ножницами'
           'ножницах' 'ножницам#d' 'ножницами#t'
    isThem = true         // Вот это чудо, которое
                          // родилось после этой статьи
;
Вернуться к основному тексту


Комментарий от ГрАнда №2:

В RTADS, где файл adv.t называется advr.t, это выглядит так:

    verDoTakeOut(actor, io) =
    {
        if (io <> nil and not self.isIn(io))
        {
            ZAG(self,&sdesc); " ";
            " не наход<<glok(self,2,1)>>ся в "; io.pdesc; ". ";
        }
        else
            self.verDoTake(actor);     /* убедиться, что объект
                                       вообще можно брать */
    }


Т.е. у нас вообще нет thedesc, а буквы делаем заглавными другой функцией. Функция glok(self,2,1) подбирает нужное окончание для глагола второго спряжения и с гласной, не включающей "й". При этом, глагол должен согласовываться с объектом self. Таким образом, так как разнообразие окончаний у нас больше(только тут: -ишь -ит -им -ите -ят), мы не могли обойтись таким перебором, каким обойдётся далее автор статьи.

Вернуться к основному тексту



Комментарий от ГрАнда №3:

Позже Майк Робертс преобразует этот фрагмент, назначив каждому предмету строковое свойство "isntdesc" (в оригинале, проблема состоит в правильной подстановке "is not" или "are not")
Теперь код таков:
    verDoTakeOut(actor, io) =
    {
        if (io <> nil and not self.isIn(io))
        {
            caps(); self.thedesc; " "; self.isntdesc;
            " in "; io.thedesc; ". ";
        }
        else
            self.verDoTake(actor);     /* убедиться, что объект
                                        вообще можно брать */
    }
Вернуться к основному тексту


Комментарий от ГрАнда №4:

Чувствительность к регистру теперь опциональна.

Вернуться к основному тексту



Комментарий от ГрАнда №5:

Поздние версии TADS позволили это сделать для РТАДС. Как видите, теперь передаются все возможные параметры. Но при этом ряд проблем ещё остаётся. Обработчик РТАДС сейчас выглядит подобным диким образом:
parseAskobjActor: function(a, v, ...)
{
    if (argcount = 3)
    {
       if (getarg(3)!=toPrep or v.type!=2) ZA(dToS(getarg(3),&sdesc));
       if (getarg(3)=onPrep or getarg(3)=thruPrep or getarg(3)=inPrep
                              or getarg(3)=atPrep) " что ";
       else  if ((getarg(3)=underPrep) or (getarg(3)=behindPrep)
               or (getarg(3)=overPrep)or (getarg(3)=betweenPrep)
               or (getarg(3)=aboutPrep)) " чем ";
       else  if (getarg(3)=goonPrep) " чему ";
       else if (getarg(3)=toPrep)
         {
          if (v.type=2) "Кому ";
           else " чему ";
         }
       else " чего ";
       if (a <> parserGetMe() or a.lico=3)
        {
         a.sdesc;" ";"долж<<ok(a,'ны','ен','но','на')>> ";
        }
        else "Вы хотите ";
        v.sdesc;" ";
        if (getarg(3)!=aboutPrep)
        {
         if (v.pred) v.pred.sdesc;
         " это?";
        }
        else (v=tellVerb)?"этому?":"этого?"; //Тоже звучит некрасиво :(
       }
    else
    {
        ZA(dToS(v,&vopr));
        if (a <> parserGetMe() or a.lico=3) 
         {
          a.sdesc;" ";"долж<<ok(a,'ны','ен','но','на')>> ";
         }
         else "<<parserGetMe().sdesc>> хо<<
         (parserGetMe().isThem)?"тите":"чешь">> ";"<<v.sdesc>>?";
    }
}
Впрочем, часть дикости объясняется моими нововведением - различается второе и третье лицо персонажа. В английском TADS это по-прежнему требует изменения кода.

Вернуться к основному тексту



Комментарий от ГрАнда №6:

О, да! Он слушал!

Вернуться к основному тексту


содержание
© Andrew Pontious
Перевод: Wizard Nash
Правка и комментарии: Андрей Гранкин aka GrAndrey
2004


Hosted by uCoz