November 13, 2011
by Zoltan
|
I made a NerdKit version of the game Simon. To play, repeat a certain sequence of LED flashes by pushing corresponding buttons in the same order that the LEDs lit up. The sequence grows longer every time you get the sequence right.
I made this to learn how to play sounds, generate random numbers, and do basic pin input and output.
The hardware setup is minimal. I left the LCD in place after I made the temperature sensor, but took out the sensor itself. I added a piezoelectric buzzer, 4 LEDs, and 4 push-button switches. I made surprisingly effective switches out of paper clips, tin-foil, tape, and cardboard from an old cereal box. I soldered a wire to each paper clip (on back) and attached the ground wire to a strip of tin foil.
I'd welcome any comments or suggestions.
The code is below:
// Play a game of Simon--Try to repeat a pattern of LED flashes
// with corresponding button presses
// for NerdKits with ATmega168
//
#define F_CPU 14745600
#include <stdlib.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
// PIN DEFINITIONS:
// PB1 - 4 are LEDs from pin to ground
// PC1 - 4 are push buttons that connect to ground when pushed
// PC5 is the piezoelectric buzzer
#define MAX_GAME 50 //Set this to the longest sequence that
//you want the game to handle.
#define LED_TIME_ON 500 //How fast the sequence is played back:
// i.e. how long (in milliseconds) the LED is on
#define LED_TIME_OFF 100 //how long the LED is off between successive LED
// flashes during sequence playback
//The play_tone function below is from "Making Music with a
// Microcontroller" video tutorial
// from the NerdKit website: file: musicbox1.c by: mrobbins@mit.edu
void play_tone(uint16_t delay, uint8_t duration) {
// delay is half-period in microseconds
// duration is in 10ms increments
// example: 440Hz --> delay=1136
// duration = 2*delay * cycles (all in same units)
// cycles = 10000 * duration / delay / 2
// cycles = 100 * duration / (delay/50)
uint16_t tmp = 100 * duration;
uint16_t delaysm = delay / 50;
uint16_t cycles = tmp / delaysm;
while(cycles > 0) {
PORTC |= (1<<PC5);
delay_us(delay);
PORTC &= ~(1<<PC5);
delay_us(delay);
cycles--;
}
}
int main() {
// Start the LCD
lcd_init();
lcd_home();
// Set the pins PC1 - PC4 to input mode. These are for the buttons.
DDRC &= ~(1<<PC1);
DDRC &= ~(1<<PC2);
DDRC &= ~(1<<PC3);
DDRC &= ~(1<<PC4);
// Turn on the internal pull-up resistors for the pins PC1 - PC4.
// When you read these pins, they read 1 until a button is pressed.
PORTC |= (1<<PC1);
PORTC |= (1<<PC2);
PORTC |= (1<<PC3);
PORTC |= (1<<PC4);
// Set pins PB1 - PB4 to be output. These pins will drive the LEDs.
DDRB |= (1<<PB1);
DDRB |= (1<<PB2);
DDRB |= (1<<PB3);
DDRB |= (1<<PB4);
// Set pin PC5 as output to drive the piezoelectric buzzer
DDRC |= (1<<PC5);
// Declare the variables.
uint8_t i, seq_length, button_number, won, seed;
uint8_t seq[MAX_GAME]; //An array to hold the sequence
//Press a button to start the game.
lcd_write_string(PSTR("Press any button"));
lcd_line_two();
lcd_write_string(PSTR("to start the game."));
//Wait for a button press.
//Time how long the user takes to press the button and use this
//number as a seed for the random number generator.
seed=0;
//Use ~PINC so a button press shows up as a 1 instead of a 0
while ((~PINC & 0b00011110)== 0) {
delay_ms(2);
seed++;
}
play_tone(800,20); //Play a short beep to give user feedback
srand(seed); //Set the seed for the rand() function.
delay_ms(2000);
while(1) {
lcd_clear_and_home();
won = 1;
//Generate a sequence of random numbers
for ( i = 0; i < MAX_GAME; i++ ) {
seq[i] = rand() & 0b00000011; //The last two bits get a # from 0-3
seq[i]++; // increment it to get a number from 1-4
}
//Play the sequence on the LEDs.
//Start with sequence that is 3 LED flashes long
//and then make it longer every time the player
//successfully repeats the sequence.
for ( seq_length = 3; seq_length < MAX_GAME; seq_length++ ) {
for ( i = 0; i < seq_length; i++ ) {
//Turn on the next LED in the sequence
PORTB |= (1<< seq[i]);
play_tone(778-60*seq[i],LED_TIME_ON/10);
//Turn off all LEDs
PORTB &= 0b11100001;
delay_ms(LED_TIME_OFF);
}
//The player tries to repeat the sequence by pressing buttons.
for ( i = 0; i < seq_length; i++ ) {
//Wait for a button press
while ((~PINC & 0b00011110)== 0) {
delay_ms(100); //So I don't have to debounce the switch.
}
//check which button was pressed
switch (~PINC & 0b00011110) {
case 0b00000010:
button_number = 1;
break;
case 0b00000100:
button_number = 2;
break;
case 0b00001000:
button_number = 3;
break;
case 0b00010000:
button_number = 4;
break;
default:
button_number = 0;
break;
}
// Light up the LED corresponding to the pressed button
if (button_number) {
PORTB |= (1<< button_number);
}
//If you pressed the wrong button, you lose
if (button_number != seq[i]) {
//Turn on LED to show the correct button
PORTB |= (1<< seq[i]);
won = 0;
break;
}
//Wait until the button is released.
while (~PINC & 0b00011110) {
play_tone(778-60*seq[i],5);
}
//Turn off the LEDs
PORTB &= 0b11100001;
} //end of inner for loop. Go back to read the next button press.
if (won) {
lcd_write_string(PSTR("So far, so good!"));
lcd_line_two();
lcd_write_string(PSTR("You cleared level "));
lcd_write_int16(seq_length-2);
delay_ms(3000);
lcd_clear_and_home();
} else {
lcd_clear_and_home();
lcd_write_string(PSTR("You lost on level "));
lcd_write_int16(seq_length-2);
play_tone(1000,100);
play_tone(1400,200);
delay_ms(2000);
// turn off the LEDs
PORTB &= 0b11100001;
break; //break out of the outer loop to start a new game.
}
}
lcd_clear_and_home();
lcd_write_string(PSTR("Press any button"));
lcd_line_two();
lcd_write_string(PSTR("to start a new game."));
while ((~PINC & 0b00011110)== 0) {
delay_ms(50);
}
play_tone(800,20);
delay_ms(2000);
}
return 0;
}
|
November 13, 2011
by Rick_S
|
Looks pretty neat.
Thoughts:
Often a timer is used to generate the random seed value based on timing of a keypress. I see you are doing it based on and incremented counter - so that will probably work similarly.
Have you thought about changing the speed at which the pattern is played to the player as gameplay progresses? Speed it up the farther you go to add more to the challange??
Overall though, it looks like a job well done!
Rick |
November 13, 2011
by Zoltan
|
Thanks for the comments; they are pretty insightful. I had originally planned on using the timer to generate a seed, but I got stuck trying to figure out how to implement it. I'll have to go over some of the projects like the Kitchen Timer to learn how the timer works.
I like your idea about speeding up the game as players advance. |