// // 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) #define C_DIMRED rgb24((int)(255*0.3),0,0) #define C_DIMGREEN rgb24(0,(int)(255*0.3),0) #define C_DIMBLUE rgb24(0,0,(int)(255*0.3)) #define C_DIMYELLOW rgb24((int)(127*0.3),(int)(110*0.3),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_GAME_STARTING, // new game, initial anim 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 */ static enum GameState_enum GameState = STATE_NEW_GAME; static volatile bool holding_new_game_button = false; /** Screen colors */ static uint32_t screen[4] = {0, 0, 0, 0}; static const uint32_t brt[4] = {C_BRT1, C_BRT2, C_BRT3, C_BRT4}; static const uint32_t dim[4] = {C_DIM1, C_DIM2, C_DIM3, C_DIM4}; //static const uint32_t dark[4] = {C_DARK, C_DARK, C_DARK, C_DARK}; //static 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 /** Nr of revealed colors in sequence */ static uint8_t game_revealed_n; /** Nr of next color to replay/input */ static uint8_t game_replay_n; /** Nr of succ repeated colors */ static uint8_t game_repeat_n; static void enterGameState(enum GameState_enum state); void idle_anim_init(void); void idle_anim(void) ; /** Show current screen colors */ static void show_screen() { leds_set(screen); } /** Enter state - callback for delayed state change */ static void delayedEnterStateCb(void *state) { // clear flag that button was held holding_new_game_button = false; enterGameState((enum GameState_enum) state); } /** starting a game */ static void newGameCountdownCb(void *state) { int steps = (int) state; if (steps == 0) { display_show(0,0); // clear all // start playback with a delay // this makes it obvious the playback is not a feedback to the pressed button schedule_task(delayedEnterStateCb, (void *) STATE_REPLAY, 500, false); return; } display_show_number((uint8_t) steps); steps--; schedule_task(newGameCountdownCb, (void *) steps, 500, false); } static void fadeInColorsCb(void *stepsToGo) { uint8_t steps = (uint8_t) (int) stepsToGo; if (led_brightness_mul < 255) led_brightness_mul += 1; steps -= 1; if (steps>0) { schedule_task(fadeInColorsCb, (void *) (int) steps, 1, false); } } static void fadeOutColorsCb(void *stepsToGo) { uint8_t steps = (uint8_t) (int) stepsToGo; if (led_brightness_mul > 0) led_brightness_mul -= 1; steps -= 1; if (steps>0) { schedule_task(fadeOutColorsCb, (void *) (int) steps, 1, false); } } /** Future task CB in replay seq */ static void replaySequenceCb(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(replaySequenceCb, (void *) 0, REPLAY_INTERVAL, false); } else { // turning off show_screen(); // Schedule next turning ON if (game_replay_n < game_revealed_n) { schedule_task(replaySequenceCb, (void *) 1, REPLAY_INTERVAL_GAP, false); } else { // fade in the input hints led_brightness_mul = 0; memcpy(screen, dim, sizeof(screen)); show_screen(); enterGameState(STATE_USER_INPUT); schedule_task(fadeInColorsCb, (void *) 255, 350, false); } } } /** SUCCESS effect */ static void successEffectCb(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(successEffectCb, 0, SUC_EFF_TIME, false); } else { for (uint8_t i = 0; i < 4; i++) screen[i] = C_DARK; schedule_task(delayedEnterStateCb, (void *) STATE_REPLAY, 250, false); } show_screen(); } /** ERROR effect */ static void failEffectCb(void *cnt) { int cn = (int)cnt; bool on = ((cn)%2)==1; if (on) { for (int i = 0; i < 4; i++) screen[i] = C_CRIMSON; } else { for (int i = 0; i < 4; i++) screen[i] = C_DARK; } show_screen(); cn--; if (cn == 0) { schedule_task(delayedEnterStateCb, (void *) STATE_NEW_GAME, 250, false); } else { schedule_task(failEffectCb, (void *) cn, 250, false); } } /** Prepare new sequence, using time for seed. */ static void prepareNewSequence() { rng_set_seed(time_ms); rng_restart(); } task_pid_t fadeout_pid; task_pid_t fadein_pid; volatile bool first_start = true; /** * @brief Enter a game state * @param state */ static void enterGameState(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 for (int i = 0; i < 4; i++) screen[i] = 0; led_brightness_mul = 0; // fading in... if (first_start) { abort_scheduled_task(fadeout_pid); fadein_pid = schedule_task(fadeInColorsCb, (void *) 255, 1, false); } break; case STATE_GAME_STARTING: first_start = false; usart_puts("game begins\r\n"); // user wants to start playing prepareNewSequence(); game_revealed_n = 1; // start with 1 revealed abort_scheduled_task(fadein_pid); fadeout_pid = schedule_task(fadeOutColorsCb, (void *) 255, 1, false); schedule_task(newGameCountdownCb, (void *) 5, 500, false); break; case STATE_REPLAY: led_brightness_mul = 255; usart_puts("State: replay\r\n"); game_replay_n = 0; rng_restart(); display_show_number(game_revealed_n-1); // Start replay replaySequenceCb((void *) 1); break; case STATE_USER_INPUT: usart_puts("State: repeat\r\n"); // 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(successEffectCb, (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(failEffectCb, (void *) 5, 250, false); break; } show_screen(); } volatile uint16_t idle_cnt = 0; /** game main function */ void game_main(void) { idle_anim_init(); display_show(0, 0); enterGameState(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) { // loop period is 100 ms if (GameState == last_state) { if (GameState == STATE_NEW_GAME) { if (!first_start && idle_cnt == 30 && !holding_new_game_button) { usart_puts("clear highscore display\r\n"); display_show(0, 0); // start fading abort_scheduled_task(fadeout_pid); fadein_pid = schedule_task(fadeInColorsCb, (void *) 255, 1, false); } 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 > 300) { // reset state usart_puts("game reset, user walked away\r\n"); enterGameState(STATE_NEW_GAME); show_screen(); display_show(0, 0); idle_cnt = 49; // fade in rainbow quickly } } } else { last_state = GameState; idle_cnt = 0; } idle_cnt++; for (int i = 0; i < 10; i++) { delay_ms(10); if (GameState == STATE_NEW_GAME) { // we can do some idle animation here idle_anim(); } } } } /** * @brief Handle a button press. Callback for debouncer. * @param button: button identifier * @param press: press state (1 = just pressed, 0 = just released) */ void onGameButton(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(SEG_D|SEG_E|SEG_F|SEG_A, SEG_A|SEG_B|SEG_C|SEG_D); holding_new_game_button = true; } if (!press) { // released enterGameState(STATE_GAME_STARTING); } 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++; enterGameState(STATE_SUCCESS_EFFECT); } } else { usart_puts("oops bad key\r\n"); enterGameState(STATE_FAIL_EFFECT); } } show_screen(); break; default: usart_puts("discard button press, not expecting input now\r\n"); break; } } // ------------------------------- const uint8_t anim_step = 1; const uint8_t anim_max = 75; xrgb_t color1; uint8_t step1; xrgb_t color2; uint8_t step2; xrgb_t color3; uint8_t step3; xrgb_t color4; uint8_t step4; void idle_anim_init(void) { color1 = xrgb(anim_max, 0, 0); step1 = 0; color2 = xrgb(anim_max, anim_max, 0); step2 = 1; color3 = xrgb(0, anim_max, 0); step3 = 2; color4 = xrgb(0, anim_max, anim_max); step4 = 3; } void idle_anim_oneled(xrgb_t *color, uint8_t *step) { switch (*step) { case 0: color->g += anim_step; if (color->g >= anim_max) *step = *step + 1; break; case 1: color->r -= anim_step; if (color->r == 0) *step = *step + 1; break; case 2: color->b += anim_step; if (color->b >= anim_max) *step = *step + 1; break; case 3: color->g -= anim_step; if (color->g == 0) *step = *step + 1; break; case 4: color->r += anim_step; if (color->r >= anim_max) *step = *step + 1; break; default: color->b -= anim_step; if (color->b == 0) *step = 0; break; } } void idle_anim(void) { idle_anim_oneled(&color1, &step1); idle_anim_oneled(&color2, &step2); idle_anim_oneled(&color3, &step3); idle_anim_oneled(&color4, &step4); screen[0] = xrgb_rgb24(color1); screen[1] = xrgb_rgb24(color2); screen[2] = xrgb_rgb24(color3); screen[3] = xrgb_rgb24(color4); show_screen(); }