/*
   Simon Game for ESP32-C3 with Score display

   Ported to Rust by Maverick -> https://wokwi.com/makers/maverick
*/

#![no_std]
#![no_main]

use esp32c3_hal::{
    clock::ClockControl,
    gpio::*,
    peripherals::Peripherals,
    prelude::*,
    timer::TimerGroup,
    Delay,
    Rtc,
    Rng,
};
use esp_println::println;
use esp_backtrace as _;

const MAX_GAME_LENGTH: usize = 100;
const TONES: [usize; 4] = [ 196, 262, 330, 784 ];

/* Define pin numbers for LEDs, buttons and speaker: */
/* If these are changed, the gpio used in fn main() also need to be changed. */
const DATA: u8 = 19;
const CLOCK: u8 = 9;
const LATCH: u8 = 18;
const BUZZER: u8 = 10;
const LED_0: u8 = 6;
const LED_1: u8 = 5;
const LED_2: u8 = 7;
const LED_3: u8 = 8;
const BUTTON_0: u8 = 2;
const BUTTON_1: u8 = 3;
const BUTTON_2: u8 = 1;
const BUTTON_3: u8 = 0;

/*  
    SIM_SPEED set for 85% simulation speed. Adjust accordingly.
    40 works well for 25% simulation speed.
    200 works well for 100% simulation speed.
*/
const SIM_SPEED: u64 = 160;

/* Digit table for the 7-segment display */
const DIGITS: [usize; 10] = [
    0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110,
    0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111
];
const DASH: usize = 0b01000000;

fn display_score(
    value: usize,
    game_over: bool,
    delay: &mut Delay,
    data: &mut GpioPin<Output<PushPull>, DATA>,
    clock: &mut GpioPin<Output<PushPull>, CLOCK>,
    latch: &mut GpioPin<Output<PushPull>, LATCH>
){
    let right_index: usize = (value % 10).into();
    let left_index: usize = (value / 10).into();

    let mut left_digit: usize = DIGITS[left_index];
    let mut right_digit: usize = DIGITS[right_index];
    
    if left_index == 0
    {
        left_digit = 0b00000000;
    }

    if game_over 
    {
        left_digit = DASH;
        right_digit = DASH;
    }

    let mut j = 7;

    for _i in 0..8
    {
        if (right_digit >> j) & 1 == 0
        {
            data.set_high().unwrap();
        }
        else
        {
            data.set_low().unwrap();
        }

        clock.set_high().unwrap();
        delay.delay_ms(10u32);
        clock.set_low().unwrap();
        j -= 1;
    }

    j = 7;

    for _i in 0..8
    {
        if (left_digit >> j) & 1 == 0
        {
            data.set_high().unwrap();
        }
        else
        {
            data.set_low().unwrap();
        }

        clock.set_high().unwrap();
        delay.delay_ms(10u32);
        clock.set_low().unwrap();
        j -= 1;
    }

    latch.set_high().unwrap();
    delay.delay_ms(10u32);
    latch.set_low().unwrap();
}

/*
   Plays the current sequence of notes that the user has to repeat
*/
fn play_sequence(
    game_index: usize,
    rtc: &mut Rtc,
    delay: &mut Delay,
    sequence: &mut [usize; MAX_GAME_LENGTH],
    led_0: &mut GpioPin<Output<PushPull>, LED_0>,
    led_1: &mut GpioPin<Output<PushPull>, LED_1>,
    led_2: &mut GpioPin<Output<PushPull>, LED_2>,
    led_3: &mut GpioPin<Output<PushPull>, LED_3>,
    buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
){
    let mut j: usize = 0;

    for _i in 0..game_index 
    {
      let current_led: usize = sequence[j];

      match current_led 
      {
        0=>led_0.set_high().unwrap(),
        1=>led_1.set_high().unwrap(),
        2=>led_2.set_high().unwrap(),
        _=>led_3.set_high().unwrap()
      } 
      
      tone(TONES[current_led], SIM_SPEED, rtc, delay, buzzer);

      led_0.set_low().unwrap();
      led_1.set_low().unwrap();
      led_2.set_low().unwrap();
      led_3.set_low().unwrap();

      delay.delay_ms(50u32);
      j += 1;
    }
}

/*
    Waits until the user pressed one of the buttons,
    and returns the index of that button
*/
fn read_buttons(
    delay: &mut Delay,
    btn_0: &mut GpioPin<Input<PullUp>, BUTTON_0>,
    btn_1: &mut GpioPin<Input<PullUp>, BUTTON_1>,
    btn_2: &mut GpioPin<Input<PullUp>, BUTTON_2>,
    btn_3: &mut GpioPin<Input<PullUp>, BUTTON_3>
) -> usize{
    loop
    {
      if btn_0.is_low().unwrap()
      {
        return 0;
      }
      else if btn_1.is_low().unwrap()
      {
        return 1;
      }
      else if btn_2.is_low().unwrap()
      {
        return 2;
      }
      else if btn_3.is_low().unwrap()
      {
        return 3;
      }
      delay.delay_ms(10u32);
    }
}

/*
   Get the user's input and compare it with the expected sequence.
*/
fn check_sequence(
    game_index: usize,
    rtc: &mut Rtc,
    delay: &mut Delay,
    sequence: &mut [usize; MAX_GAME_LENGTH],
    led_0: &mut GpioPin<Output<PushPull>, LED_0>,
    led_1: &mut GpioPin<Output<PushPull>, LED_1>,
    led_2: &mut GpioPin<Output<PushPull>, LED_2>,
    led_3: &mut GpioPin<Output<PushPull>, LED_3>,
    btn_0: &mut GpioPin<Input<PullUp>, BUTTON_0>,
    btn_1: &mut GpioPin<Input<PullUp>, BUTTON_1>,
    btn_2: &mut GpioPin<Input<PullUp>, BUTTON_2>,
    btn_3: &mut GpioPin<Input<PullUp>, BUTTON_3>,
    buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
) -> bool {
    for i in 0..game_index 
    {
        let expected_button = sequence[i];
        let actual_button = read_buttons(delay, btn_0, btn_1, btn_2, btn_3);

        match actual_button 
        {
            0=>led_0.set_high().unwrap(),
            1=>led_1.set_high().unwrap(),
            2=>led_2.set_high().unwrap(),
            _=>led_3.set_high().unwrap()
        } 
        
        tone(TONES[actual_button], SIM_SPEED, rtc, delay, buzzer);

        led_0.set_low().unwrap();
        led_1.set_low().unwrap();
        led_2.set_low().unwrap();
        led_3.set_low().unwrap();

        delay.delay_ms(50u32);

        if expected_button != actual_button
        {
            return false;
        }
  }

  return true;
}

/*
  Play the game over sequence, and report the game score
*/
fn game_over(
    game_index: usize,
    rtc: &mut Rtc,
    delay: &mut Delay,
    buzzer: &mut GpioPin<Output<PushPull>, 10>
){
    println!("Game over! your score: {}", game_index - 1);
    delay.delay_ms(200u32);
  
    // Play a Wah-Wah-Wah-Wah sound
    tone(622, SIM_SPEED * 2, rtc, delay, buzzer);
    tone(587, SIM_SPEED * 2, rtc, delay, buzzer);
    tone(554, SIM_SPEED * 2, rtc, delay, buzzer);

    for _i in 0..10
    {
        let mut j: i32 = -10;
        for _k in 0..20
        {
            tone(
                (523 + j).try_into().unwrap(),
                SIM_SPEED / 20,
                rtc,
                delay,
                buzzer
            );
            j += 1;
        }
    }
}

/* Plays a hooray sound whenever the user finishes a level */
fn play_level_up_sound(
    rtc: &mut Rtc,
    delay: &mut Delay,
    buzzer: &mut GpioPin<Output<PushPull>, BUZZER>
){
    tone(330, SIM_SPEED, rtc, delay, buzzer);
    tone(392, SIM_SPEED, rtc, delay, buzzer);
    tone(659, SIM_SPEED, rtc, delay, buzzer);
    tone(523, SIM_SPEED, rtc, delay, buzzer);
    tone(587, SIM_SPEED, rtc, delay, buzzer);
    tone(784, SIM_SPEED, rtc, delay, buzzer);
}

/* Replacement for Arduino tone() function. */
fn tone(
    note: usize,
    duration: u64,
    rtc: &mut Rtc,
    delay: &mut Delay,
    buzzer: &mut GpioPin<Output<PushPull>, 10>
){
    let frequency: u32 = (1000000 / note).try_into().unwrap();
	let duty = frequency / 2;
    let start_time = rtc.get_time_ms();
    while rtc.get_time_ms() - start_time < duration
    {
        buzzer.set_high().unwrap();
        delay.delay_us(duty);
        buzzer.set_low().unwrap();
        delay.delay_us(frequency - duty);
    }
}

/* Generates a random color to add to the sequence. */
fn random_color( rng: &mut Rng ) -> usize {
    let rand = rng.random();

    if rand < u32::MAX / 4
    {
        return 0;
    }
    else if rand > u32::MAX / 4 && rand < u32::MAX / 2
    {
        return 1;
    }
    else if rand > u32::MAX / 2 && rand < (u32::MAX / 2) + (u32::MAX / 4)
    {
        return 2;
    }
    else
    {
        return 3;
    }
}

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take();
    let mut system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    /* Disable the watchdog timers. */
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt0 = timer_group0.wdt;
    let timer_group1 = TimerGroup::new(
        peripherals.TIMG1,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt1 = timer_group1.wdt;

    rtc.swd.disable();
    rtc.rwdt.disable();
    wdt0.disable();
    wdt1.disable();
    /*----------------------------------------------------*/

    /* Define pin numbers for LEDs, buttons and speaker: */
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let mut data = io.pins.gpio19.into_push_pull_output();
    let mut clock = io.pins.gpio9.into_push_pull_output();
    let mut latch = io.pins.gpio18.into_push_pull_output();
    let mut buzzer = io.pins.gpio10.into_push_pull_output();
    let mut led_0 = io.pins.gpio6.into_push_pull_output();
    let mut led_1 = io.pins.gpio5.into_push_pull_output();
    let mut led_2 = io.pins.gpio7.into_push_pull_output();
    let mut led_3 = io.pins.gpio8.into_push_pull_output();
    let mut btn_0 = io.pins.gpio2.into_pull_up_input();
    let mut btn_1 = io.pins.gpio3.into_pull_up_input();
    let mut btn_2 = io.pins.gpio1.into_pull_up_input();
    let mut btn_3 = io.pins.gpio0.into_pull_up_input();
    /*----------------------------------------------------*/

    /* Setup RNG and delay capability */
    let mut rng = Rng::new(peripherals.RNG);
    let mut delay = Delay::new(&clocks);

    /* Global variables - store the game state */
    let mut game_sequence: [usize; MAX_GAME_LENGTH] = [0; MAX_GAME_LENGTH];
    let mut game_index: usize = 0;

    let mut initialized = false;

    /* The main game loop */
    loop 
    {
        if !initialized
        {
            let delay_time: u32 = (SIM_SPEED * 10).try_into().unwrap();
            delay.delay_ms(delay_time);
            initialized = true;
        }

        display_score(
            game_index,
            false,
            &mut delay,
            &mut data,
            &mut clock,
            &mut latch
        );

        /* Add a random color to the end of the sequence */
        game_sequence[game_index] = random_color(&mut rng);
        game_index += 1;
        if game_index >= MAX_GAME_LENGTH
        {
            game_index = MAX_GAME_LENGTH - 1;
        }

        play_sequence(
            game_index,
            &mut rtc,
            &mut delay,
            &mut game_sequence,
            &mut led_0,
            &mut led_1,
            &mut led_2,
            &mut led_3,
            &mut buzzer
        );

        if !check_sequence(
            game_index,
            &mut rtc,
            &mut delay, 
            &mut game_sequence,
            &mut led_0,
            &mut led_1,
            &mut led_2,
            &mut led_3,
            &mut btn_0,
            &mut btn_1,
            &mut btn_2,
            &mut btn_3,
            &mut buzzer
        ){
            game_over(
                game_index,
                &mut rtc,
                &mut delay, 
                &mut buzzer
            );

            display_score(game_index,
                true,
                &mut delay,
                &mut data,
                &mut clock,
                &mut latch
            );

            let delay_time: u32 = (SIM_SPEED * 10).try_into().unwrap();
            delay.delay_ms(delay_time);
            game_index = 0;
        }
      
        let delay_time: u32 = (SIM_SPEED * 2).try_into().unwrap();
        delay.delay_ms(delay_time);
      
        if game_index > 0
        {
            play_level_up_sound(&mut rtc, &mut delay, &mut buzzer);
            let delay_time: u32 = (SIM_SPEED * 2).try_into().unwrap();
            delay.delay_ms(delay_time);
        }
        
        let delay_time: u32 = SIM_SPEED.try_into().unwrap();
        delay.delay_ms(delay_time);
    }
}
74HC595
74HC595