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

}