Кодирующие взаимодействия в текстовом приключении

РЕДАКТИРОВАТЬ: Если вы не можете потрудиться, чтобы прочитать этот мамонтовый вопрос, я поставил сводку внизу.

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

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

Это все сделано, но вот точка, в которой я застрял:

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

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

ЯВНО - Предмет действия (элемент инвентаря, элемент окружающей среды или символ) изменяет его описание НАПРИМЕР. "Стена пунша" может изменить описание стены, чтобы описать вмятину в стене ИЛИ - субъект действия заменяется другим пунктом НАПРИМЕР. "Разбитая бутылка" приводит к тому, что "бутылка" меняется на "сломанную бутылку" или "убивает Джона", приводит к замене персонажа Джоном на предмет окружающей среды "Джон труп".

- возвращает ответ, описывающий предыдущее изменение НАПРИМЕР. "Сломанные кусочки бутылки разбросаны по полу".

- Изменено описание области. НАПРИМЕР. "smash lightbulb" приводит к тому, что описание комнаты меняется, чтобы описать черный черный номер.

- Элементы добавляются/удаляются из инвентаря или среды НАПРИМЕР. "забрать бутылку". Теперь у вас есть бутылка в инвентаре, и бутылка удаляется из окружающей среды.

- Указаны направления движения и области, которые они приводят к изменению НАПРИМЕР. "открыть дверь с ключом" позволяет перемещать Восток в другую комнату.

- Игрок перемещается в новую область НАПРИМЕР. "идите на север" приведет вас в другую область.

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

Например, если элемент является "Бутылкой":

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

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

Как вы можете видеть, мне нужен общий способ определения на основе "взаимодействия", каковы будут последствия этого действия и какие другие объекты, такие как Item, Player (для инвентаря) и Area соответственно.

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

РЕДАКТИРОВАТЬ: Есть ли способ определить метод во взаимодействии, чтобы я мог передать несколько методов для вызова (и их параметров)? Первоначальный ответ, возвращаемый, будет значением по умолчанию, обязательным последствием, и тогда могут быть дополнительные, если они указаны.

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

Для второго взаимодействия я бы сказал, чтобы вернуть ответ ( "Бутылка проносится по воздуху в..." ), вызовите RemoveFromInventory на предмет действия, вызовите UpdateStatus на бутылке ( "бутылка разбита" ) и окно ( "окно разбито" ) и вызовите UpdateAreaDescription, чтобы изменить текущее описание области ( "Вы стоите в комнате с одним окном, стекло разбивается на куски" ).

Звучит ли это так? Я стараюсь держать это как можно более общим, ради всех возможных возможных взаимодействий.

РЕДАКТИРОВАТЬ 2: Чтобы прояснить далее и попытаться обобщить проблему:

В моей игре есть Actionable объекты (бутылка, стена, Джон). Каждый объект Actionable имеет список Объекты взаимодействия, которые описывают, как игрок может взаимодействовать с ними. В настоящий момент взаимодействие имеет Свойство "Имя" ( "throw", "hit", "break" ) и возвращает Response ( "Вы бросаете" ).

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

"бросать стеклянную бутылку"
- Ответ возвращается ( "Ты бросил стеклянную бутылку" )

- "Бутылка" удаляется из инвентаря Игрока.
- Он заменяется новым, чтобы отразить изменение. ( "Бутылка" заменена "Сломанной бутылкой" ).
- Второй ответ возвращается ( "Кусочки стеклянной бутылки разбросаны по полу" ).

"бросать стеклянную бутылку в окно"
- Ответ возвращается ( "Ты бросил стеклянную бутылку в окно" )

- Объект "Бутылка" удаляется из инвентаря Игрока.
- Объект заменяется новым объектом, отражающим изменение. ( "Бутылка" заменена "Сломанной бутылкой" ).
- Второй, необязательный объект заменяется новым, чтобы отразить изменение. ( "Окно" заменено словом "Сломанное окно" ).
- Обновлено свойство "Описание" текущей области. ( "Ты стоишь в комнате с одним разбитым окном" ).

Когда я создаю взаимодействия, как я могу изменить дополнительные действия, которые они выполняют, например, изменения статуса для субъекта или изменения текущего описания области?

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

Ответ 1

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

Я добавил метод Invoke() в класс Interaction, новый интерфейс IActionResult, который определил метод Initiate() и ряд различных типов ActionResult для каждого возможного последствия. Я также добавил список действий для взаимодействия. Метод Invoke будет просто перебирать все объекты IActionResult и вызывать метод Initiate().

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

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

Я очень ценю все ваши предложения, и мне очень жаль, если я не понял свой вопрос или мои комментарии (или даже этот ответ). Благодарим за помощь.

Ответ 2

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

Блокировать распознанные объекты глаголов

  • Посмотрите
  • UseItemOn (Key001, LockPicks, Кувалда,...)
  • Удар

Таким образом, вы можете в целом обрабатывать глаголы, которые он не распознает, с ответом типа "Вы не можете <verb> <object> , и обрабатывать глаголы, которые он распознает с событиями или что-то еще.

Изменить

В соответствии с вашим комментарием я, очевидно, просто просмотрел ваш вопрос (слишком долго для меня). Тем не менее, я не вижу разницы, действительно. Дело в том, что объект участвует в событии. С точки зрения бутылки он попадает в стену. С точки зрения стены он попадает в бутылку. Оба объекта будут иметь список глаголов, на которые они будут реагировать определенным образом.

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

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

Бутылка участвует в столкновении со стеной. У бутылки в списке глаголов есть тип взаимодействия Collide. Он может иметь один объект, с которым он заботится о столкновении, или может иметь значение Any или AnySolid или что-то еще. Там миллион способов архитекторов. В любом случае, стена также участвует и может также иметь в своем списке глаголов тип взаимодействия Collide. Но он заботится только о том, чтобы столкнуться с объектом Sledgehammer, или, может быть, с AnySolid с массой 10 или более...

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

Он может быть заполнен жидкостью, поэтому он будет реализовывать интерфейс IContainer, который имеет метод Spill, а также интерфейс IConsumeable, который имеет метод Drink. Это может быть блокировка, которая реализует интерфейс ILockable, который имеет метод Unlock (obj Key) и метод Pick (int PickSkill). Каждый из этих методов может приводить к определенным изменениям состояния в объекте и других участниках (участках) во взаимодействии. Вы можете сделать это с помощью событий, если хотите.

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

Ответ 3

Все описанные вами действия состоят в следующем:

  • Глагол (например, "throw" )
  • объект (например, "бутылка" )
  • дополнительный дополнительный параметр, описывающий действие далее (например, "в окне" )

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

public interface IObjectBase
{
   bool HandleAction(string verb,string [] params)
}

public class Bottle: IObjectBase
{
   bool HandleAction(string verb,string [] params)
   {
     //analyze verb and params to look for appropriate actions
     //handle action and return true if a match has been found
   }
}

Ответ 4

У вас есть две вещи: игрок и окружающая среда (у вас могут быть и другие игроки).

Передайте их каждому для каждого взаимодействия:

interaction.ActOn(environment, player);

//eg:
smash.ActOn(currentRoom, hero);

Затем пусть каждое взаимодействие выясняет, что делать:

environment.ReplaceObject("window", new Window("This window is broken. Watch out for the glass!");
player.Inventory.RemoveObject("bottle");
player.Hears("The window smashes. There is glass all over the floor! If only John McLane were here...").

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

player.Inventory.ReplaceObject("bottle", new BottleOfWater());

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

См. также Двойная отправка.

Ответ 5

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

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

  • Распечатайте сообщение
  • Измените описание объекта/комнаты
  • "Заблокировать" или "разблокировать" объект. Это означает, что "исследуйте пояс" скажет "Вы не видите никакого пояса здесь". "Пока не проверять труп" не было выполнено, чтобы узнать, что "у труп блестящий пояс вокруг талии".
  • Блокировка или разблокировка выходов из комнаты
  • Переместите плеер в какую-либо комнату.
  • Добавить/удалить что-то из инвентаря игрока
  • Установить/изменить некоторые игровые переменные, например. "moveGlowingRock = true" или "numBedroomVisits = 13" и т.д.

и т.д. Это то, что я сейчас имею в виду. Это все методы, возможно, в классе API и при необходимости принимают различные параметры.

Теперь есть комнаты. В номерах есть объекты. Некоторые команды действительны для каждого объекта. Один простой способ состоит в том, чтобы каждый объект комнаты имел словарь разрешенных команд. Script - это делегат, который указывает на ваше действие script. Подумайте об этом:

delegate void Script();

class GameObject
{
    public Dictionary<string, Script> Scripts {get; set;}
    public string Name {get; set;}

    //etc...
}

И ваши скрипты, хранящиеся в соответствующем экземпляре Room:

    //In my project, I plan to have such an abstract class, and since it is a game _creator_, the app will generate a C# file that contains derived types containing info that users will specify using a GUI Editor.
abstract class Room
{
    protected Dictionary<string, GameObject> objects;

    public GameObject GetObject(string objName) {...//get relevant object from dictionary}
}


class FrontYard : Room
{

    public FrontYard()
    {
        GameObject bottle;
        bottle.Name = "Bottle";
        bottle.Scripts["FillWithWater"] = Room1_Fill_Bottle_With_Water;
        bottle.Scripts["ThrowAtWindow"] = Room1_Throw_Bottle_At_Window;
        //etc...
    }

    void void Room1_Fill_Bottle_With_Water()
    {
         API.Print("You fill the bottle with water from the pond");
         API.SetVar("bottleFull", "true");         
    }

    void Room1_Throw_Bottle_At_Window()
    {
         API.Print("With all your might, you hurl the bottle at the house window");
         API.RemoveFromInventory("bottle");
         API.UnlockExit("north");
         API.SetVar("windowBroken", "true");    
         //etc...     
    }    
}

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

Итак... все это может дать вам некоторые идеи поработать для вашего собственного проекта. Если что-то неясно, спросите. Надеюсь, я не отклонился от вашего вопроса или чего-то еще.

Я думаю, что я потратил слишком много времени, набрав все это > _ >

EDIT: PS: Мой пример скелета точно не показывает, как управлять командами с участием нескольких игровых объектов (это только одна из многих тонкостей, на которые я намекнул). Для таких вещей, как "бросать бутылку в окно", вам нужно придумать, как управлять таким синтаксисом, например. вкус моего решения заключается в том, чтобы разобрать и узнать, какая команда выдается... "Бросьте GO GO". Узнайте, что представляют собой игровые объекты, а затем посмотрите, есть ли у них текущая комната. и т.д.

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

Простите мои бред... > _ >

Ответ 6

Кажется, что ваша проблема заключается в управлении распространением событий. Microsoft обрабатывает эту проблему (для менее красочных целей) с использованием шаблона/событий Observer.

Я думаю, что сочетание шаблонов проектирования Observer и Mediator от Gamma и т.д. книга "Шаблоны проектирования" будет очень полезна для вас. В книге есть образец класса ChangeManager, который может быть полезен, но я добавил некоторые другие ссылки, которые должны служить вам хорошо.

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

Статья Microsoft по шаблону наблюдателя: http://msdn.microsoft.com/en-us/library/ee817669.aspx

DoFactory на шаблоне Mediator (с диаграммами UML): http://www.dofactory.com/Patterns/PatternMediator.aspx

DoFactory по шаблону Observer (с диаграммами UML): http://www.dofactory.com/Patterns/PatternObserver.aspx

Документация по интерфейсу IObserver в .Net 4,  http://msdn.microsoft.com/en-us/library/dd783449.aspx

другая статья о шаблоне наблюдателя. http://www.devx.com/cplus/Article/28013/1954

Ответ 7

Взаимодействие может быть определено как "Глагол + {Список фильтров} + {Список ответов}"

Например, для вашей бутылки "заливка водой", взаимодействие будет:

  • Глагол: Fill ({ "fill", "pour" })
  • Список фильтров: Have(player, "bottle"), Have(currentRoom, "water tap")
  • Список ответов: Print("You filled the bottle with water"), Remove(player, "bottle"), Add(player, "bottle of water")
  • альтернативно, список ответов может быть: SetAttribute(player.findInventory("bottle"), "fill", "water")

Тогда, если вам нужно "бросить бутылку воды в окна":

  • Глагол: Throw ({ "throw", "smash" })
  • Список фильтров: Have(player, "bottle of water"), Have(currentRoom, "windows")
  • Список ответов: Print("The bottle smashed with the windows, and both of them are broken"), Remove(player, "bottle of water"), Add(curentRoom, "broken bottle"), Remove(currentRoom, "window"), Add(currentRoom, "broken window"), SetAttribute(currentRoom, "description", "There is water on the floor")

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

Ответы будут объектом функции, который реализует интерфейс IResponse, который имеет некоторые конструкторы, и метод IResponse.do(). Фильтры будут функциональным объектом, который реализует интерфейс IFilter, снова с некоторыми конструкторами, и метод IFilter.check() возвращает логическое значение. Вы можете даже иметь фильтры And(), Or() и Not(), чтобы создавать более сложные запросы.

Вы можете сделать вещи более читабельными, используя некоторые удобные методы, у Игрока может быть удобный метод Player.have(Actionable), чтобы вы могли написать player.have( "бутылка воды" ), которая возвращает не сам объект бутылки, но объект IFilter, который будет проверять, есть ли у игрока "бутылка воды", когда вызывается метод .check(). В принципе, сделать объекты ленивыми.