Рассмотрим теперь, как изменять состояния всех прямоугольников на каждом шаге выполнения модели. Сделать это просто: сначала подсчитаем для каждого прямоугольника число его живых соседей, а потом изменим состояние каждого прямоугольника в соответствии с правилами игры "Жизнь" Это самый простой и ясный алгоритм, который, однако, не будет оптималь-ным по быстродействию. Тем не менее, мы реализуем именно его, чтобы всех шагах разработки оставалась понятной идея алгоритма.
Для реализации изменения состояний ячеек в нашей модели в корневом объекте Main требуются дополнительные переменные и функции. В поле Дополнительный код класса окна Код объекта Main введите спецификацию двумерного целого массива count, в котором будем подсчитывать число жи-вых соседей соответствующих ячеек:
Будем считать, что наша решетка тороидальна, например, следующей справа ячейкой для граничной правой ячейки является самая левая ячейка того же ряда. Иными словами, любой индекс элемента массива count, который выходит за границу размерности, берется по модулю N. Поскольку мы рассматриваем только ближайших окружающих соседей, выход за границу размерности может быть только на 1. Поэтому формально в торе пересчет значений любого индекса i, который может принимать любое значение в диапазоне от —1 до n, можно в вычислениях определить так:
(i<0)? N-l: (i==N)? 0: i;
Для определения этого индекса введите в объект Main алгоритмическую функцию (), которую назовем torus. Ее аргументом является один целый параметр i. Функция возвращает целое значение, и ее тело определяется так:
return (i<0)? N-l: (i==N)? 0: i;
Теперь легко определить функцию, подсчитывающую живых соседей каждой ячейки. Назовем ее countAliveNeighbors. Функция эта без параметров, результатом ее работы является заполненный массив count числа живых соседей каждой ячейки, подсчитанный по массиву alive всех живых ячеек. Эту функцию удобно определить в поле Дополнительный код класса окна Код объекта Main. При записи программного кода используем комментарий — поясняющий текст, который начинается с двух символов слэш // и до конца строки:
/ Подсчет числа живых соседей вокруг каждой ячейки public void countAliveNeighbors() { for(int i=0; i<N; i++)
for(int j=0; j<N; j++) {
count[i][j] = alive[i][j]? -1:0;//-1, если сама ячейка живая for(int k=-l; k<2; k++) //соседи по вертикали for(int m=-l; m<2; m++)//соседи по горизонтали count[i][j] += alivettorus(i+k)][torus(j+m)]? 1:0; } }
Функция changestates определяет новые состояния ячеек решетки. Эта функция, собственно, реализует логику игры "Жизнь":
// Определяем новые состояния ячеек решетки public void changestates() {
boolean changed = false; // Параметр определяет, будет ли //изменено состояние хотя бы у одной ячейки countAliveNeighbors(); // сначала считаем живых соседей for(int i=0; i<N; i++) //затем определяем новое состояние
for(int j=0; j<N; j++)// мертвая, имеющая З живых соседей,
// оживает if (!alive[i][j] && count[i][j] == 3) alive[i][j]= changed = true; //живая, имеющая 2 или 3 живых соседей, не меняет состояния
else if (alive[i][j]&&(count[i][j]==2 || count[i][j]==3)); // во всех остальных случаях ячейка умирает или остается мертвой else { if(alive[i][j]) changed=true; //была живая - учитываем alive[i][j]=false; } //Останавливаем модель, если состояния всех ячеек не изменились
if (!changed) Engine.finish(); }
Работа модели состоит в том, что на каждом шаге по времени вычисляется новое состояние всей решетки. Иными словами, на каждом таком шаге должна вызываться функция changestates, только что определенная нами в поле Дополнительный код класса. Вызов этой функции на каждом временном шаге можно выполнить при срабатывании циклического таймера.
В поле редактора корневого объекта Main введите новый таймер (), определите его циклическим с таймаутом 1.0 и в поле Действие при срабатыва нии этого таймера введите вызов функции changestates:
changeStates();
После запуска модели в поле анимации из случайного начального состояния на каждом шаге будут порождаться разнообразные графические фигуры (рис. 8.2).
Сравните свою модель с моделью LifeGame3.
Улучшение модели
Для того чтобы модель приобрела законченный вид, можно залить поле ани-мации каким-либо цветом и в левую его часть добавить поясняющий текст.
Сделаем еще одно добавление в модель. На многих сайтах в Интернете обсужда-ются правила игры "Жизнь" и приводятся начальные состояния решетки, которые при проигрывании в соответствии с правилами порождают последовательность интересных осциллирующих фигур. Одним из примеров такого ресурса является сайтпо математике: www.math.cora/students/wonders/life/life.htnil.
Вставим в модель возможность установки предопределенной фигуры в каче-стве начального состояния решетки. Для этого введем в левую половину по-
ля анимации элемент Кнопка (OK), которую сделаем прозрачной (без залив-ки цветом). В поле Реакция на событие окна свойств этой кнопки поместим
следующие операторы:
for(int i = 0; i<N; i++) for(int j = 0; j<N; j++) alive [i][j] = false; alive[22][23] = true; alive[22][24] = true;
Первый оператор — это вложенный цикл, он очищает решетку (устанавливает состояние всех ячеек поля анимации в false). Далее несколько операторов устанавливают в true состояния тех ячеек, которые составляют нужную нам фигуру. Для удобства пользователя в поле введенной кнопки можно эту фигуру изобразить с помощью закрашенных квадратиков (см. рис. 8.3), хотя, конечно, это изображение в нашей модели не связано с изменением состояния ячеек поля анимации. Таких фигур можно сделать несколько. Нажатие на эту кнопку (либо при остановленной, либо при работающей модели) приведет к выполнению связанного с кнопкой кода в результате чего в окне анимации в поле решетки установятся в истинное значение состояния только выбранных ячеек.
Сравните свою модель с моделью LifeGame4.
8.2. Модель " Сбор урожая " (Harvest)
Подробный анализ этой модели весьма полезен. Так же, как и многие другие модели, которые анализируются в данном пособии, эта модель интересна не сама по себе: она интересна той техникой, которая использовалась при ее построении. В модели применены методологические приемы, которые могут быть использованы и при разработке других моделей. В частности, при построении этой модели использованы интересные методы абстрагирования, имеющие достаточную общность. В модели есть дискретные и непрерывные процессы (например, движение объектов). Логика взаимодействия непрерывных и дискретных процессов с помощью событий и минимальных включений фрагментов программного кода в модель сделана koм-пактной и выразительной. В объектах модели выделены общие логические функции поведения, которые, будучи реализованы отдельно, позволяют упростить и прояснить реализацию других объектов и всей модели.
Постановка задачи
Запустите модель Harvest. Анимационная картинка (рис. 8.4) представляет общую структуру и динамику модели. Цель модели — продемонстрировать, что планирование совместных действий трех объектов (комбайна, трактора и грузовика), участвующих в простой и понятной активности (уборке урожая), требует нетривиальной логики управления, как это обычно и бывает в логистике.
Модель построена как игра, в которой нажатием соответствующих кнопок в окне анимации игрок может управлять движением объектов. Трактор (cart) может быть направлен к комбайну (combain) на погрузку либо к грузовику (Truck) на разгрузку. Грузовик может быть направлен на разгрузку к бункеру (Bin) либо от бункера в поле (Field) для погрузки в него собранной части урожая из трактора. Качество управления этими объектами оценивается временем, в течение которого весь урожай с поля будет убран в бункер
для быстрой уборки требуется непростое взаимодействие машин, организация которого и составляет задачу игрока. Игра происходит по раундам, лучшее время уборки урожая по всем предыдущим раундам показывается игроку.