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**********************