Взаимодействие машин: «Лампочка» и «Светофор»

Материал из wiki.appsalutecreator.com
Перейти к: навигация, поиск
Урок 3 << Оглавление >> Урок 5

Цель: изучить взаимодействие машин состояний и организацию циклов.

Задачи:

  • создать переключатель, включающий и выключающий лампочку;
  • сделать лампу активной, знающей о состоянии переключателя;
  • создать простой светофор, переключающий по кругу три лампы;
  • создать уличный светофор с "правильным" поведением на основе управляющей машины;
  • включать и выключать светофор при помощи кнопки.


Лампочка и выключатель

Создание машин

Создайте в учебном проекте новый экран lesson_4 и сцену stg_main. Перетащите на сцену из редактора ресурсов переключатель и лампочку:

Lesson4 switch lamp.png


Поменяйте обоим объектам тип, сделав их машинами состояний. Переименуйте (поле свойств "имя") переключатель в switch, а лампочку в lamp.

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

Lesson 4 state1.png


Верхние красные кружочки относятся к переключателю. Он может находиться в двух состояниях on и off. Лампочка (синие кружочки), также имеет два состояния, которые мы назовем dark и light.

Cоздайте эти состояния в машинах так, как это было описано в предыдущем уроке. Добавьте в каждое состояние команду draw с параметром res, в которые перетащите картинки из редактора ресурсов. Затем в переключателе добавьте команду click с параметром go. Сделайте из него "чекбокс":

Lesson 4 switch state1 new.png

В лампочке создайте пока только по команде рисования draw:

Lesson 4 lamp state1.png

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

Подключаем провода

Теперь самое время подключить к лампочке и выключателю провода. Этими проводами будет служить команда set. Вспомним теорию. При входе в состояние происходит выполнение команд инициализации, которые сразу выполняются, как только машина поменяла своё состояние. К таким командам и относится команда set. С её помощью происходит изменение состояния других машин. Добавим в каждое из состояний переключателя по команде set с параметрами obj и st. Для параметра obj из выпадалки выберем объект lamp. В параметре st зададим руками требуемые состояния лампочки. В результате должно получиться:

Lesson 4 switch state2.png


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

Активная лампочка

Давайте усложним поведение лампочки. Пусть она, независимо от переключателя, может включаться, если на неё кликнули в состоянии dark. Перейдя в горящее состояние light она горит 2 секунды, а затем сама выключается, возвращаясь в dark:

Lesson 4 states2.png

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


У Вас, конечно, должен был получиться следующий результат:

Lesson 4 lamp2.png

Условия переходов

Потребуем теперь, чтобы лампочка, при клике на неё, могла включаться только, если в этот момент включен переключатель. Таким образом, необходимо заблокировать команду click если переключатель находится в состоянии off. Делается это при помощи команды if, которую необходимо добавить в состояние dark лампочки:

Lesson 4 lamp3.png

Обратим внимание, что кроме команды if, было добавлено одноименное поле в команде click с параметром 0. Разберемся, как работают эти команды. В состоянии может быть несколько команд-условий if. Все они нумеруются начиная с нуля. Т.е. первая сверху в списке команда if имеет номер 0, следующая 1 и т.д. Однако сами по себе условия ещё ничего не делают. Запуск их на проверку выполнимости осуществляют другие команды. В нашем случае это делает команда click. В её поле if стоит ноль. Это означает, что при клике на лампочку переход go произойдет только, если выполняется условие под номером 0. В этом условии происходит проверка того, находится ли объект obj в состоянии st. Если находится, то условие срабатывает и клик приводит к требуемой смене состояния. Все просто :).

Протестируйте получившийся результат. Сначала на выключенном переключателе кликните на лампочку. Она не загорится. Теперь, включите переключатель. Лампа загорится и после 2-х секунд погаснет. Теперь, если кликнуть на лампочку, она снова загорится, так как переключатель находится в состоянии on. Стоит, как обычно, в процессе этих экспериментов понаблюдать за отладочной информацией.

Начальное состояние

Lesson 4 prop.png

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

Запустим вьювер. Обратим внимание, что лампочка сразу загорелась. Так и должно быть, так как машина переключателя начала работать из состояния on, а, следовательно, запустила команаду set, меняющую состояние лампочки.

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

Подробнее о командах

Подытожим информацию о командах, работающих в состояниях внутри машин. Существует четыре типа команд:

  • инициализаций (draw, set) - выполняется сразу как только машина оказалась в данном состоянии. Это "мгновенные" команды, т.к. их действие не растянуто во времени;
  • воздействий (click) - выполняются, если на машину было оказано внешнее воздействие, например, в неё кликнули мышкой;
  • процессов (wait) - эти команды начинают выполняться, когда произошел переход в данное состояние. В отличии от команд инициализации, их действие растянуто во времени. К этому типу команд относится также команда move, которая встречалась в машинах первого урока;
  • условий (if) - описание различных логических условий, которые могут использовать другие команды данного состояния.

К настоящему моменту нам уже известны следующие команды:

  • draw — управление внешним видом машины, определение её графического ресурса. Эта команда, как и set является командой инициализации, т.е. выполняется в момент попадания машины в данное состояние. Если в состоянии машины такой команды нет, то автоматически устанавливается графический ресурс, указанный в свойствах данной машины (поле res);
  • set – установка состояния другого объекта (команда инициализации);
  • click – что делать при клике на объект (команда воздействия). В нашем случае - переход в другое состояние той же машины;
  • wait – временная пауза, после которой происходит переход в другое состояние (время исчисляется в миллисекундах, где 1 сек = 1000 миллисекунд). Это команда процессов, т.е. её выполнение длится во времени;
  • if – логическое условие перехода (команда перехода).

Светофор

Lesson 4 lights1.png

Рассмотрим теперь обычный уличный светофор. Вспомним как он работает:

  • сначала обычно (к сожалению) горит красная лампа, остальные выключены;
  • затем, при горящей красной лампе, загорается ещё и желтая;
  • затем желтая и красная лампы гаснут и загорается зеленая;
  • в конце горения зеленой лампы она три раза мигает и выключается;
  • затем включается желтая лампа, горит и гаснет
  • затем включается красный, чтобы повторить весь процесс с самого начала.

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

Lessons 4 tree.png

Разместим на сцене подложку для светофора. Она останется статической картинкой. Затем поместим поверх неё три лампы разного цвета и сделаем их подобъектами подложки. Обложку назовем lights, а лампы light_R, light_Y, light_G. Изменяя тип у ламп, превратим их в машины.

Простой светофор

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

Lesson 4 R1.png

Lesson 4 Y1.png

Lesson 4 G1.png


Хотя "простой светофор" будет работать вполне корректно, приведенные выше машины не очень хороши. Все лампы находятся в своем стартовом состоянии off. В них есть команда инициализации set, которая устанавливает состояние горения следующей лампы. Что должно произойти в начальный момент запуска проекта? Машины работают также как и рисуются - сверху вниз по дереву проекта. Сначала инициализируется красная лампа. Это её первое попадание в состояние off, поэтому она пытается включить желтую лампу. Однако этого не происходит, так как желтая лампа еще "не существует". Когда до неё доходит очередь, она создается и пытается включить зеленую лампу которой также "ещё нет", поэтому этого тоже не происходит. И только когда доходит дело до создания зеленой лампы, она включает (уже существующую и находящуюся в состоянии off) красную лампу.

С этого момента всё начинает работать вполне логично. При входе в состояние on выключается предыдущая лампа, а при входе в состояние off включается следующая.

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

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

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

Тогда не возникнет непредсказуемости в поведении проекта.

Промежуточные состояния

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

Lesson 4 R2.png

Как мы видим, в добавленное состояние next переехала команда set, которая раньше была в состоянии off.

Lesson 4 R2 prop.png

Зададим в свойствах (Properties) стартовое состояние красной лампы в on, а остальным лампам поставим состояние off (этого можно не делать, если состояние off в линейках состояний стоит первым).

В выключенных состояниях ничего не происходит кроме рисования серой картинки для выключенной лампы. Поэтому,теперь ни каких попыток изменить состояния других ламп у light_Y и light_G не будет. Красная лампа light_R начнет работать из состояния on. Прогорев 1 сек, она перейдет в состояние next, в котором переустановит состояние следующей лампы. В состоянии next красная лампа и останется (рисуя серую картинку), пока её не включит на очередном цикле зеленая лампа.

Подчеркнем еще раз, что команда set является командой инициализации состояния. Она запускается при входе в состояние. Если бы она запускалась при выходе, дополнительное состояние next не потребовалось бы и set можно было бы вставить в on. Именно из-за "стартовости" выполнения set нам потребовалось ещё одно состояние.

Управляющая машина

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

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

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

Lesson 4 lights.png


Каждое состояние является этапом функционирования светофора, между которыми происходят переходы. Красная и желая лампы - это простые машины с двумя состояниями on и off в которых только рисуются соответствующие картинки:

Lesson 4 R3.png

Lesson 4 Y3.png


Зеленая лампа пока состоит из 3-х состояний on, off и blink. Первые два аналогичны предыдущим лампам. В состоянии мигания blink временно поставим рисование картинки желтого цвета (draw) и команду wait, после действия которой происходит переход в состояние off. Дальше мы это состояние подправим.

Lesson 4 G3.png

Как мы видим теперь вся логика работы сконцентрирована в управляющей машине (подложка) и легко прослеживается. Остальные машины очень простые (за исключением мигающей зеленой лампы). Такой стиль организации взаимодействия машин часто оказывается существенно более ясным и удобным.

Анализ поведения управляющей машины не должен составить труда. Прокомментируем только состояние G_blink. В этом состоянии зеленая лампа переводится в состояние мигания (blink). Предполагается, что когда мигание закончится, зеленая лампа должна перейти в состояние off. В управляющей машине в команде wait мы ждем этого перехода при помощи проверки условия if.

Обратим внимание, что сама команда wait вызвана без параметра t. Если время ожидания t не указано, это означает, что wait должен выполниться "мгновенно" и произойти переход в следующее состояние (go=Y). Однако это случится только, если выполнится условие if (зеленая лампа отмигает). Иначе идет переход к следующей команде wait. В ней находится единственный параметр loop со значением -1. Это означает, что все команды wait нужно зациклить, повторяя сверху вниз (до loop) бесконечное число раз, пока условие не выполнится и машина совершит переход go=Y. Подробнее использование циклов в командах мы разберем чуть ниже.

Мигание как цепочка состояний

Выше процесс мигания зеленой лампы мы временно представили при помощи рисования на её месте желтой лампы. Займемся теперь вплотную настоящим процессом мигания. Как и многие машины состояний, это поведение можно запрограммировать при помощи различных приёмов. Самым простым (но не очень удобным) является представление каждого включения и выключения лампы при помощи отдельного состояния. Для простоты, пусть она мигает не 3, а 2 раза. Тогда такая машина может выглядеть следующим образом:

Lesson 4 G4.png


Начиная с состояния blink происходит последовательный переход в состояния blink1, blink2, blink3. Из последнего состояния blink3 мы прыгаем в состояние off, где процесс мигания заканчивается.

Почему такая машина не удобна? Если нам необходимо изменить период мигания, прийдётся менять параметр t в каждой команде wait всех 4-х состояний. А если надо мигнуть не 2, а 10 раз? Понятно, что для решения такой задачи необходимо использовать другие механизмы.

Мигание как цикл

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

Lesson 4 G5.png

Запустим вьювер, убедившись, что всё работает правильно. Теперь разберемся с функционированием состояния blink.

Напомним, что команды set и draw -- это команды инициализации. Сколько бы их не было в состоянии, они все сразу выполнятся сверху вниз, как только машина переходит в это состояние. Последовательное выполнение нескольких команд draw - достаточно бессмысленное мероприятие. Эта команда задает ресурс рисования, поэтому в последовательности команд draw реально сработает только последняя, которая "перебьет" ресурс рисования предыдущих команд draw.

Последовательность выполнения инициализационных команд с одинаковым именем можно прервать, если добавить в некоторые из них параметр break (прервать). В примере выше есть 2 команды draw. Нулевая из них имеет в своих параметрах break (напоминаем, что нумерация всех команд, как и if-ов начинается с нуля). Поэтому, в момент запуска состояния, выполнится эта нулевая команда и следующая (первая) команда draw выполняться не будет. В нулевом draw мы гасим лампочку (res=light_D).

Посмотрим теперь на последовательность команд wait. Это команды процессов и, в отличии от команд инициализации, их выполнение занимает некоторое время. Команды процессов с одним названием выполняются последовательно друг за другом. В нашем случае начнет выполняться нулевая команда wait и только после t=500ms будет передано управление следующему wait-у.

В нулевой команде команде wait появился новый параметр draw со значением 1. Этот параметр означает, что когда wait закончит работать она перезапустит команду draw под номером 1 (которая еще не выполнялась). В этой команде установится картинка рисования включенной зеленой лампы (res=light_D).

Теперь настало время следующей команды wait. Отработав, она также перезапустит команду draw, но уже под номером 0 (т.е. лампочка погаснет). Если бы в этой команде wait не было параметра loop произошла бы передача управления последней команде wait.

Параметр loop в командах процессов означает, что цепочку команд с данным именем надо повторить loop раз (в нашем случае еще 2 раза). Фактически, когда вьювер пробегается по командам wait, он каждый раз уменьшает loop на 1. Если в поле loop ещё не получился ноль, он запускает по новой выполнение последовательности команд wait.

Таким образом, первые 2 команды wait выполнятся при первом прохождении по ним сверху вниз, а затем ещё два раза. В результате и получится три мигания.

Когда цикл отработает, он передаст управление последней команде wait без параметра t. Она сработает мгновенно, осуществляя переход из состояния blink в состояние off.

Заметим, что если в параметре loop стоит значение -1, то такой цикл будет длится бесконечное число раз. Он может быть прерван только в результате срабатывания условия при наличие параметра go (посмотрите еще раз на состояние blink_G управляющей машины светофора).

Инициализация на выходе

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

Lesson 4 set.png

Случайные мигания

В заключение урока cделаем отдельную машину flasher (мигалка), которую заставим случайно гореть одним из трех цветов. Для этого перетащим на сцену из редактора ресурсов одну из ламп светофора и превратим её в машину состояний. Введем следующие команды в четырех состояниях этой машины:

Lesson 4 flash1.png

Последние три состояния - это просто рисование того или иного цвета в течении секунды, после чего переход в стартовое состояние switch. В этом состоянии происходит случайный выбор следующего состояния. Делается это при помощи трех команд wait не имеющих временной задержки. В первых двух командах стоит параметр "p". Это вероятность с которой осуществляется переход в состояние, определяемое параметром "go". Если "p" отсутствует, то переход по "go" происходит в любом случае. Иначе только с вероятностью p. С вероятностью же 1-p передается управление следующей команде wait.

Таким образом, в нулевом wait-e состояния switch с вероятностью 0.33 машина перейдет в состояние R (красная лампа). С вероятностью 0.67=1-0.33 перехода не будет и запустится следующий wait. Из него с вероятностью 0.50 загорится желтая лампа (итоговая вероятность её горения будет равна 0.67*0.50=0.33). Наконец, если и этого не произойдет, то включится зеленый цвет. Все три цвета будут зажигаться с одинаковой вероятностью.

В общем случае, если есть n случайных переходов, чтобы добиться их равновероятности, необходимо использовать следующие вероятности:

p1=1/n, p2=1/(n-1), p3=1/(n-2),.... 

Ниже в таблице приведены значения вероятностей для некоторых n:

n p1 p2 p3 p4
2 0.50
3 0.33 0.50
4 0.25 0.33 0.50
5 0.20 0.25 0.33 0.50

Заметим, что иногда длительность горения может оказаться больше секунды (две, три и т.д.) Это просто означает, что данный цвет зажегся подряд несколько раз.

Упражнения

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

Урок 3 << Оглавление >> Урок 5