/* * final_project.c * * Created: 4/3/2024 10:08:56 AM * Author : bsw9xd */ #define F_CPU 16000000UL #include #include #include #include #include "serial.h" #include "clock.h" volatile int seconds_remaining; //will be used by ISR #define SPEAKER_PORT PORTE #define SPEAKER_PIN 4 #define ELEMENT_COUNT 7 /** GETTING/DISPLAYING ELEMENTS * Two of the LEDs are connected to the RX1/TX1 pins, used for serial. * Because of this, there's 7 possible elements. * Here's how elements (guesses) are retrieved from buttons and displayed on LEDs: * If the button is on PINA, the element is set to the reading from PINA, * unmodified (aside from the two invalid buttons being masked). * this allows easy LED outputting. * If the button is the single one connected to PORTE, * the 2nd bit is set. When displaying elements, we assume * the second bit is the middle PORTE LED. */ uint8_t get_button() { //debouncing is done by waiting for the user to stop pressing the button, //then waiting an amount of time for the bouncing to stop. const double debounce_wait = 100.0; if(~PINE & (1 << 6)) { while((~PINE & (1<<6))); _delay_ms(debounce_wait); return (1 << 2); } uint8_t porta_state = (~PINA) & ~(0b1100); if(porta_state) { while(PINA != 0xff); _delay_ms(debounce_wait); } return porta_state; } //for documentation see above paragraph void display_element(uint8_t element, unsigned int time) { uint8_t portd_state = 0; uint8_t porte_state = 0; if(element & (1 << 2)) porte_state = (1<<5); portd_state = element & ~(0b1100); PORTD ^= portd_state; PORTE ^= porte_state; beep(329.63, .25); _delay_ms(time * 1000); PORTD ^= portd_state; PORTE ^= porte_state; } void init_io() { //initilize IO registers //Buttons DDRA = 0x00; PORTA = 0xff; //LEDs DDRD = 0xff; PORTD = 0xff; //speaker and middle LED DDRE = (1 << 4) | (1 << 5); PORTE = 0xff; } int main(void) { cli(); timer_init_ctc(); init_io(); usart_init(); while(1) { int level = 0; //get level usart_txstr( "SIMON GAME\n" "Enter your starting difficulty level:\n" "1. Easy\n" "2. Moderate\n" "3. Give me pain."); //ask until valid input while((level > 3) || (level < 1)) level = (int)(usart_rxt_blocking() - '0'); //main simon game while(level < 3) { int display_time; int sets; int response_time; int elements_min; int elements_max; double score; uint8_t element_list[5]; // TODO //this is where the level properties are set depending on level switch(level) { case 1: sets = 3; display_time = 3; response_time = 5; elements_min = 3; elements_max = 5; break; case 2: sets = 4; display_time = 2; response_time = 7; elements_min = 3; elements_max = 10; break; case 3: sets = 5; display_time = 1; response_time = 10; elements_min = 5; elements_max = 15; break; } //it's easier to make a variable to count the number of guesses (max_score) //and increment current_score after each correct guess to calculate total score //as the number of elements per set scale. int max_score = 0; int current_score = 0; for(int set = 0; set < sets; set++) { //scale from elements_min (first set) to elements_max (last set) int elements = elements_min + ceil(((elements_max - elements_min) / (float)(sets - 1)) * set); //randomly get, display elements for(int element = 0; element < elements; element++) { uint8_t element_bit = (rand() % ELEMENT_COUNT); // button 3 should never be pressed, so if 3 is randomly generated, // we make it the last LED. We only generate 7 potential elements. if(element_bit == 3) element_bit = 7; uint8_t this_element = 1 << element_bit; usart_txt('\n'); usart_txt(element_bit + '0'); usart_txt('\n'); element_list[element] = this_element; //will be compared to guesses later display_element(this_element, display_time); } //get elements from buttono presses //we'll poll the timer to see if a second has passed //as timer only supports a max of 0xffff * (1024 / 16000000) seconds seconds_remaining = response_time; start_timer(); uint16_t guess; for(int element = 0; element < elements; element++) { guess = 0; do { if(timer_done()) { //accounts for seconds passed seconds_remaining--; stop_timer(); //TODO only need one function start_timer(); beep(261.63, .1); } else { guess = get_button(); } } while((!guess) && (seconds_remaining > 0)); max_score++; if(guess == element_list[element]) { current_score++; correct_beep(); } else { incorrect_beep(); } } } //where we check the score. Score is calculated per level. //If score under 80, we break back to the menu. score = (float)current_score / max_score; if(score >= .8) { level++; usart_txstr("\nnext level\n"); } else { loose(); break; } } if(level >= 3) win(); //you win if you get past level 3 } } void correct_beep() { usart_txstr("\nCorrect!\n"); beep(440.0, .1); } void incorrect_beep() { usart_txstr("\nIncorrect guess.\n"); beep(261.62, .2); _delay_ms(25); beep(261.62, .2); } void win() { usart_txstr("\nYou beat the game!\n"); beep(262., .5); beep(392., .5); } void loose() { usart_txstr("\nYou loose, try again?\n"); beep(330., .5); beep(294., .5); } /** can handle specific frequencies for a durientation of time. * speaker_ms is caculated by taking the period, dividing by 2 * (as we need to flip speaker state once per cycle) * then multiplies it by 1000 to convert to ms for _delay_ms. * We control how long its played by making it loop, * loop count is durientation of note / period. * loop_count will be off by a max of 1 period. **/ void beep(double frequency, double durientation) { double speaker_ms = ((1.0 / frequency) / 2.0) * 1000.0; //TODO clean up int loop_count = durientation / (1.0 / frequency); for(unsigned int i = 0; i < loop_count; i++) { _delay_ms(speaker_ms); SPEAKER_PORT ^= (1 << SPEAKER_PIN); } }