Arduino Nano ile Game of Life

free responsive web templates

Arduino Nano ile yaptığım bu projede Conway's Game of Life algoritmasını, toplam 1.6KB RAM harcayarak displayde gösteren basit ama keyifli bir gömülü sistem projesi.

Mobirise

Bir tane Arduino Nano, bir tane SSD1306 0.96" OLED Display ve 3 tane butondan oluşan bu uygulama okuldaki 'Gömülü Sistemler' dersim için proje ödevimdi aslında. Ancak projenin çok hoşuma gitmesi ve biraz da fikir üzerine düşüp azıcık geliştirmeden sonra burası için iyi bir yazı olabileceğine karar verdim.

Uygulamam çok basit çalışıyor. İşlemci yükünü Nano'ya yükleyip hesaplamaları yaptırıyorum. Daha sonra bilgiyi displaye aktarıp kullanıcıya gösteriyorum. Tabii ilk önce ekrana gelen menü sayesinde hazır şablonlardan seçim yapabiliyoruz.

Conway's Game of Life algoritması 4 kuraldan oluşur, bunlar:

-Bir canlı hücrenin, ikiden daha az canlı komşusu varsa "yalnızlık nedeniyle" ölür.                                                                                   -Bir canlı hücrenin, üçten daha çok canlı komşusu varsa "kalabalıklaşma nedeniyle" ölür.                                                                     -Bir canlı hücrenin, iki ya da üç canlı komşusu varsa değişmeden bir sonraki kuşağa kalır.                                                                     -Bir ölü hücrenin tam olarak üç canlı komşusu varsa yeni bir canlı hücre oluşturur.

Bu kurallara göre kendi algoritmamda canlı hücreler '1', ölü hücreler '0' oldu. Böylece bir grid üzerinde gelecek nesilleri simule edebildim.

Şablon dizilerini PROGMEM'e kaydettim. Çünkü RAM miktarını çok fazla zorluyordu. SSD1306 çalışabilmek için en az 1kb RAM istiyor. Siz 2KB olan Nano RAM'inden 1025 byte RAM bile kullansanız display çalışmayacaktır.

Aşağıdaki şemadan bağlantı noktalarını görebilirsiniz.

Mobirise

Aşağıdaki kodu IDE'ye yapıştırırsanız siz de kendi sürümünüzü yapabilirsiniz.

C
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define GRID_W 64 #define GRID_H 32 #define CENTER_X 32 #define CENTER_Y 16 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); #define BTN_UP 4 #define BTN_DOWN 5 #define BTN_OK 6 uint8_t grid[GRID_H][GRID_W / 8]; uint8_t newRow[GRID_W / 8]; // Glider const int8_t seed0[] PROGMEM = { 0,-1, 1,0, -1,1, 0,1, 1,1 }; // Blinker const int8_t seed1[] PROGMEM = { -1,0, 0,0, 1,0 }; // Toad const int8_t seed2[] PROGMEM = { 0,-1, 1,-1, 2,-1, -1,0, 0,0, 1,0 }; // Pulsar const int8_t seed3[] PROGMEM = { -6,-4, -5,-4, -4,-4, -2,-4, -1,-4, 0,-4, -6,-2, -1,-2, -6,-1, -1,-1, -6,0, -1,0, -6,2, -5,2, -4,2, -2,2, -1,2, 0,2, 2,-4, 3,-4, 4,-4, 6,-4, 7,-4, 8,-4, 2,-2, 7,-2, 2,-1, 7,-1, 2,0, 7,0, 2,2, 3,2, 4,2, 6,2, 7,2, 8,2, -4,-6, -3,-6, -2,-6, 4,-6, 5,-6, 6,-6, -4,4, -3,4, -2,4, 4,4, 5,4, 6,4 }; // Spaceship LWSS const int8_t seed4[] PROGMEM = { -2,-1, 1,-1, -3,0, -3,1, 1,1, -3,2, -2,2, -1,2, 0,2 }; // Glider Gun (Gosper) const int8_t seed5[] PROGMEM = { -18,0, -18,1, -17,0, -17,1, -8,-1, -8,0, -8,1, -7,-2, -7,2, -6,-3, -6,3, -5,-3, -5,3, -4,0, -3,-2, -3,2, -2,-1, -2,0, -2,1, -1,0, 2,-3, 2,-2, 2,-1, 3,-3, 3,-2, 3,-1, 4,-4, 4,0, 6,-5, 6,-4, 6,0, 6,1, 16,-3, 16,-2, 17,-3, 17,-2 }; // myPattern const int8_t myPattern[] PROGMEM = { 3,-8, 5,-8, 6,-8, 8,-8, 9,-8, -7,-7, -6,-7, -5,-7, -4,-7, -3,-7, -2,-7, -1,-7, 0,-7, 1,-7, 2,-7, 3,-7, 4,-7, 5,-7, 6,-7, 10,-7, 11,-7, -9,-6, -8,-6, 13,-6, -10,-5, -9,-5, 14,-5, -10,-4, 15,-4, 16,-4, -11,-3, 16,-3, -11,-2, 17,-2, -11,-1, -1,-1, 0,-1, 17,-1, -11,0, -1,0, 0,0, 17,0, -11,1, 17,1, -10,2, 17,2, -10,3, -9,3, 17,3, -8,4, -7,5, -6,5, 15,5, -5,6, 14,6, -4,7, -2,7, 11,7, 12,7, -1,8, 1,8, 3,8, 4,8, 6,8, 9,8 }; // Spear const int8_t spear[] PROGMEM = { 0,-1, -1,0, 0,0, 1,0, 2,0, 0,1 }; const uint8_t seedCounts[] PROGMEM = { 5, 3, 6, 48, 9, 36, 65, 6 }; const int8_t* const seeds[] PROGMEM = { seed0, seed1, seed2, seed3, seed4, seed5, myPattern, spear }; // Menü ayarları int8_t currentMenu = 0; const int8_t menuCount = 9; const int8_t visibleItems = 5; int8_t menuOffset = 0; // Menü isimleri const char m0[] PROGMEM = "Glider"; const char m1[] PROGMEM = "Blinker"; const char m2[] PROGMEM = "Toad"; const char m3[] PROGMEM = "Pulsar"; const char m4[] PROGMEM = "Spaceship"; const char m5[] PROGMEM = "Glider Gun"; const char m6[] PROGMEM = "Deneme"; const char m7[] PROGMEM = "Spear"; const char m8[] PROGMEM = "Rastgele"; const char* const menuItems[] PROGMEM = { m0, m1, m2, m3, m4, m5, m6, m7, m8 }; #define MAX_MENU_CHARS 17 inline bool getCell(uint8_t x, uint8_t y) { if (x >= GRID_W || y >= GRID_H) return false; return grid[y][x >> 3] & (1 << (x & 7)); } inline void setCell(uint8_t x, uint8_t y, bool val) { if (x >= GRID_W || y >= GRID_H) return; if (val) grid[y][x >> 3] |= (1 << (x & 7)); else grid[y][x >> 3] &= ~(1 << (x & 7)); } void clearGrid() { memset(grid, 0, sizeof(grid)); } void applySeed(uint8_t seedIndex) { clearGrid(); const int8_t* seed = (const int8_t*)pgm_read_ptr(&seeds[seedIndex]); uint8_t count = pgm_read_byte(&seedCounts[seedIndex]); for (uint8_t i = 0; i < count; i++) { int8_t dx = pgm_read_byte(&seed[i * 2]); int8_t dy = pgm_read_byte(&seed[i * 2 + 1]); uint8_t x = CENTER_X + dx; uint8_t y = CENTER_Y + dy; setCell(x, y, true); } } void applyRandomSeed() { clearGrid(); uint16_t count = random(1000, 1501); for (uint16_t i = 0; i < count; i++) { setCell(random(GRID_W), random(GRID_H), true); } } uint8_t neighbors(uint8_t x, uint8_t y) { uint8_t n = 0; for (int8_t dy = -1; dy <= 1; dy++) { for (int8_t dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; int8_t nx = x + dx; int8_t ny = y + dy; if (nx >= 0 && nx < GRID_W && ny >= 0 && ny < GRID_H) { if (getCell(nx, ny)) n++; } } } return n; } void stepLife() { uint8_t prevRow[GRID_W / 8]; bool hasPrev = false; for (uint8_t y = 0; y < GRID_H; y++) { memset(newRow, 0, sizeof(newRow)); for (uint8_t x = 0; x < GRID_W; x++) { uint8_t n = neighbors(x, y); bool alive = getCell(x, y); bool next = alive ? (n == 2 || n == 3) : (n == 3); if (next) { newRow[x >> 3] |= (1 << (x & 7)); } } if (hasPrev) { memcpy(grid[y - 1], prevRow, sizeof(prevRow)); } memcpy(prevRow, newRow, sizeof(newRow)); hasPrev = true; } memcpy(grid[GRID_H - 1], prevRow, sizeof(prevRow)); } void drawGrid() { display.clearDisplay(); for (uint8_t y = 0; y < GRID_H; y++) { for (uint8_t x = 0; x < GRID_W; x++) { if (getCell(x, y)) { uint8_t px = x * 2; uint8_t py = y * 2; display.drawPixel(px, py, SSD1306_WHITE); display.drawPixel(px + 1, py, SSD1306_WHITE); display.drawPixel(px, py + 1, SSD1306_WHITE); display.drawPixel(px + 1, py + 1, SSD1306_WHITE); } } } display.display(); } void drawMenu() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); // Başlık display.setCursor(0, 0); display.print("Game of Life - Yusa G"); // Başlık altı çizgi display.drawLine(0, 10, 127, 10, SSD1306_WHITE); // Yukarı ok göstergesi if (menuOffset > 0) { display.drawPixel(123, 14, SSD1306_WHITE); display.drawPixel(122, 15, SSD1306_WHITE); display.drawPixel(123, 15, SSD1306_WHITE); display.drawPixel(124, 15, SSD1306_WHITE); display.drawPixel(121, 16, SSD1306_WHITE); display.drawPixel(122, 16, SSD1306_WHITE); display.drawPixel(123, 16, SSD1306_WHITE); display.drawPixel(124, 16, SSD1306_WHITE); display.drawPixel(125, 16, SSD1306_WHITE); } // Menü öğelerini çiz char buf[32]; for (int8_t i = 0; i < visibleItems; i++) { int8_t itemIndex = menuOffset + i; if (itemIndex >= menuCount) break; display.setCursor(0, i * 10 + 14); if (itemIndex == currentMenu) { display.print("-> "); } else { display.print(" "); } // Menü ismini oku strcpy_P(buf, (const char*)pgm_read_ptr(&menuItems[itemIndex])); // Uzunsa kes ve ".." ekle if (strlen(buf) > MAX_MENU_CHARS) { buf[MAX_MENU_CHARS - 2] = '.'; buf[MAX_MENU_CHARS - 1] = '.'; buf[MAX_MENU_CHARS] = '\0'; } display.print(buf); } // Aşağı ok göstergesi if (menuOffset + visibleItems < menuCount) { display.drawPixel(121, 57, SSD1306_WHITE); display.drawPixel(122, 57, SSD1306_WHITE); display.drawPixel(123, 57, SSD1306_WHITE); display.drawPixel(124, 57, SSD1306_WHITE); display.drawPixel(125, 57, SSD1306_WHITE); display.drawPixel(122, 58, SSD1306_WHITE); display.drawPixel(123, 58, SSD1306_WHITE); display.drawPixel(124, 58, SSD1306_WHITE); display.drawPixel(123, 59, SSD1306_WHITE); } display.display(); } void updateMenuOffset() { if (currentMenu < menuOffset) { menuOffset = currentMenu; } else if (currentMenu >= menuOffset + visibleItems) { menuOffset = currentMenu - visibleItems + 1; } } void runLifeAnimation() { while (digitalRead(BTN_OK)) { stepLife(); drawGrid(); delay(100); } delay(200); } void setup() { pinMode(BTN_UP, INPUT_PULLUP); pinMode(BTN_DOWN, INPUT_PULLUP); pinMode(BTN_OK, INPUT_PULLUP); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); randomSeed(analogRead(A0)); drawMenu(); } void loop() { if (!digitalRead(BTN_UP)) { currentMenu--; if (currentMenu < 0) { currentMenu = menuCount - 1; menuOffset = menuCount - visibleItems; if (menuOffset < 0) menuOffset = 0; } else { updateMenuOffset(); } drawMenu(); delay(200); } if (!digitalRead(BTN_DOWN)) { currentMenu++; if (currentMenu >= menuCount) { currentMenu = 0; menuOffset = 0; } else { updateMenuOffset(); } drawMenu(); delay(200); } if (!digitalRead(BTN_OK)) { delay(200); if (currentMenu < menuCount - 1) { applySeed(currentMenu); } else { applyRandomSeed(); } drawGrid(); delay(1500); runLifeAnimation(); drawMenu(); } }

© Copyright 2025 Yuşa Göverdik - hasup.net - Tüm hakları saklıdır.