Klasyczna gra polegająca na obserwacji rozwoju sieci komórek podlegających działaniu algorytmu ustalającego, które żyją a które nie na początku każdej nowej generacji.
Płytka Arduino Uno
Sic Game Console
Zaprojektowana gra jest elektroniczną implementacją Gry w życie, zaproponowanej przez John'a Horton'a Conway'a. Program działa na mikrokontrolerze i wyświetlaczu OLED sterowanym przy użyciu bibliotek Adafruit GFX Library oraz Adafruit SH110X Library. Po uruchomieniu urządzenia na ekranie pojawia się menu startowe z napisem „Game of Life”, które informuje użytkownika o możliwości rozpoczęcia gry przyciskiem numer 5. Po naciśnięciu tego przycisku użytkownik przechodzi do trybu przygotowania początkowego układu komórek. W tym trybie na ekranie wyświetlana jest siatka pól, po której można poruszać się kursorem za pomocą przycisków kierunkowych. Użytkownik może ustawić żywe komórki w wybranych miejscach siatki, tworząc własny początkowy układ. Każda komórka jest przedstawiona jako mały biały kwadrat o wymiarach trzech pikseli. Po zakończeniu konfiguracji planszy ponowne naciśnięcie przycisku startu rozpoczyna właściwą symulację. Program następnie wykonuje kolejne iteracje zgodnie z zasadami gry w życie, które określają narodziny, przeżycie lub śmierć komórek na podstawie liczby sąsiadów. Każda iteracja reprezentuje kolejne pokolenie komórek na planszy. Użytkownik może w dowolnym momencie wstrzymać symulację przyciskiem startu, co przełącza program w tryb pauzy. W tym trybie na ekranie wyświetlany jest procent powierzchni planszy zajętej przez żywe komórki oraz liczba wykonanych pokoleń. Ponowne naciśnięcie przycisku powoduje wznowienie działania symulacji. Gra kończy się automatycznie w dwóch sytuacjach: gdy wszystkie komórki obumrą lub gdy układ komórek przestanie się zmieniać. W pierwszym przypadku na ekranie pojawia się komunikat informujący, że nie pozostały żadne żywe komórki, natomiast w drugim wyświetlana jest informacja o ustabilizowaniu się życia na planszy. Dzięki temu użytkownik może obserwować ewolucję różnych układów komórek oraz badać ich zachowanie w kolejnych pokoleniach.
#include <Wire.h> //Libraries
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#define SCREEN_WIDTH 128 //Monitor Specs
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define BUTTON_START 4 //Button assigments
#define BTN_LEFT 10
#define BTN_RIGHT 8
#define BTN_UP 7
#define BTN_DOWN 9
#define BTN_SET 5
#define CELL_SIZE 3 //Grid definition
#define GRID_W (SCREEN_WIDTH / CELL_SIZE)
#define GRID_H (SCREEN_HEIGHT / CELL_SIZE)
Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
#define ROW_BYTES ((GRID_W + 7) / 8)
uint8_t grid[GRID_H][ROW_BYTES];
uint8_t nextRow[ROW_BYTES];
bool gameStarted = false; //Game states
bool paused = false;
uint32_t generation = 0;
int cursorX = GRID_W/2;
int cursorY = GRID_H/2;
bool getCell(int x, int y) {
return grid[y][x / 8] & (1 << (x % 8));
}
void setCell(int x, int y, bool alive) {
if (alive)
grid[y][x / 8] |= (1 << (x % 8));
else
grid[y][x / 8] &= ~(1 << (x % 8));
}
int countNeighbors(int x, int y) { //Neighbour count (important for Conway rules)
int count = 0;
for (int dy = -1; dy <= 1; dy++) {
int ny = y + dy;
if (ny < 0 || ny >= GRID_H) continue;
for (int dx = -1; dx <= 1; dx++) {
int nx = x + dx;
if (nx < 0 || nx >= GRID_W) continue;
if (dx == 0 && dy == 0) continue;
if (getCell(nx, ny)) count++;
}
}
return count;
}
void setup() {
pinMode(BUTTON_START, INPUT_PULLUP);
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_SET, INPUT_PULLUP);
display.begin(OLED_ADDR, true);
display.setRotation(2);
display.clearDisplay();
while (!gameStarted) { //starting screen
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SH110X_WHITE);
int16_t x1,y1;
uint16_t w,h;
display.getTextBounds("Game of Life",0,0,&x1,&y1,&w,&h);
display.setCursor((SCREEN_WIDTH-w)/2,14);
display.print("Game of Life");
static bool showText=true;
if(showText){
display.setTextSize(1);
display.getTextBounds("Press #5",0,0,&x1,&y1,&w,&h);
display.setCursor((SCREEN_WIDTH-w)/2,46);
display.print("Press #5");
}
display.display();
showText=!showText;
if(digitalRead(BTN_SET)==LOW){ //Closing the starting screen
delay(200);
gameStarted=true;
}
delay(400);
}
display.clearDisplay();
bool editing = true; //Editing mode
while(editing){
if(digitalRead(BTN_LEFT)==LOW && cursorX>0){cursorX--; delay(120);} //Editing mode movement
if(digitalRead(BTN_RIGHT)==LOW && cursorX<GRID_W-1){cursorX++; delay(120);}
if(digitalRead(BTN_UP)==LOW && cursorY>0){cursorY--; delay(120);}
if(digitalRead(BTN_DOWN)==LOW && cursorY<GRID_H-1){cursorY++; delay(120);}
if(digitalRead(BTN_SET)==LOW){ //Placing alive cell
setCell(cursorX,cursorY,true);
delay(150);
}
if(digitalRead(BUTTON_START)==LOW){ //Leaving editing mode
delay(200);
editing=false;
}
display.clearDisplay();
for(int y=0;y<GRID_H;y++){
for(int x=0;x<GRID_W;x++){
if(getCell(x,y)){
display.fillRect(x*CELL_SIZE,y*CELL_SIZE,CELL_SIZE,CELL_SIZE,SH110X_WHITE);
}
}
}
display.drawRect(
cursorX*CELL_SIZE,
cursorY*CELL_SIZE,
CELL_SIZE,
CELL_SIZE,
SH110X_WHITE
);
display.display();
}
}
void loop() { //main game loop
if(digitalRead(BTN_SET)==LOW){
delay(200);
paused = !paused;
}
if(paused){ //pause screen
int alive = 0;
for(int y=0;y<GRID_H;y++){
for(int x=0;x<GRID_W;x++){
if(getCell(x,y)) alive++;
}
}
int total = GRID_W * GRID_H;
int percent = (alive * 100) / total;
display.clearDisplay();
display.setTextSize(1);
display.setCursor(10,20);
display.print("Alive: ");
display.print(percent);
display.print("%");
display.setCursor(10,35);
display.print("Gen: ");
display.print(generation);
display.display();
delay(1);
return;
}
display.clearDisplay();
for (int y = 0; y < GRID_H; y++) {
for (int x = 0; x < GRID_W; x++) {
if (getCell(x, y)) {
display.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE, SH110X_WHITE);
}
}
}
display.display();
bool stabilized = true; // Conway rules
int aliveCount = 0;
for (int y = 0; y < GRID_H; y++) {
for (int b = 0; b < ROW_BYTES; b++) nextRow[b] = 0;
for (int x = 0; x < GRID_W; x++) {
int n = countNeighbors(x, y);
bool alive = getCell(x, y);
bool newAlive = ((alive && (n == 2 || n == 3)) || (!alive && n == 3)); //survivors and the resurrected
if(newAlive){
nextRow[x / 8] |= (1 << (x % 8));
aliveCount++;
}
if(newAlive != alive) stabilized = false;
}
for (int b = 0; b < ROW_BYTES; b++) grid[y][b] = nextRow[b];
}
generation++;
if(aliveCount == 0){ // Extinction message
display.clearDisplay();
display.setTextSize(2);
display.setCursor(10,25);
display.print("No cells");
display.setCursor(20,45);
display.print("alive");
display.display();
while(true);
}
if(stabilized){ //Stabilisation message
display.clearDisplay();
display.setTextSize(2);
display.setCursor(5,25);
display.print("Life has");
display.setCursor(5,45);
display.print("stabilised");
display.display();
while(true);
}
delay(1);
}