воскресенье, 27 мая 2012 г.

Игра́ «Жизнь» (Conway's Game of Life). Часть 3. Почти что GUI ;-)

Исходники N3 тут

Фактически,  интерфейс должен представлять собой два блока: задание начальных параметров (rows, columns, interval, Start/Stop) и отрисовку динамически меняющейся матрицы.
В этом смысле ему необходимы методы Start/Stop с соответствующим набором параметров для запуска процесса а так же нотификации для отрисовки матрицы после выполнения каждого шага "жизни".

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



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

Упор на графику я делать не стал, взял стандартный TableLayoutPanel. Тормоза конечно, но в графику углубляться совсем не хочется.

Интересные моменты:

  • На форме всего 3 редактора и кнопка, дешевле связать их напрямую чем использовать биндинг и копцепцию DataContext.
  • Для тестирования мне нужно "кликнуть на кнопку". Мышиные/клавиатурные сообщения посылать я не хочу (миллионы леммингов готовы подтвердить что это работает), дергать методы через MethodInfo не хочется (C# - это все таки компилятор умеющий отслеживать честные вызовы методов, а MethodInfo - это уже честное мошенничество), поэтому я сделал internal метод доступный только тестам и вызываю его из обработчика события Button.Click. Вероятность ошибки в протестированном коде бесконечно велика по сравнению с отброшенным (подписка на евент и вызов метода).
  • Нотификации можно сделать через евент, но вариант с прямой передачей обработчика наравне с другими параметрами в метод Start лучше: мне не надо подписываться/отписываться в нужные моменты времени синхронно с вызовами Start/Stop (и вообще контролировать эту подписку), а равнозначность обработчика с другими параметрами подразумевает одинаковый способ передачи этого параметра в мой алгоритм. 
  • По идее тексты "Start"/"Stop" надо бы завязать на значение IsStarted (именно это свойство определяет вызываемый метод), но это уже на будущее. Возможно, именно это будущее когда-нибудь наступит?
  • Потенциальная многопоточность не учитывается вовсе, в моей задаче это только захламит код.
  • Подмену реальной функциональности в тестовых сценариях я несколько раз сделал на евентах: дернул евент перед вызовом основного кода, если его обработали то основной код не выполняется. Тестирование самого основного кода выполнено отдельно прямыми вызовами.
  • Отрисовка, привязка к данным, перерисовка отдельных областей, нотификации отрисовщика об изменении данных из конкретной области - это уже отдельная и достаточно интересная задача. Когда-то я сделал хорошего Minesweeper и TaskManager (Delphi/Builder/WinAPI) и знаю что эта работаоне на пару минут. Поэтому -  TableLayoutPanel.

Поскольку тормозит отрисовка, то дальнейший дизайн и изменения будет определять именно она. Но... Этап наглядности достигнут, а этап интереса к задаче - пройден :-)