Книга знаний

1С:Предприятие

Играем в Жизнь

Начинающие программисты на языке одинСи часто задают вопрос: "Как работать с объектом "Таблица" в 1С?". В принципе данная тема достаточно хорошо раскрыта в ЖКК, и поэтому частота появление этого вопроса вызывает недоумение, во всяком случае, у меня. Тема сама по себе большая, и ее трудно уместить в рамках одной статьи. По этому остановимся на самом популярном вопросе: "Как раскрасить таблицу в 1С?". Самым лучшим способом разобраться, как работает не понятный тебе объект, это написать программу, которая работает с данным объектом. Вот и мы сейчас попробуем реализовать что-нибудь, используя агрегатный объект "Таблица". Если быть точнее, то мы с вами сейчас реализуем игру "Жизнь", придуманную в 1970 году математиком Дж. Конвеем. Почему игру, и почему именно "Жизнь". Во-первых, разбираться как кодиться игра все-таки интереснее, чем смотреть на сухой код вывода отчета на печать. Во-вторых, код ее достаточно прост и не будет загромождать суть разбираемого нами вопроса, то есть "раскрашивание" объекта "Таблица". Итак, начнем играть.Автор статьи: Волшебник | Редакторы: Гений 1С
Последняя редакция №6 от 05.05.06 | История
URL: http://kb.mista.ru/article.php?id=67

Правила игры


Действие игры происходит на некой плоскости, разделенной на клетки. Каждая клетка плоскости может принимать два состояния. Есть на ней жизнь или нет. На состояние любой клетки оказывает влияние состояние соседних клеток. С определенной дискретностью на плоскости происходит смена поколений. Состояние любой клетки под воздействием "Генетического закона Конвея" может измениться. Говоря по-русски, жизнь в любой момент может прекратиться либо наоборот появиться на свет. Законы развития просты как три копейки, точнее две, и состоят из двух пунктов:

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

Далее интерпретация правил несколько варьируется.

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

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

Теперь не много перелопатим правила под свои нужды. Игра будет происходить на плоскости 40х17 замкнутого пространства. Играющий может влиять только на расстановку живых сил первого поколения. Иначе говоря, игрок сам создает колонию. Колония может быть создана случайно, с заданием плотности населения. Либо руками, то есть когда бог сам говорит: "Да будет жизнь". После смены поколения изменить что-либо в колонии будет нельзя. Смена поколений происходит путем применения генетического закона Конвея. Игра заканчивается смертью колонии, как от естественных причин, так и от "милости" бога, путем всемирного потопа или иного Армагеддона.

Ну, с правилами разобрались, приступаем к дизайну.

Дизайн


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

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

Тут уж есть, где разгуляться. И все будет зависеть от буйности вашей фантазии. Я буйствовать, сильно не буду. Нету на это время.

Запускаем пофигуратор и переключаемся на объект "Таблица". Для начала выставляем у 42 клеток ширину равную 3 у.е. в системе 1С. И у 23 строк выставляем высоту строки равной 15 единицам. После чего у двух строк, а именно второй и четвертой выставляем высоту строки 8,25. У строки 4 делаем высоту равной 20,25. Выделяем область таблицы с координатами верхней левой [R1C1] и нижней правой [R23C42], в дальнейшем области будем показывать так [R1C1:R23C42] и установим для ячеек этой области следующий формат используемого шрифта (см. рисунок №1).


Рис. 1

Далее, для области [R2C2:R22C41] используя свойства ячеек, сделаем жирную рамку (см. рисунок №2).


Рис.2

Теперь займемся раскраской. Для ячеек областей таблицы [R2C2:R4C41] и [R21C2:R21C41] установим в качестве фона светло-серый цвет (см. рисунок №3).


Рис.3

Теперь у нас получилась прямоугольная область с четко обозначенными границами живого пространства. Теперь немножко подумаем, как отделять нам клетки друг от друга. Можно конечно положиться на саму 1С, сказав ей выводить границы ячеек. Но мне кажется это не совсем красяво. Ведь тогда границы будут и там, где они сто лет не нунжны, а это не есть гуд. Выделяем область [R5C2:R21C41] и для ее ячеек устанавливаем следующие свойства (cм. рисунок №4)


Рис.4

Правда, после этого у нас стали не совсем кошерно отображаться границы справа и слева. Исправляем косяк. Для этого у области [R5C2:R21C2] выставляем свойства похожие на те, что показаны на рисунке №2, с одним различием. Будем не обводить, а рисовать только слева. Нам ведь только там нужна жирная граница. Для этого в группе "Рамка" вместо "Обвести" выбираем "Лево". Аналогично поступаем с областью [R5C41:R21C41], с той лишь разницей, что выставляем границу справа.

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

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

Путей тут много, я пошел одним из самых простых. Выделяем в нашей таблице следующие области: [R3C3:R3C7], [R3C9:R3C13], [R3C16:R3C20], [R3C22:R3C26], [R3C28:R3C32] и [R3C35:R3C39]. И делаем объединение ячеек. После чего, в цикле, у каждой из шести ячеек выставляем следующие свойства (см. рисунок №5)





Рис.5

У всех шести ячеек свойства совпадают за не большим различием. В том месте, где у меня написано Load, для каждой из ячеек надо написать свое значение (я использовал следующие наименования: "Load", "Save", "Begin", "Next", "Random", "Finish"). Так же вам надо обратить на поле "Расшифровка". В нем обязательно должно быть написано слово "Hook". Для чего, объясню чуть позже, когда займемся кодированием игры.

Теперь займемся строкой состояния. В ней нам надо будет выводить только два значения: количество живых существ и номер текущего поколения. Границ нам тут не надо. Единственное что я сделал (см. рисунок №5, пункт №2), правда, с небольшим отличием установил выравнивание по правому краю. Итак, поехали. Объединяем в таблице следующие области: [R22C3:R22C6], [R22C7:R22C10], [R22C12:R22C16] и [R22C17:R22C20]. В них пишем, по порядку, следующее: "Count life:", "0", "Period:", "0".

Наконец наведем несколько приятных штрихов. Для области [R5C2:R5C41] установим границу сверху, черным цветом, стилем непрерывная линия толщиной как у кнопок (см. рисунок №5, пункт №3). Аналогично, только снизу, проводим границу для области[R21C2:R21C41]. И на конец у ячеек [R22C11] и [R22C21] проводим такую же границу справа.

Вообще границы у объекта "Таблица" вещь рульная. Играя ими, можно добиться кучу поразительных визуальных эффектов. Например, такой:


Рис.6

С дизайном покончено. Прежде чем окончательно закрывать таблицу нам надо сделать две вещи. Первая это сформировать секции, в принципе не обязательно, но для избегания всяких несуразностей лучше сделать. Я сформировал две. Одну вертикальную, назвав ее "Width". Другую горизонтальную - "Height". Вторая вещь долгая и нудная. Как тут пошустрее выкрутиться описывать не буду. Вам надо, для всех ячеек области [R5C2:R21C41] в поле "Расшифровка" написать слово "Hook".

Если вы сделали все правильно, то у вас должно быть получиться, нечто, похожее на это:


Рис.7


Он сказал: "Поехали", и махнул рукой



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

Надеюсь вывод табличной части на экран, не вызывает не у кого усиленное сердцебиение. Поэтому останавливаться на этом подробно не будем. А подумаем над такой вещью. Как сделать так, что бы пользователь увидал нашу таблицу поверх формы. Правда, зачем пользователю смотреть на нее, все равно управление игрой будет происходить в таблице. Сразу напрашивается вариант с процедурой OnOpen. Но он не совсем подходит, так как форма выскакивает поверх таблицы, и для переключения на таблицу надо будет делать лишни телодвижения. Вариант с функцией на форме, тоже не прокатывает. Почему, спросите у 1С. Что делать. Делать не фих, снимаем трусы и начинаем бегать:

var Mode;
     var true, false;
     var Table;

     procedure OnClose()
          if Mode = true then
               Mode = false;
               Table = createobject("table");
               Table.SourceTable(Table);
               Table.PutSection("Height|Width");
               Table.Protection(true);
               Table.Show("game ""Life""");
               returnstatus(false);
          endif;
     endprocedure

     procedure OnOpen()
          true = 1;
          false = 0;
          Mode = true;
          Form.Close();
     endprocedure


Теперь глянем, что мы тут навыбегали. У нас тут объявлены две предопределенные процедуры. Одна из них вызывается при открытии, другая при закрытии формы. Суть всей фичи такова. В процедуре при открытии формы, пока она еще не отобразилась на экране, мы закрываем форму, предварительно выставив флаг Mode в положение true. Он нам нужен для того, что бы процедура OnClose знала, кто ее пытается закрыть, процедура OnOpen или нечто другое. Поскольку форма закрывается, наступает очередь процедуры OnClose. Та смотрит на флаг, и если он выставлен в true, начинает рисовать таблицу, предварительно опустив Mode в положение false. И как только таблица нарисована, с помощью процедуры returnstatus обламывает закрытие формы. В результате трюка Таблица рисуется поверх формы, что нам и требовалось получить.

У вас могут возникнуть вопросы, зачем я использовал true, false и Table, да к тому же вынес их в блок глобальных переменных. Глобально я их объявил, потому, как они будут нужны куче других процедур и функций. А использование true & false могу только объяснить наглядностью. Во всяком случае, мне кажется так понятнее, чем просто 1 и 0, как учит нас 1С.

Следующее, что бросается в глаза, так это отсутствие функции Random в языке 1С. А ведь нам она понадобиться. По условию задачи, жизнь может генерироваться случайно. И тут на помощь приходит avb он же Алексей Бажитов, надо заметить, что и трюк с выводом таблицы я узнал от него. И так функция Random:

// объявляем глобальную переменную
     var GlSeed;

     function Random(MaxValue)
          GlSeed = GlSeed * 1103515245 + 12345;
          return ((GlSeed / 65536) % 32768) % MaxValue + 1;
     endfunction

     // добавляем в процедуру OnOpen
     GlSeed = _getperformancecounter();


Объяснить тут мало, что можно. Стоит только сказать, что данный алгоритм был предложен, комитетом ANSI-C, для тех, кто не знает, он вырабатывает стандарты для языка Си. В том, что добавляется в процедуру OnOpen, используется не документированная функция языка 1С, которая возвращает число миллисекунд прошедших с момента включения компьютера. И в данном случае выполняет функцию, аналогичную паскалевской или бейсиковой функции randomize. Сама функция Random возвращает случайно число из диапазона от 1 до MaxValue. Все теперь рандом у нас есть, и поверьте мне не плохой. Из всех виденных мною вариантов, самый лучший, для одинЭс конечно.

Далее нам нужен двухмерный массив размером 40х17. Не спешите хлопать в ладоши, говоря, что у 1С нет двухмерных массивов. Спорим есть. Надо просто внимательно глянуть на агрегатный объект, слово то придумали в фирме 1С, "ТаблицаЗначений". Конечно, у него есть не которые минусы по сравнению с обычным массивом, но зато есть и куча плюсов. Останавливаться на них мы не будем, а просто объявим глобальную переменную и создадим для нее пустой агрегатный объект "ТаблицаЗначений", имеющий 40 колонок и 17 строк.

     var tabTemp;

     tabTemp = createobject("valuetable");
     for x = 1 to 40 do
          tabTemp.NewColumn("K" + string(x), "number", 1, 0);
     enddo;
     for x = 1 to 17 do
          tabTemp.NewLine();
     enddo;


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

     var Life, Period;
 //и инициализируем:
     Life = 0;
     Period = 0;


Вроде все готово к штурму замка. Сохраняем обработку. Запускаем интерпретатор. Отрываем обработку там. Любуемся (см. рисунок №8).


Рис.8

Вроде все так, но что-то не так. Зачем нам в самом начале нужны активные кнопки "Save", "Begin", "Next" и "Finish". Правильно, все они нужны в тот момент, когда игра уже началась. Да и то не всегда. Вот тут-то ребята мы и подошли к первой части нашего вопроса о раскраске таблиц. Научимся менять цвет текста в таблице. Для изменения цвета текста ячейки таблицы служит функция для работы с агрегатным объектом "ОбластьТаблицы" TextColor. Данная функция может использоваться как для изменения цвета текста области таблицы, так и для получения информации о цвете текста у заданной области. Все это понятно, но что за новый объект "ОбластьТаблицы". В ЖКК про него ни гугу, и как нам его получить. И тут вы не правы. ЖКК упоминает о нем, конечно не так подробно как о "Таблице", но на безрыбье и рак рыба. А если положить лапу на сердце то можно утверждать, что в данной книжке достаточно материала об агрегатном типе данных "ОбластьТаблицы". Для того, что бы получить объект "ОбластьТаблицы" нам нужно использовать метод Area объекта "Таблица". Пока неясно, ни чего страшного, далее все будет понятнее. Продолжаем кодить игру. Итак, у нас есть пять кнопок, активностью которых мы хотим управлять. Если текст кнопки черный, значит кнопка - активна. Если же цвет текста темно-серый, значит кнопочка энейбл. Для этого объявим пять глобальных переменных:

     var flLoad, flSave, flBegin, flNext, flRandom, flFinish;

//установим им значения на начало игры:

     flLoad = true;
     flSave = false;
     flBegin = false;
     flNext = false;
     flRandom = true;
     flFinish = false;


а дальше делаем процедуру, которая будет менять цвет текста в зависимости от выставленных флагов:

     procedure RefreshTable()
          Table.Area("R3C9").TextColor(?(flSave = true, 0, 8421504));
          Table.Area("R3C16").TextColor(?(flBegin = true, 0, 8421504));
          Table.Area("R3C22").TextColor(?(flNext = true, 0, 8421504));
          Table.Area("R3C28").TextColor(?(flRandom = true, 0, 8421504));
          Table.Area("R3C36").TextColor(?(flFinish = true, 0, 8421504));
     endprocedure


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

     Table.Area("R22C7").Text = string(Life);
     Table.Area("R22C17").Text = string(Period);


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

Table.Show();


Ну и теперь добавим вызов вновь созданной процедуры сразу же после первого вывода таблицы на экран, в процедуру OnOpen, следом за строкой "Table.Show("game ""Life""");". Сохраним изменения и полюбуемся на результат:


Рис.9

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

Раскраска



Наверное вы уже сами просекли как красить ячейки. Да? Правильно. Все то же самое с точностью наоборот. Только теперь мы будем устанавливать, а не брать. И на это раз рассмотрим фон области. Хотя тут вы можете сами позаниматься рукоблудством, сколько вашей душе будет угодно. А я пока накалякаю процедуру по отображению виртуального мирка на очке вашего монитора, или функцию, которая отобразит на карте текущее положение дел в нашей колонии строго режима.

Для работы с цветом фона области у объекта "ОбластьТаблицы" есть метод BackGroundColor(). Если ему в качестве параметра передать число из диапазона от 0 до 16777215, то он закрасит фон выделенной области в какой либо цвет. Вот только вопрос, ведь надо не в какой-либо, а который хочется мне. У данной проблемы есть несколько решений. Первый самый простой. В режиме конфигурирования закрасить ячейку нужным цветом, а потом программно считать, значение фона, для данной ячейки. У этого способа есть парочка минусов. Во-первых, в конфигураторе мы не можем выбрать любой цвет. Во-вторых, куча лишних телодвижений. Можно пойти другим путем. Используя возможность данной функции принимать в качестве параметра не одно число, а целых три. Да ребятишки из 1С реализовали для нашей раскраски так называемую цветовую схему RGB color(8 bit/Channel). Что сие значит. Да только то, что для кодирования любого цвета используется три составляющих. Это интенсивность красного, зеленого и синего цвета. Сама интенсивность задается 8 битами и может принимать значение от 0 до 255. То есть для отображения чистого красного цвета нам надо передать функции три байта интенсивность красного 255, синего и зеленого по нулям. Соответственно формируются синий и зеленый цвет. Все остальные цвета надуманы, и создаться за счет обмана зрения, из смеси этих трех цветов. Так черный цвет - это помесь красного, синего и зеленного с интенсивностью равной нулю. Белый цвет все то же самое, только интенсивность цветов равна 255. Интенсивность составляющих для других цветов можно узнать в любом графическом редакторе. Когда вы получите интенсивность трех составляющих, нужного вам цвета, вычислить его номер можно будет по следующей формуле:



Надеюсь доступно объяснил, чем вызван такой диапазон передоваемых значений. Количеством возможных цветов и их градаций, то есть от черного цвета ((0 * 65536) + (0 * 256) + 0) = 0, до 16777215, белого ((255 * 65536) + (255 * 256) + 255). Все выше сказанное справедливо и для других функций при работе с цветом.

Итак, с цветом разобрались, начинаем кодить. Как уже отмечалась, наша процедура должна раскрасить мир в зависимости от текущего состояния колонии. То есть она, процедура, должна знать, что в какой ячейки есть жизнь, а в какой нет. Для этого будем передавать ей, в качестве параметра, массив размером 40х17, в котором значение true для ячейки [x, y] сигнализирует о том, что в данной клетке жизнь есть. И тогда становиться все просто. Последовательно пробежать по всем ячейкам, просмотреть значение. И в зависимости оттого, что там есть, раскрасить соответствующую ячейку в нужный цвет. В конце не забыв перерисовать таблицу с помощью уже реализованной нами процедуры RefreshTable():

     procedure DrawTable(tabProg)
          for y = 1 to 17 do
               for x = 1 to 40 do
                    Address = "R" + string(4 + y) + "C" + string(1 + x);
                    Table.Area(Address).BackGroundColor(
                                        ?(tabProg.GetValue(y, x) = true, 16711680, 16777215));
               enddo;
          enddo;
          RefreshTable();
     endprocedure


Надеюсь, вас не смущает, что при вычислении адреса, мы делали приращение строк и колонок. Ведь в действительности ячейки нашего "мира" с адресом [R5C2] соответствует ячейка массива с адресом [1, 1].

Случайно конечно хорошо, но хотелось бы иметь возможность раскрашивать табличку ручками, да и тычки по "кнопкам" тоже надо ловить. Что для этого надо? Ответ напрашивается сам - будем делать "Хук".

Ловушка



Кое-что для реализации ловушки мы уже сделали. Помните когда занимались дизайном, в свойствах некоторых ячеек, в поле расшифровка ставили "Hook", а если вы уже запускали игру, то получали кучу ошибок (см. рисунок №11).


Рис. 11

Прибить ошибку просто. Для этого в блоке глобальных переменных надо описать переменную Hook:

   
var Hook;


Проверяем. Точно ошибка пропала. Курсор при наведении на ячейки принимает форму креста с лупой. Винда, если кликнуть по данной ячейке, издает нечленораздельные звуки, сигнализируя об ошибке, конечно, если у вас не прибита звуковая схема. И все, больше ничего. Нам надо немножко не это. Опять книжка от Федора Достоевского. Лезем в ЖКК и открываем главу №31 посвященную работе со специальным агрегатным типом данных "Таблица". Внематочно читаем. Тем более читать там не много, так что сильно не напрягайтесь. Фирма 1С с девизом: "Краткость - сестра таланта", делает всех доступно серьезными или серьезно доступными, уделив этому вопросу целых два абзаца в ЖКК. Могет и три, точно не помню. Суть их сводиться к следующему: "Поле Расшифровка, в свойствах Ячейки, служит для связи агрегатного объекта Таблица с модулем отчета или отображения различных объектов используемых в отчете, и никак не влияет на отображение самого объекта Таблица". Во как. Теперь понятненько, почему добавление глобальной переменной Hook прибивает ошибку, показанную на рисунке №11. Ну что же попробуем присвоить хуку какое-либо значение, например единицу. Для этого в процедуру OnOpen вставляем строку:

   
Hook = 1;


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


Рис. 12

Это уже больше походит на правду и начинает радовать. Реагировать на клики 1С научили. Но нам же надо, что бы реагировала наш отчет. Копаем в ЖКК дальше. Копать придется далеко. До самого конца главы посвященной объекту "Таблица". Вот там то мы и найдем описание двух процедур, которые и обрабатывают пинки по ячейкам таблицы. Почему именно две, и чем они отличаются между собой, разгребайте сами. Я лишь скажу, что нам нужна SheetCellProcessing. Итак, шлепаем в модуль нашего отчета и добавляем следующий код:

     procedure SheetCellProcessing(Value, Flag, tabProg, Address)
          Flag = false;
     endprocedure;


Сначала поговорим о том, что мы передаем данной процедуре:
    1. Value - мы ее использовать в нашей игре не будем. Но так на всякий случай. Сюда толкается то значение, которое имела переменная, указанная в поле "Расшифровка", в момент формирования "таблицы". В нашем случае тут всегда будет единица.
    2. Flag - флаг стандартной обработки. Если флаг не сброшен, то после завершения процедуры SheetCellProcessing обработка пинка отдается самой одноэсине. А та, в зависимости от значения ячейки (Value) выводит всякие боксы, как частный случай (см. рисунок №12). Для разных типов данных выводятся разные боксы, для элемента справочника - форма элемента справочника, для документа - форма документа и т.д.
    3. tabProg - сам объект таблица, в ячейку которой пнул зверь.
    4. Address - адрес ячейки которую пнули.

Если с этим все ясно, то едем дальше, иначе ЖКК в руки и бога в помощь.

Как видно из примера мы сразу роняем флаг стандартной обработки. И в правду на фиха нам всякое не понятное во время игры. Проводим тест-драйв. Запускаем нашу обработочку и начинаем пинать ячейки.

Вери вел. Ячейки пинаются, Винда не пикает, и не выкидывает на экран всякие глюпые мессаги. То, что на данном этапе и нужно. Как говорят пилоты: "Высота - 10936 ярдов. Полет нормальный". Летим дальше.
Если быть точнее, то займемся планированием. Откинемся на спинку кресла и расслабимся, как советуют девелоперы из компании Майкрософт. У нас в таблице получилась куча ячеек клики, по которым нам надо ловить. И в зависимости от того, какую ячейку пнули выполнять те или иные действия. Первое, что приходит на ум - это адрес ячейки. В действительности данный способ не совсем рационален и мы его сразу списываем в помойку. Глянем внимательно на нашу таблицу, закурим и подумаем, чем наши ячейки отличаются друг от друга. Правильно - содержанием. То есть текстом, который отображается в ячейке.

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

Добавляем в процедуру следующий код:

     Text = tabProg.Area(Address).Text;
     if emptyvalue(Text) = false then
          message("Пнули кнопку управления");
     else
          message("Пнули ячейку мира");
     endif;


Попробуем разобрать, чего это мы тут понаделали. Первой строкой мы получаем значение свойство Текст у объекта "ОбластьТаблицы" с нужным нам адресом. Далее просто смотрим пустое оно или нет. Если значение не пустое - значит кликнули ячейку управления, иначе - игрового поля. Надеюсь вы догадались, что после отладки строчки с функцией message надо изничтожить. Итак пока все просто. Дальше будет еще проще, во всяком случае я на это надеюсь.
На время забудем про панель управления и займемся игровым полем и жизнью на нем. После того как мы поймали клик на ячейки игрового поля, нам надо узнать если в этой клетке жизнь. Как? По цвету фона. Глядим какой фон у данной ячейки таблицы:

   
Color = tabProg.Area(Address).BackGroundColor();


У меня в игре цвет может принимать два значения. Синенький (16711680) - есть жизнь, и беленький (16777215) - жизнь отсутствует. Добавляем нижеследующий код:

     if Color = 16711680 then
          // Здесь буду действия связанные с убийством.
     else
          // Здесь наоборот, с рождением.
     endif;


Для начала рассмотрим убийство. Сперва спросим игрока об осознанности его деяния, так на всякий случай, исходя из правил хорошего тона. Спрашивать будем используя функцию doquerybox (см. ЖКК). Получив подтверждение умышленности данного деяния уменьшаем показатель количества жизней на убитый организм. Смотрим количества оставшихся организмов, и если таковых не делаем не доступными кнопки управления игрой: "Begin" - смысл играть на пустом поле и "Save" - да еще и хранить его.
Все вышесказанное на языке 1С будет выглядеть примерно так:

     if doquerybox("You really want to kill a life?", 4) = 6 then
          Life = Life - 1;
          if Life = 0 then
               flSave = false;
               flBegin = false;
          endif;
          Color = 16777215;
     endif;


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

     if doquerybox("You really want to create a life?", 4) = 6 then
          Life = Life + 1;
          if Life = 1 then
               flSave = true;
               flBegin = true;
          endif;
          Color = 16711680;
     endif;


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

     tabProg.Area(Address).BackGroundColor(Color);
     RefreshTable();


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

В конце добавлю, что было бы неплохо отрубить возможность изменять состояние игрового поля после начала игры. То есть убивать и рожать можно только на этапе создания первого поколения. Все остальные поколения должны формироваться согласно генетическим законам Конвея. Я для этих целей использовал флаг доступности кнопки "Next", имея следующий код:

     if flNext = false then
          // Здесь размещен код по созданию/убийству организмов
          // на игровом поле.
     endif;


Также тут можно было бы использовать флаг кнопки "Finish". Ну или воткнуть что-то свое. Как гриться: "Хозяин - барин". Вам решать, а мы пока переходим к управлению игрой.

Панель управления



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

    1. Load - для чтения ранее сохраненной игры.
    2. Save - сохранения текущей игры.
    3. Begin - начать новую игру.
    4. Next - сменить поколение.
    5. Random - сгенерировать первое поколение случайным образом.
    6. Finish - прекратить текущую игру.

Теперь подумает о доступности тех или иных функциях на разных стадиях игры. Начнем пожалуй с определения количества стадий игры. А их у нас всего две: подготовка первого поколения и сама игра ;). Хотя первую можно разделить тоже на две: первоначальная инициализация - на поле нет ни одного живого организма, и готовность номер один - на поле появилась жизнь. Исходя из вышеизложенного, получаем следующую таблицу доступности функций:

РежимLoadSaveBeginNextRandomFinish
1a+---+-
1b+++-+-
2++-+-+


Кратко объясню, что почем и зачем хоккей с мячом:

    Load - доступна всегда. Пользователь должен в любой момент иметь возможность вернуться к раннее сохраненной игре.
    Save - не доступна только в самом начале игры. Зачем сохранять пустое поле.
    Begin - доступна только в режиме готовности. На пустом поле как играть, а если игра уже идет, зачем ее начинать.
    Next - доступна только в игре. Что продолжать, если еще не начато?
    Random - не доступна в игре. Смена поколений должна идти по генетическим законам.
    Finish - доступна только в игре. Как и Next - пока не начата игра, менять и заканчивать нечего.

Как визуально так и программно о доступности той или иной кнопки будем судить по цвету текста, которым отображается заголовок данной кнопки. Мы это уже обсуждали и если вы еще помните, сделали кое-какие шаги в данном направлении. О смене статуса той или иной кнопки мы будем думать по мере возникновения необходимости. Сейчас же займемся обработкой тычка по кнопкам управления. Начинаем тварить. Или творить? В принципе не важно, будем кодить. И для начала получим цвет текста кнопки.

     Color = tabProg.Area(Address).TextColor();
     if Color = 0 then
     endif;


Получив цвет и убедившись, что он черный((0 * 65536) + (0 * 256) + 0) = 0) принимаем решение, что данная кнопка доступна и продолжаем обработку. В противном случае грим: "сказано тебе... бурундук - это птица... пшел нах крылья искать".

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

     if Text = "Load" then
          // здесь будем считывать файл сохраненки игры.
     elsif Text = "Save" then
          // а здесь сохранять.
     elsif Text = "Begin" then
          // please "Start" for begin game.
     elsif Text = "Next" then
          // смена поколений.
     elsif Text = "Random" then
          // генерим первое, случайно разумеется
     elsif Text = "Finish" then
          // устроим армагедончик барашкам.
     endif;


Random


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

     procedure FillRandom(Percent)
          vtRand = createobject("valuetable");
          tabTemp.Unload(vtRand);
          Life = false;
          for y = 1 to 17 do
               for x = 1 to 40 do
                    Value = Random(100);
                    if Value <= Percent then
                         vtRand.SetValue(y, x, true);
                         Life = Life + true;
                    endif;
               enddo;
          enddo;
          if Life > false then
               flSave = true;
               flBegin = true;
          endif;
          DrawTable(vtRand);
     endprocedure


Для начала создадим таблицу значений и выгрузим в нее tabTemp. "А это еще что за зверь?" - спросит внимательный читатель. Блин, точно пропустил. Ладушки займемся tabTemp'ом. 1С не самый шустрый интерпретатор. Надеюсь спорить ни кто не будет. Так вот, что бы немного оптимизировать нашу игрушку в плане быстродействия я создал таблицу значений из 17 строчек и 40 колонок. Так как она нам будет нужна часто, для манипуляций данными в игре, вместо того что бы создавать новую таблицу или очищать старую будем выгружать чистую. Что в плане быстродействия несколько шустрее, а значит есть гуд. Советую это сделать и вам, для этого добавьте в наш проект следующие строчки:

     // в блок глобальных переменных
     var tabTemp;

     // в процедуру OnOpen
     for x = 1 to 17 do
          tabTemp.NewLine();
          tabTemp.NewColumn("K" + string(x), "number", 1, 0);
     enddo;
     for x = 18 to 40 do
          tabTemp.NewColumn("K" + string(x), "number", 1, 0);
     enddo;


Ну вот, с tabTemp разобрались, едем дальше. Дальше обнуляем Life, думаю что присвоив ему false я вас в заблуждение не вел. И потом, циклом по ячейкам. Я понимаю что двойной цикл, не есть гуд. Но боюсь конструкцией типа:

     x = 1;
     y = 1;
     Make = true;
     while Make = true do
          Value = Random(100);
          if Value <= Percent then
               vtRand.SetValue(y, x, true);
               Life = Life + true;
          endif;
          x = x + 1;
          if x = 41 then
               y = y + 1;
               x = 1;
               if y = 18 then
                    Make = false;
               endif;
          endif;
     enddo;


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

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

     vlPopulation = createobject("valuelist");
     vlPopulation.AddValue(10, "Seldom");
     vlPopulation.AddValue(20, "Less seldom");
     vlPopulation.AddValue(30, "Normally");
     vlPopulation.AddValue(40, "Less densely");
     vlPopulation.AddValue(50, "Densely");
     if vlPopulation.ChooseValue(Value, , , , 1) = true then
          FillRandom(Value);
     endif;


Создаем список значений. Добавляем в него пять значений: 10, 20, 30, 40, 50. И текстовые представления данных значений: "Seldom", "Less Seldom", "Normally", "Less Densely", "Densely". Все-таки у нас игрушка, и пользователю должно быть удобно. Что бы он свою репу не чесал, думая что мы хотели от него добиться, предлагая выбрать пять различных чисел. Выбор значения будет осуществляться в форме выпадающего списка который однаЭсина сама создаст и подпихнет куда надо. Программеру здесь лафа. Для организации выбора значения использовал метод ChooseValue объекта "СписокЗначений", за более подробной информацией лезь в ЖКК. Тут коротко отмечу, что нам тут интересно. Метод предоставляет три различных диалоговых окон для выбора значения из списка. Выбраем первый, о чем сообщим объекту указав в качестве последнего аргумента при вызове метода - единицу. Первым же аргументом в данной методе является переменная в которую объект вернет выбранное в диалоге значение. Сигналом о том, что пользователь выбрал значение является код возврата функции равный единице.

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

Begin



Итак мир построен, можно начинать. Не много подумаем. Умеете? Я неа. Мне и мама за это говорила. Ну а если серьезно то давайте прикинем, чем на первый взгляд должны отличаться действия по кнопке "Begin" от действий по кнопке "Next". Правильно почти не чем. Точнее при бегине первый раз происходит смена поколений да и происходит изменения статуса некоторых кнопочек на панельке управления. Значит саму код по смене поколений выносим в отдельную процедуру. Для этого создадим процедуру Move:

     procedure Move()
     endprocedure


На этом с данной процедурой пока закончим. Посмотрим что у нас должно поменяться в панели управления. Ну само собой разумеется меняться статус кнопок "Begin" и "Next". Начать начали, теперь хотим продолжения. Также меняется статус "Random". Помните за генетический закон Конвея. Будем придерживаться его, а не прихоти новоявленного божества. За исключением одного случая. Всемирного потопа или "Finish". Все-таки мы не дятлы сутками крутить клетки по полю. Со статусом кнопок покончено, смотрим что у нас меняется в SheetCellProcessing, эта та процедура где мы обрабатываем пинки по клеткам таблицы, если вдруг кто-то случайно запамятовал. Меняется у нас следующее. После строки, elsif Text = "Begin" then, добавляем код по изменению статуса кнопок и вызов процедуры Move:

     flBegin = false;
     flNext = true;
     flRandom = false;
     flFinish = true;
     Move();


После строки, elsif Text = "Next" then, добовляем одну строчку, вызов процедуры Move.
Все понятно. Если да, то возвращаемся к процедуре Move. Смотрим на нее и думаем. Думаем и курим. Курим и прикидываем, что тут нам надо творить. Во-первых, увеличить счетчик количества жизней. Во-вторых пробежаться по всем клеткам и провести подворный опрос соседей, и в зависимости от той или иной ситуации, прибить старую или родить новую жизнь. Только прибивать и рождать надо аккуратно, что бы ни влиять на текущую ситуацию фондового рынка, то есть нашего игрового поля. Значит потребуется дополнительный массив, для хранения временных расчетов. Создадим его. Когда все клетки будут опрошены, вывести временный расчет, он же новое поколение, в нашу таблицу. Ну и в конце в зависимости от количества оставшихся существ либо ждем продолжения, либо заканчиваем игру. Заканчивать игру будем финишем. Ведь действия в обоих случаях одинаковые. Саму процедуру разберем позже, когда будем рассматривать кнопку "Finish". Сейчас ограничемся одной декларацией:

     procedure Finish()
     endprocedure


Ну что еще можно дополнить. Да вроде ничего. Итак в процедуру Move вставляем нижеследующий код:

     Period = Period + 1;
     tabRand = createobject("valuetable");
     tabTemp.Unload(tabRand);
     Life = false;
     for y = 1 to 17 do
          for x = 1 to 40 do
               // здесь будем решать жить следующей клетке или не жить
          enddo;
     enddo;
     if Life > false then
          DrawTable(tabRand);
     else
          Finish();
     endif;


Недокументированная функция template



Позвольте не много отклониться от темы и прояснить ситуацию с недокументированной функцией template. Вернее сама функция документирована, просто не описана одна из ее возможностей. Так что правильнее было бы сказать недокументированная возможность функции template. Но и тут вопрос стоит двояко. В принципе возможность хорошо проглядывается из тех крох описания, что есть. И как говориться да имеющий глаза увидит, а имеющий уши услышит. Наверное этим принципом руководствовались авторы ЖКК. Бог им судья. Ну ладно хватит воду лить, суть фишчи такова, что имея эту функцию мы можем выполнять функции получая их наименования через переменные. Непонятно. Хм-м-м. Тогда попробуем грубо на примере, смотрим следующий код:

     //*******************************************
     function One()
          return "one";
     endfunction

     //*******************************************
     function Two()
          return "two";
     endfunction

     //*******************************************
     procedure Print(What)
          Answer = "";
          if What = "One" then
               Answer = One();
          elsif What = "Two" then
               Answer = Two();
          endif;
          message(Answer);
     endprocedure

     Print("Two");


Ну вроде код как код. Ну это хорошо когда What в процедуре Print принимает только два значения, а если десять или сто. Что делать? Снимать трусы и бегать. Не вопрос если иметь тут фичу от template. Смотрим следующий код:

     //*******************************************
     function One()
          return "one";
     endfunction

     //*******************************************
     function Two()
          return "two";
     endfunction

     //*******************************************
     procedure Print(What)
          Answer = template("[" + What + "()]");
          message(Answer);
     endprocedure

     Print("Two");


Как говорится: "Найдите три различия?". Теперь вопрос в количестве отпадает сам собой. Главное не забыть на тылкать все функции, или смотреть что передается в What процедуре Print.
Попробуем разобраться в сути происходящего процесса. Открываем ЖКК или СП и читаем: "Возвращает строку, полученную по шаблону заменой встроенных выражений на значения и форматированием". Куда уж дальше документировать-то? Глядим на наш пример. Что мы передаем функции template. Правильно:

     "[" + What + "()]"


теперь в уме прикиньте, чему равно данное выражение, в процессе выполнения. Фактически функции получит выражение:

     "[Two()]"


Результатом же выполнения функции Two(), будет строка:

     "two"


которую, нам и показала радостная 1С. Порадуемся же вместе с ней. Приношу свои извинения, тем кто знал. Статья рассчитана на новичков в 1С, а они скорее всего за оное либо не знали, либо слыхали звон да не знают о чем он. А без понимания, того, что творит template, дальше не айда. Почему? Потому что будем считать соседей. На посошок, немного перца. Функция template всегда возвращает строковое представление объекта, или говоря иначе строку. В нашем случае это есть гуд. В других случаях нужно будет учитывать это, так как:

    "123.40" не есть 123.40

<H3Считаем соседей

Задачка для получения информации о статусе восьми ближайших клетках в матрице, получила название "Соседи Мура". На сегодняшний день она имеет туево кучу решений. Рассматривать их здесь не будем, не за этим сюда приехали. Остановимся на одном. Не знаю есть ли у него аналоги, скорее всего есть, но по чесноку решил его сам. В те далекие времена, когда еще не было тырнета, и единственной книгой, на русском языке, по программированию, из известных мне, была "Архитектура Ассемблера для БК-0010" Зальцмана. Кстати и реализовывал его на том же БК-0010 с помощью Васика из Вильнюса.

Кто из вас сталкивался с ассемблером знает, что в нем наглухо отсутствует понятие переменная. А для чтения данных, да и просто переходов, используется указатель. Указатель состоит из двух частей. Точки отсчета(база) и насколько относительно сместиться(смещение). Вообще указатели универсальная вещь и используются даже в армии. Ориентир - одиноко стоящая береза, вправо 32 фута. Минометный расчет. Аллах Акбар. Суть ясна? Правильно. Координаты нашей клетки будет считать базой, а положение соседних клеток, будем получать приращением. Действительно, если считать координаты нашей клетки равными [x, y], то координаты верхней левой будут равны [x - 1, y - 1]. Ну вроде все. Нам осталось решить куда затолкнуть приращения для всех восьми соседей, и в цикле получить данные от них. Ну куда затолкнуть вопросов не возникает, я затолкнул в массив, тьфу таблицу значений. Вы можете использовать с таким же успехом и список значений. Ура. В рот вам килограмм печенья. Вы получите грабли в ситуации изображенной на рисунке:


Рис. 13

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

Рассказывать по большому и не чего. Как видно из рисунка, в данной ситуации соседом оказывается клетка. Вопрос только в том, как выкрутиться с минимальными потерями. Тут на помощь нам придут войска общего назначения, или просто функции общего назначения среды управления базами данных 1С. Если быть точнее то помогать будет функция "?". Описывать ее тут не буду, даже в СП она описана достаточно хорошо. Творим. В качестве логического выражения будем передавать проверку на достижения по проверяемой абсциссе координат условной границы нашего мира. Говоря по русский, и для нашего мира, проверяем по координате икс равенство на 1 или 40, по координате игрек - 1 или 17. Если равенство истинно, то возвращаем координату противоположной границы. Иначе, результат приращения. Проиллюстрируем сказанное примером:

     ?(Coordinate = Border, OppositeBorder, Coordinate + Offset);


где:
    Coordinate - Координата текущей клетки на проверяемой абсциссе.
    Border - Координата границы на проверяемой абсциссе.
    OppositeBorder - Координата границы противоположной Border.
    Offset - Смещение, оно и Африке смещение.

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

     var vtOffset;


а нижеследующий код в процедуру OnOpen, хотя ни кто не запрещает иметь данное и в блоке инициализации модуля:

     vtOffset = createobject("valuetable");
     vtOffset.NewColumn("Idiom", "string");
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 1, 17, y - 1)) +
                       |""C"" + string(1 + ?(x = 1, 40, x - 1))).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 1, 17, y - 1)) +
                       |""C"" + string(1 + x)).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 1, 17, y - 1)) +
                       |""C"" + string(1 + ?(x = 40, 1, x + 1))).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + y) +
                       |""C"" + string(1 + ?(x = 40, 1, x + 1))).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 17, 1, y + 1)) +
                       |""C"" + string(1 + ?(x = 40, 1, x + 1))).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 17, 1, y + 1)) +
                       |""C"" + string(1 + x)).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + ?(y = 17, 1, y + 1)) +
                       |""C"" + string(1 + ?(x = 1, 40, x - 1))).BackGroundColor()]";
     vtOffset.NewLine();
     vtOffset.Idiom = "[Table.Area(""R"" + string(4 + y) +
                       |""C"" + string(1 + ?(x = 1, 40, x - 1))).BackGroundColor()]";


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

     Neighbor = false;
     vtOffset.SelectLines();
     while vtOffset.GetLine() > false do
          Color = template(vtOffset.Idiom);
          if Color = "16711680" then
               Neighbor = Neighbor + 1;
          endif;
     enddo;


после того как соседи подсчитаны, не плохо было бы узнать что же в нашей клетке:

     Color = Table.Area("R" + string(4 + y) + "C" + string(1 + x))
                                                     .BackGroundColor();


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

     if Color = 16711680 then
          Chip = true;
          if ((Neighbor < 2) or (Neighbor > 3)) then
               Chip = false;
          endif;
     else
          Chip = false;
          if Neighbor = 3 then
               Chip = true;
          endif;
     endif;


По концовке увеличим счетчик тварей и отметим сей фактор в временном массиве, для последующего отображения на экране мониторов:

     Life = Life + Chip;
     tabRand.SetValue(y, x, Chip);


У нас все готово. Запускаем. Проверяем. Если все гуд, то едем дальше. Нет, опять внематочно читаем и смотрим где и что пропустили.

Finish


По богатому тут то и рассказывать нечего. Нам просто надо выставить флажки доступности кнопочек и раскрасить мир в девственно белый цвет. С кнопками все понятно. Да и раскраской тоже. Нам надо передать процедуре DrawTable массив размерчиком с наш мир и заполненный одними нулями. Если помните, мы специально, нечто подобное сотворили. Дальше балаболить не фих. Пишем процедуру Finish:

     procedure Finish()
          flSave = false;
          flBegin = false;
          flNext = false;
          flRandom = true;
          flFinish = false;
          Life = false;
          Period = false;
          DrawTable(tabTemp);
     endprocedure


Да чуть не забыл. Заодно надо опустить в ноль показания счетчиков живых существ и поколений. Хотя по чесноку думается мне, что если бы я пропустил весь абзац, то мало кто заметил. Уж только совсем полные бараны, которые шаг влево или вправо расценивают как провокация и открывают огонь на поражение без предупредительных сигналов. Но таким идиотам не то что программировать разрешать, к компу пускать нельзя. Ибо баран с гранатой на порядок опасней обезьяны.
Ну вроде все. Хотя нет. Забыл про самые часто используемые функции данного приложения. Ведь действительно как без них. Учим игрушку сохраняться и читаться. Абы можно было сохранить интересный этюд ;-Р

Save & Load



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

     if fs.SelectFile(1, File, Path, "Select file for saved game.",
                      "Saved file (*.sav)|*.sav", "sav") > 0 then
          vtSave = createobject("valuetable");
          tabTemp.Unload(vtSave);
          for y = 1 to 17 do
               for x = 1 to 40 do
                    Color = Table.Area("R" + string(4 + y) + "C" +
                                       string(1 + x)).BackGroundColor();
                    if Color = 16711680 then
                         vtSave.SetValue(y, x, true);
                    endif;
               enddo;
          enddo;
          vtSave.NewLine();
          vtSave.K1 = Life;
          vtSave.K2 = Period;
          valuetofile(Path + File, vtSave, false);
     endif;


В принципе объяснять тут не чего. Просим пользователя выбрать файл. Циклом копируем таблицу в массив. Добавляем еще одну строку и в нее заносим показания необходимых счетчиков. Выталкиваем все на диск.

Тоже самое творим с чтением:

     if fs.SelectFile(0, File, Path, "Select file with saved for load.",
                      "Saved file (*.sav)|*.sav", "sav") > 0 then
          vtLoad = createobject("valuetable");
          tabTemp.Unload(vtLoad);
          valuefromfile(Path + File, vtLoad, false);
          Life = vtLoad.GetValue(18, 1);
          Period = vtLoad.GetValue(18, 2);
          if Life > 0 then
               flSave = true;
               if Period > 0 then
                    flBegin = false;
                    flNext = true;
               else
                    flBegin = true;
                    flNext = false;
               endif;
               flRandom = false;
               flFinish = true;
               DrawTable(vtLoad);
          else
               Finish();
          endif;
     endif;


Спрашиваем. Считываем. Разукрашиваем. Теперь точно все. Можно начинать играть. Вот вы пожалуй и играете, а я попрусь домой. Адью.

А здесь можно взять сыр к даной статье
http://www.kb.mista.ru/files/67/life.zip

Август 2005 года. Санкт-Петербург.

С уважением, Скунки...
---
не пройдя преисподни, нам не выстроить рай

P.S.: если вам хочется смотреть на жизнь на очень большой скорости, то программисты написали игру "Жизнь", которая использует возможности параллельных вычислений современных ускорителей видеокарт. Жизнь на таком движке просто летает.


Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2025 | Mista.ru

Яндекс.Метрика