/*************************************************************************************
  EZUBI | E-HACKS
  ARDUINO-AUDIO-PONG
 ************************************************************************************
  Arduino Pong
  By Pete Lamonica
  modified by duboisvb
  updated by James Bruce (http://www.makeuseof.com/tag/author/jbruce
  modified for audio input by Roland Batroff (http://www.kollektorbase.net/)
  updated for audio input by David Ziegler (http://www.goleon.de/)
 ****************************************************************************************/
#include "TVout.h"
#include "TVoutfonts/fontALL.h"
#define WHEEL_ONE_PIN 0  // ANALOG AUDIO INPUT 1
#define WHEEL_TWO_PIN 1  // ANALOG AUDIO INPUT 2
#define BUTTON_ONE_PIN 2 // BUTTON PIN
#define PADDLE_HEIGHT 14
#define PADDLE_WIDTH 1
#define RIGHT_PADDLE_X (TV.hres()-4)
#define LEFT_PADDLE_X 2
#define IN_GAMEA 0 //in game state - draw constants of the game box
#define IN_GAMEB 0 //in game state - draw the dynamic part of the game
#define IN_MENU 1 //in menu state
#define GAME_OVER 2 //game over state
#define LEFT_SCORE_X (TV.hres()/2-15)
#define RIGHT_SCORE_X (TV.hres()/2+10)
#define SCORE_Y 4
#define MAX_Y_VELOCITY 6
#define PLAY_TO 7
#define LEFT 0
#define RIGHT 1
TVout TV;
unsigned char x, y;
/*************************************************************************************
* VARIABLES FOR AUDIO INPUT AND SAMPLING
*************************************************************************************/
const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned long startMillis = millis(); // Start of sample window
unsigned int sample_1;
unsigned int sample_2;
unsigned int peakToPeak_1 = 0;   // peak-to-peak level
unsigned int peakToPeak_2 = 0;
unsigned int signalMax_1 = 0;
unsigned int signalMax_2 = 0;
unsigned int signalMin_1 = 1024;
unsigned int signalMin_2 = 1024;
/*********************************************/
boolean button1Status = false;
int wheelOnePosition = 0;
int wheelTwoPosition = 0;
int rightPaddleY = 0;
int leftPaddleY = 0;
unsigned char ballX = 0;
unsigned char ballY = 0;
char ballVolX = 2;
char ballVolY = 2;
int leftPlayerScore = 0;
int rightPlayerScore = 0;
int frame = 0;
int state = IN_MENU;
int damperOne[5];
int damperTwo[5];
void processInputs() {
  /*************************************************************************************
  * AUDIO SAMPLING FOR BOTH MICROPHONES
  *************************************************************************************/
  // collect data for 50 mS
  unsigned long startMillis = millis(); // Start of sample window
  unsigned int signalMax_1 = 0;
  unsigned int signalMin_1 = 512;
  unsigned int signalMax_2 = 0;
  unsigned int signalMin_2 = 512;
  while (millis() - startMillis < sampleWindow) {
    sample_1 = analogRead(0);
    sample_2 = analogRead(1);
    if (sample_1 < 1024) {
      if (sample_1 > signalMax_1) {
        signalMax_1 = sample_1;  // save just the max levels
      }
      else if (sample_1 < signalMin_1) {
        signalMin_1 = sample_1;  // save just the min levels
      }
    }
    if (sample_2 < 1024) {
      if (sample_2 > signalMax_2) {
        signalMax_2 = sample_2;  // save just the max levels
      }
      else if (sample_2 < signalMin_2) {
        signalMin_2 = sample_2;  // save just the min levels
      }
    }
  }
  /*************************************************************************************/
  /*************************************************************************************
  * Signal Smoothing via Dampervals
  *************************************************************************************/
  for (int i = 0; i < 4; i++) {
    damperOne[i] = damperOne[i + 1];
    damperTwo[i] = damperTwo[i + 1];
  }
  damperOne[4] = (signalMax_1 - signalMin_1) * 2;
  damperTwo[4] = (signalMax_2 - signalMin_2) * 2;
  int val1 = 0;
  int val2 = 0;
  for (int i = 0; i < 5; i++) {
    val1 += damperOne[i];
    val2 += damperTwo[i];
  }
  wheelOnePosition = val1 / 5;
  wheelTwoPosition = val2 / 5;
  button1Status = (digitalRead(BUTTON_ONE_PIN));
  if ((button1Status == HIGH) && (state == GAME_OVER)) {
    Serial.println("game over, drawing menu");
    drawMenu ();
  }
  /*************************************************************************************/
}
void drawGameScreen() {
  //draw right paddle
  rightPaddleY = ((wheelOnePosition / 8) * (TV.vres() - PADDLE_HEIGHT)) / 128;
  x = RIGHT_PADDLE_X;
  for (int i = 0; i < PADDLE_WIDTH; i++) {
    TV.draw_line(x + i, rightPaddleY, x + i, rightPaddleY + PADDLE_HEIGHT, 1);
  }
  //draw left paddle
  leftPaddleY = ((wheelTwoPosition / 8) * (TV.vres() - PADDLE_HEIGHT)) / 128;
  x = LEFT_PADDLE_X;
  for (int i = 0; i < PADDLE_WIDTH; i++) {
    TV.draw_line(x + i, leftPaddleY, x + i, leftPaddleY + PADDLE_HEIGHT, 1);
  }
  //draw score
  TV.print_char(LEFT_SCORE_X, SCORE_Y, '0' + leftPlayerScore);
  TV.print_char(RIGHT_SCORE_X, SCORE_Y, '0' + rightPlayerScore);
  //draw ball
  TV.set_pixel(ballX, ballY, 2);
}
void playerScored(byte player) {
  if (player == LEFT) leftPlayerScore++;
  if (player == RIGHT) rightPlayerScore++;
  //check for win
  if (leftPlayerScore == PLAY_TO || rightPlayerScore == PLAY_TO) {
    state = GAME_OVER;
  }
  ballVolX = -ballVolX;
}
void drawBox() {
  TV.clear_screen();
  //draw net
  for (int i = 1; i < TV.vres() - 4; i += 6) {
    TV.draw_line(TV.hres() / 2, i, TV.hres() / 2, i + 3, 1);
  }
  // had to make box a bit smaller to fit tv
  TV.draw_line(0, 0, 0, 95, 1 ); // left
  TV.draw_line(0, 0, 126, 0, 1 ); // top
  TV.draw_line(126, 0, 126, 95, 1 ); // right
  TV.draw_line(0, 95, 126, 95, 1 ); // bottom
  state = IN_GAMEB;
}
void drawMenu() {
  x = 0;
  y = 0;
  char volX = 3;
  char volY = 3;
  TV.clear_screen();
  TV.select_font(font8x8);
  TV.print(10, 5, "E-HACKS");
  TV.print(10, 20, "Arduino Pong");
  TV.select_font(font4x6);
  TV.print(22, 45, "Press Button");
  TV.print(30, 55, "To Start");
  delay(1000);
  while (button1Status) {
    processInputs();
    TV.delay_frame(3);
    if (x + volX < 1 || x + volX > TV.hres() - 1) volX = -volX;
    if (y + volY < 1 || y + volY > TV.vres() - 1) volY = -volY;
    if (TV.get_pixel(x + volX, y + volY)) {
      TV.set_pixel(x + volX, y + volY, 0);
      if (TV.get_pixel(x + volX, y - volY) == 0) {
        volY = -volY;
      }
      else if (TV.get_pixel(x - volX, y + volY) == 0) {
        volX = -volX;
      }
      else {
        volX = -volX;
        volY = -volY;
      }
    }
    TV.set_pixel(x, y, 0);
    x += volX;
    y += volY;
    TV.set_pixel(x, y, 1);
  }
  TV.select_font(font4x6);
  state = IN_GAMEA;
}
void setup()  {
  x = 0;
  y = 0;
  TV.begin(_NTSC);
  ballX = TV.hres() / 2;
  ballY = TV.vres() / 2;
  pinMode(BUTTON_ONE_PIN, INPUT_PULLUP);
}
void loop() {
  processInputs();
  if (state == IN_MENU) {
    drawMenu();
  }
  if (state == IN_GAMEA) {
    drawBox();
  }
  if (state == IN_GAMEB) {
    //every third frame
    if (frame % 3 == 0) {
      ballX += ballVolX;
      ballY += ballVolY;
      //change if hit top or bottom
      if (ballY <= 1 || ballY >= TV.vres() - 1) {
        ballVolY = -ballVolY;
        delay(100);
        TV.tone(2000, 30);
      }
      // test left side for wall hit
      if (ballVolX < 0 && ballX == LEFT_PADDLE_X + PADDLE_WIDTH - 1 && ballY >= leftPaddleY && ballY <= leftPaddleY + PADDLE_HEIGHT) {
        ballVolX = -ballVolX;
        ballVolY += 2 * ((ballY - leftPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
        delay(100);
        TV.tone(2000, 30 );
      }
      // test right side for wall hit
      if (ballVolX > 0 && ballX == RIGHT_PADDLE_X && ballY >= rightPaddleY && ballY <= rightPaddleY + PADDLE_HEIGHT) {
        ballVolX = -ballVolX;
        ballVolY += 2 * ((ballY - rightPaddleY) - (PADDLE_HEIGHT / 2)) / (PADDLE_HEIGHT / 2);
        delay(100);
        TV.tone( 2000, 30  );
      }
      //limit vertical speed
      if (ballVolY > MAX_Y_VELOCITY) ballVolY = MAX_Y_VELOCITY;
      if (ballVolY < -MAX_Y_VELOCITY) ballVolY = -MAX_Y_VELOCITY;
      // Scoring
      if (ballX <= 1) {
        playerScored(RIGHT);
        delay(100);
        TV.tone( 500, 300 );
      }
      if (ballX >= TV.hres() - 1) {
        playerScored(LEFT);
        // sound
        delay(100);
        TV.tone(  500, 300 );
      }
    }
    drawGameScreen();
  }
  if (state == GAME_OVER) {
    drawGameScreen();
    TV.select_font(font8x8);
    TV.print(29, 25, "GAME");
    TV.print(68, 25, "OVER");
    while (!button1Status) {
      processInputs();
      delay(50);
    }
    TV.select_font(font4x6); //reset the font
    //reset the scores
    leftPlayerScore = 0;
    rightPlayerScore = 0;
    state = IN_MENU;
  }
  TV.delay_frame(1);
  if (++frame == 60) frame = 0; //increment and/or reset frame counter
}