252 lines
6.1 KiB
C
252 lines
6.1 KiB
C
/*
|
|
* final_project.c
|
|
*
|
|
* Created: 4/3/2024 10:08:56 AM
|
|
* Author : bsw9xd
|
|
*/
|
|
#define F_CPU 16000000UL
|
|
|
|
#include <avr/io.h>
|
|
#include <avr/interrupt.h>
|
|
#include <util/delay.h>
|
|
#include <stdbool.h>
|
|
|
|
#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);
|
|
}
|
|
} |