Project tutorial
ATtiny85 Mini Arcade: Snake

ATtiny85 Mini Arcade: Snake © GPL3+

A miniature gaming console that lets you play snake using only an ATtiny85 and a simple I2C OLED display.

  • 2,196 views
  • 7 comments
  • 18 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
3drag
3D Printer (generic)

Apps and online services

About this project

Inspiration and Past Project

In December of 2017, I created a Handheld Arduino Pong Console that used an Arduino Nano and OLED screen, along with two buttons.

This was fine at the time, but the console was a bit too large and cumbersome. Recently, however, I have been trying to recreate some of my past projects. This time, I wanted to make a very tiny console that people can play snake on.

The Chosen Components

To make the console small, I couldn’t use any Arduino dev board, which is why I went with an ATtiny85.

It features enough I/O to run the game, including two ADC pins and, I2C port, and a GPIO pin. I opted to use a simple 2-axis joystick/switch module, as it is easy to interface with and only needs 3 pins for signaling.

Finally, it was time to decide which display was needed. Since DFRobot’s 128 x 64 OLED screen has a small size but plenty of resolution, I went with it.

Soldering a System

To begin, I attached an ATtiny85 to a SOP-8 to DIP-8 breakout PCB and soldered it to a small piece of perf-board. Next, I soldered on two headers- one for programming and the other for the screen. After doing that, I connected the analog joystick’s pins to the perf-board and wired it accordingly. For the final step, I wired a micro USB breakout board to the rest of the system for power.

Designing an Enclosure

The enclosure was designed in Fusion 360. I began by creating and laying out the components I used in the actual build, then I created an enclosure around them.

I wanted to mimic an arcade box from the 80’s, but still maintain a very small size. Below are some renders of the enclosure:

Programming the Game

Snake is a fairly easy game to program. I set a maximum snake length of 30 to save on RAM, meaning that once the snake has eaten 29 pixels, the player wins. To keep track of the segments of the snake, I created a 2-dimensional array that stores ordered pairs for each segment.

Whenever the head moves to a new location, its previous positions get cascaded downwards. Every time a segment is consumed, a new one is spawned at a random point. Collision checking is done by iterating through the coordinates of each segment and seeing if the head’s coordinates are the same. Additionally, hitting the snake along any of the walls will cause the player to lose as well.

Playing Snake

I started by powering on the console and waiting for the screen to load in the food and first segment of the snake. Then I simply piloted the snake by moving the joystick in the correct direction while watching it eat the food. This game is fun to play and is a great boredom killer in a tiny package.

Code

ATTiny85 CodeC/C++
Make sure to install U8g2lib first
//#include <TinyWireM.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g(U8G2_R0,/* clock=/ 2, / data=/ 0, / reset=*/ U8X8_PIN_NONE);

#define MAX_LENGTH 30 //30 segments max

#define X 0
#define Y 1

#define JOYSTICK_X 2
#define JOYSTICK_Y 3

#define DIR_THRESH 300 //Values must be either 0-300 or 723-1023 to count

uint8_t segmentPositions[MAX_LENGTH][2];

uint8_t headPosition[2] = {63, 31}; //Place snake in beginning to start
uint8_t foodPosition[2]; //Where the food is located
uint8_t tempPosition0[2]; //Store position of previous segment to pass on to next
uint8_t tempPosition1[2]; //Store position of previous segment to pass on to next

int segmentLength = 1;

void gameUpdate();

enum DIRECTIONS {
  RIGHT,
  DOWN,
  LEFT,
  UP
} currentDirection;

void setup() {
  //TinyWireM.begin();
  u8g.begin();
  u8g.setPowerSave(0);
  pinMode(JOYSTICK_X, INPUT);
  pinMode(JOYSTICK_Y, INPUT);
  randomSeed(analogRead(0));
  beginGame();
}

void loop() {
  u8g.firstPage();
  do {
    gameUpdate();
    u8g.setColorIndex(1);
  }
  while(u8g.nextPage());

}

void beginGame(){
  currentDirection = RIGHT;
  spawnFood();
  delay(1000);
}

bool checkCollisions(){
  for(int i=1; i<segmentLength; i++){
    if(segmentPositions[i][X] == headPosition[X]){
      if(segmentPositions[i][Y] == headPosition[Y]){
        return 1;
      }
    }
  }
  if(headPosition[X] <= 0 || headPosition[X] >= 128) return 1;
  else if(headPosition[Y] <= 0 || headPosition[Y] >= 64) return 1;
  return false;
}

void spawnFood(){
  int randomX = random(5, 123);
  int randomY = random(5, 60);
  foodPosition[X] = randomX;
  foodPosition[Y] = randomY;
}

void checkFoodEaten(){
  if(headPosition[X] == foodPosition[X] || headPosition[Y] == foodPosition[Y]){
    segmentLength += 1;
    spawnFood();
  }
}

void updateDirection(){
  int joy_x_val = analogRead(JOYSTICK_X);
  int joy_y_val = analogRead(JOYSTICK_Y);
  if(joy_x_val <= DIR_THRESH) currentDirection = LEFT;
  else if(joy_x_val >= 1023-DIR_THRESH) currentDirection = RIGHT;
  else if(joy_y_val <= DIR_THRESH) currentDirection = UP;
  else if(joy_y_val >= 1023-DIR_THRESH) currentDirection = DOWN;
}

void displaySegments(){
  for(int segment=0; segment < segmentLength; segment++){
    u8g.drawPixel(segmentPositions[segment][X], segmentPositions[segment][Y]);
  }
  u8g.drawPixel(foodPosition[X], foodPosition[Y]);
}

void gameUpdate(){
  updateDirection();
  tempPosition0[X] = headPosition[X];
  tempPosition0[Y] = headPosition[Y];
  switch(currentDirection){
    case UP:
      headPosition[Y] -= 1;
      break;
    case DOWN:
      headPosition[Y] += 1;
      break;
    case LEFT:
      headPosition[X] -= 1;
      break;
    case RIGHT:
      headPosition[X] += 1;
      break;
  }
  //Update each segment's position relative to the head
  for(int segment=1; segment < segmentLength; segment+= 2){
    tempPosition1[X] = segmentPositions[segment][X];
    tempPosition1[Y] = segmentPositions[segment][Y];
    segmentPositions[segment][X] = tempPosition0[X];
    segmentPositions[segment][Y] = tempPosition0[Y];
    
    tempPosition0[X] = segmentPositions[segment+1][X];
    tempPosition0[Y] = segmentPositions[segment+1][Y];
    segmentPositions[segment+1][X] = tempPosition1[X];
    segmentPositions[segment+1][Y] = tempPosition1[Y];
  }
  displaySegments();
  checkFoodEaten();
  if(checkCollisions()) endGame();
  if(segmentLength>= MAX_LENGTH) endGame();
  delay(50);
}

void endGame(){
  segmentLength = 1;
  headPosition[0] = 63;
  headPosition[1] = 31;
  beginGame();
}

Custom parts and enclosures

Main Housing
Side Panel

Schematics

Schemtaic
Uploads2ftmp2f97e17c53 6425 4c28 886f ef802e0121ab2fattiny85 pinout wa5b0cf4iw

Comments

Similar projects you might like

Arduino Arcade LEGO Games Box

Project tutorial by LenkaDesign

  • 7,120 views
  • 9 comments
  • 36 respects

Otto DIY+ Arduino Bluetooth robot easy to 3D Print

Project tutorial by Team Otto builders

  • 72,322 views
  • 134 comments
  • 212 respects

Hacking Qualcomm (Quick Charge) QC 2.0/3.0 With ATtiny85

Project tutorial by Shahariar

  • 12,202 views
  • 15 comments
  • 46 respects

Tri-Mode Digital Clock with ATtiny85 and RTC

Project tutorial by LAGSILVA

  • 7,696 views
  • 2 comments
  • 23 respects

3D-Printed Nanoleaf ATtiny85

Project tutorial by Arnov Sharma

  • 2,644 views
  • 0 comments
  • 10 respects

NeoMatrix Arduino Pong

Project tutorial by Arduino “having11” Guy

  • 5,365 views
  • 2 comments
  • 22 respects
Add projectSign up / Login