Из задач для Microsoft Visual C++

Если вы такие умные, то почему строем не ходите?!
Из армейского юмора.

После публикации статьи «Батарея, огонь!»[1] преподаватель из Рыбинска В.Н. Пинаев (pinaev@rgata. adm.yar.ru) прислал мне письмо с критическими замечаниями, а в ходе завязавшейся вслед за этим переписки поделился со мной еще одной задачей, которая перекликается с разбираемой в статье задачей Майхилла о синхронной стрельбе. Мне показалось интересным продемонстрировать на ее примере возможности сочетания объектного подхода и автоматной модели, прояснить роль автоматов в формировании «жизни» объектов в ООП и рассказать о том, как решается проблема доступа к общему ресурсу в рамках автоматной модели и библиотеки FSA.

В.Н. Пинаев предпочитает формулировать задачу Майхилла в «демилитаризованном» варианте: построенные в шеренгу роботы по команде должны одновременно поливать цветы. А вот новая задача — назовем ее условно «новобранцы» — сохранила армейский антураж. Впрочем, она совсем не воинственная, скорее наоборот:

Новобранцы по команде «Нале-во!» повернулись кто куда: одни - налево, другие - направо. В следующую секунду те из них, кто столкнулся с соседом нос к носу, поняли, что ошиблись, развернулись на 180o, и т.д. Вопрос: закончится ли когда-нибудь это безобразие, а если да, то через сколько шагов и как именно расположатся новобранцы?

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

Поведение новобранца

Создавая алгоритм для новобранца, мы не затронем действий стрелка, основной целью которых остается подготовка к выполнению команды (раньше это был выстрел, сейчас — поворот). А вот вид и поведение пули должны измениться значительно, поэтому целесообразно, используя механизм наследования, породить от нее класс лица новобранца CHead. Нам потребуется:

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

Рассмотрим теперь изменения уже имеющихся классов TBounce и CBullet, а также новый класс CHead.

Класс TBounce

Итак, лицо (порожденное пулей) должно вертеться туда-сюда. Чтобы это обеспечить, введем в описание базового класса пули TBounce свойство cstrFace. Этот класс содержит метод MakeNewBall, который ранее рисовал на экране эллипс, символизирующий пулю, а теперь будет отображать лицо.

Рисование эллипса в методе MakeNewBall заменим на вывод символа, отражающего положение лица новобранца. Кроме того, свяжем TBounce с породившим его автоматным классом: введем указатель pFsaShot для хранения ссылки и метод SetAddrMan для установки pFsaShot (см. листинг 1).

Добавим в конструктор класса еще параметр, определяющий приоритет автоматного объекта (nPri). Приоритеты служат для управления порядком исполнения процессов в параллельной среде, и данный случай не является исключением (для чего конкретно они нам пригодятся, будет рассказано ниже). Приоритет передается методу Fload базового автоматного класса LfsaAppl, загружающему объект в среду исполнения (см. конструктор класса TBounce).

Классы CBullet и CHead

Изменения в классе CBullet (пуля) минимальны. Мы лишь внесем в него предикат для анализа пошагового режима и ссылку на глобальную переменную bStep, которая определяет момент следующего дискретного шага алгоритма.

Что же касается нового алгоритма (таблицы переходов автоматного класса) для лица новобранца, то он будет задан в классе CHead. В наследство от пули CHead получает возможность определить моменты синхронизации (выстрел/поворот) и достижения границ окна отображения — предикаты x1, x2. Заголовок CHead приведен в листинге 2, реализация его предикатов и действий — в листинге 3, алгоритм действий в виде таблицы переходов соответствующего автомата — в листинге 4.

Действия y6 и y7 в момент поворота присваивают значение указателю на соседа, в сторону которого смотрит новобранец. Действие y5 обеспечивает случайный поворот при переходе из начального состояния st в состояние b1, а y10 отображает положение лица. Заметим, что в классе CBullet за рисование пули отвечало действие y1. Мы изменили его номер, чтобы избежать введения промежуточных состояний. Формально действия на одном переходе считаются параллельными, но фактически интерпретатор выполняет их в порядке возрастания номеров, чем мы и воспользовались для упрощения модели.

В программе для задачи Майхилла пули просто летели от одной границы окна к другой. Лица же не только летят («полет» оставлен, чтобы отображать динамику изменения состояния строя новобранцев), но при этом еще и вертятся в разные стороны в зависимости от состояния соседей.

Цепь стрелков: CChainShot

Цепь стрелков, ныне новобранцев, также претерпит определенные изменения. Во-первых, сформируем взвод из тридцати человек (ранее их было четверо). Во-вторых, метод SetLink, создававший ссылки лишь в одну сторону (от пуль к стрелкам), теперь будет строить их симметрично: не только от лица новобранца к самому новобранцу, но и от новобранца к его лицу. Усовершенствуем связи между соседними объектами в цепи, установив их более корректно, а для отладки добавим в методы класса код, который, используя автоматный метод FSetName, будет устанавливать имена объектам «Офицер» и «Стрелок». Кроме того, в CChainShot реализуется подсчет числа поворотов, о котором мы расскажем чуть позже.

Литература

1. Любченко В.С. Батарея, огонь!, или Задача Майхилла для Microsoft Visual C++:О синхронизации процессов в среде Windows// Мир ПК. 2000. № 2. с.148—155.

Окончание в следующем номере.