Forum.iFiction.Ru

iFiction.Ru · ifHub · FAQ · IFWiki · QSP · URQ · INSTEAD · AXMA

форум об interactive fiction, текстовых приключенческих играх и всём таком...

Вы не зашли.

0    0    #1
09.04.2011 19:09

Kephra
Участник (+1, -1)
Откуда: Украина
Зарегистрирован: 04.04.2011
Сообщений: 45

Игра пример

В примере из демонстрационной игры (http://rtads.org/man/TADSSMPL.HTM), следующая штука выходит:
Чтобы взять череп с пьедестала, нужно предварительно положить на него камень, тогда не сработает ловушка... но, если забрать только что положенный камень, ловушка так же не сработает:), ведь, по логике самого метода, при отсутствии на пьедестале объекта, вышеназванного бага, не должно быть...  Сам метод: 

Код:

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

Отредактировано Kephra (09.04.2011 19:52)

Неактивен

1    0    #2
30.08.2018 03:10

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

Одно не понятно что это за стрелочки больше-меньше? Они эквивалентны оператору «!=» в Си?

Да, <> - это неравно. В принципе, != TADS тоже понимает, так что можно использовать и этот оператор.

Синтаксис языка описан в главе 5.

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

Неактивен

1    0    #3
30.08.2018 15:10

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

Я сделал так:

Нет, это не совсем верно:

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

Во-вторых, команда pass передаёт обработку указанному методу объекта родителя. То есть мы в текущем объекте модифицировали какой-то метод, а потом хотим, чтобы в остальном он отработал по стандартной схеме, и для этого как раз и вызываем его по pass. Однако вы модифицируете метод moveInto, то есть перемещение объекта, но дальше передаёте обработку в doTake, то есть метод взятия, тогда как перемещаться череп может не только из-за взятия его в руки, а например, из-за перекладывания на пол. Как раз командой "положить череп на пол" исходная реализация пазла и ломается. В общем если модифицируем moveInto, то и дальше по pass передаём обработку тоже в moveInto.

В итоге, должно быть так:

Код:

moveInto(obj) =
{
    // В момент передвижения череп на пьедестале, но камня на нём нет
    if(self.location = pedestal && smallRock.location != pedestal)
    {
        "Бла-бла-бла. ";
        die();
    }
    // moveInto работает дальше как обычно
    pass moveInto;
}

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

Неактивен

1    0    #4
30.08.2018 22:55

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

Я не совсем понимаю как работает этот метод. Зачем вообще передавать что-то в него?

moveInto - это универсальный внутренний метод для изменения местоположения объекта. Когда нам надо будет переместить какой-то объект, мы будем  вызывать его с методом moveInto, передавая объект нового местоположения:

Код:

// Перемещаем объект book в/на объект table
book.moveInto(table);
// Убираем book из игрового мира в небытие, например, книгу сожгли
book.moveInto(nil);

Может показаться, что достаточно просто изменить свойство location у объекта, но так перемещать объекты не стоит, потому что перемещение объекта - это ещё и изменение ряда свойств модели мира, типа внутренних системных списков содержимого объектов и прочего. moveInto делает это всё вместе с изменением location, так что его и надо использовать.

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

Итак, аргумент moveInto - это объект, который в итоге станет новым значением location перемещаемого объекта, поэтому без него никуда. Ну или аргументом может быть nil, чтобы убрать объект из мира.

Kephra написал:

Как в «doTake» так и в «moveInto» можно передать любой локальный, нейтральный, пустой объект вида «asdasd», но если этого не сделать, интерпретатор при срабатывании такого метода, выдаст ошибку. Но дело в том, что совершенно неважно что я туда впишу.

Для начала, конструкция doTake(actor) означает не то, что вы в doTake передали actor, а то, что вы от движка, который отправил вам аргумент в doTake, приняли этот аргумент в виде переменной actor. Содержимое actor определяет движок, который и вызывает doTake, а вы лишь это содержимое принимаете под определённым названием.

Все методы действий, типа doTake или doOpen, в качестве аргумента принимают объект персонажа (актёра), который выполняет это действие. Отдельные методы могут принимать и дополнительный аргумент, но персонаж будет всегда. Например, игре нужно проверить, можно ли взять горячий уголёк, а для этого надо обратиться к объекту персонажа, совершающего действие, и проверить, надет ли на него объект рукавиц. Дело в том, что действия над объектами может совершать не только персонаж главного героя, но и другие персонажи игры, а также в TADS возможно реализовать переключение игры между несколькими главными героями, например, первую половину поиграли за Красную шапочку, а вторую за Серого волка. Именно поэтому надо иметь возможность чётко знать, какой именно персонаж выполняет действие, а не просто сразу тянуться к объекту Me, являющемся главным героем по умолчанию.

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

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

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

Kephra написал:

поясняется следующее: «Определение атрибута doTake использует в качестве аргумента персонаж (actor), который пытается взять объект.» и «Система вызывает doTake каждый раз, когда игрок пытается взять объект.»

Первое означает, что вам в метод (атрибут) doTake прилетит в качестве аргумента объект персонажа, который берёт объект. В частности, в атрибут doTake внутри объекта черепа под именем actor прилетит объект Me, который является объектом главного героя по умолчанию.

Второе означает, что когда какой-то предмет кем-то берётся, в объекте этого предмета вызывается метод doTake. В частности, когда вы в игре пишите "взять череп", то происходит вызов doTake в объекте черепа.

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

Неактивен

0    1    #5
31.08.2018 16:22

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

То есть, если Красная Шапочка возьмёт череп с пьедестала, до этого не поместив на него камень, а в методе doTake Серый Волк, то погибнет он вместо Шапочки?

В случае обсуждаемого кода, нет, потому что там поражение через die() не привязано к персонажу, совершающему действие Take (код die() можно посмотреть в библиотеке stdr.t). Но вот если бы мы в методе doTake делали бы нанесение ущерба персонажу с вычитанием здоровья и запрограммировали бы там жёстко Me, то в случае того, когда иной персонаж брал бы череп, ущерб всё равно наносился Me. Именно для этого в doTake, как и в любой другой метод обработки действия, всегда движок передаёт объект персонажа, чтобы можно было работать именно с ним.

Ну и ещё раз обращаю внимание, что аргумент doTake вы можете назвать не actor, а хоть abcde. Это ничего не изменит, так как это всего лишь имя локальной переменной, в которой входные данные метода будут доступны внутри его тела. Меняя имя этой переменной вы никак не влияете на то, что туда передаёт движок, а передаёт он там всегда объект персонажа. Можете в этом убедиться, добавив в метод действия вот такую строку:

Код:

    "Действие осуществляется <<actor.tdesc>>. ";

Можете менять actor в объявлении метода и в этой строке на что угодно, но данные вы не поменяете. Это, кстати, ещё один случай, для чего в любой метод действия передаётся объект персонажа. Как минимум, вы можете правильным образом на основе параметров персонажа просклонять игровое сообщение, например, дать правильное окончание рода: "Ты взял/взяла/взяло". Для этого в advr.t существует специальная функция iao(), генерирующая окончание по параметрам объекта персонажа. Соответственно Красной шапочке вы бы по объекту персонажа генерировали в игровых сообщениях окончания женского рода, а Серому волку - мужского, и не надо было бы мучатся с гендеронезависимыми формулировками всего текста.

Kephra написал:

Я только что в doTake для проверки вписал пьедестал. Скомпилировал. Пошел в пещеру, взял череп и не умер — все стрелы были выпущены в пьедестал smile. Поменял на «me» и умер как полагается.

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

Неактивен

0    0    #6
01.09.2018 01:13

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

Локальная переменная? Не указатель на объект?

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

Неактивен

1    0    #7
02.09.2018 22:46

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

Мне понятно, что первый аргумент в doTake это actor (смотрел код doTake в advr.t), но разве мы вот таким способом передаём???

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

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

Методы verDoTake и doTake наследуются объектами от своих родителей. TADS - это язык с суперклассами, так что в объекте и его прямом родителе в явном виде какого-то метода может не быть, а работать будет метод у родителя через несколько уровней наследования, где он определён явным образом. Существует общий класс thing, от которого наследованы практически все классы и объекты стандартной библиотеки для моделирования мира (базовый класс ещё выше - object, но в моделировании мира он напрямую не применяется). В отдельных классах verDoTake и doTake явно переопределены, чтобы изменить поведение по умолчанию, например, в классе fixeditem, предназначенном для создания фиксированных объектов, типа стены или дома. Там на уровне verDoTake блокируется попытка взять такой фиксированный предмет.

Когда вы в черепе начинаете работать с verDoTake или doTake, равно как и с любым наследованным методом, вы всего лишь переопределяете его тело, то есть тот код, который будет выполняться при его вызове. Интерфейс вызова вы никак не переопределяете, вернее не должны переопределять, если не хотите получить ошибки. Движок как ожидал, что в любом объекте метод действия будет готов от него принять объект персонажа, так и ожидает, с учётом чего и вызывает doTake с одним аргументом, в который передаёт объект персонажа

Код:

class printer: object
    print(msg) =
    {
        say(msg);
    }
;

messager: printer
;

Если для такого кода вызвать messager.print('Hello World!'), то сработает метод print, определённый в его родителе. В этом и суть ООП, что нам не нужно явно определять методы и свойства объектов, если они уже определены у их родителей и не требуют модификации. Мы можем явно переопределить метод print в объекте messager, чтобы, например, не просто печатать текст, а печатать его курсивом, но если вся система заточена на интерфейс вызова из одного аргумента в виде строки, то менять его в переопределённом методе не нужно, чтобы ничего не поломать, можно просто менять процедуры обработки внутри метода.

Проще говоря, работая с doTake в любом объекте мира, вы должны писать

Код:

doTake(actor) =
{
    // ...
}

И всё, просто на текущем этапе примите это как данность, если пока нет полного понимания. Можете делать разные дополнительные вещи внутри метода, типа проверки условий на поражение, но интерфейс вызова не трогайте. Если сильно хочется, вы можете вместо actor написать другой локальный идентификатор, но объект, который будет доступен по этому идентификатору внутри блока doTake, вы всем этим никак не измените. Все остальные попытки шаманить с аргументами doTake ни к чему хорошему не приведут.

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

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

Неактивен

1    0    #8
03.09.2018 01:27

Nikita
Модератор (+404, -135)
Зарегистрирован: 29.10.2016
Сообщений: 139

Re: Игра пример

Kephra написал:

То есть, даже если я захочу передать в doTake другой объект вместо актёра, например камень, (smallRock), просто вписав его в аргументы doTake, то это не произойдёт? Система создаст локальную переменную/объект smallRock в которой всё равно будут данные объекта персонажа, поскольку таков шаблон.

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

В общих чертах под капотом происходит следующее:

Движок принимает из интерфейса интерпретатора команду "взять череп", раскладывает её на лексемы, определяет, что нужно выполнить действие, у которого есть название "взять" в отношении объекта, у которого есть название "череп". После этого движок смотрит, какое внутреннее название у действия "взять" и выясняет, что это Take. Затем в объекте с названием "череп" из текущей локации движок вызывает метод verDoTake, отправляя ему в качестве аргумента объект текущего персонажа. Если при вызове verDoTake в объекте с названием "череп" ничего не происходит, а именно вывода на экран, то движок считает, что выполнению действия ничего не мешает, так что вызывает уже doTake, также отправляя ему в качестве аргумента объект текущего персонажа.

Можно, например, в череп вписать такое:

Код:

verDoTake(actor) =
{
    "А морда у <<actor.rdesc)>> не треснет? ";
}

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

Если у вас в игре два персонажа, у одного из которого не треснет, а у другого таки да, может треснуть, то как раз передаваемый actor позволит нам проверить, кто из них вызывает действие и либо разрешить, либо запретить. На этот случай движок и построен таким образом, чтобы всегда в метод действия передавать персонажа. Ну или просто по объекту персонажа можно дотянуться до его содержимого. Например, если бы у нас в игре был щит, то можно было бы по actor внутри doTake проверить инвентарь персонажа и, если есть, то увести на ветку pass, а если нет, то на ветку die().

Важно понять, что объявление метода не является его вызовом.

Код:

test: object
    message(string) =
    {
        say(string);
    }
;

Здесь объявляется метод message, но не вызывается. Вызов - это строка с функцией say(), но она выполнится только если где-то кто-то вызовет метод как test.message('Hello World!').

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

Объявляя внутри объекта метод doTake(actor), вы всего лишь говорите: "Ну OK! Если кто-нибудь захочет в этом объекте вызвать метод с названием doTake, то я в принципе готов это обработать, ну а то, что вы мне в него передадите, я внутри этого метода буду называть actor".

Неактивен

Powered by PunBB
© copyright 2001–2024 iFiction.Ru