Simon-Says/Firmware/Simon_Wireless/Simon_Wireless.ino

508 lines
15 KiB
C++

//***********************************************************
// Simon_Says_Wireless.ino
// Simon Says PTH Kit Wireless
// Written by Chris Taylor for SparkFun Electronics
// 1/29/13
//
// Released under the Beerware License:
// If you use this code to make something cool, and you ever
// meet me, you owe me a beer.
//***********************************************************
// This code allows two Simon Says PTH boards to be used in
// wireless battle mode when connected to two XBee explorers
// which house XBee modules.
//
// In order to deliver power to the XBee module on the
// explorer, you will need to solder a wire from the VCC via
// near the Analog outputs on the board to the VCC input of
// the FTDI header (the via is the third pin in from the
// label "BLK".
//***********************************************************
// Gameplay:
// Power up both of the units, and they will play an intro
// sequence with some beeps and boops, and then will go into
// "attract" mode where they both flash lights in sequence.
// The player that wants to go first presses a button. Both
// Devices will go dark and wait for the first player to
// press a button. This is the first button in the sequence.
// Once player one presses a button, it will play the
// sequence back to the next player who must repeat it, and
// then add another button. Play continues until a player
// incorrectly repeats the sequence or takes too long to
// repeat it. The devices will play their respective "winner"
// and "loser" sequences, and then return to their initial
// state.
//***********************************************************
// Physical Description:
//
// The Simon device consists of four buttons with four LEDs.
// The LEDs and buttons are used in the code as follows:
//
// _____________________________
// | | |
// | | |
// | | |
// | Button/LED | Button/LED |
// | 1 | 2 |
// | | |
// |______________|______________|
// | | |
// | | |
// | Button/LED | Button/LED |
// | 3 | 4 |
// | | |
// | | |
// |______________|______________|
//
//**********************************************************
// Pin definitions for the buttons, LEDs, and buzzer
#define BUTTON1 9
#define BUTTON2 2
#define BUTTON3 12
#define BUTTON4 6
#define LED1 10
#define LED2 3
#define LED3 13
#define LED4 5
#define BUZZER1 4
#define BUZZER2 7
// Bitmasks
// These are 4-bit bitmasks used with bitwise logic to
// communicate which button has been pressed or which LED
// to light
#define LED1_MASK 1
#define LED2_MASK 2
#define LED3_MASK 4
#define LED4_MASK 8
#define BUTTON1_MASK 1
#define BUTTON2_MASK 2
#define BUTTON3_MASK 4
#define BUTTON4_MASK 8
// Game constants
#define MOVES_TO_WIN 20 // If the players can remember 20
// moves, they both win
#define TIME_LIMIT 3000 // Three second limit on a move
// Game variables
byte moves[MOVES_TO_WIN]; // Array of moves already made
byte nmoves = 0; // Number of moves already made
byte first_turn = 0; // Flag indicating whether or not this
// is the first turn of the game
void setup()
{
// Initialize Serial (Wireless) port
Serial.begin(9600);
// Initialize pins for buttons, LEDs, and buzzer
pinMode(BUTTON1, INPUT); // BUTTON1
pinMode(BUTTON2, INPUT); // BUTTON2
pinMode(BUTTON3, INPUT); // BUTTON3
pinMode(BUTTON4, INPUT); // BUTTON4
pinMode(LED1, OUTPUT); // LED1
pinMode(LED3, OUTPUT); // LED3
pinMode(LED2, OUTPUT); // LED2
pinMode(LED4, OUTPUT); // LED4
pinMode(BUZZER1, OUTPUT); // BUZZER1
pinMode(BUZZER2, OUTPUT); // BUZZER2
// Enable pull-ups on buttons
// Button I/Os will read LOW when pressed
digitalWrite(BUTTON1, HIGH);
digitalWrite(BUTTON3, HIGH);
digitalWrite(BUTTON2, HIGH);
digitalWrite(BUTTON4, HIGH);
}
void loop()
{
play_winner(); // Intro sequence for startup
// Wait in attract mode until a button is pressed
// attract mode will return 1 if this player is the first
// player to press a button
first_turn = attract_mode();
// Begin game: flash all LEDs, then go into game mode
set_leds(LED1_MASK | LED2_MASK | LED3_MASK | LED4_MASK);
delay(1000);
set_leds(0);
delay(250);
// Play game and handle result
// game_mode() function returns 1 for win, 0 for loss
if(game_mode()) { play_winner(); }
else { play_loser(); }
}
/**********************************************************************
game_mode
This function chooses the operations to be executed during a game. It
has two sections, one for if the players are on the first turn, and
one for if the players have pressed a first move. It returns 1 for a
win and 0 for a loss.
**********************************************************************/
byte game_mode(void)
{
nmoves = 0;
// Loop while players have not exceeded the moves to win
// variable
while(nmoves < MOVES_TO_WIN)
{
// If this is the first turn, we don't need to wait for the
// other player.
if(first_turn)
{
add_move();
first_turn = 0;
}
else
{
// Wait for move from other player. If this receive function
// returns 0, this is a win.
if(!receive_move()) { return 1; }
// Wait a half second before playing back the moves
delay(500);
play_moves();
// Test the player, if test is failed, this is a loss.
if(!test_moves()) { return 0; }
// Test is successful, add a new move and send it to the
// other player.
add_move();
}
}
return 1;
}
/**********************************************************************
Test that the player can remember the sequence.
Return 1 for success and 0 for failure
**********************************************************************/
byte test_moves(void)
{
byte move;
for(move = 0; move < nmoves; move++)
{
// Wait for a button press
byte choice = wait_for_button();
// Light LED and play sound for chosen button
toner(choice, 150);
// Translate the button from its bitmask
if(choice == BUTTON1_MASK) { choice = 0; }
if(choice == BUTTON2_MASK) { choice = 1; }
if(choice == BUTTON3_MASK) { choice = 2; }
if(choice == BUTTON4_MASK) { choice = 3; }
// If the choice is incorrect, player loses
if(choice != moves[move])
{
delay(100); // Wait for the button release
Serial.print('l'); // Send 'l' to other player to indicate loss
return 0;
}
}
// Player was correct, return 1
return 1;
}
/**********************************************************************
add_move
Wait for the user to press a button and add the move to the move array.
Then send the new move to the other user.
**********************************************************************/
void add_move(void)
{
byte choice = 0;
// Wait for the user to press a button
choice = wait_for_button();
// Light the LED and beep to indicate received press
toner(choice, 150);
// Translate the button from its bitmask
if(choice == BUTTON1_MASK) { choice = 0; }
if(choice == BUTTON2_MASK) { choice = 1; }
if(choice == BUTTON3_MASK) { choice = 2; }
if(choice == BUTTON4_MASK) { choice = 3; }
moves[nmoves++] = choice; // Add the move to the list
// Send the new move to the other player
Serial.print(choice);
}
/**********************************************************************
receive_move
Wait for the other player to send the move over XBee. The player will
either send the char 0, 1, 2, or 3 for the corresponding button press,
or send the character 'l' for a 'l'oss, meaning that they were unable
to successfully repeat the sequence. This function returns 1 for a
move or 0 for a loss by the other player.
**********************************************************************/
byte receive_move(void)
{
byte choice = 0;
// Wait for the other player to send a character
while(1)
{
if(Serial.available())
{
choice = Serial.read(); // Read in the sent character
if(choice == 'l') { return 0; } // Other player has lost, return 0
else { // Sent char was a button press
choice = choice - 48; // Convert char from ASCII to button value
moves[nmoves++] = choice; // Add to list of moves
return 1;
}
}
}
}
/*********************************************************************
toner()
Accepts a button press and a tone length in ms. It lights the LED of
the chosen button press and plays the respective buzzer tone for a
time of buzz_length_ms.
**********************************************************************/
void toner(byte which, int buzz_length_ms)
{
// Light the LED of the chosen button press
set_leds(which);
// Play the appropriate tone on the buzzer for the chosen button
switch(which)
{
case LED1_MASK:
buzz_sound(buzz_length_ms, 1136);
break;
case LED2_MASK:
buzz_sound(buzz_length_ms, 568);
break;
case LED3_MASK:
buzz_sound(buzz_length_ms, 851);
break;
case LED4_MASK:
buzz_sound(buzz_length_ms, 638);
break;
}
// Turn off all LEDs
set_leds(0);
}
/*********************************************************************
play_moves()
"Playback" the moves in the moves[] array. This function shows the
player what moves to remember and press to continue the game.
**********************************************************************/
void play_moves(void)
{
byte move;
// Play the appropriate button fo the next move in the array.
for(move = 0; move < nmoves; move++)
{
toner((1 << moves[move]), 150);
delay(150);
}
}
/*********************************************************************
attract_mode()
Play a sequence of flashing LEDs to attract a player. Keep playing
until a button is pressed.
*********************************************************************/
byte attract_mode(void)
{
byte current_led = LED1_MASK; // Start blinking with LED1
char received = 0;
while(1) // Loop until a button is pressed (return a value to exit loop)
{
set_leds(current_led);
delay(100);
// Check if a button is pressed. Once a button is pressed, send
// a 'c' character to the other player to indicate that a game has
// started, then return 1 to start game
if(check_button()) { Serial.print('c'); return 1; } // Return 1 if button is pressed. This means player is player 1
// Check if the other player has sent a 'c'. This means a game has started
if(Serial.available())
{
received = Serial.read();
if(received == 'c') { return 0; } // Return 2 if we receive a 'c'hallenge from another player
// This means player is player 2
}
current_led = (current_led << 1) % 0x0F; // bit shift 1 > 2 > 4 > 8 > 1 > 2...
}
}
/*********************************************************************
play_winner()
Play a sequence of LEDs and a buzzer sound to indicate a win.
*********************************************************************/
void play_winner(void)
{
for(byte x = 0; x < 5; x++) // Repeat 5 times
{
set_leds(LED2_MASK | LED3_MASK); // Light LED2 and LED3
winner_sound(); // Play a winner sound
set_leds(LED1_MASK | LED4_MASK); // Light LED1 and LED4
winner_sound();
}
}
/*********************************************************************
play_loser()
Play a sequence of LEDs and a buzzer sound to indicate a loss.
*********************************************************************/
void play_loser(void)
{
for(byte x = 0; x < 5; x++) // Repeat 5 times
{
set_leds(LED1_MASK | LED2_MASK); // Light LED1 and LED2
buzz_sound(255, 1500); // Play a buzzer sound
set_leds(LED3_MASK | LED4_MASK); // Light LED3 and LED4
buzz_sound(255, 1500);
}
}
/*********************************************************************
buzz_sound()
Play a buzz sound of length buzz_length_ms with delay of
buzz_delay_us.
*********************************************************************/
void buzz_sound(int buzz_length_ms, int buzz_delay_us)
{
long buzz_length_us;
// Convert to microseconds for loop calculation
buzz_length_us = buzz_length_ms * (long)1000;
while(buzz_length_us > (buzz_delay_us * 2))
{
buzz_length_us -= (buzz_delay_us * 2);
// Modulate buzzer lines
digitalWrite(BUZZER1, LOW);
digitalWrite(BUZZER2, HIGH);
delayMicroseconds(buzz_delay_us);
digitalWrite(BUZZER1, HIGH);
digitalWrite(BUZZER2, LOW);
delayMicroseconds(buzz_delay_us);
}
}
/*********************************************************************
set_leds()
Light the chosen LED (0 to turn off all).
*********************************************************************/
void set_leds(uint8_t leds)
{
if(leds & LED1_MASK) { digitalWrite(LED1, HIGH); }
else { digitalWrite(LED1, LOW); }
if(leds & LED2_MASK) { digitalWrite(LED2, HIGH); }
else { digitalWrite(LED2, LOW); }
if(leds & LED3_MASK) { digitalWrite(LED3, HIGH); }
else { digitalWrite(LED3, LOW); }
if(leds & LED4_MASK) { digitalWrite(LED4, HIGH); }
else { digitalWrite(LED4, LOW); }
}
/*********************************************************************
winner_sound()
Play a sequence of beeps to indicate a win
*********************************************************************/
void winner_sound(void)
{
byte x, y;
// Toggle the buzzer at various speeds
for(x = 250; x > 70; x--) {
for(y = 0; y < 3; y++) {
digitalWrite(BUZZER2, HIGH);
digitalWrite(BUZZER1, LOW);
delayMicroseconds(x);
digitalWrite(BUZZER2, LOW);
digitalWrite(BUZZER1, HIGH);
delayMicroseconds(x);
}
}
}
/*********************************************************************
check_button()
Read the button pin to detect if buttons are pressed. Return a bitmask
of the pressed buttons.
*********************************************************************/
byte check_button(void)
{
byte button_pressed = 0;
if(!digitalRead(BUTTON1)) { button_pressed |= BUTTON1_MASK; }
if(!digitalRead(BUTTON2)) { button_pressed |= BUTTON2_MASK; }
if(!digitalRead(BUTTON3)) { button_pressed |= BUTTON3_MASK; }
if(!digitalRead(BUTTON4)) { button_pressed |= BUTTON4_MASK; }
return button_pressed;
}
/*********************************************************************
wait_for_button()
Wait until the user presses a button. The amount of time to wait is
set by the definition TIME_LIMIT.
*********************************************************************/
byte wait_for_button(void)
{
int timeout = 0;
byte button = 0;
while(timeout < TIME_LIMIT) // Loop until button is pressed or
// time limit is violated.
{
button = check_button();
if(button)
{
delay(10); // Debounce
while(check_button()); // wait for release
delay(10);
// Make sure just one button is pressed
if( button == BUTTON1_MASK ||
button == BUTTON2_MASK ||
button == BUTTON3_MASK ||
button == BUTTON4_MASK)
{
return button;
}
}
delay(1);
timeout++;
}
return 0; // Timed out
}