Игра «Жизнь» (Game of Life) — клеточный автомат, придуманный английским программистом Джоном Конвеем в 1970 году. Наверное, по популярности среди программистов игра Жизнь занимает второе место после программы «Привет Мир!».
Место действия моей игры — «чашка Петри» — это размеченный на клетки прямоугольник. По периметру прямоугольника расположены пустые клетки. Они во время игры не меняются и на экране не отображаются. Каждая клетка на этой поверхности может находиться в двух состояниях: быть «живой» 1 или быть «мёртвой» 0 – пустой.
Клетка имеет восемь соседей, окружающих её. Распределение живых клеток в начале игры называется первым поколением. Каждое следующее поколение рассчитывается на основе предыдущего по правилам. Клетка жива ровно 1 поколение. Если у клетки имеется ровно 2 соседа, в клетке появляется жизнь. Более двух клеток не приносят плодов из-за перенаселённости.
Игра заканчивается когда умирают все клетки или ситуация на поле повторяется, что не всегда бывает просто определить.
Схема подключения LED дисплея SSD1306 I2C очень простая, поэтому её здесь не привожу. У дисплея всего 4 вывода, из них 2 питание от 3,3 до 5 Вольт. Подключите вывод SDA на плате дисплея к выводу A4, а вывод SCK к выводу A5 на плате Arduino. Будем использовать аппаратный I2C интерфейс микроконтроллера ATMega 328 со стандартной для среды Arduino IDE библиотекой Whare.
#include <Wire.h>
#define SSD1306_Address 0x3C
#define SSD1306_Command_Mode 0x80
#define SSD1306_Dats_Mode 0x40
#define SSD1306_MEMORYMODE 0x20 //2< Автоматическая адресация
#define SSD1306_COLUMNADDR 0x21 //3< See datasheet
#define SSD1306_PAGEADDR 0x22 //3< See datasheet
#define SSD1306_SETSTARTLINE 0x40 ///< See datasheet
#define SSD1306_SETCONTRAST 0x81 //2< Контрастность
#define SSD1306_CHARGEPUMP 0x8D //2< Умножитель напряжения
#define SSD1306_SEGREMAP 0xA1 ///< Развёртка слева/направо
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< отображение содержимого RAM
#define SSD1306_NORMALDISPLAY 0xA6 ///< Нет инверсии
#define SSD1306_SETMULTIPLEX 0xA8 //2< See datasheet
#define SSD1306_DISPLAYOFF 0xAE ///< Выключить дисплей
#define SSD1306_DISPLAYON 0xAF ///< Дисплей включен
#define SSD1306_COMSCANDEC 0xC8 ///< Развёртка сверху/вниз
#define SSD1306_SETDISPLAYOFFSET 0xD3 //2< See datasheet
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 //2< Частота обновления
#define SSD1306_SETCOMPINS 0xDA //2< Разрешение 0x02-128x32, 0x12-128x64
unsigned char ledComIni1[] {
SSD1306_SETMULTIPLEX, 0x3F,
SSD1306_SETDISPLAYOFFSET, 0x00,
SSD1306_SETSTARTLINE,
SSD1306_MEMORYMODE, 0x00,
SSD1306_COLUMNADDR, 0x00, 0x7F,
SSD1306_PAGEADDR, 0x00, 0x07,
SSD1306_SEGREMAP,
SSD1306_COMSCANDEC,
SSD1306_SETCOMPINS, 0x12,
SSD1306_SETCONTRAST, 0x7F,
SSD1306_NORMALDISPLAY,
SSD1306_SETDISPLAYCLOCKDIV, 0x20,
SSD1306_CHARGEPUMP, 0x14,
SSD1306_DISPLAYALLON_RESUME,
SSD1306_DISPLAYON
};
unsigned char scrBlock[8], blBlock[8];
unsigned char block1[8] {0xFC, 0xFC, 0xCC, 0xCC, 0xFC, 0xFC, 0x00, 0x00};
#define X 18 /* 18 maximum */
#define Y 10 /* 10 maximum */
bool state[X][Y], old[X][Y];
void setup() {
Wire.begin();
Wire.setClock(400000);
for (int n = 0; n < sizeof(ledComIni1); n++) {
sendCommand(ledComIni1[n]);
}
for (int n = 0; n < 512; n++) {
sendDats(blBlock);
}
int n = (X - 2) * 8 - 1;
ledComIni1[9] = n;
n = Y - 3;
ledComIni1[12] = n;
state[10][3] = 1;
state[10][5] = 1;
state[6][3] = 1;
state[5][5] = 1;
prnt();
delay (1000);
}
void loop() {
delay (100);
play();
prnt();
}
void prnt () {
for (int n = 0; n < sizeof(ledComIni1); n++) {
sendCommand(ledComIni1[n]);
}
for (int y = 1; y < Y - 1; y++) {
for (int x = 1; x < X - 1; x++) {
if (state[x][y]) {
sendDats(block1);
}
else {
sendDats(blBlock);
}
}
}
}
void play() {
for (int x = 1; x < X - 1; x++) {
for (int y = 1; y < Y - 1; y++) {
old[x][y] = state[x][y];
state[x][y] = 0;
}
}
for (int x = 1; x < X - 1; x++) {
for (int y = 1; y < Y - 1; y++) {
if ((old[x][y + 1] + old[x][y - 1] + old[x + 1][y] + old[x - 1][y] + old[x + 1][y + 1] + old[x - 1][y - 1] + old[x + 1][y - 1] + old[x - 1][y + 1]) == 2) {
state[x][y] = !old[x][y];
}
}
}
}
void sendDats(unsigned char dim[8]) {
Wire.beginTransmission(SSD1306_Address);
Wire.write(SSD1306_Dats_Mode);
for (int n = 0; n < 8; n++)
Wire.write(dim[n]);
Wire.endTransmission();
}
void sendCommand(unsigned char command) {
Wire.beginTransmission(SSD1306_Address);
Wire.write(SSD1306_Command_Mode);
Wire.write(command);
Wire.endTransmission();
}
Рис. 2. Программа для игры Жизнь.
В 1940-х годах известный программист Джон фон Нейман создал гипотетическую машину, которая может воспроизводить сама себя. Математическая модель машины фон Неймана получилась с очень сложными правилами. Конвей упростил идеи, предложенные Нейманом, и создал правила, которые стали правилами игры «Жизнь».
Играть в «Жизнь» можно на поле любого размера, на поле свёрнутом в трубочку или в тор. Могут быть различные начальные условия. Разные правила рождения. Одно правило остаётся неизменным – следующие поколения ничего не знают о предыдущих.