Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
This is my version of the ATtiny Arcade keychain game kit from Webboggles. I replaced the ATtiny85 microprocessor with one of the new ATtiny1614 microprocessors. These have twice the Flash memory and Static RAM. This allowed me to have four games in my console instead of one.
Building your own consoleThe only real downside of the ATtiny1614 microprocessor is it is only available as a Surface Mount Device (SMD). This sort of means that a PCB is required. You could use an adapter board such as the one I made for the Using the New ATtiny Processors with Arduino IDE tutorial but being a 14 pin IC it wouldn't fit the form factor of an ATtiny85 Arcade console.
The Eagle files are included should you wish to get the printed circuit board commercially made or do as I did and make it yourself. I used the Toner method.
Solder the surface mount components first, followed by the link, speaker, pin headers and finally the two switches.
There is a 3-pin header that connects to the UPDI programmer. If you don't have one, they are easy to build see Create Your Own UPDI Programmer on the details. You also need to install the board support in the Arduino IDE. See Using the New ATtiny Processors with Arduino IDE for a step-by-step on what you need to do.
Printing the caseThe case can be 3D printed after passing the STL files attached through your slicer software. I used a 0.2mm layer height with no supports.
There are a number of other games available for the ATtiny85 Arcade Console. They are fairly easy to port to the ATtiny1614 as most of the changes are in the SSD1306 library. One thing to note is that on my design, the buttons are active LOW where as on the ATtiny85 Arcade Console, they are active HIGH.
/* 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;
}
/* 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);
}
}
/* 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);
}
}
}
/*
* 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);
}
/*
* 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
};
// ----------------------------------------------------------------------------
/* 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();
}
}
/*--------------------------------------------------------
* 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
/* 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;
}
}
Comments