John Bradnam
Published © GPL3+

ATtiny1614 Arcade

An ATtiny1614 based console that plays Breakout, Snake, UFO Escape and Pong games on a 0.96 inch OLED screen powered by a button battery,

IntermediateFull instructions provided8 hours1,720
ATtiny1614 Arcade

Things used in this project

Hardware components

Microchip ATtiny1614
×1
0.96" OLED 64x128 Display Module
ElectroPeak 0.96" OLED 64x128 Display Module
×1
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
17mm shafts with button caps
×2
Capacitor 10uF 1206 SMD Ceramic
×1
Coin Cell Battery CR2032
Coin Cell Battery CR2032
×1
Battery Holder Through Hole for CR2032
×1
17mm Diam Piezo Piezoelectric Passive Buzzers
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Case - Front

Case - Back

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle fornat

Code

ATtiny1614Arcade.ino

C/C++
/* ATtiny1614 Arcade
*  2020 - jbrad2089@gmail.com
*         Create console menu for games.
*/
#include <EEPROM.h>
#include "font6x8.h"
#include "SSD1306_ATtiny1614.h"

#include "Breakout1614.h"
#include "Snake1614.h"
#include "UFO1614.h"
#include "BatBonanza1614.h"

//Uncomment next line to clear out EEPROM and reset all high scores
//#define RESET_EEPROM

#define EEPROM_MENU_SELECT 0

enum MODES {MODE_MENU, MODE_BREAKOUT, MODE_SNAKE, MODE_UFO, MODE_PONG, MODE_OFF};
MODES currentMode = MODE_MENU;
MODES selection = MODE_BREAKOUT;

int8_t menuDirection = 0;
bool repaintMenuItem = false;

void setup() 
{
  #ifdef RESET_EEPROM
  for (int a = 0; a < 32; a++)
  {
    EEPROM.write(a,(byte)0); 
  }
  #endif
  
  buttonSetup();

  ssd1306_init();
  ssd1306_fillscreen(0x00);

  int t = EEPROM.read(EEPROM_MENU_SELECT);
  selection = (t == 0 || t > (int)MODE_OFF) ? MODE_BREAKOUT : (MODES)t;
  wakeup();
}

void loop() 
{
  //Vector main loop 
  //Loop handlers in submodules have been modified to return false if they want to sleep 
  switch (currentMode)
  {
    case MODE_MENU: 
      menuLoop();
      break;
      
    case MODE_BREAKOUT: 
      if (!breakout::loop())
      {
        currentMode = MODE_OFF;
      }
      break;
    
    case MODE_SNAKE:
      if (!snake::loop())
      {
        currentMode = MODE_OFF;
      }
      break;
    
    case MODE_UFO:
      if (!ufo::loop())
      {
        currentMode = MODE_OFF;
      }
      break;
      
    case MODE_PONG:
      if (!pong::loop())
      {
        currentMode = MODE_OFF;
      }
      break;
      
    case MODE_OFF:
      //Sleep
      ssd1306_sleep();
      wakeup();
      break;
  }
}

void menuLoop()
{
  MODES newSelection = selection;
  if (leftButtonPressed) //select next menu item
  {
    leftButtonPressed = false;
    ssd1306_beep(SPEAKER,50,200);
    newSelection = (selection == MODE_OFF) ? MODE_BREAKOUT : (MODES)((int)selection + 1);
  }
  if (newSelection != selection || repaintMenuItem)
  {
    //Draw selection arrow
    ssd1306_char_f6x8(10, (int)selection + 1, "  ");
    ssd1306_char_f6x8(10, (int)newSelection + 1, "->");
    selection = newSelection;
    repaintMenuItem = false;
  }
  if (rightButtonPressed)
  {
    rightButtonPressed = false;
    ssd1306_beep(SPEAKER,50,200);
    EEPROM.write(EEPROM_MENU_SELECT,(byte)selection); 

    currentMode = selection;
    if (currentMode != MODE_OFF)
    {
      ssd1306_init();
      ssd1306_fillscreen(0x00);
      ssd1306_char_f6x8(0, 2, "--------------------");
      switch (currentMode)
      {
        case MODE_BREAKOUT: ssd1306_char_f6x8(16, 3, "B R E A K O U T"); break;
        case MODE_SNAKE: ssd1306_char_f6x8(30, 3, "S N A K E"); break;
        case MODE_UFO: ssd1306_char_f6x8(8, 3, "U F O  E S C A P E"); break;
        case MODE_PONG: ssd1306_char_f6x8(38, 3, "P O N G"); break;
      }
      ssd1306_char_f6x8(0, 4, "--------------------");
      ssd1306_char_f6x8(18, 6, "for ATtiny1614");
      ssd1306_char_f6x8(4, 7, "jbrad2089@gail.com");
      playInitialSound();
      delay(2000);
      switch (currentMode)
      {
        case MODE_BREAKOUT: breakout::setup(); break;
        case MODE_SNAKE: snake::setup(); break;
        case MODE_UFO: ufo::setup(); break;
        case MODE_PONG: pong::setup(); break;
      }
    }
  }
  delay(200);
}

//Display screen
void wakeup()
{
  ssd1306_char_f6x8(16, 0, "ATtiny1614 Arcade");
  ssd1306_char_f6x8(26, 2, "Breakout");
  ssd1306_char_f6x8(26, 3, "Snake");
  ssd1306_char_f6x8(26, 4, "UFO");
  ssd1306_char_f6x8(26, 5, "Pong");
  ssd1306_char_f6x8(26, 6, "Off");
  
  playInitialSound();
  
  currentMode = MODE_MENU;
  leftButtonPressed = false;
  rightButtonPressed = false;
  repaintMenuItem = true;
}

BatBonanza1614.h

C/C++
/* 2015 / 2016 /2017 - Andy Jackson - Twitter @andyhighnumber
*         Inital Pong game 
*  2020 - jbrad2089@gmail.com
*         Modified SSD1306 library, interrupt handlers and sleep routines to run on ATtiny1614.
*         Reduced code size by replacing divisions with shifts and modulus with bitwise AND
*/
#pragma once

//Need to have unique addeesses for each game
#define EEPROM_PONG_MUTE 9
#define EEPROM_PONG_MODE 10

#include <EEPROM.h>
#include "font6x8.h"
#include "SSD1306_ATtiny1614.h"
#include "Buttons.h"

namespace pong
{

#define WINSCORE 7

enum GAME_MODES {NORMAL, TOUGH, EXPERT};

int player; //0 to 128-platformWidth  - this is the position of the player
int player2; //0 to 128-platformWidth  - this is the position of the player
int lastPlayer;
int lastPlayer2;
int platformWidth = 16; 
boolean mute = 0;
boolean newHigh = 0;
int score = 0; // score - this affects the difficulty of the game
int score2 = 0; // score - this affects the difficulty of the game
int perturbation = 0;
int pFactor = 12;

int ballx = 62*8; // coordinate of the ball
int bally = 50*4; // coordinate of the ball
int vdir = -4; // vertical direction and step  distance
int hdir = -8; // horizontal direction and step distance

int actualy, actualx, factor, waitCount, lastx, lasty;

GAME_MODES mode = NORMAL;

//-------------- forward references -------------------------------------

void setup();
bool loop(); 
void drawPlatform();
void drawPlatform2();
void sendBlock(int fill);
void blankBall(int x, int y);
void drawBall(int x, int y);
void doDrawRS(long P1, byte P2);
void doDrawLS(long P1, byte P2);
void startGame(void);
void doNumber (int x, int y, int value);

//-------------------- functions ------------------------------------------

//Setup game
void setup() 
{
  mute = EEPROM.read(EEPROM_PONG_MUTE);
  uint8_t lastMode = EEPROM.read(EEPROM_PONG_MODE);

  if (mute != 0 && mute != 1) 
  {
    mute = 0;
    EEPROM.write(EEPROM_PONG_MUTE,mute);
  }

  if (lastMode > (int)EXPERT) 
  {
    mode = NORMAL;
    EEPROM.write(EEPROM_PONG_MODE,(int)mode);
  }
  else
  {
    mode = (GAME_MODES)lastMode;
  }
  
  ballx = 64*8;
  bally = 32*4;
  hdir = -8;
  vdir = -4;
  factor = 0;
  waitCount = 0;

  lastx = 64*8;
  lasty = 32*4;

  player=64;
  player2=64;
  lastPlayer = 64;
  lastPlayer2 = 64;
  score = 0; // obvious
  score2 = 0; // obvious
  perturbation = 0;
  
  startGame();
}

//Main loop, returns false to go to sleep
bool loop() 
{
  waitCount++;
      
  if (digitalRead(RIGHT_BTN) == LOW) 
  {
    boolean sChange = false;
    long startT = millis();
    long nowT =0;
    while (digitalRead(RIGHT_BTN) == LOW) 
    {
      nowT = millis();
      if (nowT - startT > 1500) 
      {
        sChange = true;
        if (mute == 0) 
        { 
          mute = 1; 
          ssd1306_char_f6x8(32, 0, "-- MUTE --");
        } 
        else 
        { 
          mute = 0; 
          ssd1306_char_f6x8(23, 0, "-- SOUND ON --"); 
        }
        break;
      }
    }
    while(digitalRead(RIGHT_BTN) == LOW)
      ;
    if (!sChange)
    {
      switch (mode)
      {
        case NORMAL:
          mode = TOUGH;
          pFactor = 11;
          ssd1306_char_f6x8(20, 0, "- TOUGH MODE -");
          break;
            
        case TOUGH:
          mode = EXPERT;
          pFactor = 10;
          ssd1306_char_f6x8(16, 0, "- EXPERT MODE -");
          break;

        case EXPERT:
          mode = NORMAL;
          pFactor = 12;
          ssd1306_char_f6x8(32, 0, "-- NORMAL --");
          break;
      }
      delay(1000);
    } 
    ssd1306_fillscreen(0x00);
    EEPROM.write(EEPROM_PONG_MUTE,mute);
    EEPROM.write(EEPROM_PONG_MODE,mode);
  }

  if (digitalRead(LEFT_BTN) == LOW)
  {
    player -= 3;
  }
  player += 1;
  if (player > 48) player = 48;
  if (player < 0) player = 0;
      
  if(waitCount >= 3) 
  {
    waitCount = 0;
    perturbation = perturbation - 2 + random(0,5);
    if (perturbation > pFactor) perturbation = pFactor - 2;
    if (perturbation < pFactor*-1) perturbation = (pFactor*-1)+2;
  }
  player2 = (bally/4 -8)+perturbation;

  if (player2 > 48) player2 = 48;
  if (player2 <0) player2 = 0;

  actualy = bally >> 2;
  actualx = ballx >> 3;
      
  // bounce off the sides of the screen
  if ((actualy+vdir<63&&vdir>01) || (actualy- vdir>6&&vdir<0))
  {
    bally+=vdir;
  }
  else 
  {
    vdir = vdir*-1;
  }
  ballx+=hdir;

  actualy = bally >> 2;
  actualx = ballx >> 3;
      
  // check it hits the left pad and deal with bounces and misses
  if (actualx <= 4) 
  {
    if(actualy<player-1||actualy>player+platformWidth+1)
    {
      score2++;
          
      ballx = 5*8;
      bally = player*4;

      hdir = 13;
      if (vdir > 0) {
        vdir = 2;
      } else vdir = -2;
          
      ssd1306_fillscreen(0x00);
      doNumber(46,4,score);
      doNumber(78,4,score2);
      if (score2 < WINSCORE) 
      {
        for (int i = 0; i<1000; i = i+ 100)
        {
          ssd1306_beep(SPEAKER,50,i);
        }
        for (int incr=0;incr<3;incr++) 
        {
          ssd1306_send_data_stop();
          ssd1306_setpos(78,4);
          ssd1306_send_data_start();
          sendBlock(0);
          sendBlock(0);
          ssd1306_send_data_stop();
          delay(350);
          doNumber(78,4,score2);
          delay(350);
        }
        startGame();
      }
      perturbation = 0;
      return true;
    }
    else if (actualy<player+1)
    {
      vdir = -6;
      hdir = 7;
    }
    else if (actualy<player+4)
    {
      vdir = -4;
      hdir = 10;
    }
    else if (actualy<player+7)
    {
      vdir = -2;
      hdir = 13;
    }
    else if (actualy<player+9)
    {
      vdir = 0;
      hdir = 14;
    }
    else if (actualy<player+12)
    {
      vdir = 2;
      hdir = 13;
    }
    else if (actualy<player+15)
    {
      vdir = 4;
      hdir = 10;
    }
    else 
    {
      vdir = 6;
      hdir = 7;
    }
    ssd1306_beep(SPEAKER,20,600);
  }
      
  // check it hits the right pad and deal with bounces
  if(actualx >= 122) 
  {
    if (actualy<player2-1||actualy>player2+platformWidth+1)
    {
      score++;
          
      ballx = 120*8;
      bally = player2*4;

      hdir = -13;
      vdir = (vdir > 0) ? 2 : -2;

      ssd1306_fillscreen(0x00);
      doNumber(46,4,score);
      doNumber(78,4,score2);
      if (score < WINSCORE) 
      {
        for (int i = 0; i<1000; i = i+ 100)
        {
          ssd1306_beep(SPEAKER,50,i);
        }
            
        for (int incr=0;incr<3;incr++) 
        {
          ssd1306_setpos(46,4);
          ssd1306_send_data_start();
          sendBlock(0);
          sendBlock(0);
          ssd1306_send_data_stop();
          delay(350);
          doNumber(46,4,score);
          delay(350);
        }
        perturbation = 0;
        startGame();
      }
      return true;
    }
    else if (actualy<player2+1)
    {
      vdir = -6;
      hdir = -7;
    }
    else if (actualy<player2+4)
    {
      vdir = -4;
      hdir = -10;
    }
    else if (actualy<player2+7)
    {
      vdir = -2;
      hdir = -13;
    }
    else if (actualy<player2+9)
    {
      vdir = 0;
      hdir = -14;
    }
    else if (actualy<player2+12)
    {
      vdir = 2;
      hdir = -13;
    }
    else if (actualy<player2+15)
    {
      vdir = 4;
      hdir = -10;
    }
    else 
    {
      vdir = 6;
      hdir = -7;
    }
    ssd1306_beep(SPEAKER,20,300);
  }

      
  if (mode == NORMAL)
  {
    factor = 20-((score-score2) >> 1); // normal modes
    if (factor < 10) factor = 10;
  }
  else 
  {
    factor = 8-((score-score2) >> 1); // expert modes
    if (factor < 2) factor = 2;
  } 
  delay(factor);
      
  // draw ball
  blankBall(lastx >> 3,lasty >> 2);
  drawPlatform();
  drawPlatform2();
  drawBall(ballx >> 3,bally >> 2);
  lastx = ballx;
  lasty = bally;
      
  doNumber(28,0,score);
  doNumber(92,0,score2);
  if (score == WINSCORE || score2 == WINSCORE) 
  {
    blankBall(lastx >> 3,lasty >> 2);
    blankBall(ballx >> 3,bally >> 2);
      
    if (score > score2) 
    {
      ssd1306_char_f6x8(27, 3, "P L A Y E R 1");
    } 
    else 
    {
      ssd1306_char_f6x8(27, 3, "P L A Y E R 2");
    }
    ssd1306_char_f6x8(27, 4, "             ");
    ssd1306_char_f6x8(27, 5, "   W I N S   ");
      
    for (int i = 0; i<1000; i = i+ 50)
    {
      ssd1306_beep(SPEAKER,50,i);
    }
      
    for (int incr=0;incr<6;incr++) 
    {
      ssd1306_setpos(28,0);
      ssd1306_send_data_start();
      sendBlock(0);
      sendBlock(0);
      ssd1306_send_data_stop();
      ssd1306_setpos(92,0);
      ssd1306_send_data_start();
      sendBlock(0);
      sendBlock(0);
      ssd1306_send_data_stop();
      delay(350);
      doNumber(28,0,score);
      doNumber(92,0,score2);
      delay(350);
    }
        
    delay(3500);
    return false;
  }
  return true;
}

void drawPlatform() 
{
  if (player != lastPlayer) 
  {
    ssd1306_setpos(0,(lastPlayer >> 3));
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
    ssd1306_setpos(0,(lastPlayer >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
    ssd1306_setpos(0,(lastPlayer >> 3) + 2);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
  }

  if ((player & 0x07) !=0)
  {
    ssd1306_setpos(0,player >> 3);
    ssd1306_send_data_start();
    ssd1306_send_byte((B11111111) << (player & 0x07));
    ssd1306_send_data_stop();
    ssd1306_setpos(0,(player >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B11111111);
    ssd1306_send_data_stop();
    ssd1306_setpos(0,(player >> 3) + 2);
    ssd1306_send_data_start();
    ssd1306_send_byte((B01111110) >> 8 - (player >> 3));
    ssd1306_send_data_stop();
  } 
  else 
  {
    ssd1306_setpos(0,player >> 3);
    ssd1306_send_data_start();
    ssd1306_send_byte(B11111111);
    ssd1306_send_data_stop();
    ssd1306_setpos(0,(player >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B11111111);
    ssd1306_send_data_stop();

  }
  lastPlayer = player;
}

void drawPlatform2() 
{
  if (player2 != lastPlayer2) 
  {
    ssd1306_setpos(127,(lastPlayer2 >> 3));
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
    ssd1306_setpos(127,(lastPlayer2 >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
    ssd1306_setpos(127,(lastPlayer2 >> 3) + 2);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
  }
  
  if ((player2 & 0x07)!=0)
  {
    ssd1306_setpos(127,(player2 >> 3));
    ssd1306_send_data_start();
    ssd1306_send_byte((B11111111)<<(player2 & 0x07));
    ssd1306_send_data_stop();
    ssd1306_setpos(127,(player2 >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B11111111);
    ssd1306_send_data_stop();
    ssd1306_setpos(127,(player2 >> 3) + 2);
    ssd1306_send_data_start();
    ssd1306_send_byte((B01111110)>>8-(player2 & 0x07));
    ssd1306_send_data_stop();
  } 
  else 
  {
    ssd1306_setpos(127,(player2 >> 3));
    ssd1306_send_data_start();
    ssd1306_send_byte((B11111111)<<0);
    ssd1306_send_data_stop();
    ssd1306_setpos(127,(player2 >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte((B11111111)<<0);
    ssd1306_send_data_stop();
  }
  lastPlayer2 = player2;
}

void sendBlock(int fill)
{
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
  ssd1306_send_byte(B00000000);
}

void blankBall(int x, int y) 
{
  if ((y & 0x07) != 0)
  {
    ssd1306_setpos(x,y >> 3);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
    
    ssd1306_setpos(x,(y >> 3) + 1);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
  } 
  else 
  {
    ssd1306_setpos(x,y >> 3);
    ssd1306_send_data_start();
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_data_stop();
  }
}


void drawBall(int x, int y) 
{
  if (y%8!=0)
  {
    ssd1306_setpos(x,y >> 3);
    ssd1306_send_data_start();
    doDrawLS(0,y & 0x07);
    ssd1306_send_data_stop();
    
    ssd1306_setpos(x,(y >> 3) + 1);
    ssd1306_send_data_start();
    doDrawRS(0,8-(y & 0x07));
    ssd1306_send_data_stop();
  } 
  else 
  {
    ssd1306_setpos(x,y >> 3);
    ssd1306_send_data_start();
    doDrawLS(0,0);
    ssd1306_send_data_stop();
  }
}

void doDrawRS(long P1, byte P2) 
{
  ssd1306_send_byte((B00000011 | P1)>>P2);
  ssd1306_send_byte((B00000011 | P1)>>P2);
}

void doDrawLS(long P1, byte P2) 
{
  ssd1306_send_byte((B00000011 | P1)<<P2);
  ssd1306_send_byte((B00000011 | P1)<<P2);
}

void startGame(void) 
{
  
  ssd1306_fillscreen(0x00);

  ssd1306_char_f6x8(16, 3, "-- GET READY --");
  doNumber(60,5,3);
  delay(1000);
  doNumber(60,5,2);
  delay(1000);
  doNumber(60,5,1);
  delay(1000);
  ssd1306_fillscreen(0x00);

  for (int i = 800; i>200; i = i - 200)
  {
    ssd1306_beep(SPEAKER,30,i);
  }

}

void doNumber (int x, int y, int value) 
{
  char temp[10] = {0,0,0,0,0,0,0,0,0,0};
  itoa(value,temp,10);
  ssd1306_char_f6x8(x, y, temp);
}

}

Breakout1614.h

C/C++
/* 2014 - Ilya Titov 
*         Initial Breakout game
*  2020 - jbrad2089@gmail.com
*         Modified SSD1306 library, interrupt handlers and sleep routines to run on ATtiny1614.
*         Increased ball size from 1x1 to 2x2 pixels
*         Added speed increase each time board is cleared
*
*  This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
*  Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
*/
#pragma once

//Need to have unique addeesses for each game
#define EEPROM_BREAKOUT_SCORE_LOW 2
#define EEPROM_BREAKOUT_SCORE_HIGH 3

#include <EEPROM.h>
#include "font6x8.h"
#include "SSD1306_ATtiny1614.h"
#include "Buttons.h"

#define START_SPEED_DELTA 30
#define INCREASE_SPEED_DELTA 10
#define BAT_WIDTH 16

namespace breakout
{

volatile byte player = 0; //0 to 128-BAT_WIDTH  - this is the position of the bounce platform
byte speedDelay = START_SPEED_DELTA;
byte ballx = 62; // coordinate of the ball
byte bally = 50; // coordinate of the ball
int vdir = -1; // vertical direction and step  distance
int hdir = -1; // horizontal direction and step distance
long lastFrame = 0; // time since the screen was updated last
boolean row1[16]; // on-off array of blocks
boolean row2[16];
boolean row3[16];
int score = 0; // score - counts the number of blocks hit and resets the array above when devisible by 48(total blocks)

//-------------- forward references -------------------------------------

void setup();
bool loop(); 
void collision();
void drawBall();
void drawBat();
void sendBlock(boolean fill);

//-------------------- functions ------------------------------------------

//Setup game
void setup() 
{
  for (byte i =0; i<16;i++)
  { // reset blocks
    row1[i]=1; 
    row2[i]=1; 
    row3[i]=1;
  }
  speedDelay = START_SPEED_DELTA;
  ballx = 64;
  bally = 50;
  hdir = -1;
  vdir = -1;
  score = 0;
  player = random(0,128-BAT_WIDTH);
  ballx = player+BAT_WIDTH/2;
  lastFrame = millis();
}

//Main loop, returns false to go to sleep
bool loop() 
{
  if (leftButtonPressed)
  {
    player = max(player - 1, 0);  
  }
  if (rightButtonPressed)
  {
    player = min(player + 1, 128 - BAT_WIDTH);
  }
  
  // continue moving after the interrupt
  if (digitalRead(RIGHT_BTN) == LOW) 
  {
    player = min(player + 3, 128 - BAT_WIDTH);
  }
  if (digitalRead(LEFT_BTN) == LOW) 
  {
    player = max(player - 3, 0);
  }
  // bounce off the sides of the screen
  if ((bally+vdir<54&&vdir==1)||(bally-vdir>1&&vdir==-1)){bally+=vdir;}else {vdir = vdir*-1;}
  if ((ballx+hdir<127&&hdir==1)||(ballx-hdir>1&&hdir==-1)){ballx+=hdir;}else {hdir = hdir*-1;}
  
  // frame actions
  if (lastFrame+10<millis())
  {
    if(bally>10&&bally+vdir>=54&&(ballx<player||ballx>player+BAT_WIDTH)) // game over if the ball misses the platform
    { 
      int topScore = EEPROM.read(EEPROM_BREAKOUT_SCORE_HIGH);
      topScore = topScore << 8;
      topScore = topScore |  EEPROM.read(EEPROM_BREAKOUT_SCORE_LOW);
      
      if (score>topScore)
      {
        topScore = score; 
        EEPROM.write(EEPROM_BREAKOUT_SCORE_LOW,topScore & 0xFF); 
        EEPROM.write(EEPROM_BREAKOUT_SCORE_HIGH,(topScore>>8) & 0xFF); 
      }
      ssd1306_fillscreen(0x00);
      ssd1306_char_f6x8(32, 3, "Game Over");
      ssd1306_char_f6x8(32, 5, "score:");
      char temp[4] = {0,0,0,0};
      itoa(score,temp,10);
      ssd1306_char_f6x8(70, 5, temp);
      ssd1306_char_f6x8(32, 6, "top score:");
      itoa(topScore,temp,10);
      ssd1306_char_f6x8(90, 6, temp);
      for (int i = 0; i<1000; i++)
      {
        ssd1306_beep(SPEAKER,1,random(0,i*2));
      }
      delay(1000);
      return false;   //Go to sleep
    }
    else if (ballx<player+BAT_WIDTH/2&&bally>10&&bally+vdir>=54)
    { // if the ball hits left of the platform bounce left
      hdir=-1; ssd1306_beep(SPEAKER,20,600);
    }
    else if (ballx>player+BAT_WIDTH/2&&bally>10&&bally+vdir>=54)
    {  // if the ball hits right of the platform bounce right
      hdir=1; ssd1306_beep(SPEAKER,20,600);
    }
    else if (bally+vdir>=54)
    {
      hdir=1; ssd1306_beep(SPEAKER,20,600);
    }
    
    collisionCheck: // go back to here if a collision was detected to prevent flying through a rigid
    if (floor((bally+vdir)/8)==2){
      if (row3[ballx/8]==1){row3[ballx/8]=0; score++;
        collision(); goto collisionCheck; // check collision for the new direction to prevent flying through a rigid
      }
      }else if (floor((bally+vdir)/8)==1){
      if (row2[ballx/8]==1){row2[ballx/8]=0; score++;
        collision(); goto collisionCheck;
      }
      }else if (floor((bally+vdir)/8)==0){
      if (row1[ballx/8]==1){row1[ballx/8]=0; score++;
        collision(); goto collisionCheck;
      }
    }
    
    // reset blocks if all have been hit
    if (score%48==0){
      for (byte i =0; i<16;i++){
        row1[i]=1; row2[i]=1; row3[i]=1;
      }
      if (speedDelay > 20)
      {
        speedDelay -= INCREASE_SPEED_DELTA;
      }
    }
  }
  
  
  // update whats on the screen
  noInterrupts();
  
  // blocks
  ssd1306_setpos(0,0);
  ssd1306_send_data_start();
  for (int bl = 0; bl <16; bl++){
    if(row1[bl]==1){
      sendBlock(1);
      }else {
      sendBlock(0);
    }
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,1);
  ssd1306_send_data_start();
  for (int bl = 0; bl <16; bl++){
    if(row2[bl]==1){
      sendBlock(1);
      }else {
      sendBlock(0);
    }
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,2);
  ssd1306_send_data_start();
  for (int bl = 0; bl <16; bl++){
    if(row3[bl]==1){
      sendBlock(1);
      }else {
      sendBlock(0);
    }
  }
  ssd1306_send_data_stop();
  
  // clear area below the blocks
  ssd1306_setpos(0,3);
  ssd1306_send_data_start();
  for (byte i =0; i<128; i++){
    ssd1306_send_byte(B00000000);
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,4);
  ssd1306_send_data_start();
  for (byte i =0; i<128; i++){
    ssd1306_send_byte(B00000000);
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,5);
  ssd1306_send_data_start();
  for (byte i =0; i<128; i++){
    ssd1306_send_byte(B00000000);
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,6);
  ssd1306_send_data_start();
  for (byte i =0; i<128; i++){
    ssd1306_send_byte(B00000000);
  }
  ssd1306_send_data_stop();
  ssd1306_setpos(0,7);
  ssd1306_send_data_start();
  for (byte i =0; i<128; i++){
    ssd1306_send_byte(B00000000);
  }
  ssd1306_send_data_stop();
  
  drawBall();
  drawBat();
  interrupts();
  delay(speedDelay);
  return true;
}

//the collsision check is actually done befor this is called, this code works out where the ball will bounce
void collision()
{ 
  if ((bally+vdir)%8==7&&(ballx+hdir)%8==7)
  { // bottom right corner
    if (vdir==1){hdir=1;}else if(vdir==-1&&hdir==1){vdir=1;}else {hdir=1;vdir=1;}
  }
  else if ((bally+vdir)%8==7&&(ballx+hdir)%8==0)
  { // bottom left corner
    if (vdir==1){hdir=-1;}else if(vdir==-1&&hdir==-1){vdir=1;}else {hdir=-1;vdir=1;}
  }
  else if ((bally+vdir)%8==0&&(ballx+hdir)%8==0)
  { // top left corner
    if (vdir==-1){hdir=-1;}else if(vdir==1&&hdir==-1){vdir=-1;}else {hdir=-1;vdir=-1;}
  }
  else if ((bally+vdir)%8==0&&(ballx+hdir)%8==7)
  { // top right corner
    if (vdir==-1){hdir=1;}else if(vdir==1&&hdir==1){vdir=-1;}else {hdir=1;vdir=-1;}
  }
  else if ((bally+vdir)%8==7)
  { // bottom side
    vdir = 1;
  }
  else if ((bally+vdir)%8==0)
  { // top side
    vdir = -1;
  }
  else if ((ballx+hdir)%8==7)
  { // right side
    hdir = 1;
  }else if ((ballx+hdir)%8==0)
  { // left side
    hdir = -1;
  }else 
  {
    hdir = hdir*-1; vdir = vdir*-1;
  }
  ssd1306_beep(SPEAKER,30,300);
}

void drawBall()
{
  //X = 0..127, Y = 0..7
  noInterrupts();
  switch (bally & 0x07)
  {
    case 6:
      ssd1306_setpos(ballx, bally >> 3);
      ssd1306_send_data_start();
      ssd1306_send_byte(0x80);
      ssd1306_send_data_stop();
      ssd1306_setpos(ballx, (bally >> 3) + 1);
      ssd1306_send_data_start();
      ssd1306_send_byte(0x01);
      ssd1306_send_data_stop();
      break;

    case 7:
      ssd1306_setpos(ballx, (bally >> 3) + 1);
      ssd1306_send_data_start();
      ssd1306_send_byte(0x01);
      ssd1306_send_byte(0x02);
      ssd1306_send_data_stop();
      break;

    default:
      ssd1306_setpos(ballx, bally >> 3);
      ssd1306_send_data_start();
      uint8_t t3 = 3 << (bally & 0x07);
      ssd1306_send_byte(t3);
      ssd1306_send_byte(t3);
      ssd1306_send_data_stop();
      break;
  }

  interrupts();
}

void drawBat()
{
  noInterrupts();
  ssd1306_setpos(player,7);
  ssd1306_send_data_start();
  for (byte pw = 1; pw < BAT_WIDTH; pw++)
  {
    ssd1306_send_byte(B00000011);
  }
  ssd1306_send_data_stop();
  interrupts();
}

void sendBlock(boolean fill)
{
  if (fill==1)
  {
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B01111110);
    ssd1306_send_byte(B00000000);
  }
  else 
  {
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
    ssd1306_send_byte(B00000000);
  }
}
}

Buttons.h

C/C++
/*
 * Button interrupt handler
 */

#pragma once

#define LEFT_BTN 0   //PA4 D0
#define RIGHT_BTN 10 //PA3 D10
#define SPEAKER 1    //PA5 D1

uint16_t leftDebounceTimeout = 0;
uint16_t rightDebounceTimeout = 0;
bool leftButtonPressed = false;
bool rightButtonPressed = false;

// PA4 pin button interrupt
void leftButtonInterrupt()
{
  if (digitalRead(LEFT_BTN) == LOW)
  {
    leftDebounceTimeout = millis() + 10;
  }
  else if (leftDebounceTimeout > 0 && millis() > leftDebounceTimeout)
  {
    leftDebounceTimeout = 0;
    leftButtonPressed = true;
  }
}

// PA3 pin button interrupt
void rightButtonInterrupt()
{
  if (digitalRead(RIGHT_BTN) == LOW)
  {
    rightDebounceTimeout = millis() + 10;
  }
  else if (rightDebounceTimeout > 0 && millis() > rightDebounceTimeout)
  {
    rightDebounceTimeout = 0;
    rightButtonPressed = true;
  }
}

void playInitialSound()
{
  ssd1306_beep(SPEAKER,200,600);
  ssd1306_beep(SPEAKER,300,200);
  ssd1306_beep(SPEAKER,400,300);
}

void buttonSetup() 
{
  pinMode(LEFT_BTN, INPUT_PULLUP);
  pinMode(RIGHT_BTN, INPUT_PULLUP);
  pinMode(SPEAKER, OUTPUT);

  //LEFT button Interrupt will wake up from sleep mode
  attachInterrupt(digitalPinToInterrupt(LEFT_BTN),leftButtonInterrupt,CHANGE);
  attachInterrupt(digitalPinToInterrupt(RIGHT_BTN),rightButtonInterrupt,CHANGE);
}

font6x8.h

C/C++
/*
 * SSD1306xLED - Drivers for SSD1306 controlled dot matrix OLED/PLED 128x64 displays
 *
 * @created: 2014-08-12
 * @author: Neven Boyanov
 *
 * Source code available at: https://bitbucket.org/tinusaur/ssd1306xled
 *
 */
#pragma once
// ----------------------------------------------------------------------------

#include <avr/pgmspace.h>

// ----------------------------------------------------------------------------

/* Standard ASCII 6x8 font */
const uint8_t ssd1306xled_font6x8 [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sp
  0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, // !
  0x00, 0x00, 0x07, 0x00, 0x07, 0x00, // "
  0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14, // #
  0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12, // $
  0x00, 0x62, 0x64, 0x08, 0x13, 0x23, // %
  0x00, 0x36, 0x49, 0x55, 0x22, 0x50, // &
  0x00, 0x00, 0x05, 0x03, 0x00, 0x00, // '
  0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, // (
  0x00, 0x00, 0x41, 0x22, 0x1c, 0x00, // )
  0x00, 0x14, 0x08, 0x3E, 0x08, 0x14, // *
  0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, // +
  0x00, 0x00, 0x00, 0xA0, 0x60, 0x00, // ,
  0x00, 0x08, 0x08, 0x08, 0x08, 0x08, // -
  0x00, 0x00, 0x60, 0x60, 0x00, 0x00, // .
  0x00, 0x20, 0x10, 0x08, 0x04, 0x02, // /
  0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
  0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, // 1
  0x00, 0x42, 0x61, 0x51, 0x49, 0x46, // 2
  0x00, 0x21, 0x41, 0x45, 0x4B, 0x31, // 3
  0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, // 4
  0x00, 0x27, 0x45, 0x45, 0x45, 0x39, // 5
  0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
  0x00, 0x01, 0x71, 0x09, 0x05, 0x03, // 7
  0x00, 0x36, 0x49, 0x49, 0x49, 0x36, // 8
  0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, // 9
  0x00, 0x00, 0x36, 0x36, 0x00, 0x00, // :
  0x00, 0x00, 0x56, 0x36, 0x00, 0x00, // ;
  0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // <
  0x00, 0x14, 0x14, 0x14, 0x14, 0x14, // =
  0x00, 0x00, 0x41, 0x22, 0x14, 0x08, // >
  0x00, 0x02, 0x01, 0x51, 0x09, 0x06, // ?
  0x00, 0x32, 0x49, 0x59, 0x51, 0x3E, // @
  0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C, // A
  0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, // B
  0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, // C
  0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, // D
  0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, // E
  0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, // F
  0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A, // G
  0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, // H
  0x00, 0x00, 0x41, 0x7F, 0x41, 0x00, // I
  0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, // J
  0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, // K
  0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, // L
  0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, // M
  0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, // N
  0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, // O
  0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, // P
  0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
  0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, // R
  0x00, 0x46, 0x49, 0x49, 0x49, 0x31, // S
  0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, // T
  0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, // U
  0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, // V
  0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, // W
  0x00, 0x63, 0x14, 0x08, 0x14, 0x63, // X
  0x00, 0x07, 0x08, 0x70, 0x08, 0x07, // Y
  0x00, 0x61, 0x51, 0x49, 0x45, 0x43, // Z
  0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [
  0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55, // 55
  0x00, 0x00, 0x41, 0x41, 0x7F, 0x00, // ]
  0x00, 0x04, 0x02, 0x01, 0x02, 0x04, // ^
  0x00, 0x40, 0x40, 0x40, 0x40, 0x40, // _
  0x00, 0x00, 0x01, 0x02, 0x04, 0x00, // '
  0x00, 0x20, 0x54, 0x54, 0x54, 0x78, // a
  0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, // b
  0x00, 0x38, 0x44, 0x44, 0x44, 0x20, // c
  0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, // d
  0x00, 0x38, 0x54, 0x54, 0x54, 0x18, // e
  0x00, 0x08, 0x7E, 0x09, 0x01, 0x02, // f
  0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, // g
  0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, // h
  0x00, 0x00, 0x44, 0x7D, 0x40, 0x00, // i
  0x00, 0x40, 0x80, 0x84, 0x7D, 0x00, // j
  0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k
  0x00, 0x00, 0x41, 0x7F, 0x40, 0x00, // l
  0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, // m
  0x00, 0x7C, 0x08, 0x04, 0x04, 0x78, // n
  0x00, 0x38, 0x44, 0x44, 0x44, 0x38, // o
  0x00, 0xFC, 0x24, 0x24, 0x24, 0x18, // p
  0x00, 0x18, 0x24, 0x24, 0x18, 0xFC, // q
  0x00, 0x7C, 0x08, 0x04, 0x04, 0x08, // r
  0x00, 0x48, 0x54, 0x54, 0x54, 0x20, // s
  0x00, 0x04, 0x3F, 0x44, 0x40, 0x20, // t
  0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C, // u
  0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, // v
  0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, // w
  0x00, 0x44, 0x28, 0x10, 0x28, 0x44, // x
  0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C, // y
  0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, // z
  0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // horiz lines
};

// ----------------------------------------------------------------------------

Snake1614.h

C/C++
/* 2015 - Ilya Titov
*         Initial Oroboros game
*  2020 - jbrad2089@gmail.com
*         Modified SSD1306 library, interrupt handlers and sleep routines to run on ATtiny1614.
*         Rewrote snake game - replaced fixed array with circular queue
*         Add hit wall detection
*
*  This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
*  Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
*
*/
#pragma once

#include <EEPROM.h>
#include "font6x8.h"
#include "SSD1306_ATtiny1614.h"
#include "Buttons.h"

#define EEPROM_SNAKE_SCORE_LOW 4
#define EEPROM_SNAKE_SCORE_HIGH 5

namespace snake {
  
enum PLAYER_MOVE {PLAYER_NONE, PLAYER_LEFT, PLAYER_RIGHT};
enum SNAKE_MOVE {SNAKE_LEFT, SNAKE_RIGHT, SNAKE_UP, SNAKE_DOWN };
struct POS
{
	uint8_t x;
	uint8_t y;
};

#define START_SPEED 300
#define SNAKE_START_LENGTH 4
#define SNAKE_START_X 14
#define SNAKE_START_Y 7
#define MAX_SNAKE_LENGTH 100
POS snake[MAX_SNAKE_LENGTH];
uint8_t snakeHead = 0;
uint8_t snakeTail = 0;
uint8_t snakeLength = 0;
SNAKE_MOVE snakeDirection = SNAKE_RIGHT;
PLAYER_MOVE playerMove = PLAYER_NONE;

POS apple;
bool moveApple = true;
bool hitWall = false;
bool hitSelf = false;

unsigned long lastFrame = 0;
int frameDelay = START_SPEED;
unsigned long screenBuffer[16];

int score = 0; // score - this affects the difficulty of the game

//-------------- forward references -------------------------------------

void setup();
bool loop(); 
void updateApplePositionAndAddToScreenBuffer(bool moveApple);
bool addSnakeToScreenBuffer();
void renderScreen();

//-------------------- functions ------------------------------------------

//Setup game
void setup()
{
  //Initialise snake
  snakeLength = SNAKE_START_LENGTH;
  snakeHead = 0;
  snakeTail = 0;
  for (int x = 0; x < snakeLength; x++)
  {
    snake[snakeHead].y = SNAKE_START_Y;
    snake[snakeHead].x = SNAKE_START_X + x;
    snakeHead++;
  }
  snakeHead--;
  snakeDirection = SNAKE_RIGHT;
  playerMove = PLAYER_NONE;
  moveApple = true;
  score = 0;
  frameDelay = START_SPEED;
  hitWall = false;
  hitSelf = false;
  
  ssd1306_fillscreen(0x00);

  lastFrame = millis();
}

//Main loop, returns false to go to sleep
bool loop() 
{
  if (leftButtonPressed)
  {
    leftButtonPressed = false;
    playerMove = PLAYER_LEFT;
  }
  if (rightButtonPressed)
  {
    rightButtonPressed = false;
    playerMove = PLAYER_RIGHT;
  }
  
  if ((millis()>lastFrame+frameDelay || playerMove != PLAYER_NONE) && !hitWall && !hitSelf)
  {
    lastFrame = millis();
    if (playerMove != PLAYER_NONE)
    {
      ssd1306_beep(SPEAKER,50,200);
      if (score<100) 
      {
        frameDelay  = START_SPEED - score*3 ;
      }
    }

  	//Handle and change in movment by player
  	switch (playerMove)
  	{
      case PLAYER_LEFT:
        switch (snakeDirection)
        {
          case SNAKE_LEFT: snakeDirection = SNAKE_DOWN; break;
          case SNAKE_RIGHT: snakeDirection = SNAKE_UP; break;
          case SNAKE_UP: snakeDirection = SNAKE_LEFT; break;
          case SNAKE_DOWN: snakeDirection = SNAKE_RIGHT; break;
        }
        break;
        
      case PLAYER_RIGHT:
        switch (snakeDirection)
        {
          case SNAKE_LEFT: snakeDirection = SNAKE_UP; break;
          case SNAKE_RIGHT: snakeDirection = SNAKE_DOWN; break;
          case SNAKE_UP: snakeDirection = SNAKE_RIGHT; break;
          case SNAKE_DOWN: snakeDirection = SNAKE_LEFT; break;
        }
        break;
  	}
    playerMove = PLAYER_NONE;

    //Move head of snake in its current or new direction
    uint8_t x = snake[snakeHead].x;
    uint8_t y = snake[snakeHead].y;
    bool hitWall = false;
    switch (snakeDirection)
    {
      case SNAKE_LEFT: if (x > 1) { x--; } else { hitWall = true; }; break;
      case SNAKE_RIGHT: if (x < 31) { x++; } else { hitWall = true; }; break;
      case SNAKE_UP: if (y > 0) { y--; } else { hitWall = true; }; break;
      case SNAKE_DOWN: if (y < 15) { y++; } else { hitWall = true; }; break;
    }
    //Move snake forward. Add the new square co-ordinates and remove the tail co-ordinates.
    //This is a circular queue (no shifting of co-ordinates required)
    snakeHead++;
    if (snakeHead == MAX_SNAKE_LENGTH)
    {
      snakeHead = 0;
    }
    snake[snakeHead].x = x;
    snake[snakeHead].y = y;
    snakeTail++;
    if (snakeTail == MAX_SNAKE_LENGTH)
    {
      snakeTail = 0;
    }
      
    // CLEAR SCREEN BUFFER
    for (int i = 0; i<16; i++)
    {
      screenBuffer[i] = 0;
    }
      
    // Drop apple
    updateApplePositionAndAddToScreenBuffer(moveApple);
    moveApple = false;

    //Test if snake hit apple
    if (snake[snakeHead].x == apple.x && snake[snakeHead].y == apple.y)
    {
      ssd1306_beep(SPEAKER,150,100);
      delay(50);
      ssd1306_beep(SPEAKER,50,150);
      delay(50);
      ssd1306_beep(SPEAKER,50,150);
      delay(50);
      ssd1306_beep(SPEAKER,100,150);
      delay(50);

      //Increase length of snake
      if ((snakeLength + 1) < MAX_SNAKE_LENGTH)
      {
        snakeLength++;
        uint8_t x = snake[snakeHead].x;
        uint8_t y = snake[snakeHead].y;
        switch (snakeDirection)
        {
          case SNAKE_LEFT: x--; break;
          case SNAKE_RIGHT: x++; break;
          case SNAKE_UP: y--; break;
          case SNAKE_DOWN: y++; break;
        }
        snakeHead++;
        if (snakeHead == MAX_SNAKE_LENGTH)
        {
          snakeHead = 0;
        }
        snake[snakeHead].x = x;
        snake[snakeHead].y = y;
      }
      score++;
      moveApple = true;
    }

    //Add snake and test for self collision
    hitSelf = addSnakeToScreenBuffer();
    renderScreen();
  }  
    
    
  // display score
  if (hitWall || hitSelf)
  {
    int topscore = EEPROM.read(EEPROM_SNAKE_SCORE_HIGH);
    topscore = topscore << 8;
    topscore = topscore |  EEPROM.read(EEPROM_SNAKE_SCORE_LOW);
    if (score>topscore)
    {
      topscore = score; 
      EEPROM.write(EEPROM_SNAKE_SCORE_LOW,topscore & 0xFF); 
      EEPROM.write(EEPROM_SNAKE_SCORE_HIGH,(topscore>>8) & 0xFF); 
    }

    ssd1306_char_f6x8(32, 3, "Game Over");
    ssd1306_char_f6x8(32, 5, "score:");
    char temp[10] = {0,0,0,0,0,0,0,0,0,0};
    itoa(score,temp,10);
    ssd1306_char_f6x8(70, 5, temp);
    ssd1306_char_f6x8(32, 6, "top score:");
    itoa(topscore,temp,10);
    ssd1306_char_f6x8(90, 6, temp);
    for (int i = 0; i<1000; i++)
    {
      ssd1306_beep(SPEAKER,1,random(0,i*2));
    }
    delay(3000);
    return false;
  }
  return true;
}

//Find a new place for the apple and add it to the screen buffer
void updateApplePositionAndAddToScreenBuffer(bool moveApple)
{
  bool appleMoved = !moveApple;
  while (!appleMoved)
  {
    appleMoved = true;
    apple.x = random(2,29);
    apple.y = random(2,13);
    int t = snakeTail;
    for (int i = 0; i < snakeLength; i++)
    {
      if (snake[t].x == apple.x && snake[t].y == apple.y)
      {
        appleMoved = false;
        break;
      }
      t++;
      if (t == MAX_SNAKE_LENGTH)
      {
        t = 0;
      }
    }
  }
  screenBuffer[apple.y] = screenBuffer[apple.y] | ((0UL | B00000001) << (31-apple.x));
}

//Add snake to screen buffer and test for self collision
bool addSnakeToScreenBuffer()
{
  bool collision = false;
  int t = snakeTail;
  for (int i = 0; i < snakeLength; i++)
  {
    //Add to screen buffer
    screenBuffer[snake[t].y] = screenBuffer[snake[t].y] | ((0UL | B00000001) << (31-snake[t].x));
    if (t != snakeHead && snake[t].x == snake[snakeHead].x && snake[t].y == snake[snakeHead].y)
    {
      collision = true;
    }
    t++;
    if (t == MAX_SNAKE_LENGTH)
    {
      t = 0;
    }
  }
  return collision;
}

// RENDER THE 32x16 GAME GRID
void renderScreen()
{
  noInterrupts();
  for (byte r = 0; r<8; r++)
  {
    ssd1306_setpos(0,r);
    ssd1306_send_data_start();
    for (byte col = 1; col<=31; col++)
    {
      for (byte box = 0; box < 4; box++)
      {
        ssd1306_send_byte( // draw screen buffer data and screen boundaries
        // first byte is top //   && dir == 0 ? B00001000
        (screenBuffer[r*2]>>(31-col)   & B00000001 ? ((box==1||box==2)&&apple.x==col&&apple.y==r*2&&(!moveApple)?B00001001:B00001111):B00000000)
        |
        (screenBuffer[r*2+1]>>(31-col) & B00000001 ? ((box==1||box==2)&&apple.x==col&&apple.y==r*2+1&&(!moveApple)==1?B10010000:B11110000):B00000000)
        |
        (r==0?B00000001:B00000000)
        |
        (r==7?B10000000:B00000000)
        |
        ((col == 1 && box == 0)||(col == 31 && box == 3)?B11111111:B00000000)
        );
      }
    }
    ssd1306_send_data_stop();
  }
  interrupts();
}

}

SSD1306_ATtiny1614.h

C/C++
/*--------------------------------------------------------
 * I2C SSD1306 128x64 OLED support routines for ATtiny1614
 * 
 * @created: 2020-05-12
 * @author: John Bradnam
 *
 * Base on ATTiny85 source code available at: https://bitbucket.org/tinusaur/ssd1306xled
 */

#ifndef SSD1306_ATtiny1614_h
#define SSD1306_ATtiny1614_h

#include <avr/sleep.h>

//---------------------- ATtiny1614 ----------------------

#define SSD1306_SCL   PIN0_bm   // ----> [SCL]  Pin 3 on the SSD1306 display board
#define SSD1306_SDA   PIN1_bm   // ----> [SDA]  Pin 4 on the SSD1306 display board
#define SSD1306_SA    0x78  // Slave address

#define DIGITAL_WRITE_HIGH(PORT) PORTB.OUTSET = PORT
#define DIGITAL_WRITE_LOW(PORT) PORTB.OUTCLR = PORT

//----------------- Forward references -------------------

void ssd1306_init(void);
void ssd1306_xfer_start(void);
void ssd1306_xfer_stop(void);
void ssd1306_send_byte(uint8_t byte);
void ssd1306_send_command(uint8_t command);
void ssd1306_send_data_start(void);
void ssd1306_send_data_stop(void);
void ssd1306_setpos(uint8_t x, uint8_t y);
void ssd1306_fillscreen(uint8_t fill_Data);
void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]);
void ssd1306_char_font6x8(char ch) ;
void ssd1306_string_font6x8(char *s); 
void ssd1306_char_f8x16(uint8_t x, uint8_t y, const char ch[]);
void ssd1306_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[]);
//Extra routines commonly used in ATtiny Arcade Games
void ssd1306_beep(uint8_t pin, int bCount, int bDelay);
void ssd1306_sleep();

//----------------- Functions -------------------

// Some code based on "IIC_wtihout_ACK" by http://www.14blog.com/archives/1358

const uint8_t ssd1306_init_sequence [] PROGMEM = {  // Initialization Sequence
  0xAE,     // Display OFF (sleep mode)
  0x20, 0b00,   // Set Memory Addressing Mode
          // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
          // 10=Page Addressing Mode (RESET); 11=Invalid
  0xB0,     // Set Page Start Address for Page Addressing Mode, 0-7
  0xC8,     // Set COM Output Scan Direction
  0x00,     // ---set low column address
  0x10,     // ---set high column address
  0x40,     // --set start line address
  0x81, 0x3F,   // Set contrast control register
  0xA1,     // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. 
  0xA6,     // Set display mode. A6=Normal; A7=Inverse
  0xA8, 0x3F,   // Set multiplex ratio(1 to 64)
  0xA4,     // Output RAM to Display
          // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
  0xD3, 0x00,   // Set display offset. 00 = no offset
  0xD5,     // --set display clock divide ratio/oscillator frequency
  0xF0,     // --set divide ratio
  0xD9, 0x22,   // Set pre-charge period
  0xDA, 0x12,   // Set com pins hardware configuration    
  0xDB,     // --set vcomh
  0x20,     // 0x20,0.77xVcc
  0x8D, 0x14,   // Set DC-DC enable
  0xAF      // Display ON in normal mode
  
};

void ssd1306_init(void)
{
  PORTB.DIRSET = SSD1306_SDA; // Set port as output
  PORTB.DIRSET = SSD1306_SCL; // Set port as output
  
  for (uint8_t i = 0; i < sizeof (ssd1306_init_sequence); i++) {
    ssd1306_send_command(pgm_read_byte(&ssd1306_init_sequence[i]));
  }
}

void ssd1306_xfer_start(void)
{
  DIGITAL_WRITE_HIGH(SSD1306_SCL);  // Set to HIGH
  DIGITAL_WRITE_HIGH(SSD1306_SDA);  // Set to HIGH
  DIGITAL_WRITE_LOW(SSD1306_SDA);   // Set to LOW
  DIGITAL_WRITE_LOW(SSD1306_SCL);   // Set to LOW
}

void ssd1306_xfer_stop(void)
{
  DIGITAL_WRITE_LOW(SSD1306_SCL);   // Set to LOW
  DIGITAL_WRITE_LOW(SSD1306_SDA);   // Set to LOW
  DIGITAL_WRITE_HIGH(SSD1306_SCL);  // Set to HIGH
  DIGITAL_WRITE_HIGH(SSD1306_SDA);  // Set to HIGH
}

void ssd1306_send_byte(uint8_t byte)
{
  uint8_t i;
  for(i=0; i<8; i++)
  {
    if((byte << i) & 0x80)
      DIGITAL_WRITE_HIGH(SSD1306_SDA);
    else
      DIGITAL_WRITE_LOW(SSD1306_SDA);
    
    DIGITAL_WRITE_HIGH(SSD1306_SCL);
    DIGITAL_WRITE_LOW(SSD1306_SCL);
  }
  DIGITAL_WRITE_HIGH(SSD1306_SDA);
  DIGITAL_WRITE_HIGH(SSD1306_SCL);
  DIGITAL_WRITE_LOW(SSD1306_SCL);
}

void ssd1306_send_command(uint8_t command)
{
  ssd1306_xfer_start();
  ssd1306_send_byte(SSD1306_SA);  // Slave address, SA0=0
  ssd1306_send_byte(0x00);  // write command
  ssd1306_send_byte(command);
  ssd1306_xfer_stop();
}

void ssd1306_send_data_start(void)
{
  ssd1306_xfer_start();
  ssd1306_send_byte(SSD1306_SA);
  ssd1306_send_byte(0x40);  //write data
}

void ssd1306_send_data_stop(void)
{
  ssd1306_xfer_stop();
}

void ssd1306_setpos(uint8_t x, uint8_t y)
{
  //X = 0..127, Y = 0..7
  ssd1306_xfer_start();
  ssd1306_send_byte(SSD1306_SA);  //Slave address,SA0=0
  ssd1306_send_byte(0x00);  //write command

  ssd1306_send_byte(0xb0+y);             //Set Page Start Address for Page Addressing Mode 10110xxx
  ssd1306_send_byte(((x&0xf0)>>4)|0x10); //Set Lower Column Start Address for Page Addressing Mode 0000xxxx
  ssd1306_send_byte((x&0x0f)|0x01);      //Set Higher Column Start Address for Page Addressing Mode 0001xxxx

  ssd1306_xfer_stop();
}

void ssd1306_fillscreen(uint8_t fill_Data)
{
  uint8_t m,n;
  for(m=0;m<8;m++)
  {
    ssd1306_send_command(0xb0+m);  //Set Page Start Address for Page Addressing Mode 10110xxx
    ssd1306_send_command(0x00);    //Set Lower Column Start Address for Page Addressing Mode 0000xxxx
    ssd1306_send_command(0x10);    //Set Higher Column Start Address for Page Addressing Mode 0001xxxx
    ssd1306_send_data_start();
    for(n=0;n<128;n++)
    {
      ssd1306_send_byte(fill_Data);
    }
    ssd1306_send_data_stop();
  }
}

void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[])
{
  uint8_t c,i,j=0;
  while(ch[j] != '\0')
  {
    c = ch[j] - 32;
    /*
    if (c >0) c = c - 12;
    if (c >15) c = c - 6;
    if (c>40) c=c-9;
    */
    if(x>126)
    {
      x=0;
      y++;
    }
    ssd1306_setpos(x,y);
    ssd1306_send_data_start();
    for(i=0;i<6;i++)
    {
      ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c*6+i]));
    }
    ssd1306_send_data_stop();
    x += 6;
    j++;
  }
}

void ssd1306_char_font6x8(char ch) 
{
  uint8_t i; 
  uint8_t c = ch - 32;
  ssd1306_send_data_start();
  for (i= 0; i < 6; i++)
  {
    ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c * 6 + i]));
  }
  ssd1306_send_data_stop();
}

void ssd1306_string_font6x8(char *s) 
{
  while (*s) 
  {
    ssd1306_char_font6x8(*s++);
  }
}

#ifdef ssd1306xled_font8x16

void ssd1306_char_f8x16(uint8_t x, uint8_t y, const char ch[])
{
  uint8_t c, j, i = 0;
  while (ch[j] != '\0')
  {
    c = ch[j] - 32;
    if (x > 120)
    {
      x = 0;
      y++;
    }
    ssd1306_setpos(x, y);
    ssd1306_send_data_start();
    for (i = 0; i < 8; i++)
    {
      ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font8x16[c * 16 + i]));
    }
    ssd1306_send_data_stop();
    ssd1306_setpos(x, y + 1);
    ssd1306_send_data_start();
    for (i = 0; i < 8; i++)
    {
      ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font8x16[c * 16 + i + 8]));
    }
    ssd1306_send_data_stop();
    x += 8;
    j++;
  }
}

#endif

void ssd1306_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t bitmap[])
{
  uint16_t j = 0;
  uint8_t y, x;
  if (y1 % 8 == 0) y = y1 / 8;
  else y = y1 / 8 + 1;
  for (y = y0; y < y1; y++)
  {
    ssd1306_setpos(x0,y);
    ssd1306_send_data_start();
    for (x = x0; x < x1; x++)
    {
      ssd1306_send_byte(pgm_read_byte(&bitmap[j++]));
    }
    ssd1306_send_data_stop();
  }
}

//--------------------- Extra -------------------------------

//Simple tone generator
void ssd1306_beep(uint8_t pin, int bCount, int bDelay)
{
  for (int i = 0; i<=bCount; i++) 
  {
    digitalWrite(pin,HIGH);
    for(int i2=0; i2<bDelay; i2++) 
    {
      __asm__("nop\n\t");
    }
    digitalWrite(pin,LOW);
    for(int i2=0; i2<bDelay; i2++)
    {
      __asm__("nop\n\t");
    }
  }
}

//Shut down OLED and put ATtiny to sleep
//Will wake up when LEFT button is pressed
void ssd1306_sleep() 
{
  ssd1306_fillscreen(0x00);
  ssd1306_send_command(0xAE);
  //cbi(ADCSRA,ADEN);                   // switch Analog to Digitalconverter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
  sleep_enable();
  sleep_mode();                         // System actually sleeps here
  sleep_disable();                      // System continues execution here when watchdog timed out
  //sbi(ADCSRA,ADEN);                   // switch Analog to Digitalconverter ON
  ssd1306_send_command(0xAF);
}
#endif

UFO1614.h

C/C++
/* 2015 - Ilya Titov
*         Initial UFO Escape game
*  2020 - jbrad2089@gmail.com
*         Modified SSD1306 library, interrupt handlers and sleep routines to run on ATtiny1614.
*
*  This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
*  Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
*
*/
#pragma once

#include <EEPROM.h>
#include "font6x8.h"
#include "SSD1306_ATtiny1614.h"
#include "Buttons.h"

#define EEPROM_UFO_SCORE_LOW 6
#define EEPROM_UFO_SCORE_HIGH 7

namespace ufo {
  
boolean stopAnimate = 0; // this is set to 1 when a collision is detected
byte maxObstacles = 1; // this defines the max number of in game obstacles
byte obstacleStep = 2; // pixel step of obstacles per frame
int obstacle[9] = {-50,-50,-50,-50,-50,-50,-50,-50,-50}; // x offset of the obstacle default position, out of view
byte gapOffset[9] = {0,0,0,0,0,0,0,0,0}; // y offset of the fly-through gap
int gapSize[9]; // y height of the gap
byte maxGap = 60; // max height of the gap
int stepsSinceLastObstacle = 0; // so obstacles are not too close
byte gapBlock[9] = {0,0,0,0,0,0,0,0,0}; // if the fly-through gap is closed
byte blockChance = 0; // this higher value decreases the likelihood of gap being closed
boolean fire = 0; // set to 1 when the fire interrupt is triggered
byte fireCount = 0; // the shot is persistent for several frames
byte playerOffset = 0; // y offset of the top of the player
byte flames = 0; // this is set to 1 when the move up interrupt is triggered

byte flameMask[2]={B00111111,B11111111}; // this is used to only show the flame part of the icon when moving up

int score = 0; // score - this affects the difficulty of the game

//-------------- forward references -------------------------------------

void setup();
bool loop(); 

//-------------------- functions ------------------------------------------

//Setup game
void setup()
{
  ssd1306_fillscreen(0x00);
  stopAnimate = 0;
  score = 0;

  maxObstacles = 3;
  obstacleStep = 2;
  for (byte i = 0; i<9; i++)
  {
    obstacle[i] = -50;
    gapOffset[i]=0;
  }
  stepsSinceLastObstacle = 0;
  playerOffset = 0; // y offset of the top of the player
}

//Main loop, returns false to go to sleep
bool loop()
{
  if (rightButtonPressed)
  {
    rightButtonPressed = false;
    fire = 1;
    fireCount = 5; // number of frames the shot will persist
  }
  
  //update game vars to make it harder to play
  if (score < 500)
  {
    blockChance = 11-score/50; 
    if (maxObstacles<5)
    {
      maxObstacles=(score+40)/70+1;
    } 
    delayMicroseconds(16000/maxObstacles);
  }

  if (score < 2000)
  {
    maxGap = 60-score/100;
  }
  if (fire == 1)
  {
    score--;
  }
  if (fireCount>0)
  {
    fireCount--;
  }

  int tests = 0;
  while (digitalRead(LEFT_BTN)==LOW && playerOffset >0 && stopAnimate==0 && tests < 3)
  {
    playerOffset--; 
    flames = 1; // move player up
    for (int i = 0; i<2; i++)
    {
      ssd1306_beep(SPEAKER,1,random(0,i*2));
    }
    tests++;
  }
  stepsSinceLastObstacle += obstacleStep;
  for (byte i = 0; i<maxObstacles;i++)
  { // fly obstacles
    if (obstacle[i] >= 0 && obstacle[i] <= 128 && stopAnimate==0)
    {
      obstacle[i] -= obstacleStep;
      if (gapBlock[i]>0 && obstacle[i] < 36  && playerOffset>gapOffset[i] && playerOffset+5<gapOffset[i]+gapSize[i] && fireCount > 0)
      {//
        gapBlock[i] = 0;
        score += 5;
        for (byte cp = 400; cp>0; cp--)
        {
          ssd1306_beep(SPEAKER,1,cp);
        }
      }
    }
    
    if (obstacle[i]<=4 && stepsSinceLastObstacle>=random(30,100))
    { // generate new obstacles
      obstacle[i] = 123;
      gapSize[i] = random(25,maxGap);
      gapOffset[i] = random(0,64-gapSize[i]);
      gapBlock[i] = (random(0,blockChance)==0) ? 1 : 0;
      stepsSinceLastObstacle = 0;
      score+=1;
    }
  }
  
  if (playerOffset < 56 && stopAnimate==0)
  {
    playerOffset++;
  } // player gravity
  

  for (byte r=0; r<8; r++)
  {
    if (r<playerOffset/8 | r >= playerOffset/8+1)
    {
      ssd1306_setpos(0,r);
      ssd1306_send_data_start();
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      ssd1306_send_byte(B00000000);
      
      ssd1306_send_data_stop();
    }
  }
  
  //erase fire
  ssd1306_setpos(16,playerOffset/8);
  ssd1306_send_data_start();
  if (fireCount == 0)
  {
    for (byte f = 0; f<=28; f++)
    {
      ssd1306_send_byte(B00000000);
    }
  }
  ssd1306_send_data_stop();
  
  
  // Send Obstacle
  for (byte i = 0; i<maxObstacles;i++)
  {
    if (obstacle[i] >= -5 && obstacle[i] <= 128)
    { // only deal with visible obstacles
      if (obstacle[i] > 8 && obstacle[i] <16)
      { // look for collision if obstacle is near the player
        if (playerOffset < gapOffset[i] || playerOffset+5 > gapOffset[i]+gapSize[i] || gapBlock[i] != 0)
        {
          // collision!
          stopAnimate = 1;
          // process collision after drawing explosion
        }
      }
      
      for (byte row = 0; row <8; row++)
      {
        
        ssd1306_setpos(obstacle[i],row);
        ssd1306_send_data_start();
        
        if (obstacle[i]>0&&obstacle[i] < 128)
        {
          
          if ((row+1)*8 - gapOffset[i] <= 8)
          { // generate obstacle : top and transition
            byte temp = B11111111>>((row+1)*8 - gapOffset[i]);
            byte tempB = B00000000;
            if (gapBlock[i]>0)
            {
              tempB=B10101010;
            }
            ssd1306_send_byte(temp);
            ssd1306_send_byte(temp|tempB>>1);
            ssd1306_send_byte(temp|tempB);
            ssd1306_send_byte(temp);
          }
          else if (row*8>=gapOffset[i] && (row+1)*8<=gapOffset[i]+gapSize[i])
          { // middle gap
            byte tempB = B00000000;
            if (gapBlock[i]>0)
            {
              tempB=B10101010;
            }
            ssd1306_send_byte(B00000000);
            ssd1306_send_byte(B00000000|tempB>>1);
            ssd1306_send_byte(B00000000|tempB);
            ssd1306_send_byte(B00000000);
          }
          else if ((gapOffset[i] +gapSize[i]) >= row*8 && (gapOffset[i] +gapSize[i]) <= (row+1)*8)
          { // bottom transition
            //}else if ((gapOffset[i] +gapSize[i]) >= row*8 && (gapOffset[i] +gapSize[i]) <= (row+1)*8){ // bottom transition
            //byte temp = B11111111<<((gapOffset[i] + gapSize[i])%8);
            byte temp = B11111111<<((gapOffset[i] + gapSize[i])%8);
            byte tempB = B00000000;
            if (gapBlock[i]>0)
            {
              tempB=B10101010;
            }
            ssd1306_send_byte(temp);
            ssd1306_send_byte(temp|tempB>>1);
            ssd1306_send_byte(temp|tempB);
            ssd1306_send_byte(temp);
            
          } 
          else 
          { // fill rest of obstacle
            ssd1306_send_byte(B11111111);
            ssd1306_send_byte(B11111111);
            ssd1306_send_byte(B11111111);
            ssd1306_send_byte(B11111111);
          }
          
          ssd1306_send_byte(B00000000);
          ssd1306_send_byte(B00000000);
          
          ssd1306_send_data_stop();
        }
      }
      
    }
  }
  
  

  if (playerOffset%8!=0)
  { // overflow the player icon into the next screen row if split
    ssd1306_setpos(8,playerOffset/8);
    ssd1306_send_data_start();
    if (stopAnimate==0)
    {
      ssd1306_send_byte((B00001100&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames])<<playerOffset%8);
      ssd1306_send_byte((B00001100&flameMask[flames])<<playerOffset%8);
      if (fireCount >0)
      {
        for (byte f = 0; f<=24; f++)
        {
          ssd1306_send_byte(B00000100<<playerOffset%8);
        }
        ssd1306_send_byte(B00010101<<playerOffset%8);
        ssd1306_send_byte(B00001010<<playerOffset%8);
        ssd1306_send_byte(B00010101<<playerOffset%8);
        if (fire==1){ssd1306_beep(SPEAKER,50,100);}
        fire = 0;
        
      }
    }
    else 
    {
      ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))<<playerOffset%8);
      ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))<<playerOffset%8);
    }
    
    ssd1306_send_data_stop();
    ssd1306_setpos(8,playerOffset/8+1);
    ssd1306_send_data_start();
    if (stopAnimate==0)
    {
      ssd1306_send_byte((B00001100&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames])>>8-playerOffset%8);
      ssd1306_send_byte((B00001100&flameMask[flames])>>8-playerOffset%8);
      if (fireCount >0)
      {
        for (byte f = 0; f<=24; f++)
        {
          ssd1306_send_byte(B00000100>>8-playerOffset%8);
        }
        ssd1306_send_byte(B00010101>>8-playerOffset%8);
        ssd1306_send_byte(B00001010>>8-playerOffset%8);
        ssd1306_send_byte(B00010101>>8-playerOffset%8);
        if (fire==1)
        {
          ssd1306_beep(SPEAKER,50,100);
        }
        fire = 0;
      }
    }
    else 
    {
      ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B01010011&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B10010111&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B01011110&flameMask[flames] | random(0,255))>>8-playerOffset%8);
      ssd1306_send_byte((B00001100&flameMask[flames] | random(0,255))>>8-playerOffset%8);
    }
    ssd1306_send_data_stop();
  }
  else 
  {
    ssd1306_setpos(8,playerOffset/8);
    ssd1306_send_data_start();
    if (stopAnimate == 0)
    {
      ssd1306_send_byte(B00001100&flameMask[flames]);
      ssd1306_send_byte(B01011110&flameMask[flames]);
      ssd1306_send_byte(B10010111&flameMask[flames]);
      ssd1306_send_byte(B01010011&flameMask[flames]);
      ssd1306_send_byte(B01010011&flameMask[flames]);
      ssd1306_send_byte(B10010111&flameMask[flames]);
      ssd1306_send_byte(B01011110&flameMask[flames]);
      ssd1306_send_byte(B00001100&flameMask[flames]);
      if (fireCount >0)
      {
        for (byte f = 0; f<=24; f++)
        {
          ssd1306_send_byte(B00000100);
        }
        ssd1306_send_byte(B00010101);
        ssd1306_send_byte(B00001010);
        ssd1306_send_byte(B00010101);
        if (fire==1)
        {
          ssd1306_beep(SPEAKER,50,100);
        }
        fire = 0;
      }
    }
    else 
    {
      ssd1306_send_byte(B00001100&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B01011110&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B10010111&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B01010011&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B01010011&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B10010111&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B01011110&flameMask[flames] | random(0,255));
      ssd1306_send_byte(B00001100&flameMask[flames] | random(0,255));
    }
    ssd1306_send_data_stop();
  }
  
  // display score
  if (stopAnimate==1)
  {
    int topScore = EEPROM.read(EEPROM_UFO_SCORE_HIGH);
    topScore = topScore << 8;
    topScore = topScore |  EEPROM.read(EEPROM_UFO_SCORE_LOW);
    if (score>topScore)
    {
      topScore = score; 
      EEPROM.write(EEPROM_UFO_SCORE_LOW,topScore & 0xFF); 
      EEPROM.write(EEPROM_UFO_SCORE_HIGH,(topScore>>8) & 0xFF); 
    }

    ssd1306_char_f6x8(32, 3, "Game Over");
    ssd1306_char_f6x8(32, 5, "score:");
    char temp[10] = {0,0,0,0,0,0,0,0,0,0};
    itoa(score,temp,10);
    ssd1306_char_f6x8(70, 5, temp);
    ssd1306_char_f6x8(32, 6, "top score:");
    itoa(topScore,temp,10);
    ssd1306_char_f6x8(90, 6, temp);
    for (int i = 0; i<1000; i++)
    {
      ssd1306_beep(SPEAKER,1,random(0,i*2));
    }
    interrupts();
    delay(3000);
    return false;
  }
  else
  {
    char temp[10] = {0,0,0,0,0,0,0,0,0,0};
    itoa(score,temp,10);
    ssd1306_char_f6x8(92, 0, temp);
    flames = 0;
  }
  return true;
}

}

Credits

John Bradnam

John Bradnam

141 projects • 167 followers
Thanks to Ilya Titov, Andy Jackson, and tinusaur.

Comments