Gra w Życie (The Game of Life) | Arduino UNO

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

Projekt implementuje popularną grę symulacyjną "Gra w Życie" (ang. "Game of Life"). Oryginalną grę wraz z opisem zasad stworzył John Conway. 

Gra rozgrywa się na dwuwymiarowej planszy podzielonej na kwadratowe komórki, o których przetrwaniu, narodzinach czy śmierci z iteracji na iterację decyduje kilka prostych reguł.

Niezbędne elementy

1. Płytka Arduino UNO

2. Konsolka SIC z 6 przyciskami

3. Wyświetlacz OLED 0.96'' 128x64 px

Sprzęt

 

 

Opis projektu

Projekt implementuje popularną grę symulacyjną "Gra w Życie" (ang. "Game of Life"). Oryginalną grę wraz z opisem zasad stworzył John Conway. 

Gra rozgrywa się na dwuwymiarowej planszy podzielonej na kwadratowe komórki, o których przetrwaniu, narodzinach czy śmierci z iteracji na iterację decyduje kilka prostych reguł. Na planszy mieści się siatka 14x6 komórek z aktywnym "zawijaniem ekranu" tzn. jeśli przejdzie się przez lewą ścianę, ląduje się po prawej stronie ekranu i na odwrót.

ELEMENTY PROGRAMU

1.Ekran początkowy

Początek działania programu to zainicjowanie przycisków działających w trybie INPUT_PULLUP, zainicjowanie wyświetlacza i tablic przechowujących stan komórek, w których 0 przedstawia martwą komórkę a 1 - żywą oraz wyświetlenie ekranu startowego.

2.Obsługa wyświetlacza

Wyświetlanie aktualnego stanu gry odbywa się z użyciem biblioteki Adafruit SSD1306 używając kilku prostych funkcji jak wyczyszczenie ekranu, rysowanie linii, rysowanie okręgu, rysowanie prostokąta i drukowanie napisów. Komunikację Arduino z wyświetlaczem zapewnia magistrala szeregowa I2C.

3.Funkcje

Wszystkie wykorzystywane w działaniu programu funkcje (poza natywnymi dla Arduino IDE oraz wykorzystanej biblioteki) umieszczone są w pliku nagłówkowym funkcje.h i pliku funkcje.cpp w celu poprawy czytelności programu.

4.Reguły gry

Tym, co się dzieje z komórką z iteracji na iterację decyduje stan 8 sąsiadujących z nią komórek.

  • Komórka umiera z "samotności" gdy wokół siebie ma 2 lub mniej żywe komórki, bądź z "przeludnienia" gdy jest ich więcej niż 3.
  • Komórka staje się żywa lub taką pozostaje gdy wokół niej znajdują się dokładnie 3 żywe komórki

5.Aktualizacja stanu planszy

Gdy symulacja jest włączona, plansza aktualizuje się co 250 milisekund zmieniając stan komórek adekwatnie do powyższych reguł.

6.Kontrola użytkownika

Gracz na początku gry ustawia z pomocą kursora kierowanego przyciskami kierunkowymi początkowy stan komórek zatwierdzając zmiany przyciskiem BTN1. Uruchomienie symulacji odbywa się przyciskiem BTN2, którym także można symulację zatrzymać w celu wprowadzenia zmian. Ponowne jej uruchomienie uwzględnia zmiany wprowadzone przez użytkownika podczas przerwania.

7.Działanie ważnych funkcji

  • fill_cell() - wyświetla żywe komórki na podstawie wprowadzanej do niej tablicy typu bool
  • check_neighbours() - zlicza ilość żywych komórek wokół każdej na planszy i w sobie wywołuje cell_check()
  • cell_check() - implementuje reguły gry i odpowiednio zmienia stan komórek

  • fill_tab() - wypełnia jednolicie tablicę zadaną wartością

  • clone_tab() - klonuje wartości jednej tablicy do drugiej

  • buttonRead() - sprawdza, który przycisk został wciśnięty

Zdjęcia
kod programu
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

#define OLED_RESET -1        // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C  ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// 14 x 6 - plansza
#define UP 7
#define DOWN 9
#define LEFT 10
#define RIGHT 8
#define BTN1 4
#define BTN2 5

#include "funcs.h"

int cursorX = 5;
int cursorY = 9 + 4;
bool cursorVisible = true;
unsigned long previousBlinkMillis = 0;
const unsigned long blinkInterval = 250;

//  zmienne pomocnicze
const int W = 14;
const int H = 6;
bool** cell = NULL;
bool** check = NULL;
bool RunSim;

void setup() {
  Serial.begin(9600);
  pinMode(UP, INPUT_PULLUP);
  pinMode(DOWN, INPUT_PULLUP);
  pinMode(LEFT, INPUT_PULLUP);
  pinMode(RIGHT, INPUT_PULLUP);
  pinMode(BTN1, INPUT_PULLUP);
  pinMode(BTN2, INPUT_PULLUP);

  cell = (bool**)malloc(W * sizeof(bool*));
  for (int i = 0; i < W; i++) cell[i] = (bool*)malloc(H * sizeof(bool));

  check = (bool**)malloc(W * sizeof(bool*));
  for (int i = 0; i < W; i++) check[i] = (bool*)malloc(H * sizeof(bool));

  fill_tab(false, W, H, cell);
  fill_tab(true, W, H, check);

  RunSim = false;

  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }

  display.setRotation(2);

  display.clearDisplay();
  intro(display);
}

void loop() {
  bool moved = false;

  buttonRead(&moved, &RunSim, &cursorX, &cursorY, cell);

  if (moved) {
    cursorVisible = true;
    previousBlinkMillis = millis();
  }

  while (RunSim) {
    check_neighbours(W, H, cell, check);
    display.clearDisplay();
    drawGrid(display);
    fill_cell(display, W, H, cell);
    display.display();
    delay(250);
    if(digitalRead(BTN2) == LOW) RunSim = false;
  }

  // Handle blinking
  unsigned long currentMillis = millis();
  if (currentMillis - previousBlinkMillis >= blinkInterval) {
    previousBlinkMillis = currentMillis;
    if (RunSim == false) {
      cursorVisible = !cursorVisible;
    } else cursorVisible = false;
  }

  // Draw display
  display.clearDisplay();
  drawGrid(display);
  fill_cell(display, W, H, cell);

  if (cursorVisible) {
    display.fillCircle(cursorX - 1, cursorY - 1, 1, SSD1306_WHITE);
  } else {
    display.fillCircle(cursorX - 1, cursorY - 1, 1, SSD1306_BLACK);
  }
  display.display();

  delay(100);
}
Pliki_projektu
Youtube
Tagi
gra-w-życie Arduino symulacja SSD1306 macierz-binarna