NICTA ed1‎ > ‎

NICTA ed1 Pong

/* 1D Pong 2.0!
 * 1 Dimensional Pong is now much more intelligent!!
 * Compatible with: NICTA ed1 board, or Arduino Duemilanove ATmega328 board
 * System requirements (if not NICTA ed1)(configure program according to your board design):
 * Potentiometer
 * LCD screen
 * Piezo buzzer
 * Two pushbuttons
 * LED shift register
 *
 * A PiAMP Production
 *
 * Instructions:
 * A menu will appear on the LCD screen. Navigate it using the potentiometer to
 * navigate to your choice and press D15 to select your choice.
 *
 * The Game:
 * Simply type in the name of each player in the Serial monitor and press D15
 * to confirm that player, starting with Player 1 (D14), then Player 2 (D15).
 * Press D14 to re - enter the name. Then the game begins!
 * Once the light reaches your button, press it to reflect it back. But beware!
 * The more the light is reflected, the faster it will get! If you press the
 * button before the light reaches you, or after it reaches you, or not at all,
 * you will lose! (NOTE: If the light is travelling away from you, pressing the button
 * will not affect the game in any way whatsoever). Once the game is over, the accumulated
 * score will be counted and the program will determine the winner and display the winner's
 * name on the LCD screen.
 *
 * More instructions can be accessed through the menu.
 * Do you have what it takes to become a Pong champion???
 *
 * The demo video can be accessed at this web address: http://www.archive.org/download/Pieman1DPong2/Creative_Challenge.m4v
 * NOTE: iTunes and/or QuickTime are required to view this video
 * This program is open - source under Creative Commons Attribution 2.5 Australia licensing.
 * Source code is available at: http://piemanvideocast.game-host.org/~andrew/The_Pieman_Videocast_Show/
 * Programs/Entries/2009/10/8_pong2.html
 */
// ******************BEGIN GLOBAL DECLARATIONS**********************
#include <LiquidCrystal.h>
 
// LED Shift Register constants
#define CLOCK (10)
#define LATCH (11)
#define DATA (12)
 
// Piezo and pushbutton pin constants
#define PIEZO (9)
#define D14 (14)
#define D15 (15)
#define POT (3)
 
// maximum serial input size constant
#define MAXSIZE (17)
 
// tone definitions in microseconds
#define HalfPer250Hz 2000     // half period of tone
#define HalfPer1000Hz 500
#define toneLength 500000  // play tone for 500ms
 
// Defintions to make program easier to read
#define D14_PRESS 0x01  //  bit 1
#define D15_PRESS 0x02  // bit 2
#define BOTH_PRESS 0x03 // note both bots set
#define M_LEFT -1
#define M_RIGHT 1
 
// GLOBAL VARIABLES
 
LiquidCrystal lcd(6, 7, 8, 2, 3, 4, 5); // initialise LiquidCrystal object
 
char name1[MAXSIZE]; // player 1
char name2[MAXSIZE]; // player 2
 
int n1; // size of name1 string
int n2; // size of mane2 string
 
int score1 = 0; // cumulative score for Player 1
int score2 = 0; // cumulative score for Player 2
// ******************END GLOBAL DECLARATIONS**********************
 
// ******************BEGIN MAIN**********************
// BEGIN EXECUTION
void setup()
{
  // initialise the piezo buzzer
  pinMode(PIEZO, OUTPUT);
 
  // initialise the LED shift register
  pinMode(DATA, OUTPUT);
  pinMode(LATCH, OUTPUT);
  pinMode(CLOCK, OUTPUT);
 
  // configure the push buttons and their pullup resistors
  pinMode(D14, INPUT);
  digitalWrite(D14, HIGH);
  pinMode(D15, INPUT);
  digitalWrite(D15, HIGH);
 
  // initialise the serial port and illuminate all LEDs
  ledWrite(255);
  Serial.begin(9600);
 
  // welcome screen displays only on initial start up
  lcd.clear();
  lcd.print("Welcome to Pong!");
  lcd.setCursor(0, 1);
  lcd.print("Now loading...");
  delay(5000);
}
 
void loop()
{
  menu(); // delegate execution to the menu function
  // display the initial score (ie. 0 for both players)
  score(score1, score2, name1, name2); // delegate execution to the score function
 
  // begin the game
  delegate(); // delegate execution to the main delegate function
  lcd.clear();
 
  // display the scoreboard
  tally(); // delegate execution to the tally function
}
// ******************END MAIN**********************
 
// ******************BEGIN DELEGATE FUNCTION**********************
void delegate() // the gameplay function
{
  int tone;
  int num_games = 0;
  for(;;) // loop infinitely until 9 rounds have passed
  {
    int dir;    // M_RIGHT or M_LEFT
    int pos;    // LED1 == 0, LED8 == 7
    int play;   // set to zero to halt game
    int period; // delay in ms for each LED light
    int b;      // return from button function
    int count;  // keep track of game progress
 
    // starting conditions for new game
    pos = 4; // LED5
    dir = M_LEFT;
    play = 1;
    period = 1000;
 
    // play single game in while loop
    // exit at game end
    while(play)
    {
      // light up LED
      ledWrite(1 << pos);
 
      // pause and detect any button press
      b = detectButtonPress(period); // assign b to the return value of detectButtonPress
 
      // check for game state
      if ((pos == 0) && (!(b & D14_PRESS)))
      {
        // D14 lose as button not pressed
          play = 0;
          tone = HalfPer250Hz;
          score2++;
      }
 
      if ((pos == 7) && (!(b & D15_PRESS)))
      {
        // D15 lose as button not pressed
        play = 0 ;
        tone = HalfPer1000Hz;
        score1++;
      }
 
      // ignore D14 if motion is right
      if ((pos > 0) && (pos < 7) && (dir == M_RIGHT) && (b & D15_PRESS))
      {
        // D15 lose
        play = 0;
        tone = HalfPer1000Hz;
        score1++;
      }
 
      // ignore D15 if motion is left
      if ((pos > 0) && (pos < 7) && (dir == M_LEFT) && (b & D14_PRESS))
      {
        // D14 lose
        play = 0;
        tone = HalfPer250Hz;
        score2++;
      }
 
      // else play on
      pos += dir;
 
    // change direction if necessary
      if (pos == 0)
      {
        count++; // increment game progress
        dir = M_RIGHT;
      }
      if (pos == 7)
        dir = M_LEFT;
 
      // speed up by 5%
      period *= 0.95;
    }  // while loop
 
    // only get here when someone loses
    ledWrite(0);
    playSound(tone);
    score(score1, score2, name1, name2);
    num_games++;
 
    if (num_games == 9)
    {
      break; // terminate loop and delay before returning
             // to calling function (ie. loop function)
    }
  }
  // delay before return
  delay(5000);
}
// ******************END DELEGATE FUNCTION**********************
 
// ******************BEGIN EXCEPTION HANDLING**********************
void assert(const char errorName[])
{
  char message[] = "Error: See Serial Monitor";
  lcd.clear();
  scroll(message, 25, 0);
  Serial.println(errorName);
}
// ******************END EXCEPTION HANDLING**********************
 
// ******************BEGIN GENERIC FUNCTIONS**********************
int getLine(char buffer[], int size) // for retrieval of player names
{
  int n = 0; // loop variable
  int waittime = 50; // maximum time in ms to wait between characters
  while( Serial.available() && (n < (size-1)))
  {
    while( Serial.available() && (n < (size-1)))
    {
      buffer[n] = (char)Serial.read();
      n = n + 1;
    }
    if (n < (size-1))
    delay(waittime);
  }
  buffer[n] = NULL ;
  if ( (n == (size-1)) && Serial.available() )
    n = size;
  // n contains number of characters placed into the buffer
  return(n);
}
 
void ledWrite(byte a) // LED Shift Register manipulation function
{
  digitalWrite(LATCH, LOW);
  shiftOut(DATA, CLOCK, MSBFIRST, a);
  digitalWrite(LATCH, HIGH);
}
 
void tally() // scoreboard function
{
  char separator[] = ": ";
  // closing message, constant, but must have periods inserted by scroller function
  char message[22] = "A PiAMP Production";
  const char genericError[25] = "An error has occurred";
  if (score1 < score2)
  {
    lcd.print(name2);
    lcd.print(" wins!");
    delay(5000);
    lcd.clear();
  }
  else if (score2 < score1)
  {
    lcd.print(name1);
    lcd.print(" wins!");
    delay(5000);
    lcd.clear();
  }
  else
  {
    lcd.clear();
    assert(genericError);
    delay(500);
  }
 
  // print credits
  lcd.print("Thanks 4 playing"); // no need to scroll, 16 characters exactly
  lcd.setCursor(0, 1);
  lcd.print(message);
  delay(500);
  scroll(message, 18, 1); // delegate execution to the scroller function
  delay(1000);
  lcd.clear(); // program ends here, restarts with name input
}
void scroll(char news[], int n, int cursor)
// the sentence scrolling function, can be interrupted with the press of a button.
{
  int i;
  int j;
  int start;
  if (n <= 16)
  {
    news[n] = NULL; // terminate string
    lcd.print(news);
  }
  else
  {
    // add period characters
    for (i = n; i < (n + 3); i++)
    {
      news[i] = '.';
    }
    n = i;
 
    // 'start' points to first character in 16 character
    // sub-string that will be display on the LCD
    start = 0;
 
    for(int k = 0; k <= n; k++)
    {
      // jump out if new string available
      lcd.setCursor(0, cursor);
 
      // print 16 characters starting at index 'start'
      j = start;
      for (i = 0; i < 16; i++)
      {
        lcd.print(news[j]);
        j++;
        // wrap if need be
        if (j >= n )
        {
          j = 0;
        }
      }
 
      // move sub-string one character along
      start++;
 
      // if necessary, reset to start
      if (start >= n)
      {
        start = 0;
      }
      if (digitalRead(D14) == LOW || digitalRead(D15) == LOW)
        return;
 
      // delay between characters
      delay(250);
    }
  }
}
 
void score(int player1, int player2, char p1[], char p2[])
// prints the current score
{
  lcd.clear();
  lcd.print(p1);
  lcd.print(": ");
  lcd.print(player1);
  lcd.setCursor(0, 1);
  lcd.print(p2);
  lcd.print(": ");
  lcd.print(player2);
}
 
void menu() // displays the menu items according to mapped potentiometer input
{
  int last_region = -1;
  int raw;
  int mapped;
  for (;;)
  {
    lcd.clear();
    raw = analogRead(POT);
    mapped = map(raw, 0, 1023, 2, 0);
    switch (mapped) // allows region 0 to operate as region 1
    {
      case (0):
      // FALL THROUGH
      case (1):
        lcd.clear();
        lcd.print("1. Play Game");
        if (digitalRead(D15) == LOW)
        {
          lcd.setCursor(0, 1);
          lcd.print("Selected: ");
          if (mapped == 0)
            lcd.print("1");
          else
            lcd.print(mapped);
          delay(100);
          lcd.clear();
          gameSetup();
          return;
        }
        break;
      case (2):
        lcd.print("2. Instructions");
        if (digitalRead(D15) == LOW)
        {
          lcd.setCursor(0, 1);
          lcd.print("Selected: ");
          lcd.print(mapped);
          delay(100);
          lcd.clear();
          instructions();
          break;
        }
        break;
      default:
        assert("Invalid region");
        break;
    }
    delay(100);
  }
}
 
void instructions() // displays the instructions for 1D Pong 2.0
{
  char notice[] = "See Serial Monitor";
  scroll(notice, 18, 0);
  Serial.println("Welcome to Pong Reinvented!");
  Serial.println("A game of skill and hand - eye coordination. Do you have what it takes to win?");
  Serial.println();
  Serial.println("Controls:");
  Serial.println("In - Game:");
  Serial.println("D14: Player 1, D15: Player 2");
  Serial.println("Menus:");
  Serial.println("Turn potentiometer to navigate menus, press D15 to select an item");
  Serial.println();
  Serial.println("Playing the game:");
  Serial.println("To reflect the ball, the player must press the button when the LED directly");
  Serial.println("beside their button is lit up. If it is pressed otherwise, that player will lose.");
  Serial.println("If a player wins 5 rounds, they win the game. Both scores will be saved to the scoreboard.");
  Serial.println();
  Serial.print("Press any button to return to the menu...");
  while (digitalRead(D15) == HIGH && digitalRead(D14) == HIGH)
    ;
  if (digitalRead(D14) == LOW || digitalRead(D15) == LOW)
    return;
}
// ******************END GENERIC FUNCTIONS**********************
 
// ******************BEGIN GAME FUNCTIONS**********************
void playSound(int halfTone) // play the loss tone
{
  int iterations;
  int i;
 
  iterations = toneLength / (halfTone * 2);
 
  for(i = 0; i < iterations; i++)
  {
    digitalWrite(PIEZO, HIGH);
    delayMicroseconds(halfTone);
    digitalWrite(PIEZO, LOW);
    delayMicroseconds(halfTone);
  }
  lcd.clear();
}
 
/*
 * Routine to detect press of a button within
 * passed time period
 *
 * Returns pin number of pressed button or:
 *   0 for no buttons pressed
 *   D14_PRESS (1) for d14 button pressed
 *   D15_PRESS (2) for d15 button pressed
 *   D14_PRESS & D15_PRESS (3) for both buttons pressed
 */
int detectButtonPress(int waitFor)
{
  unsigned long waitTill;
  int buttons = 0;
 
  waitTill = millis() + (unsigned long)waitFor;
 
  // wait for button to be pressed
  while(millis() < waitTill)
  {
      buttons |= (digitalRead(D14) == LOW) ? D14_PRESS : 0;
      buttons |= (digitalRead(D15) == LOW) ? D15_PRESS : 0;
  }
 
  // note, have not checked for de-press
  return (buttons);
}
void gameSetup() // sets up the player names for the game
{
  // Button prompt constant, not declared const char[] or #define as periods
  // must be inserted into string by scroller function
  char buttons[29] = "D15: Confirm, D14: Cancel";
 
  // loop indefinitely for player 1 name input (terminated if confirmed).
  for (;;)
  {
    lcd.clear();
    lcd.print("Player 1:");
    while (!Serial.available())
      ;
    n1 = getLine(name1, MAXSIZE); // get player 1 name from serial input
    lcd.clear();
    lcd.print(name1);
    lcd.setCursor(0, 1);
    lcd.print(buttons);
    delay(500);
    scroll(buttons, 25, 1);
    while (digitalRead(D15) == HIGH && digitalRead(D14) == HIGH)
      ;
    if (digitalRead(D15) == LOW)
    {
      break; // terminate loop and move to player 2 input
    }
    else if(digitalRead(D14) == LOW) // this may be redundant
    {
      continue; // restart loop
    }
  }
 
  // loop indefinitely for player 2 name input (terminated if confirmed)
  for(;;)
  {
    lcd.clear();
    lcd.print("Player 2:");
    while (!Serial.available())
      ;
    n2 = getLine(name2, MAXSIZE); // get player 2 name from serial input
    lcd.clear();
    lcd.print(name2);
    lcd.setCursor(0, 1);
    lcd.print(buttons);
    delay(500);
    scroll(buttons, 25, 1);
    while (digitalRead(D15) == HIGH && digitalRead(D14) == HIGH)
      ;
    if (digitalRead(D15) == LOW)
    {
      break; // terminate loop and begin game
    }
    else if (digitalRead(D14) == LOW) // this may be redundant
    {
      continue; // restart loop
    }
  }
  return;
}
// ******************END GAME FUNCTIONS**********************