
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ł.
1. Płytka Arduino UNO
2. Konsolka SIC z 6 przyciskami
3. Wyświetlacz OLED 0.96'' 128x64 px
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




#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);
}