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