Gra w Życie Conwaya

Typ_projektu
Arduino
Zdjecie główne
Krótki opis projektu

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. 

Niezbędne elementy

Płytka Arduino Uno

Sic Game Console

Opis projektu

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.

Zdjęcia
Ekran Edycji
Ekran Pauzy
kod programu
#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); 
} 
Youtube
Tagi
Conway Arduino SIC gra życie