Пишем игру "Жизнь" (Life) на VBA в Excel
Развлекая - поучай.
(Гораций)
Если вы уже имели какой-то опыт программирования в прошлой жизни (привет, Basic, Pascal и т.д.), то, скорее всего, уже прошли этап "игрописательства". Однако, тряхнуть стариной и размять мозги вполне можно. Если же вы никогда не программировали игр, то никогда не поздно начать этот весьма увлекательный процесс. Всё, что нам потребуется - это Excel (любой версии) и 15-20 минут времени для начала.
Тренироваться будем на известной в узких кругах программистов игре "Жизнь" (Life). Её придумал британский математик Джон Конвей еще в 1970 году на основе работ легендарного Джона фон Неймана - прадедушки всех современных компьютеров. Если вы не сталкивались с ней раньше - не проблема, правила можно объяснить за полминуты:
-
Игра идет на большом (иногда даже бесконечном) поле в клеточку ("вселенной"). Как вы понимаете, Excel для такого подходит идеально :)
-
В один момент времени каждая клетка может быть в двух состояниях - живой (обозначим её каким-нибудь значком или просто единичкой) или же мертвой (пустой). Начальное состояние всех клеток в игре называют первым поколением.
-
Если брать блок клеток 3х3 с текущей клеткой в середине, то вокруг неё оказывается 8 клеток-соседей. Дальнейшая судьба клетки зависит от того, сколько именно живых клеток (N) окажется в этой окружающей области. Вариантов несколько:
![Правила игры Правила игры](/upload/medialibrary/60f/life2.png)
- Если клетка была пустая (мертвая), но у нее есть ровно 3 живых соседа, то в ней зарождается жизнь.
- Если клетка живая, но у неё меньше 2 соседей, то она умирает от одиночества.
- Если клетка живая, но у неё больше 3 соседей, то она умирает от перенаселения.
- Если клетка живая и у нее 2-3 соседа, то клетка продолжает жить.
Вот, собственно, и все правила. Можно даже сказать, что всё это неправильно называть игрой, т.к. здесь нет соперников в привычном понимании. Вы расставляете первое поколение, запускаете процесс и затем просто наблюдаете за развитием вашей "колонии" на протяжении нескольких поколений.
Однако, не стоит недооценивать обманчивую простоту этой логики - количество комбинаций, сценариев игры и многообразие фигур в такой игровой вселенной поражает своим разнообразием и поистине бесконечно. В математике подобные модели называют клеточными автоматами. А самое интересное, что реализовать подобную модель можно в любой версии Excel буквально на 20 строчках кода.
Поехали.
Шаг 1. Готовим игровое пространство
Создадим в новой книге три листа:
- game - это будет основной листы игры, где мы будем наблюдать за развитием нашей "колонии"
- next - этот лист будет формировать следующее поколение, которое затем придет на смену текущему
- start - на этом листе мы будем задавать начальную конфигурацию, т.е. первое поколение в нашей игре
На каждом листе (можно выделить их заранее, удерживая клавишу Shift или Ctrl, чтобы не повторять трижды одни и те же действия), разметим игровое поле размером, допустим, 30 на 30 ячеек. Впоследствии размер поля можно будет подправить в соответствии с вашими аппетитами и мощью вашего ПК:
На листе start разметим с помощью единичек первое поколение любым желаемым образом:
Шаг 2. Пишем макрос
Теперь пришла пора расчехлить наш VBA и написать макрос, который и будет делать всю работу, а именно:
- Копировать первое поколение с листа start на лист game.
- Проходить по ячейкам игрового поля на листе game и проверять окружающих соседей (блок 3х3) для каждой из них.
- В зависимости от результатов проверки помечать на листе следующего поколения next ту же ячейку как живую (1) или мертвую (пусто).
- Копировать получившееся новое поколение с листа next вместо текущего на листы игры game.
- Повторять пункты 2-4 несколько раз, сменяя одно поколение другим и отображая на экране изменения в нашей "колонии".
Для начала откроем редактор Visual Basic на вкладке Разработчик (Developer). Если такой вкладки не видно, то её нужно будет сначала отобразить через Файл - Параметры - Настройка ленты (File - Options - Customize Ribbon), включив соответствующий флажок.
В открывшемся окне редактора создадим новый модуль с помощью команды меню Insert - Module, а затем скопируем и вставим туда код нашего макроса:
Sub Life() Dim cell As Range, n As Integer, i As Integer Set rGame = Worksheets("Game").Range("B2:AE31") Set rStart = Worksheets("Start").Range("B2:AE31") Set rNext = Worksheets("Next").Range("B2:AE31") Set wNext = Worksheets("Next") rStart.Copy Destination:=rGame For i = 1 To 50 rNext.ClearContents For Each cell In rGame.Cells n = WorksheetFunction.CountA(cell.Offset(-1, -1).Resize(3, 3)) - cell.value If cell = "" And n = 3 Then wNext.Cells(cell.Row, cell.Column) = 1 If cell = 1 And (n = 2 Or n = 3) Then wNext.Cells(cell.Row, cell.Column) = 1 If cell = 1 And (n < 2 Or n > 3) Then wNext.Cells(cell.Row, cell.Column) = "" Next cell rNext.Copy Destination:=rGame Next i End Sub
Теперь давайте разберем его построчно для понятности:
Поскольку в коде нам придется несколько раз ссылаться и много раз работать с диапазонами игрового пространства (B2:AE31) на каждом из трёх листов книги, то имеет смысл сразу оформить их как переменные. Это делается в блоке:
Set rGame = Worksheets("Game").Range("B2:AE31") Set rStart = Worksheets("Start").Range("B2:AE31") Set rNext = Worksheets("Next").Range("B2:AE31")
Заодно мы создаем ещё и переменную wNext, которая ссылается на весь лист next целиком - это нам тоже пригодится в будущем:
Set wNext = Worksheets("Next")
Затем, перед началом игры, мы должны перенести первое поколение с листа start на лист game. Это выполяется командой прямого копирования с использованием уже созданных переменных:
rStart.Copy Destination:=rGame
Поскольку мы хотим прокрутить в нашей игре не одно, а несколько (например, 50 для начала) поколений, то дальнейшие действия заключены в цикл:
For i = 1 to 50 ... Next i
А внутри этого цикла мы, во-первых, сначала очищаем рабочее пространство на листе next для формирования следующего поколения:
rNext.ClearContents
А, во-вторых, запускаем вложенный цикл прохода по всем ячейкам игровой вселенной на листе game, чтобы проверить каждую из них - это реализовано циклом прохода по коллекции:
For Each cell in rGame.Cells ... Next cell
Ссылка на очередную проверяемую ячейку будет храниться в переменной cell. Для этой ячейки нам нужно сначала построить окрестность 3х3 с ней в середине. Это выполняется с помощью конструкции:
cell.Offset(-1, -1).Resize(3, 3)
Здесь метод Offset(-1,-1) виртуально сдвигает текущую проверяемую ячейку на одну строку вверх и на один столбец влево, а потом метод Resize(3,3) опять же виртуально растягивает эту одну ячейку до новых размеров 3 на 3:
Чтобы посчитать количество заполненных ячеек в полученной окрестности применяется функция рабочего листа СЧЁТЗ (COUNTA), которую в VBA можно вызвать с помощью объекта WorksheetFunction. Таким образом количество живых соседей в окружающей текущую ячейку области 3 на 3 мы получаем выражением (не забыв вычесть из полученного количества текущую ячейку):
n = WorksheetFunction.CountA(cell.Offset(-1, -1).Resize(3, 3)) - WorksheetFunction.CountA(cell)
Дальше нам нужно проверить полученное количество соседей и пометить на листе следующего поколения текущую ячейку как живую или мертвую, согласно правилам игры. Это выполняет блок из трёх проверок:
If cell = "" And n = 3 Then wNext.Cells(cell.Row, cell.Column) = 1 If cell = 1 And (n = 2 Or n = 3) Then wNext.Cells(cell.Row, cell.Column) = 1 If cell = 1 And (n < 2 Or n > 3) Then wNext.Cells(cell.Row, cell.Column) = ""
Когда цикл прохода по ячейкам будет завершен, то сформированное следующее поколение с листа next нужно скопировать на место текущего на листе game - делаем это уже знакомой конструкцией:
rNext.Copy Destination:=rGame
Вот, собственно, и вся логика.
Осталось вернуться в Excel на лист game, запустить нашу игру через вкладку Разработчик - Макросы (Developer - Macro) и насладиться процессом развития нашей колонии:
![Игра Жизнь Life Игра Жизнь Life](/upload/medialibrary/c3e/life-gif.gif)
Ссылки по теме
- Что такое макросы и как их программировать в Microsoft Excel
- Справочник по игре "Жизнь" - сайт LifeWiki
Задача на форуме
Сам макрос работает корректно, есть проблема с отображением. Хотелось бы узнать, у всех так? или только в моем случае?))