Arduino OLED Snake Game
Hi and welcome, to our instructables on how to make and arduino OLED Game, this project came about as we were attempting to make our first ever game with an arduino, soooo, we thought where better to start than the nokia classic Snake (well at least a snake clone:) ).
What you Will Need
Supplies
optional
Please note these links are for example only
The Circuit
In the above picture you can see our circuit, we use pin d3 on the arduino as an interrupt request pin so that the arduino will give priority to reading the controller inputs which are d4 d5 d6 d7. The basics of the circuit is that a direction button is pressed which goes high 5v this activates the interrupt request pin (d3 purple wire) and a corresponding direction pin, the interrupt function calls an update direction function and that code moves the snake accordingly. Pin 9 is used as the sound pin as it is a PWM (~ pulse width modulation) which is connected directly to a 5v piezo on the + pin and the - goes back to 0v/ground.
(FYI on the arduino uno and clones only d2 and d3 can act as interrupt request pins).
Direction pins:
d4 Up ORANGE
d5 Down PINK
d6 Left BLUE
d7 Right BROWN
d9 sound GREY
Each button has a in 5v connection input and an output that is connected first to their respective digital input on the arduino, this same output of each button is then connected to its own diode, we use the diodes to stop voltage feeding back through to the other buttons and activating them. On the cathode (-) end of all 4 diodes we join them together to create an output junction which connects to d3 and then through the a resistor to 0v/earth to pull the arduino pins low so as not to leave floating pins when not being activated.
(FYI a floating pin can receive phantom voltage and cause unusual behavior)
2 analog pins are used to drive the display, these are the arduino hardware i2c pins.
A5 is connected to SCL YELLOW
A4 is connected to SDA GREEN
The +5v and 0v (earth) output from the arduino is used as the source of the power for the whole circuit which can be powered by usb or phone charger.
The Code
<p>//------------------------ ANJAWARE SNAKE Games With help from the net peoples ----------------------</p><p>#include<spi.h> #include<adafruit_gfx.h> // <a href="https://github.com/adafruit/Adafruit-GFX-Library" rel="nofollow"> https://github.com/adafruit/Adafruit-GFX-Library </a> #include<adafruit_ssd1306.h> // <a href="https://github.com/adafruit/Adafruit_SSD1306" rel="nofollow"> https://github.com/adafruit/Adafruit-GFX-Library </a> // display set (width,height) Adafruit_SSD1306 display(128,64); // define input pins these are the pins on the arduino they never change so #define #define INTPIN 3 // only pins 2 and 3 can be interupt pins on UNO #define UPPIN 4 // these are pins connected tp relevant switch #define DWNPIN 5 #define LFTPIN 6 #define RHTPIN 7 #define SND 9 // define directions #define DIRUP 1 // these values is what the "snake" looks at to decide- #define DIRDOWN 2 // the direction the snake will travel #define DIRLEFT 3 #define DIRRIGHT 4</adafruit_ssd1306.h></adafruit_gfx.h></spi.h></p><p>// set button variables</p><p>// volitile cos we need it to update with the interupt so can be any bit of cycle value // is never higher than 4 so only need 8bit int to save resources volatile uint8_t buttonpressed=0; bool butup=0; bool butdown=0; // we use this to set true to "detect" which direction pressed bool butleft=0; bool butright=0;</p><p> // snake ints byte snakePosX[30]; // array to make body of snake byte snakePosY[30];</p><p>int snakeX=30; // snake head position int snakeY=30; int snakeSize=1; // snake size count limited to the size of the array</p><p>// world ints</p><p>uint8_t worldMinX=0; // these set the limits of the play area uint8_t worldMaxX=128; uint8_t worldMinY=10; uint8_t worldMaxY=63;</p><p>// collect scran(food) and position of scran bool scranAte =0; uint8_t scranPosX=0; uint8_t scranPosY=0;</p><p>// scores variables long playscore=0; long highscore=30; // set high score to 3 collect as a starting point</p><p>//--------------------------- this is what the interupt executes on voltage rise ------------------------- void interruptpressed() { delay(150); // slight delay for added "bounce" protection updatedirection(); } // ------------------ update the direction value from button press ----------------- void updatedirection() { // Serial.println("updatingdirection"); butup=digitalRead(UPPIN); // check which input went high and set relevant bool true butdown=digitalRead(DWNPIN); butleft=digitalRead(LFTPIN); butright=digitalRead(RHTPIN); // these if statemeents look at which input went high and enters the relevant value in "buttonpressed" // variable, this variable dictates the direction of movement if(butup==true) { buttonpressed=DIRUP; // Serial.println("UP pressed"); // Serial.println(buttonpressed); butup=false; tone(SND,1500,10); } if(butdown==true) { buttonpressed=DIRDOWN; // Serial.println("DOWN pressed"); // Serial.println(buttonpressed); butdown=false; tone(SND,1500,10); }</p><p> if(butleft==true) { buttonpressed=DIRLEFT; // Serial.println("LEFT pressed"); // Serial.println(buttonpressed); butleft=false; tone(SND,1500,10); } if(butright==true) { buttonpressed=DIRRIGHT; // Serial.println("RIGHT pressed"); // Serial.println(buttonpressed); butright=false; tone(SND,1500,10); } }</p><p>//-------------------------- draw the display routines-----------------------------------</p><p>void updateDisplay() // draw scores and outlines { // Serial.println("Update Display");</p><p> display.fillRect(0,0, display.width()-1,8,BLACK); display.setTextSize(0); display.setTextColor(WHITE); // draw scores display.setCursor(2,1); display.print("Score:"); display.print(String(playscore, DEC)); display.setCursor(66,1); display.print("High:"); display.print(String(highscore ,DEC)); // draw play area // pos 1x,1y, 2x,2y,colour display.drawLine(0,0,127,0,WHITE); // very top border display.drawLine(63,0,63,9,WHITE); // score seperator display.drawLine(0,9, 127,9,WHITE); // below text border display.drawLine(0,63,127,63,WHITE); // bottom border display.drawLine(0,0,0,63,WHITE); // left border display.drawLine(127,0,127,63,WHITE); //right border</p><p> } //----------------------------------- update play area ------------------------------</p><p>void updateGame() // this updates the game area display { display.clearDisplay();</p><p> display.drawPixel(scranPosX,scranPosY, WHITE); scranAte = scranFood(); </p><p> // check snake routines</p><p> if (outOfArea()||selfCollision()) { gameOver(); }</p><p> // display snake for(int i=0;i<snakesize;i++) ="" {="" display.drawpixel(snakeposx[i],snakeposy[i],white);="" }="" ="" remove="" end="" pixel="" as="" movement="" occurs="" for(int="" i="snakeSize;i">0;i--) { snakePosX[i] = snakePosX[i-1]; snakePosY[i] = snakePosY[i-1]; } // add a extra pixel to the snake if(scranAte) { snakeSize+=1; snakePosX[snakeSize-1]=snakeX; snakePosY[snakeSize-1]=snakeY; }</snakesize;i++)></p><p> switch(buttonpressed) // was snakeDirection { 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(); // --------------------- place the scran -------------------
void placeScran() { scranPosX=random(worldMinX+1,worldMaxX-1); scranPosY=random(worldMinY+1,worldMaxY-1); } //------------------------ SCRAN ATE POINT UP ---------------- bool scranFood() { if(snakeX==scranPosX&&snakeY==scranPosY) { playscore=playscore+10; tone(SND,2000,10); updateDisplay(); placeScran(); return 1; } else { return 0; } } //--------------------- out of area---------------------- bool outOfArea() { return snakeX <= worldMinX||snakeX >=worldMaxX ||snakeY<=worldMinY|| snakeY>=worldMaxY; } //---------------------- game over-------------------------- 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) //check to see if score higher than high score { highscore=playscore; //single if statment to update high score } for(int i=0;i<=16;i++) // this is to draw rectanlges around game over { display.drawRect(rectX1,rectY1,rectX2,rectY2,WHITE); Serial.println("if loop"); display.display(); rectX1-=2; // shift over by 2 pixels rectY1-=2; rectX2+=4; // shift over 2 pixels from last point rectY2+=4; tone(SND,i*200,3); } display.display(); //Screen Wipe after fame over rectX1=0; // set start position of line 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; // reset snake and player details snakeSize=1; snakeX=display.width()/2; snakeY=display.height()/2; waitForPress(); // wait for player to start game } //-------------------------wait for presss loop ------------------------- void waitForPress() { bool waiting=0; // loop ends whjen this is true display.clearDisplay(); while(waiting==0) { drawALineForMe(WHITE); // draw a random white line drawALineForMe(BLACK); // draw a random black line so that the screen not completely fill white display.fillRect(19,20,90,32,BLACK); // blank background for text display.setTextColor(WHITE); display.setCursor(35,25); display.setTextSize(2); // bigger font display.println("SNAKE"); // x y w h r col display.drawRoundRect(33,22,62,20,4,WHITE); // border Snake display.drawRect(19,20,90,32,WHITE); // border box - 3 display.setCursor(28,42); display.setTextSize(0); // font back to normal display.println("press any key"); display.display(); waiting = digitalRead(INTPIN); // check to see if key pressed waiting will change to 1 ending while buttonpressed=0; // reset button press } } //--------------------DRAW a random line input colour uint8_t------------------- void drawALineForMe(uint8_t clr) { uint8_t line1X,line1Y,line2X,line2Y=0; // set random co-ordinates for a line then draw it // variable no less no more 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); } //------------------------------------- collision detecion -------------------------------<br>
for(byte i=4;i<snakeSize;i++) { if(snakeX==snakePosX[i]&&snakeY==snakePosy[i]) { return 1; tone(SND,2000,20); tone(SND,1000,20); } return 0; }
//-------------------------------- SETUP---------------------------------------------- void setup() { delay(100); // just give stuff a chance to "boot" // Serial.begin(9600); // uncheck this if you want to see the serial outputs display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); // start with a clean display display.setTextColor(WHITE);// set up text color rotation size etc display.setRotation(0); display.setTextWrap(false); display.dim(0); // set the display brighness pinMode(INTPIN,INPUT); // set the correct ports to inputs pinMode(UPPIN,INPUT); pinMode(DWNPIN,INPUT); pinMode(LFTPIN,INPUT); pinMode(RHTPIN,INPUT); // this is the interupt command this "halts" the arduino to read the inputs //command- function- pin-function to execute-condition on pin attachInterrupt(digitalPinToInterrupt(INTPIN),interruptpressed,RISING); // Serial.println("Setup Passed"); waitForPress(); // display the snake start up screen placeScran(); // place first bit of food } //---------------------MAIN LOOP--------------------------------------- void loop() { updateGame(); // this function is what carries the main code }