Mini konsola do gry w Snake – powrót do lat 2000

Share on facebook
Share on twitter
Share on linkedin

Większość z nas pamięta z dzieciństwa grę Snake. Była ona dostępna jako wbudowana aplikacja w telefonach fińskiego koncernu Nokia. Gra bez wątpienia jest kultowa, jednak po co instalować ją na smartfonie? To proste – bo jest kultowa. Zbudujmy dziś retro konsolę, na której będziemy mogli sterować wężem zjadającym jabłka.

Co będzie podstawą naszego projektu?

Nasza konsola oparta będzie o płytkę uruchomieniową Arduino Pro Mini i wyświetlacz OLED, cztery przyciski, przełącznik ON-OFF przetwornicę step-up/step-down, koszyk na dwie baterie AAA, same baterie i obudowę wydrukowaną w 3D. Dlaczego zdecydowaliśmy się na Pro Mini i wyświetlacz OLED? Głównie dlatego, że zajmują mało miejsca i ze względu na małe rozmiary pochłaniają mniej energii elektrycznej. Dodatkowo ekran OLED nie ma stałego podświetlenia, co pozwala na jeszcze większą oszczędność prądu. Dokładna lista podzespołów znajduje się poniżej:

Elementy elektroniczne do budowy konsoli.

Tworzenie prototypu na płytce stykowej

Do sprawdzenia funkcjonalności urządzenia warto wykonać prototyp. Jak na miłośników Arduino przystało, zbudujemy go na płytce stykowej. Jeśli mamy pod ręką płytkę Arduino UNO, to z pewnością dużo łatwiej będzie nam testować poszczególne rozwiązania, a jeśli nie, to możemy wpiąć Pro Mini w płytkę stykową i programować przy pomocy konwertera UART-USB. Duże przyciski zastąpimy małymi tact-switchami. Układ musimy zbudować tak, jak na schemacie poniżej.

Prototyp konsoli na płytce stykowej.

Rezystory których użyliśmy mają wartość 10kOhm. Przyciski są podłączone w kolejności:
1. Przycisk w prawo – pin 7
2. Przycisk w dół – pin 5
3. Przycisk w górę – pin 4
4. Przycisk w lewo – pin 6

Nasz wyświetlacz komunikuje się przez magistralę I2C, więc poza podłączeniem zasilania musimy podłączyć piny SDA i SCL. Pin SDA wyświetlacza łączymy z pinem A4 płytki, a pin SCL z pinem A5. 

Jak programować? Aby prawidłowo zrobić to prawidłowo, musimy podłączyć do płytki programator. Na samym początku podłączamy piny zasilania, następnie łączymy pin TX Arduino z RX programatora, a pin RX Pro Mini z TX konwertera. Teraz możemy podłączyć przewód USB do programatora i uruchomić program Arduino IDE. Kod, który wgramy do naszej płytki, dostępny jest w poniżej.

#include<SPI.h>
#include<Adafruit_GFX.h>
#include<Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128,64);  
#define INTPIN    3 
#define UPPIN     4 
#define DWNPIN    5
#define LFTPIN    6
#define RHTPIN    7
#define SND       9
#define DIRUP     1 
#define DIRDOWN   2 
#define DIRLEFT   3
#define DIRRIGHT  4

volatile uint8_t buttonpressed=0;        
bool butup=0;
bool butdown=0;
bool butleft=0;
bool butright=0;

byte snakePosX[30];
byte snakePosY[30];

int snakeX=30;     
int snakeY=30;
int snakeSize=1;  

uint8_t worldMinX=0;
uint8_t worldMaxX=128;
uint8_t worldMinY=10;
uint8_t worldMaxY=63;

bool scranAte =0;
uint8_t scranPosX=0;
uint8_t scranPosY=0;

long playscore=0;   
long highscore=30;

void interruptpressed()
    {
      delay(150);
      updatedirection();
    }
void updatedirection()
    {
      butup=digitalRead(UPPIN);
      butdown=digitalRead(DWNPIN);
      butleft=digitalRead(LFTPIN);
      butright=digitalRead(RHTPIN);
        if(butup==true)
           {
              buttonpressed=DIRUP;
              butup=false;
              tone(SND,1500,10);
          }
        if(butdown==true)
          {
              buttonpressed=DIRDOWN;
              butdown=false;
              tone(SND,1500,10);
          }

        if(butleft==true)
          {
              buttonpressed=DIRLEFT;
              butleft=false;
              tone(SND,1500,10);
          }
        if(butright==true)
          {
               buttonpressed=DIRRIGHT;
                butright=false;
                tone(SND,1500,10);
          }
     }

void updateDisplay()
    {
            display.fillRect(0,0, display.width()-1,8,BLACK);
            display.setTextSize(0);
            display.setTextColor(WHITE);
            display.setCursor(2,1);
            display.print("Score:");
            display.print(String(playscore, DEC));
            display.setCursor(66,1);
            display.print("High:");
            display.print(String(highscore ,DEC));
            display.drawLine(0,0,127,0,WHITE);
            display.drawLine(63,0,63,9,WHITE);
            display.drawLine(0,9, 127,9,WHITE);
            display.drawLine(0,63,127,63,WHITE);
            display.drawLine(0,0,0,63,WHITE);
            display.drawLine(127,0,127,63,WHITE);
    }
    
void updateGame()
    {
        display.clearDisplay();

          display.drawPixel(scranPosX,scranPosY, WHITE);
          scranAte = scranFood();   
              if (outOfArea()||selfCollision())
                   {
                      gameOver();
                   }
              for(int i=0;i<snakeSize;i++)
                  {
                    display.drawPixel(snakePosX[i],snakePosY[i],WHITE);
                  }
              for(int i=snakeSize;i>0;i--)
                    {
                      snakePosX[i] = snakePosX[i-1];
                      snakePosY[i] = snakePosY[i-1];
                    }
              if(scranAte)
                  {
                    snakeSize+=1;
                    snakePosX[snakeSize-1]=snakeX;
                    snakePosY[snakeSize-1]=snakeY;
                  }
            switch(buttonpressed)
                  {
                    case DIRUP:
                        snakeY-=1;
                        break;
                    case DIRDOWN:
                        snakeY+=1;
                        break;
                    case DIRLEFT:
                        snakeX-=1;
                        break;
                    case DIRRIGHT:
                        snakeX+=1;
                        break;
                  }
        snakePosX[0] = snakeX;
        snakePosY[0] = snakeY;
          updateDisplay();
          display.display();
    }
        void placeScran()
            {
              scranPosX=random(worldMinX+1,worldMaxX-1);
              scranPosY=random(worldMinY+1,worldMaxY-1);

               }

        bool scranFood()
            {
                  if(snakeX==scranPosX&&snakeY==scranPosY)
                      {
                          playscore=playscore+10;

                           tone(SND,2000,10);
                              updateDisplay();
                              placeScran();
                            return 1;
                      }
                    else
                      {
                          return 0;
                      }
            }

    bool outOfArea()
        {
          return snakeX <= worldMinX||snakeX >=worldMaxX ||snakeY<=worldMinY|| snakeY>=worldMaxY;
        }

    void gameOver()
       {
          uint8_t rectX1,rectY1,rectX2,rectY2;
         
          rectX1=38;
          rectY1=28;
          rectX2=58;
          rectY2=12;
          display.clearDisplay();
          display.setCursor(40,30);
          display.setTextSize(1);
          tone(SND,2000,50);
          display.print("GAME ");
          tone(SND,1000,50);
          display.print("OVER");
          if(playscore>=highscore)
              {  
                highscore=playscore;
              }

             
          for(int i=0;i<=16;i++)
                {
                  display.drawRect(rectX1,rectY1,rectX2,rectY2,WHITE);
                  Serial.println("if loop");
                  display.display();
                    rectX1-=2; 
                    rectY1-=2;
                    rectX2+=4; 
                    rectY2+=4;
                    tone(SND,i*200,3);
                }
           display.display();

                    rectX1=0;
                    rectY1=0;
                    rectX2=0;
                    rectY2=63;

                for (int i =0;i<=127;i++)
                      {
                           uint8_t cnt=0;
                           display.drawLine(rectX1,rectY1,rectX2,rectY2,BLACK); 
                            rectX1++;
                            rectX2++;
                            display.display();                  
                         
                      }
            display.clearDisplay();
          playscore=0;  
          snakeSize=1;
          snakeX=display.width()/2;
          snakeY=display.height()/2;

           waitForPress();

          }
        void waitForPress()
              {
                bool waiting=0;
                display.clearDisplay();
                while(waiting==0)
                     
                    {
                              drawALineForMe(WHITE);
                              drawALineForMe(BLACK); 
                              display.fillRect(19,20,90,32,BLACK);
                              display.setTextColor(WHITE);
                              display.setCursor(35,25);
                              display.setTextSize(2);
                              display.println("SNAKE");
                              display.drawRoundRect(33,22,62,20,4,WHITE);
                              display.drawRect(19,20,90,32,WHITE);
                              display.setCursor(25,42);
                              display.setTextSize(0);
                              display.println("press any key");
                              display.fillRect(0,0,127,8,BLACK);
                              display.setCursor(10,0);
                              display.print("High Score :");
                              display.print(highscore);
                              display.display();
                              waiting = digitalRead(INTPIN);
                              buttonpressed=0;
                        
                    }
              }
    void drawALineForMe(uint8_t clr)
            {
                     uint8_t line1X,line1Y,line2X,line2Y=0;
                              line1X = random(worldMinX+1,worldMaxX-1);
                              line1Y = random(worldMinY+1,worldMaxY-1);
                              line2X = random(worldMinX+1,worldMaxX-1);
                              line2Y = random(worldMinY+1,worldMaxY-1);
                              display.drawLine(line1X,line1Y,line2X,line2Y,clr);
            }
    bool selfCollision()
        {
          for(byte i=4;i<snakeSize;i++)
              { 
                  if (snakeX==snakePosX[i]&&snakeY==snakePosY[i])  
                        {
                          tone(SND,2000,20);
                          tone(SND,1000,20);
                          
                          return 1; 
                        }
              }
            return 0;
           }

void setup() 
  {
    delay(100);
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 
    display.clearDisplay();
    display.setTextColor(WHITE); 
    display.setRotation(0); 
    display.setTextWrap(false);
    display.dim(0);
    pinMode(INTPIN,INPUT);
    pinMode(UPPIN,INPUT);
    pinMode(DWNPIN,INPUT);
    pinMode(LFTPIN,INPUT);
    pinMode(RHTPIN,INPUT);
    attachInterrupt(digitalPinToInterrupt(INTPIN),interruptpressed,RISING); 
    waitForPress(); 
    placeScran();
  }
void loop() 
  {
    updateGame();
  }

Teraz standardowo wybieramy płytkę Pro Mini i wybieramy port, do którego wpięty jest nasz programator. Po zatwierdzeniu klikamy na przycisk “Wgraj”. Jeśli po wgraniu uruchomi się gra, to przechodzimy do następnego kroku, jakim jest…

Budowa przenośnej konsoli do gry

Po sprawdzeniu funkcjonalności na płytce stykowej możemy przejść do budowy samej konsoli, zacznijmy od wydrukowania obudowy. Plik w formacie stl dostępny jest na naszym profilu w witrynie Thingiverse. Ja wydrukowałem swoją obudowę 3D w technologii DLP z częściowo transparentnego materiału.

Tworzenie końcowego urządzenia zaczynamy od przylutowania przewodów do przycisków i zabezpieczenia ich przy pomocy koszulek termokurczliwych. Piny przycisków są duże, więc bardzo łatwo będzie je przylutować. Od każdego pinu powinien wychodzić przewód o długości minimum 5 cm. Możemy je zabezpieczyć przy pomocy koszulek termokurczliwych.

Schemat połączenia komponentów w urządzeniu

Po przylutowaniu przewodów możemy przy pomocy kleju cyjanoakrylowego zamocować przyciski w obudowie.

Aby urządzenie nie było przez cały czas włączone, musimy zastosować włącznik. Do montażu tego elementu przecinamy na pół czerwony przewód wychodzący z koszyka na baterie. Po przecięciu przewodu zdejmujemy z obu fragmentów izolację, jeden przewód lutujemy do jednej nóżki włącznika, a drugi do drugiej. Nie ma znaczenia, które nóżki wybierzemy.

Teraz przylutujmy pin koszyka na baterie i pin wychodzący z włącznika do przetwornicy. Czarny przewód lutujemy do pinu GND przetwornicy, a czerwony do pinu Vin. Do pinu GND lutujemy jeszcze jeden przewód; to samo robimy z pinem Vout. Teraz łączenia możemy zabezpieczyć przy pomocy koszulek termokurczliwych.

Do przewodu Vout przetwornicy lutujemy dwie wiązki. Jedna zasili Arduino, druga wyświetlacz, tak więc końcówki przewodów łączymy kolejno z pinem VCC Arduino, a drugą z pinem VCC wyświetlacza.

Wróćmy teraz do przycisków. Musimy wziąć po jednym z przewodów wychodzących z przycisków. Aby mogły podawać sygnał, łączymy je ze sobą i lutujemy do przewodu wychodzącego z pinu Vout przetwornicy.

Do dodatkowego przewodu wychodzącego ze złącza GND przetwornicy lutujemy dwa kolejne przewody. Jeden łączymy z złączem GND Arduino, a drugi ze złączem GND wyświetlacza.

Jeśli w Arduino mamy przylutowane piny dla magistrali I2C, to możemy je połączyć za pomocą przewodów żeńsko-żeńskich. W innym przypadku lutujemy przewody do miejsc odpowiedzialnych za te złącza.

Teraz przy pomocy kleju cyjanoakrylowego montujemy wyświetlacz i włącznik.Jeśli napotkamy problemy związane z łączeniem tych elementów przy pomocy kleju cyjanoakrylowego, możemy go zastąpić klejem na gorąco.

Po zamocowaniu wyświetlacza i włącznika lutujemy ostatnie elementy, czyli przewody sygnałowe wychodzące z naszych przycisków. Logika łączenia jest taka sama, jak w przypadku płytki stykowej:

1. Przycisk w prawo – pin 7
2. Przycisk w dół – pin 5
3. Przycisk w górę – pin 4
4. Przycisk w lewo – pin 6

Gdy przylutujemy przyciski, to wóczas możemy włożyć baterię do koszyka i ułożyć elementy w obudowie. Po ułożeniu elementów skręcamy obudowę i cieszymy się z własnoręcznie wykonanej konsoli do gry w Snake!

Podziel się:

Share on facebook
Share on linkedin
Share on twitter
Marcin Lesiński

Marcin Lesiński

Miłośnik elektroniki, druku 3D i nauk stosowanych, swoją wiedzę gromadzi od 14-tego roku życia. W Botlandzie tworzy projekty i poradniki dla klientów, a w wolnym czasie zajmuje się automatycznymi uprawami i prototypowaniem. Fan muzyki rockowej i brytyjskiego kina.

Zobacz więcej:

Dodaj komentarz

Twój adres email nie zostanie opublikowany.