Evan Rust
Published © GPL3+

ATtiny85 Mini Arcade: Snake

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

IntermediateFull instructions provided12 hours8,286
ATtiny85 Mini Arcade: Snake

Things used in this project

Hardware components

ATtiny85
Microchip ATtiny85
×1
Graphic OLED, 128 x 64
Graphic OLED, 128 x 64
×1
SparkFun microB USB Breakout
SparkFun microB USB Breakout
×1
Arduino Nano R3
Arduino Nano R3
×1

Software apps and online services

Fusion
Autodesk Fusion
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Main Housing

Side Panel

Schematics

Schemtaic

Code

ATTiny85 Code

C/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();
}

Credits

Evan Rust

Evan Rust

122 projects • 1087 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments