Back to list
Sep 20 2017
CX

CX Руководство: Использование доступностей (affordances) при создании небольшой текстовой игры-приключения

Введение

В данном руководстве представлена текстовая «игра» (где пользователь не взаимодействует с программой напрямую и не может влиять на решения персонажа), которая использует архитектуру “вызов-ответ”, чтобы определить какие действия герой игры может предпринять. Исходный код можно найти в CX’s repository, файл - examples/text-based-adventure.cx.

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

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

Вы выжили.

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

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

Вы погибли.

Call's State:
flag:			true
nonAssign_32:		""

halt() Arguments:
0: "You died."

65: call to halt

Как вы видите, появляется ошибка, если вы умираете (это нормально, так как это страшная ситуация для программиста).

Архитектура “вызов-ответ”

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

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

func walk (flag bool) () {}
func noise (flag bool) () {}
func consider (flag bool) () {}
func chance (flag bool) () {}
func fightResult (flag bool) () {}
func theEnd (flag bool) () {}

Affordance System

Другая функция должна координировать вызовы функций. В данном случае, система доступности CX (Affordance System CX) используется для определения того, разрешено ли действие “бежать” или нет.

yes := true
no := false

remArg("walk")
affExpr("walk", "yes|no", 0)
:tag walk;
walk(false)

В приведенном выше коде remArg () ищет выражение с тегом “walk” (“идти”) и удаляет его аргумент. Это сделано для того, чтобы система доступностей перечислила аргументы, которые которые могут быть отправлены оператору, управляющему выражениями. Далее, affExpr() сообщает CX “из всех аргументов, которые могут быть отправлены на walk, сообщи мне возможность использования yes или no в качестве аргументов и примени 0-й вариант из списка возможностей.”

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

setClauses("
          aff(walk, yes, X, R) :- X = monster, R = false.
          aff(noise, yes, X, R) :- X = monster, R = false.

          aff(consider, yes, X, R) :- R = false.
          aff(chance, yes, X, R) :- R = false.
          aff(fightResult, yes, X, R) :- R = false.
          aff(theEnd, yes, X, R) :- R = false.

          aff(consider, yes, X, R) :- X = monster, R = true.
          aff(chance, yes, X, R) :- X = fight, R = true.
          aff(fightResult, yes, X, R) :- X = fight, R = true.
          aff(theEnd, yes, X, R) :- X = died, R = true.
        ")

Первое правило может быть прочитано как “Я буду запрошен, если вы планируете отправить yes аргумент в walk действие. Если объект monster сейчас находится здесь, тогда этот аргумент не вариант.”

Правила второго блока (4 правила после первой пустой строки) говорят системе “never” (“никогда”) не принимать аргумент yes. Мы делаем это, потому что хотим, чтобы это стало поведением по умолчанию, но позже мы сможем ввести правила, которые будут переопределять это поведение. Этот процесс переопределения происходит с последними 4 правилами. В основном, этот блок правил говорит CX принимать yes в качестве аргументов, если конкретный объект присутствует в стеке объектов.

Объекты

Некоторые действия добавляют или удаляют объекты из стека объектов. Например, всякий раз, когда действие noise (“шум”) раешает, чтобы монстр появился, addObject (“monster”) выполняется. Если путешественник решает сбежать от боя, объект “monster” удаляется из стека.

В случае действия chance (“шанс”), монстр может дать путешественнику несколько секунд на раздумья, чтобы посмотреть, что он будет делать дальше. Для этого объект “fight” удаляется (так как монстр еще не хочет начинать бой), но объект “monster” остается.

Заключение

CX’s affordance system (система доступностей CX) использует объекты и правила, чтобы принимать сложные решения о том, как будет производиться фильтрация.

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