/* 2015 / 2016 /2017
* Pong game by Andy Jackson - Twitter @andyhighnumber
* Inspired by http://webboggles.com/ and includes some code from the #AttinyArcade games on that site
* The code that does not fall under the licenses of sources listed below can be used non commercially with attribution.
*
* When the game is running :
* LEFT BUTTON - Controls the player's bat
* RIGHT BUTTON - Press and relese to change mode (difficulty) - Press and hold to toggle sound
*
* Also, from standby....
* Press and hold left button to reset the system
*
* If you have problems uploading this sketch, this is probably due to sketch size - you need to update ld.exe in arduino\hardware\tools\avr\avr\bin
* https://github.com/TCWORLD/ATTinyCore/tree/master/PCREL%20Patch%20for%20GCC
*
* 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
* **Note that this highly size-optimised version requires modified library functions (which are in this source code file)
* and a modified font header
*
* Sleep code is based on this blog post by Matthew Little:
* http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85
*/
#include <EEPROM.h>
#include <Wire.h>
#include "font6x8AJ.h"
#include "screen.h"
#define SPEAKER_PIN 13
#define STACKER_PIN 10
#define LEFT_PIN 10
#define RIGHT_PIN 11
#define FIRE_PIN 12
#define SSD1306_SA 0x3C // Slave address
#define WINSCORE 7
// Function prototypes
void startGame(void);
void drawPlatform(void);
void drawPlatform2(void);
void sendBlock(int);
void playPong(void);
void beep(int,int);
void drawBall(int x, int y);
void blankBall(int x, int y);
void doDrawLS(long, byte);
void doDrawRS(long, byte);
void doNumber (int x, int y, int value);
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_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t bitmap[]);
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 stopAnimate = 0; // this is set to 1 when a collision is detected
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 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 mode = 0;
int perturbation = 0;
int pFactor = 12;
// Interrupt handlers
void btnLeftClicked() { // PB0 pin button interrupt
}
void playerIncPong(){ // PB2 pin button interrupt
}
// Arduino stuff - setup
void setup() {
Serial.begin(115200);
pinMode(SPEAKER_PIN, OUTPUT);
pinMode(LEFT_PIN, INPUT_PULLUP);
pinMode(RIGHT_PIN, INPUT_PULLUP);
pinMode(FIRE_PIN, INPUT_PULLUP);
if(!SCREEN_Init(SSD1306_SA)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
} else {
Serial.println("OLED init ok!");
delay(2000);
SCREEN_Clear();
SCREEN_Display();
// while(1){ yield(); }
}
}
// Arduino stuff - loop
void loop() {
ssd1306_init();
ssd1306_fillscreen(0x00);
// The lower case character set is seriously compromised because I've had to truncate the ASCII table
// to release space for executable code - hence lower case y and w are remapped to h and / respectively.
// There is no z in the table (or h!) as these aren't used anywhere in the text here and most of the
// symbols are also missing for the same reason (see my hacked version of font6x8.h - font6x8AJ.h for more detail)
ssd1306_char_f6x8(0, 1, " --------------- ");
ssd1306_char_f6x8(0, 2, " B A T ");
ssd1306_char_f6x8(0, 4, " B O N A N Z A ");
ssd1306_char_f6x8(0, 5, " --------------- ");
ssd1306_char_f6x8(0, 7, " bh andh jackson "); // see comments above !
long startT = millis();
long nowT =0;
boolean sChange = 0;
while(digitalRead(LEFT_PIN) == LOW) {
nowT = millis();
if (nowT - startT > 2000) {
sChange = 1;
EEPROM.write(0,0);
EEPROM.write(1,1);
ssd1306_char_f6x8(16, 0, "- SYSTEM RESET -");
break;
}
if (sChange == 1) break;
}
while(digitalRead(LEFT_PIN) == LOW);
mute=EEPROM.read(0);
mode=EEPROM.read(1);
if (mute != 0 && mute != 1) {
mute = 0;
EEPROM.write(0,0);
}
if (mode != 1 && mode != 3 && mode != 4) {
mode = 1;
EEPROM.write(1,1);
}
if (sChange != 1) {
delay(1500);
ssd1306_init();
ssd1306_fillscreen(0x00);
stopAnimate = 0;
score = 0;
score2 = 0;
playPong();
delay(3500);
}
system_sleep();
}
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);
}
void ssd1306_init(void){
}
void ssd1306_xfer_start(void){
Wire.beginTransmission(SSD1306_SA);
}
void ssd1306_xfer_stop(void){
Wire.endTransmission();
}
void ssd1306_send_byte(uint8_t byte){
Wire.write(byte);
}
void ssd1306_send_command(uint8_t command){
ssd1306_xfer_start();
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(0x40); //write data
}
void ssd1306_send_data_stop(void){
ssd1306_xfer_stop();
}
void ssd1306_setpos(uint8_t x, uint8_t y)
{
if (y>7) return;
ssd1306_xfer_start();
ssd1306_send_byte(0x00); //write command
ssd1306_send_byte(0xb0+y);
ssd1306_send_byte(((x&0xf0)>>4)|0x10); // |0x10
ssd1306_send_byte((x&0x0f)|0x01); // |0x01
ssd1306_xfer_stop();
}
void ssd1306_fillscreen(uint8_t fill_Data){
SCREEN_FillScreen(fill_Data, DISPLAY_SCREEN);
}
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-6;
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 system_sleep() {
#if 1
SCREEN_DisplayOff();
#else
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
}
void beep(int bCount,int bDelay){
if (mute) return;
#if 0
for (int i = 0; i<=bCount; i++){digitalWrite(1,HIGH);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}digitalWrite(1,LOW);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}}
#endif
}
/* ------------------------
* Pong Code
*/
void playPong() {
ballx = 64*8;
bally = 32*4;
hdir = -8;
vdir = -4;
int actualy, actualx;
int factor = 0;
int waitCount = 0;
int lastx=64*8, lasty=32*4;
player=64;
player2=64;
lastPlayer = 64;
lastPlayer2 = 64;
score = 0; // obvious
score2 = 0; // obvious
perturbation = 0;
startGame();
while (stopAnimate == 0) {
while(1) {
waitCount++;
if(digitalRead(RIGHT_PIN)==LOW) {
boolean sChange = 0;
long startT = millis();
long nowT =0;
while(digitalRead(RIGHT_PIN) == LOW) {
nowT = millis();
if (nowT - startT > 1500) {
sChange = 1;
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_PIN) == LOW);
if (sChange == 1) {
} else if (mode == 1) {
mode = 3;
pFactor = 11;
ssd1306_char_f6x8(20, 0, "- TOUGH MODE -");
} else if (mode == 3) {
mode = 4;
pFactor = 10;
ssd1306_char_f6x8(16, 0, "- EXPERT MODE -");
} else if (mode == 4) {
mode = 1;
pFactor = 12;
ssd1306_char_f6x8(32, 0, "-- NORMAL --");
}
if (sChange == 0) delay(1000);
ssd1306_fillscreen(0x00);
EEPROM.write(0,mute);
EEPROM.write(1,mode);
}
if (digitalRead(LEFT_PIN) == LOW) {
player-= 3;
}
player+=1;
if (player > 48) player = 48;
if (player <0) player = 0;
if (mode == 1 || mode == 3 || mode == 4) {
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 = floor(bally/4);
actualx = floor(ballx/8);
// 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 = floor(bally/4);
actualx = floor(ballx/8);
// 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){
beep(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;
break;
}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;
}
beep(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;
if (vdir > 0) {
vdir = 2;
} else vdir = -2;
ssd1306_fillscreen(0x00);
doNumber(46,4,score);
doNumber(78,4,score2);
if (score < WINSCORE) {
for (int i = 0; i<1000; i = i+ 100){
beep(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();
}
break;
}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;
}
beep(20,300);
}
if (mode == 2 || mode == 3 || mode == 4) {
factor = 8-floor((score-score2)/2); // expert modes
if (factor < 2) factor = 2;
} else {
factor = 20-floor((score-score2)/2); // normal modes
if (factor < 10) factor = 10;
}
delay(factor);
// draw ball
blankBall(floor(lastx/8),floor(lasty/4));
drawPlatform();
drawPlatform2();
drawBall(floor(ballx/8),floor(bally/4));
lastx = ballx;
lasty = bally;
doNumber(28,0,score);
doNumber(92,0,score2);
if (score == WINSCORE || score2 == WINSCORE) {
stopAnimate = 1;
break;
}
}
}
blankBall(floor(lastx/8),floor(lasty/4));
blankBall(floor(ballx/8),floor(bally/4));
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){
beep(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);
}
}
void drawPlatform() {
if (player != lastPlayer) {
ssd1306_setpos(0,lastPlayer/8);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
ssd1306_setpos(0,lastPlayer/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
ssd1306_setpos(0,lastPlayer/8+2);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
}
if (player%8!=0){
ssd1306_setpos(0,player/8);
ssd1306_send_data_start();
ssd1306_send_byte((B11111111)<<player%8);
ssd1306_send_data_stop();
ssd1306_setpos(0,player/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B11111111);
ssd1306_send_data_stop();
ssd1306_setpos(0,player/8+2);
ssd1306_send_data_start();
ssd1306_send_byte((B01111110)>>8-player%8);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(0,player/8);
ssd1306_send_data_start();
ssd1306_send_byte(B11111111);
ssd1306_send_data_stop();
ssd1306_setpos(0,player/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B11111111);
ssd1306_send_data_stop();
}
lastPlayer = player;
}
void drawPlatform2() {
if (player2 != lastPlayer2) {
ssd1306_setpos(127,lastPlayer2/8);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
ssd1306_setpos(127,lastPlayer2/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
ssd1306_setpos(127,lastPlayer2/8+2);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
}
if (player2%8!=0){
ssd1306_setpos(127,player2/8);
ssd1306_send_data_start();
ssd1306_send_byte((B11111111)<<player2%8);
ssd1306_send_data_stop();
ssd1306_setpos(127,player2/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B11111111);
ssd1306_send_data_stop();
ssd1306_setpos(127,player2/8+2);
ssd1306_send_data_start();
ssd1306_send_byte((B01111110)>>8-player2%8);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(127,player2/8);
ssd1306_send_data_start();
ssd1306_send_byte((B11111111)<<0);
ssd1306_send_data_stop();
ssd1306_setpos(127,player2/8+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%8!=0){
ssd1306_setpos(x,y/8);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
ssd1306_setpos(x,y/8+1);
ssd1306_send_data_start();
ssd1306_send_byte(B00000000);
ssd1306_send_byte(B00000000);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(x,y/8);
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/8);
ssd1306_send_data_start();
doDrawLS(0,y%8);
ssd1306_send_data_stop();
ssd1306_setpos(x,y/8+1);
ssd1306_send_data_start();
doDrawRS(0,8-y%8);
ssd1306_send_data_stop();
} else {
ssd1306_setpos(x,y/8);
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){
beep(30,i);
}
}