// // Created by MightyPork on 2017/06/08. // originally written 2016/9/2 for STM32F103 bluepill, adapted // #include "game.h" #include #include #include #include #include #include #include "lib/color.h" #include "leds.h" #include "lib/timebase.h" #include "display.h" #include "pinout.h" #include "rng.h" //region Colors #define C_DARK rgb24(0,0,0) #define C_DIMWHITE rgb24(30,30,30) #define C_OKGREEN rgb24(20,160,0) #define C_CRIMSON rgb24(220,0,5) #define C_DIMRED rgb24(100,0,0) #define C_DIMGREEN rgb24(0,100,0) #define C_DIMBLUE rgb24(0,0,80) #define C_DIMYELLOW rgb24(50,45,0) #define C_BRTRED rgb24(255,0,0) #define C_BRTGREEN rgb24(0,255,0) #define C_BRTBLUE rgb24(0,0,255) #define C_BRTYELLOW rgb24(127,110,0) // assign to positions #define C_DIM1 C_DIMRED #define C_DIM2 C_DIMGREEN #define C_DIM3 C_DIMBLUE #define C_DIM4 C_DIMYELLOW #define C_BRT1 C_BRTRED #define C_BRT2 C_BRTGREEN #define C_BRT3 C_BRTBLUE #define C_BRT4 C_BRTYELLOW //endregion enum GameState_enum { STATE_NEW_GAME, // new game, waiting for key STATE_REPLAY, // showing sequence STATE_USER_INPUT, // waiting for user input of repeated sequence STATE_SUCCESS_EFFECT, // entered OK, show some fireworks STATE_FAIL_EFFECT, // entered wrong, show FAIL animation, then reset. }; /** Current game state */ enum GameState_enum GameState = STATE_NEW_GAME; volatile bool holding_new_game_button = false; /** Screen colors */ uint32_t screen[4] = {0, 0, 0, 0}; const uint32_t brt[4] = {C_BRT1, C_BRT2, C_BRT3, C_BRT4}; const uint32_t dim[4] = {C_DIM1, C_DIM2, C_DIM3, C_DIM4}; const uint32_t dark[4] = {C_DARK, C_DARK, C_DARK, C_DARK}; const uint32_t dimwhite[4] = {C_DIMWHITE, C_DIMWHITE, C_DIMWHITE, C_DIMWHITE}; #define REPLAY_INTERVAL 400 #define REPLAY_INTERVAL_GAP 75 #define SUC_EFF_TIME 500 #define FAIL_EFF_TIME 1000 /** Nr of revealed colors in sequence */ uint8_t game_revealed_n; /** Nr of next color to replay/input */ uint8_t game_replay_n; /** Nr of succ repeated colors */ uint8_t game_repeat_n; void enter_state(enum GameState_enum state); /** Show current screen colors */ void show_screen() { leds_set(screen); } /** Enter state - callback for delayed state change */ void deferred_enter_state(void *state) { // clear flag that button was held holding_new_game_button = false; enter_state((enum GameState_enum) state); } /** Future task CB in replay seq */ void replay_callback(void *onOff) { bool on = (bool) onOff; screen[0] = C_DARK; screen[1] = C_DARK; screen[2] = C_DARK; screen[3] = C_DARK; if (on) { uint8_t color = rng_next_item(); game_replay_n++; screen[color] = brt[color]; show_screen(); schedule_task(replay_callback, (void *) 0, REPLAY_INTERVAL, false); } else { // turning off show_screen(); // Schedule next turning ON if (game_replay_n < game_revealed_n) { schedule_task(replay_callback, (void *) 1, REPLAY_INTERVAL_GAP, false); } else { enter_state(STATE_USER_INPUT); //schedule_task(deferred_enter_state, (void *) STATE_USER_INPUT, 50, false); } } } /** SUCCESS effect */ void suc_eff_callback(void *onOff) { bool on = (bool) onOff; if (on) { display_show_number(game_revealed_n-1); for (uint8_t i = 0; i < 4; i++) screen[i] = C_OKGREEN; schedule_task(suc_eff_callback, 0, SUC_EFF_TIME, false); } else { for (uint8_t i = 0; i < 4; i++) screen[i] = C_DARK; schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 250, false); } show_screen(); } /** ERROR effect */ void fail_eff_callback(void *onOff) { bool on = (bool) onOff; if (on) { for (int i = 0; i < 4; i++) screen[i] = C_CRIMSON; schedule_task(fail_eff_callback, 0, FAIL_EFF_TIME, false); } else { for (int i = 0; i < 4; i++) screen[i] = C_DARK; schedule_task(deferred_enter_state, (void *) STATE_NEW_GAME, 250, false); } show_screen(); } /** * @brief Enter a game state * @param state */ void enter_state(enum GameState_enum state) { GameState = state; switch (state) { case STATE_NEW_GAME: usart_puts("State: new game\r\n"); // new game - idle state before new game is started // all dimly lit for (int i = 0; i < 4; i++) screen[i] = 0; //C_DIMWHITE break; case STATE_REPLAY: usart_puts("State: replay\r\n"); game_replay_n = 0; rng_restart(); // Start replay replay_callback((void *) 1); break; case STATE_USER_INPUT: usart_puts("State: repeat\r\n"); memcpy(screen, dim, sizeof(screen)); // Start entering & checking game_repeat_n = 0; rng_restart(); break; case STATE_SUCCESS_EFFECT: usart_puts("State: succ\r\n"); memcpy(screen, dim, sizeof(screen)); //suc_eff_callback((void *) 1); schedule_task(suc_eff_callback, (void *) 1, 250, false); break; case STATE_FAIL_EFFECT: usart_puts("State: fail\r\n"); memcpy(screen, dim, sizeof(screen)); //fail_eff_callback((void *) 1); schedule_task(fail_eff_callback, (void *) 1, 250, false); break; } show_screen(); } /** Prepare new sequence, using time for seed. */ void prepare_sequence() { rng_set_seed(time_ms); rng_restart(); } volatile uint16_t idle_cnt = 0; /** game main function */ void game_main(void) { display_show(SEG_G, SEG_G); // two dashes... enter_state(STATE_NEW_GAME); // we'll init the sequence when user first presses a button - the time is used as a seed enum GameState_enum last_state = STATE_NEW_GAME; while (1) { if (GameState == last_state) { if (GameState == STATE_NEW_GAME) { if (idle_cnt == 25 && !holding_new_game_button) { usart_puts("clear highscore display\r\n"); display_show(SEG_G, SEG_G); } if (idle_cnt == 3000) { usart_puts("automatic shutdown\r\n"); screen[0] = C_CRIMSON; screen[1] = C_CRIMSON; screen[2] = C_CRIMSON; screen[3] = C_CRIMSON; show_screen(); delay_s(1); pin_down(PIN_PWR_HOLD); while(1); // wait for shutdown } } else { if (idle_cnt > 200) { // reset state usart_puts("game reset, user walked away\r\n"); enter_state(STATE_NEW_GAME); show_screen(); display_show(SEG_G, SEG_G); idle_cnt = 0; } } } else { last_state = GameState; idle_cnt = 0; } idle_cnt++; delay_ms(100); } } /** * @brief Handle a button press. Callback for debouncer. * @param button: button identifier * @param press: press state (1 = just pressed, 0 = just released) */ void game_button_handler(uint8_t button, bool press) { // convert to 0-3 button--; switch (GameState) { case STATE_NEW_GAME: if (press) { usart_puts("pressed a new-game button\r\n"); // feedback display_show_number(0); // show 0 holding_new_game_button = true; } if (!press) { // released usart_puts("game begins\r\n"); // user wants to start playing prepare_sequence(); game_revealed_n = 1; // start with 1 revealed // darken //memcpy(screen, dark, sizeof(screen)); //show_screen(); // start playback with a delay // this makes it obvious the playback is not a feedback to the pressed button schedule_task(deferred_enter_state, (void *) STATE_REPLAY, 500, false); //enter_state(STATE_REPLAY); } break; case STATE_USER_INPUT: // Reset idle counter, so it doesn't cut off in the middle of input idle_cnt = 0; // user is entering a color memcpy(screen, dim, sizeof(screen)); if (press) { // Button is down screen[button] = brt[button]; } else { // Button is released // Verify correctness uint8_t expected = rng_next_item(); if (expected == button) { usart_puts("good key!\r\n"); game_repeat_n++; if (game_repeat_n == game_revealed_n) { usart_puts("repeated all, good work!\r\n"); game_revealed_n++; enter_state(STATE_SUCCESS_EFFECT); } } else { usart_puts("oops bad key\r\n"); enter_state(STATE_FAIL_EFFECT); } } show_screen(); break; default: usart_puts("discard button press, not expecting input now\r\n"); break; } }