#include "step3.h" #define ONE_SECOND_TIME 90'000 #define THREE_SECOND_TIME 90'000 #define SIX_SECOND_TIME 75'000 #define TIMES_TO_COMPLETE 4 __attribute__((unused)) static const char *TAG = "step3"; static int tone = 0; static int times = 0; static const char* TONE_FILES[] = { MOUNT_POINT "/low-1.wav", MOUNT_POINT "/low-3.wav", MOUNT_POINT "/low-6.wav", MOUNT_POINT "/high-1.wav", MOUNT_POINT "/high-3.wav", MOUNT_POINT "/high-6.wav", }; static const size_t LCD_STRING_SOMETHING = 0; static const size_t LCD_STRING_NOTHING = 1; static const char* LCD_STRINGS[] = { "something", "nothing", "", "a word", "somethink", "what?", "LCD", "display", }; static int indicator_led_idxs[LED_COUNT] = {0}; static bool contains_coconut = false; static const char* COCONUT = "coconut"; static char lcd_random_char_set[] = "aeiou tnsrhldm"; static char random_lcd_text[21] = {0}; static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_int_distribution<> tone_dist(0, 5); static std::uniform_int_distribution<> color_dist(0, 6); static std::uniform_int_distribution<> lcd_string_dist(0, 7); static std::uniform_int_distribution<> lcd_number_dist(0, 15); static std::uniform_int_distribution<> lcd_rand_char_dist(0, sizeof(lcd_random_char_set)-2); static std::uniform_int_distribution<> has_coconut_dist(0, 2); static std::uniform_int_distribution<> coconut_position_dist(0, 13); const static uint32_t NEOPIXEL_COLOR_IDX_RED = 0; const static uint32_t NEOPIXEL_COLOR_IDX_YELLOW = 1; const static uint32_t NEOPIXEL_COLOR_IDX_GREEN = 2; const static uint32_t NEOPIXEL_COLOR_IDX_BLUE = 3; const static uint32_t NEOPIXEL_COLOR_IDX_PINK = 4; const static uint32_t NEOPIXEL_COLOR_IDX_WHITE = 5; const static uint32_t NEOPIXEL_COLOR_IDX_OFF = 6; static uint32_t NEOPIXEL_COLORS[7] = { LEDColor::LED_COLOR_RED, LEDColor::LED_COLOR_YELLOW, LEDColor::LED_COLOR_GREEN, LEDColor::LED_COLOR_BLUE, LEDColor::LED_COLOR_PINK, LEDColor::LED_COLOR_WHITE, LEDColor::LED_COLOR_OFF, }; static bool one_second(); static bool three_second(); static bool six_second(); void step3(void) { SemaphoreHandle_t continue_sem = xSemaphoreCreateBinary(); if (continue_sem == nullptr) { ESP_LOGE(TAG, "could not create semaphore"); return; } StarCodeEntry start_code = { .code = "1642", .display_text = "Starting...", .delay_ticks = pdMS_TO_TICKS(2000), .callback = nullptr, .triggered_sem = continue_sem, }; add_star_code(start_code); xSemaphoreTake(continue_sem, portMAX_DELAY); rm_star_code(start_code.code); vSemaphoreDelete(continue_sem); while (times < TIMES_TO_COMPLETE) { tone = tone_dist(gen); // tone = 2; while (get_button_pressed(nullptr)) vTaskDelay(pdMS_TO_TICKS(10)); play_clip_wav(MOUNT_POINT "/ready.wav", true, false, 3, 0); // The high pitched tones need to be scaled down by 3 more play_clip_wav(TONE_FILES[tone], false, false, 1 + (tone/3) * 4, 0); bool correct = false; switch (tone % 3) { case 0: correct = one_second(); break; case 1: correct = three_second(); break; case 2: correct = six_second(); break; } if (correct) { times++; clean_bomb(); if (times < TIMES_TO_COMPLETE) { play_clip_wav(MOUNT_POINT "/partdone.wav", true, false, 0, 0); } else { play_clip_wav(MOUNT_POINT "/stepdone.wav", true, false, 1, 0); } } else { vTaskDelay(pdMS_TO_TICKS(1500)); } vTaskDelay(pdMS_TO_TICKS(3000)); } } static void generate_random_lcd_text(void) { for (int i = 0; i < 20; i++) { int char_idx = lcd_rand_char_dist(gen); random_lcd_text[i] = lcd_random_char_set[char_idx]; } contains_coconut = (has_coconut_dist(gen) == 0); if (contains_coconut) { int idx = coconut_position_dist(gen); for (int i = 0; i < 7; i++) { random_lcd_text[idx+i] = COCONUT[i]; // ESP_LOGI(TAG, "Writing idx %d to %c. Is %c", idx+i, COCONUT[i], random_lcd_text[idx+i]); } // ESP_LOGI(TAG, "Now: %s", random_lcd_text); } } /// Sets the leds to random values. /// /// This does not flush the leds. static void rng_leds() { for (int i = 0; i < LED_COUNT; i++) { indicator_led_idxs[i] = color_dist(gen); } } static void write_leds() { // update all the leds for (int i = 0; i < LED_COUNT; i++) { led_set(i, NEOPIXEL_COLORS[indicator_led_idxs[i]]); } leds_flush(); } static uint8_t four_bit_flag(bool b0, bool b1, bool b2, bool b3) { return (b0 << 0) | (b1 << 1) | (b2 << 2) | (b3 << 3) ; } static void print_4bin(char* out_str, uint8_t n) { out_str[0] = ((n & 0b1000) ? '1' : '0'); out_str[1] = ((n & 0b0100) ? '1' : '0'); out_str[2] = ((n & 0b0010) ? '1' : '0'); out_str[3] = ((n & 0b0001) ? '1' : '0'); out_str[4] = ' '; out_str[5] = 'i'; out_str[6] = 'n'; out_str[7] = ' '; out_str[8] = 'o'; out_str[9] = 'r'; out_str[10] = 'd'; out_str[11] = 'e'; out_str[12] = 'r'; out_str[13] = ':'; out_str[14] = ' '; out_str[15] = ((n & 0b0001) ? '1' : '0'); out_str[16] = ((n & 0b0010) ? '1' : '0'); out_str[17] = ((n & 0b0100) ? '1' : '0'); out_str[18] = ((n & 0b1000) ? '1' : '0'); } static void debug_correct_values(uint8_t correct_buttons, uint8_t button_mask, uint8_t correct_switches) { char buf[20] = {0}; print_4bin(buf, correct_switches); ESP_LOGI(TAG, "Expected Switch State: 0b%s", buf); print_4bin(buf, correct_buttons); ESP_LOGI(TAG, "Expected Button State: 0b%s", buf); print_4bin(buf, button_mask); ESP_LOGI(TAG, "Button Mask: 0b%s", buf); } static void debug_actual_values(uint8_t buttons, uint8_t switch_) { char buf[20] = {0}; print_4bin(buf, switch_); ESP_LOGI(TAG, "Actual Switch State: 0b%s", buf); print_4bin(buf, buttons); ESP_LOGI(TAG, "Actual Button State: 0b%s", buf); ESP_LOGI(TAG, ""); } static void wait_for_timer(void) { KeypadKey key; while (get_module_time() > 0) { if (get_keypad_pressed(&key) && key == KeypadKey::kd) { set_module_time(0); return; } vTaskDelay(pdMS_TO_TICKS(100)); } } static bool one_second() { clean_bomb(); set_module_time(ONE_SECOND_TIME); start_module_timer(); rng_leds(); int speaker_color = indicator_led_idxs[IndicatorLED::LED_SPEAKER]; int lcd_string_idx = lcd_string_dist(gen); bool was_high = (tone / 3) == 1; write_leds(); lcd_clear(); lcd_print(1, 1, LCD_STRINGS[lcd_string_idx]); int red_led_count = 0; int blue_led_count = 0; for (int i = 0; i < LED_COUNT; i++) { if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_RED) { red_led_count++; } else if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_BLUE) { blue_led_count++; } } uint8_t correct_switches = four_bit_flag( speaker_color == NEOPIXEL_COLOR_IDX_RED || speaker_color == NEOPIXEL_COLOR_IDX_YELLOW || speaker_color == NEOPIXEL_COLOR_IDX_PINK, lcd_string_idx == LCD_STRING_SOMETHING || lcd_string_idx == LCD_STRING_NOTHING, was_high, !was_high ); uint8_t correct_button_mask = 0b1011; uint8_t correct_buttons = four_bit_flag( indicator_led_idxs[IndicatorLED::LED_LCD] != 6, // green red_led_count > blue_led_count, // red 0, // yellow UNCHECKED indicator_led_idxs[IndicatorLED::LED_RFID] == 4 || indicator_led_idxs[IndicatorLED::LED_RFID] == 6 // blue ); debug_correct_values(correct_buttons, correct_button_mask, correct_switches); wait_for_timer(); debug_actual_values(get_button_state(), get_switch_state()); if (get_switch_state() != correct_switches) { clean_bomb(); strike("Incorrect Switches"); return false; } if ((get_button_state() & correct_button_mask) != correct_buttons) { clean_bomb(); strike("Incorrect Buttons"); return false; } return true; } static bool three_second() { clean_bomb(); set_module_time(THREE_SECOND_TIME); start_module_timer(); int lcd_number = lcd_number_dist(gen); char lcd_number_string[9] = {0}; sprintf(lcd_number_string, "%d", lcd_number); lcd_print(1, 1, lcd_number_string); bool was_high = (tone / 3) == 1; rng_leds(); write_leds(); int red_led_count = 0; int blue_led_count = 0; for (int i = 0; i < LED_COUNT; i++) { if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_RED) { red_led_count++; } else if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_BLUE) { blue_led_count++; } } // reverse the ordering of the bits uint8_t correct_switches = four_bit_flag( (lcd_number >> 3) & 1, (lcd_number >> 2) & 1, (lcd_number >> 1) & 1, (lcd_number >> 0) & 1 ); if (!was_high) { correct_switches = (~correct_switches) & 0b1111; } uint8_t correct_button_mask = 0b1110; uint8_t correct_buttons = four_bit_flag( 0, // green UNCHECKED was_high, // red (lcd_number % 2) == 0, // yellow blue_led_count > red_led_count // blue ); debug_correct_values(correct_buttons, correct_button_mask, correct_switches); wait_for_timer(); debug_actual_values(get_button_state(), get_switch_state()); if (get_switch_state() != correct_switches) { clean_bomb(); strike("Incorrect Switches"); return false; } if ((get_button_state() & correct_button_mask) != correct_buttons) { clean_bomb(); strike("Incorrect Buttons"); return false; } return true; } static bool six_second() { clean_bomb(); set_module_time(SIX_SECOND_TIME); start_module_timer(); generate_random_lcd_text(); vTaskDelay(pdMS_TO_TICKS(10)); lcd_print(0, 0, random_lcd_text); int vowels = 0; for (int i = 0; i < 20; i++) { char c = random_lcd_text[i]; if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { vowels++; } } bool was_high = (tone / 3) == 1; bool second_switch_correct_state = (indicator_led_idxs[IndicatorLED::LED_S2] == NEOPIXEL_COLOR_IDX_RED) || (indicator_led_idxs[IndicatorLED::LED_S2] == NEOPIXEL_COLOR_IDX_OFF); second_switch_correct_state = second_switch_correct_state || was_high; rng_leds(); write_leds(); int green_led_count = 0; int blue_led_count = 0; for (int i = 0; i < LED_COUNT; i++) { if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_BLUE) { blue_led_count++; } else if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_GREEN) { green_led_count++; } } int pink_led_on_bottom_count = 0; for (int i = IndicatorLED::LED_RFID; i < LED_COUNT; i++) { if (indicator_led_idxs[i] == NEOPIXEL_COLOR_IDX_PINK) { pink_led_on_bottom_count++; } } uint8_t correct_switches = four_bit_flag( vowels > 7, second_switch_correct_state, true, !(pink_led_on_bottom_count > 1) ); uint8_t correct_button_mask = 0b1101; uint8_t correct_buttons = four_bit_flag( (!was_high) || (green_led_count >= 2) || indicator_led_idxs[IndicatorLED::LED_KEYPAD] == 4, // green 0, // red UNCHECKED blue_led_count >= 3, // yellow contains_coconut // blue ); debug_correct_values(correct_buttons, correct_button_mask, correct_switches); wait_for_timer(); debug_actual_values(get_button_state(), get_switch_state()); if (get_switch_state() != correct_switches) { clean_bomb(); strike("Incorrect Switches"); return false; } if ((get_button_state() & correct_button_mask) != correct_buttons) { clean_bomb(); strike("Incorrect Buttons"); return false; } return true; }