From 73a975d0d9e3045102ce74c9d4489ff0a19876ef Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 08:50:11 -0400 Subject: [PATCH 01/18] Added endless-runner face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/endless_runner_face.c | 408 ++++++++++++++++++ .../complication/endless_runner_face.h | 63 +++ 4 files changed, 473 insertions(+) create mode 100644 movement/watch_faces/complication/endless_runner_face.c create mode 100644 movement/watch_faces/complication/endless_runner_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b0..7114856a 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/endless_runner_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 35571109..74856a6f 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "endless_runner_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c new file mode 100644 index 00000000..6b2bb019 --- /dev/null +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -0,0 +1,408 @@ +/* + * MIT License + * + * Copyright (c) 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "endless_runner_face.h" + +typedef enum { + NOT_JUMPING = 0, + JUMP, + JUMPING_1, + JUMPING_2, + JUMP_COUNT +} ScrollingJumpState; + +typedef enum { + SCREEN_TITLE = 0, + SCREEN_PLAYING, + SCREEN_LOSE, + SCREEN_COUNT +} ScrollingCurrScreen; + +typedef enum { + DIFF_NORM = 0, // 8x speed; 4 0's min; + DIFF_HARD, // 8x speed; 3 0's min; 2 - Easy 4x speed; 4 0's min + DIFF_EASY, // 4x speed; 4 0's min + DIFF_COUNT +} ScrollingDifficulty; + +#define NUM_GRID 12 +#define FREQ 8 +#define FREQ_EASY 4 +#define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 + +typedef struct { + uint32_t obst_pattern; + int16_t obst_indx : 8; + int16_t jump_state : 3; + int16_t sec_before_moves : 3; + bool loc_2_on; + bool loc_3_on; +} game_state_t; + +static game_state_t game_state; +static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; + +static void print_binary(uint32_t value, int bits) { + for (int i = bits - 1; i >= 0; i--) { + // Print each bit + printf("%d", (value >> i) & 1); + // Optional: add a space every 4 bits for readability + if (i % 4 == 0 && i != 0) { + printf(" "); + } + } + printf("\r\n"); +} + +static uint32_t get_random(uint32_t max) { + #if __EMSCRIPTEN__ + return rand() % max; + #else + return arc4random_uniform(max); + #endif +} + +static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { +/** @brief A legal random number starts with the previous number (which should be the 12 bits on the screen). + * @param prev_val The previous value to tack onto. The return will have its first NUM_GRID MSBs be the same as prev_val, and the rest be new + * @param difficulty To dictate how spread apart the obsticles must be + * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val + */ + uint8_t min_zeros = difficulty == DIFF_HARD ? 3 : 4; + uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; + uint32_t rand = get_random(max); + uint32_t rand_legal = 0; + prev_val = prev_val & ~max; + + for (int i = (NUM_GRID + 1); i <= _num_bits_obst_pattern; i++) { + uint32_t mask = 1 << (_num_bits_obst_pattern - i); + bool msb = (rand & mask) >> (_num_bits_obst_pattern - i); + if (msb) { + rand_legal = rand_legal << min_zeros; + i+=min_zeros; + } + rand_legal |= msb; + rand_legal = rand_legal << 1; + } + + rand_legal = rand_legal & max; + for (int i = 0; i <= min_zeros; i++) { + if (prev_val & (1 << (i + _num_bits_obst_pattern - NUM_GRID))){ + rand_legal = rand_legal >> (min_zeros - i); + break; + } + } + rand_legal = prev_val | rand_legal; + print_binary(rand_legal, _num_bits_obst_pattern); + return rand_legal; +} + +static void display_ball(bool jumping) { + if (jumping == NOT_JUMPING) { + watch_set_pixel(0, 21); + watch_set_pixel(1, 21); + watch_set_pixel(0, 20); + watch_set_pixel(1, 20); + watch_clear_pixel(1, 17); + watch_clear_pixel(2, 20); + watch_clear_pixel(2, 21); + } + else { + watch_clear_pixel(0, 21); + watch_clear_pixel(1, 21); + watch_clear_pixel(0, 20); + watch_set_pixel(1, 20); + watch_set_pixel(1, 17); + watch_set_pixel(2, 20); + watch_set_pixel(2, 21); + } +} + +static void display_score(uint8_t score) { + char buf[3]; + if (score > MAX_DISP_SCORE) watch_display_string(" -", 2); + else{ + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); + } +} + +static void display_difficulty(uint16_t difficulty) { + switch (difficulty) + { + case DIFF_EASY: + watch_display_string("E", 9); + break; + case DIFF_HARD: + watch_display_string("H", 9); + break; + case DIFF_NORM: + default: + watch_display_string("n", 9); + break; + } +} + +static void display_title(endless_runner_state_t *state) { + state -> curr_screen = SCREEN_TITLE; + memset(&game_state, 0, sizeof(game_state)); + game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. + if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second + watch_display_string("SC SEL ", 0); + display_score(state -> hi_score); + display_difficulty(state -> difficulty); +} + +static void display_lose_screen(endless_runner_state_t *state) { + movement_request_tick_frequency(1); + state -> curr_screen = SCREEN_LOSE; + state -> curr_score = 0; + watch_display_string(" LOSEr", 4); + if (state -> soundOn) + watch_buzzer_play_note(BUZZER_NOTE_A1, 600); + else + delay_ms(600); +} + +static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { + bool success_jump = false; + switch (grid_loc) + { + case 2: + game_state.loc_2_on = obstacle; + if (obstacle) + watch_set_pixel(0, 20); + else if (game_state.jump_state != NOT_JUMPING) + watch_clear_pixel(0, 20); + break; + case 3: + game_state.loc_3_on = obstacle; + if (obstacle) + watch_set_pixel(1, 21); + else if (game_state.jump_state != NOT_JUMPING) + watch_clear_pixel(1, 21); + break; + + case 1: + if (obstacle) { // If an obstacle is here, it means the ball cleared it + success_jump = true; + if (state -> curr_score < MAX_DISP_SCORE) { + state -> curr_score++; + if (state -> curr_score > state -> hi_score) + state -> hi_score = state -> curr_score; + display_score(state -> curr_score); + } + } + //fall through + case 0: + case 5: + if (obstacle) + watch_set_pixel(0, 18 + grid_loc); + else + watch_clear_pixel(0, 18 + grid_loc); + break; + case 4: + if (obstacle) + watch_set_pixel(1, 22); + else + watch_clear_pixel(1, 22); + break; + case 6: + if (obstacle) + watch_set_pixel(1, 0); + else + watch_clear_pixel(1, 0); + break; + case 7: + case 8: + if (obstacle) + watch_set_pixel(0, grid_loc - 6); + else + watch_clear_pixel(0, grid_loc - 6); + break; + case 9: + case 10: + if (obstacle) + watch_set_pixel(0, grid_loc - 5); + else + watch_clear_pixel(0, grid_loc - 5); + break; + case 11: + if (obstacle) + watch_set_pixel(1, 6); + else + watch_clear_pixel(1, 6); + break; + default: + break; + } + return success_jump; +} + +static bool display_obstacles(endless_runner_state_t *state) { + bool success_jump = false; + for (int i = 0; i < NUM_GRID; i++) { + // Use a bitmask to isolate each bit and shift it to the least significant position + uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); + bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); + if (display_obstacle(obstacle, i, state)) success_jump = true; + + } + game_state.obst_pattern = game_state.obst_pattern << 1; + game_state.obst_indx++; + if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { + game_state.obst_indx = 0; + game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); + } + return success_jump; +} + +void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(endless_runner_state_t)); + memset(*context_ptr, 0, sizeof(endless_runner_state_t)); + } +} + +void endless_runner_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + endless_runner_state_t *state = (endless_runner_state_t *)context; + bool success_jump = false; + + switch (event.event_type) { + case EVENT_ACTIVATE: + state -> curr_screen = SCREEN_TITLE; + if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); + display_title(state); + state -> curr_score = 0; + break; + case EVENT_TICK: + switch (state -> curr_screen) + { + case SCREEN_TITLE: + case SCREEN_LOSE: + break; + default: + if (game_state.sec_before_moves == 0) + success_jump = display_obstacles(state); + else if (event.subsecond == 0) + if(--game_state.sec_before_moves == 0) game_state.obst_pattern = get_random_legal(0, state -> difficulty); + switch (game_state.jump_state) + { + case JUMP: + game_state.jump_state = JUMPING_1; + break; + case JUMPING_1: + game_state.jump_state = JUMPING_2; + break; + case JUMPING_2: + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state); + if (state -> soundOn){ + if (success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + break; + default: + break; + } + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) + display_lose_screen(state); + break; + } + break; + case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_BUTTON_UP: + if (state -> curr_screen == SCREEN_TITLE) { + state -> curr_screen = SCREEN_PLAYING; + movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); + watch_display_string(" ", 4); + display_ball(false); + display_score(state -> curr_score); + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 200); + watch_buzzer_play_note(BUZZER_NOTE_E5, 200); + watch_buzzer_play_note(BUZZER_NOTE_G5, 200); + } + } + else if (state -> curr_screen == SCREEN_LOSE) { + display_title(state); + } + break; + case EVENT_LIGHT_LONG_PRESS: + if (state -> curr_screen == SCREEN_TITLE) { + state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; + display_difficulty(state -> difficulty); + if (state -> soundOn) { + if (state -> difficulty == DIFF_EASY) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + } + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + game_state.jump_state = JUMP; + display_ball(game_state.jump_state); + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state -> curr_screen != SCREEN_PLAYING) + { + state -> soundOn = !state -> soundOn; + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void endless_runner_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h new file mode 100644 index 00000000..b588f354 --- /dev/null +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ENDLESS_RUNNER_FACE_H_ +#define ENDLESS_RUNNER_FACE_H_ + +#include "movement.h" + +/* + ENDLESS_RUNNER face + + This is a basic endless-runner, like the [Chrome Dino game](https://en.wikipedia.org/wiki/Dinosaur_Game). + On the title screen, you can select a difficulty by long-pressing LIGHT or toggle sound by long-pressing ALARM. + LED or ALARM are used to jump. + High-score is displayed on the top-right on the title screen. During a game, the current score is displayed. +*/ + +typedef struct { + // These are values that need saving between uses + uint16_t hi_score : 6; + uint16_t curr_score : 6; + uint16_t curr_screen : 2; + uint16_t difficulty : 2; + bool soundOn; + bool unused; +} endless_runner_state_t; + +void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void endless_runner_face_activate(movement_settings_t *settings, void *context); +bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void endless_runner_face_resign(movement_settings_t *settings, void *context); + +#define endless_runner_face ((const watch_face_t){ \ + endless_runner_face_setup, \ + endless_runner_face_activate, \ + endless_runner_face_loop, \ + endless_runner_face_resign, \ + NULL, \ +}) + +#endif // ENDLESS_RUNNER_FACE_H_ + From e2870eb7aff72c05573d78d407a75651e23c7618 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 08:51:45 -0400 Subject: [PATCH 02/18] Removed the binary print debug function --- .../watch_faces/complication/endless_runner_face.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 6b2bb019..2227d27f 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -65,18 +65,6 @@ typedef struct { static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; -static void print_binary(uint32_t value, int bits) { - for (int i = bits - 1; i >= 0; i--) { - // Print each bit - printf("%d", (value >> i) & 1); - // Optional: add a space every 4 bits for readability - if (i % 4 == 0 && i != 0) { - printf(" "); - } - } - printf("\r\n"); -} - static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ return rand() % max; @@ -116,7 +104,6 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } } rand_legal = prev_val | rand_legal; - print_binary(rand_legal, _num_bits_obst_pattern); return rand_legal; } From ed3c4d3c3034bdc301b4bdbf7629188b3539a40a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 09:29:19 -0400 Subject: [PATCH 03/18] Fixed the long delays when beginning a game --- .../complication/endless_runner_face.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 2227d27f..44ba2cda 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -55,9 +55,9 @@ typedef enum { typedef struct { uint32_t obst_pattern; - int16_t obst_indx : 8; - int16_t jump_state : 3; - int16_t sec_before_moves : 3; + uint16_t obst_indx : 8; + uint16_t jump_state : 3; + uint16_t sec_before_moves : 3; bool loc_2_on; bool loc_3_on; } game_state_t; @@ -287,10 +287,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti switch (event.event_type) { case EVENT_ACTIVATE: - state -> curr_screen = SCREEN_TITLE; if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); - state -> curr_score = 0; break; case EVENT_TICK: switch (state -> curr_screen) @@ -299,10 +297,11 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case SCREEN_LOSE: break; default: - if (game_state.sec_before_moves == 0) - success_jump = display_obstacles(state); - else if (event.subsecond == 0) - if(--game_state.sec_before_moves == 0) game_state.obst_pattern = get_random_legal(0, state -> difficulty); + if (game_state.sec_before_moves != 0) { + if (event.subsecond == 0) --game_state.sec_before_moves; + break; + } + success_jump = display_obstacles(state); switch (game_state.jump_state) { case JUMP: @@ -336,6 +335,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); watch_display_string(" ", 4); display_ball(false); + do // Avoid the first array of obstacles being a boring line of 0s + { + game_state.obst_pattern = get_random_legal(0, state -> difficulty); + } while (game_state.obst_pattern == 0); display_score(state -> curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); From abc0bedbde221f303b3960278f827b4a6ff1518a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 22:35:07 -0400 Subject: [PATCH 04/18] Gave an extra jumping frame for non-hard mode; Curr scroll now loops; Title changed to ER --- .../complication/endless_runner_face.c | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 44ba2cda..67a7b54f 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -30,23 +30,24 @@ typedef enum { NOT_JUMPING = 0, JUMP, JUMPING_1, - JUMPING_2, + JUMPING_2, + JUMPING_3, JUMP_COUNT -} ScrollingJumpState; +} RunnerJumpState; typedef enum { SCREEN_TITLE = 0, SCREEN_PLAYING, SCREEN_LOSE, SCREEN_COUNT -} ScrollingCurrScreen; +} RunnerCurrScreen; typedef enum { - DIFF_NORM = 0, // 8x speed; 4 0's min; - DIFF_HARD, // 8x speed; 3 0's min; 2 - Easy 4x speed; 4 0's min - DIFF_EASY, // 4x speed; 4 0's min + DIFF_NORM = 0, // 8x speed; 4 0's min; jump is 3 frames + DIFF_HARD, // 8x speed; 3 0's min; jump is 2 frames + DIFF_EASY, // 4x speed; 4 0's min; jump is 3 frames DIFF_COUNT -} ScrollingDifficulty; +} RunnerDifficulty; #define NUM_GRID 12 #define FREQ 8 @@ -158,7 +159,7 @@ static void display_title(endless_runner_state_t *state) { memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second - watch_display_string("SC SEL ", 0); + watch_display_string("ER SEL ", 0); display_score(state -> hi_score); display_difficulty(state -> difficulty); } @@ -167,7 +168,7 @@ static void display_lose_screen(endless_runner_state_t *state) { movement_request_tick_frequency(1); state -> curr_screen = SCREEN_LOSE; state -> curr_score = 0; - watch_display_string(" LOSEr", 4); + watch_display_string(" U LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); else @@ -195,16 +196,19 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - success_jump = true; - if (state -> curr_score < MAX_DISP_SCORE) { - state -> curr_score++; - if (state -> curr_score > state -> hi_score) - state -> hi_score = state -> curr_score; - display_score(state -> curr_score); - } + // Counter will continuously roll over, but high score will max out on the first roll-over + state -> curr_score = (state -> curr_score + 1) % (MAX_DISP_SCORE + 1); + if (state -> curr_score == 0) // This means the counter rolled over + state -> hi_score = MAX_DISP_SCORE + 1; + else if (state -> curr_score > state -> hi_score) + state -> hi_score = state -> curr_score; + display_score(state -> curr_score); } //fall through case 0: + if (obstacle) // If an obstacle is here, it means the ball cleared it + success_jump = true; + //fall through case 5: if (obstacle) watch_set_pixel(0, 18 + grid_loc); @@ -308,9 +312,12 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti game_state.jump_state = JUMPING_1; break; case JUMPING_1: - game_state.jump_state = JUMPING_2; + game_state.jump_state = (state -> difficulty == DIFF_HARD) ? JUMPING_3 : JUMPING_2; break; case JUMPING_2: + game_state.jump_state = JUMPING_3; + break; + case JUMPING_3: game_state.jump_state = NOT_JUMPING; display_ball(game_state.jump_state); if (state -> soundOn){ @@ -323,8 +330,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti default: break; } - if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { + delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. display_lose_screen(state); + } break; } break; @@ -332,7 +341,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_ALARM_BUTTON_UP: if (state -> curr_screen == SCREEN_TITLE) { state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); + movement_request_tick_frequency((state -> difficulty == DIFF_EASY) ? FREQ_EASY : FREQ); watch_display_string(" ", 4); display_ball(false); do // Avoid the first array of obstacles being a boring line of 0s From defd01f9f0c21e79e93bf1321aeca83756fdadf4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 17 Jul 2024 17:05:48 -0400 Subject: [PATCH 05/18] Added baby mode which used to be easy mode; easy mode is now same speed as normal, but 3 frames to jump and normal is 2 frames. --- .../complication/endless_runner_face.c | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 67a7b54f..1e6abaeb 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -43,15 +43,16 @@ typedef enum { } RunnerCurrScreen; typedef enum { - DIFF_NORM = 0, // 8x speed; 4 0's min; jump is 3 frames - DIFF_HARD, // 8x speed; 3 0's min; jump is 2 frames - DIFF_EASY, // 4x speed; 4 0's min; jump is 3 frames + DIFF_BABY = 0, // 0.5x speed; 4 0's min; jump is 3 frames + DIFF_EASY, // 1x speed; 4 0's min; jump is 3 frames + DIFF_NORM, // 1x speed; 4 0's min; jump is 2 frames + DIFF_HARD, // 1x speed; 3 0's min; jump is 2 frames DIFF_COUNT } RunnerDifficulty; #define NUM_GRID 12 #define FREQ 8 -#define FREQ_EASY 4 +#define FREQ_SLOW 4 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -80,7 +81,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { * @param difficulty To dictate how spread apart the obsticles must be * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val */ - uint8_t min_zeros = difficulty == DIFF_HARD ? 3 : 4; + uint8_t min_zeros = (difficulty == DIFF_HARD) ? 3 : 4; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; uint32_t rand = get_random(max); uint32_t rand_legal = 0; @@ -141,6 +142,9 @@ static void display_score(uint8_t score) { static void display_difficulty(uint16_t difficulty) { switch (difficulty) { + case DIFF_BABY: + watch_display_string("b", 9); + break; case DIFF_EASY: watch_display_string("E", 9); break; @@ -277,6 +281,8 @@ void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(endless_runner_state_t)); memset(*context_ptr, 0, sizeof(endless_runner_state_t)); + endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr; + state->difficulty = DIFF_NORM; } } @@ -312,7 +318,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti game_state.jump_state = JUMPING_1; break; case JUMPING_1: - game_state.jump_state = (state -> difficulty == DIFF_HARD) ? JUMPING_3 : JUMPING_2; + game_state.jump_state = (state -> difficulty >= DIFF_NORM) ? JUMPING_3 : JUMPING_2; break; case JUMPING_2: game_state.jump_state = JUMPING_3; @@ -341,7 +347,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_ALARM_BUTTON_UP: if (state -> curr_screen == SCREEN_TITLE) { state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency((state -> difficulty == DIFF_EASY) ? FREQ_EASY : FREQ); + movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); watch_display_string(" ", 4); display_ball(false); do // Avoid the first array of obstacles being a boring line of 0s @@ -364,7 +370,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; display_difficulty(state -> difficulty); if (state -> soundOn) { - if (state -> difficulty == DIFF_EASY) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); } } From 6f3f09c5babbabfa8471f6f070e442c40df73a5e Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 19 Jul 2024 08:43:15 -0400 Subject: [PATCH 06/18] Reformat to remove some hardocded variables --- .../complication/endless_runner_face.c | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 1e6abaeb..cd672f66 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -27,12 +27,9 @@ #include "endless_runner_face.h" typedef enum { - NOT_JUMPING = 0, - JUMP, - JUMPING_1, - JUMPING_2, - JUMPING_3, - JUMP_COUNT + JUMPING_FINAL_FRAME = 0, + NOT_JUMPING, + JUMPING_START, } RunnerJumpState; typedef enum { @@ -53,12 +50,16 @@ typedef enum { #define NUM_GRID 12 #define FREQ 8 #define FREQ_SLOW 4 +#define JUMP_FRAMES 2 +#define JUMP_FRAMES_EASY 20 +#define MIN_ZEROES 4 +#define MIN_ZEROES_HARD 3 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { uint32_t obst_pattern; uint16_t obst_indx : 8; - uint16_t jump_state : 3; + uint16_t jump_state : 5; uint16_t sec_before_moves : 3; bool loc_2_on; bool loc_3_on; @@ -81,7 +82,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { * @param difficulty To dictate how spread apart the obsticles must be * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val */ - uint8_t min_zeros = (difficulty == DIFF_HARD) ? 3 : 4; + uint8_t min_zeros = (difficulty == DIFF_HARD) ? MIN_ZEROES_HARD : MIN_ZEROES; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; uint32_t rand = get_random(max); uint32_t rand_legal = 0; @@ -110,7 +111,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } static void display_ball(bool jumping) { - if (jumping == NOT_JUMPING) { + if (!jumping) { watch_set_pixel(0, 21); watch_set_pixel(1, 21); watch_set_pixel(0, 20); @@ -168,6 +169,24 @@ static void display_title(endless_runner_state_t *state) { display_difficulty(state -> difficulty); } +static void begin_playing(endless_runner_state_t *state) { + state -> curr_screen = SCREEN_PLAYING; + movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); + watch_display_string("ER ", 0); + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + do // Avoid the first array of obstacles being a boring line of 0s + { + game_state.obst_pattern = get_random_legal(0, state -> difficulty); + } while (game_state.obst_pattern == 0); + display_score(state -> curr_score); + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 200); + watch_buzzer_play_note(BUZZER_NOTE_E5, 200); + watch_buzzer_play_note(BUZZER_NOTE_G5, 200); + } +} + static void display_lose_screen(endless_runner_state_t *state) { movement_request_tick_frequency(1); state -> curr_screen = SCREEN_LOSE; @@ -294,6 +313,7 @@ void endless_runner_face_activate(movement_settings_t *settings, void *context) bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; bool success_jump = false; + uint8_t curr_jump_frame = 0; switch (event.event_type) { case EVENT_ACTIVATE: @@ -314,26 +334,25 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti success_jump = display_obstacles(state); switch (game_state.jump_state) { - case JUMP: - game_state.jump_state = JUMPING_1; + case NOT_JUMPING: break; - case JUMPING_1: - game_state.jump_state = (state -> difficulty >= DIFF_NORM) ? JUMPING_3 : JUMPING_2; - break; - case JUMPING_2: - game_state.jump_state = JUMPING_3; - break; - case JUMPING_3: + case JUMPING_FINAL_FRAME: game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state); + display_ball(game_state.jump_state != NOT_JUMPING); if (state -> soundOn){ if (success_jump) watch_buzzer_play_note(BUZZER_NOTE_C5, 60); else watch_buzzer_play_note(BUZZER_NOTE_C3, 60); } - break; + break; default: + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. break; } if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { @@ -345,25 +364,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (state -> curr_screen == SCREEN_TITLE) { - state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); - watch_display_string(" ", 4); - display_ball(false); - do // Avoid the first array of obstacles being a boring line of 0s - { - game_state.obst_pattern = get_random_legal(0, state -> difficulty); - } while (game_state.obst_pattern == 0); - display_score(state -> curr_score); - if (state -> soundOn){ - watch_buzzer_play_note(BUZZER_NOTE_C5, 200); - watch_buzzer_play_note(BUZZER_NOTE_E5, 200); - watch_buzzer_play_note(BUZZER_NOTE_G5, 200); - } - } - else if (state -> curr_screen == SCREEN_LOSE) { + if (state -> curr_screen == SCREEN_TITLE) + begin_playing(state); + else if (state -> curr_screen == SCREEN_LOSE) display_title(state); - } break; case EVENT_LIGHT_LONG_PRESS: if (state -> curr_screen == SCREEN_TITLE) { @@ -378,8 +382,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ - game_state.jump_state = JUMP; - display_ball(game_state.jump_state); + game_state.jump_state = JUMPING_START; + display_ball(game_state.jump_state != NOT_JUMPING); } break; case EVENT_ALARM_LONG_PRESS: From 2d7aaceff7c73ae2438a5c6a11a1749bf0191203 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 06:46:08 -0400 Subject: [PATCH 07/18] hi score resets weekly --- .../watch_faces/complication/endless_runner_face.c | 13 +++++++++++++ .../watch_faces/complication/endless_runner_face.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index cd672f66..6c761dfa 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -25,6 +25,7 @@ #include #include #include "endless_runner_face.h" +#include "watch_utility.h" typedef enum { JUMPING_FINAL_FRAME = 0, @@ -314,9 +315,21 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti endless_runner_state_t *state = (endless_runner_state_t *)context; bool success_jump = false; uint8_t curr_jump_frame = 0; + watch_date_time date_time; + uint32_t weeknumber; switch (event.event_type) { case EVENT_ACTIVATE: + date_time = watch_rtc_get_date_time(); + weeknumber = watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day); + if ((state -> weeknumber_prev_hi_score != weeknumber) || + (state -> year_prev_hi_score != date_time.unit.year)) + { + // The high score resets itself every new week. + state -> hi_score = 0; + state -> weeknumber_prev_hi_score = weeknumber; + state -> year_prev_hi_score = date_time.unit.year; + } if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index b588f354..03ec4182 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -44,6 +44,8 @@ typedef struct { uint16_t difficulty : 2; bool soundOn; bool unused; + uint8_t weeknumber_prev_hi_score; + uint8_t year_prev_hi_score; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 07d2bc91a54b4a817f3deaba92b5218e92e97bf8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 07:48:50 -0400 Subject: [PATCH 08/18] Modified hi score display to allow for 3 digits in hi-score, it now resets at the beginning of each month --- .../complication/endless_runner_face.c | 88 ++++++++++--------- .../complication/endless_runner_face.h | 12 ++- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 6c761dfa..3016f885 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -25,7 +25,6 @@ #include #include #include "endless_runner_face.h" -#include "watch_utility.h" typedef enum { JUMPING_FINAL_FRAME = 0, @@ -52,9 +51,10 @@ typedef enum { #define FREQ 8 #define FREQ_SLOW 4 #define JUMP_FRAMES 2 -#define JUMP_FRAMES_EASY 20 +#define JUMP_FRAMES_EASY 3 #define MIN_ZEROES 4 #define MIN_ZEROES_HARD 3 +#define MAX_HI_SCORE 999 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -62,6 +62,8 @@ typedef struct { uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; + uint16_t curr_score : 10; + uint16_t curr_screen : 4; bool loc_2_on; bool loc_3_on; } game_state_t; @@ -134,44 +136,52 @@ static void display_ball(bool jumping) { static void display_score(uint8_t score) { char buf[3]; - if (score > MAX_DISP_SCORE) watch_display_string(" -", 2); - else{ - sprintf(buf, "%2d", score); - watch_display_string(buf, 2); - } + score %= (MAX_DISP_SCORE + 1); + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); } static void display_difficulty(uint16_t difficulty) { switch (difficulty) { case DIFF_BABY: - watch_display_string("b", 9); + watch_display_string("b", 3); break; case DIFF_EASY: - watch_display_string("E", 9); + watch_display_string("E", 3); break; case DIFF_HARD: - watch_display_string("H", 9); + watch_display_string("H", 3); break; case DIFF_NORM: default: - watch_display_string("n", 9); + watch_display_string("N", 3); break; } } static void display_title(endless_runner_state_t *state) { - state -> curr_screen = SCREEN_TITLE; + char buf[10]; + uint16_t hi_score = state -> hi_score; + uint8_t difficulty = state -> difficulty; + bool sound_on = state -> soundOn; memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. - if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second - watch_display_string("ER SEL ", 0); - display_score(state -> hi_score); - display_difficulty(state -> difficulty); + if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second + watch_set_colon(); + if (hi_score <= 99) + sprintf(buf, "ER HS%2d ", hi_score); + else if (hi_score <= MAX_HI_SCORE) + sprintf(buf, "ER HS%3d ", hi_score); + else + sprintf(buf, "ER HS-- ", hi_score); + watch_display_string(buf, 0); + display_difficulty(difficulty); } static void begin_playing(endless_runner_state_t *state) { - state -> curr_screen = SCREEN_PLAYING; + game_state.curr_screen = SCREEN_PLAYING; + watch_clear_colon(); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); watch_display_string("ER ", 0); game_state.jump_state = NOT_JUMPING; @@ -180,7 +190,7 @@ static void begin_playing(endless_runner_state_t *state) { { game_state.obst_pattern = get_random_legal(0, state -> difficulty); } while (game_state.obst_pattern == 0); - display_score(state -> curr_score); + display_score( game_state.curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); watch_buzzer_play_note(BUZZER_NOTE_E5, 200); @@ -189,9 +199,8 @@ static void begin_playing(endless_runner_state_t *state) { } static void display_lose_screen(endless_runner_state_t *state) { - movement_request_tick_frequency(1); - state -> curr_screen = SCREEN_LOSE; - state -> curr_score = 0; + game_state.curr_screen = SCREEN_LOSE; + game_state.curr_score = 0; watch_display_string(" U LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); @@ -220,13 +229,12 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - // Counter will continuously roll over, but high score will max out on the first roll-over - state -> curr_score = (state -> curr_score + 1) % (MAX_DISP_SCORE + 1); - if (state -> curr_score == 0) // This means the counter rolled over - state -> hi_score = MAX_DISP_SCORE + 1; - else if (state -> curr_score > state -> hi_score) - state -> hi_score = state -> curr_score; - display_score(state -> curr_score); + if (game_state.curr_score <= MAX_HI_SCORE) { + game_state.curr_score++; + if (game_state.curr_score > state -> hi_score) + state -> hi_score = game_state.curr_score; + } + display_score(game_state.curr_score); } //fall through case 0: @@ -316,25 +324,23 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti bool success_jump = false; uint8_t curr_jump_frame = 0; watch_date_time date_time; - uint32_t weeknumber; switch (event.event_type) { case EVENT_ACTIVATE: date_time = watch_rtc_get_date_time(); - weeknumber = watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day); - if ((state -> weeknumber_prev_hi_score != weeknumber) || - (state -> year_prev_hi_score != date_time.unit.year)) + if ((state -> year_last_hi_score != date_time.unit.year) || + (state -> month_last_hi_score != date_time.unit.month)) { - // The high score resets itself every new week. + // The high score resets itself every new month. state -> hi_score = 0; - state -> weeknumber_prev_hi_score = weeknumber; - state -> year_prev_hi_score = date_time.unit.year; + state -> year_last_hi_score = date_time.unit.year; + state -> month_last_hi_score = date_time.unit.month; } if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; case EVENT_TICK: - switch (state -> curr_screen) + switch (game_state.curr_screen) { case SCREEN_TITLE: case SCREEN_LOSE: @@ -377,13 +383,13 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (state -> curr_screen == SCREEN_TITLE) + if (game_state.curr_screen == SCREEN_TITLE) begin_playing(state); - else if (state -> curr_screen == SCREEN_LOSE) + else if (game_state.curr_screen == SCREEN_LOSE) display_title(state); break; case EVENT_LIGHT_LONG_PRESS: - if (state -> curr_screen == SCREEN_TITLE) { + if (game_state.curr_screen == SCREEN_TITLE) { state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; display_difficulty(state -> difficulty); if (state -> soundOn) { @@ -394,13 +400,13 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: - if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ game_state.jump_state = JUMPING_START; display_ball(game_state.jump_state != NOT_JUMPING); } break; case EVENT_ALARM_LONG_PRESS: - if (state -> curr_screen != SCREEN_PLAYING) + if (game_state.curr_screen != SCREEN_PLAYING) { state -> soundOn = !state -> soundOn; if (state -> soundOn){ diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index 03ec4182..56d22a06 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -38,14 +38,12 @@ typedef struct { // These are values that need saving between uses - uint16_t hi_score : 6; - uint16_t curr_score : 6; - uint16_t curr_screen : 2; - uint16_t difficulty : 2; + uint32_t hi_score : 10; + uint32_t difficulty : 2; bool soundOn; - bool unused; - uint8_t weeknumber_prev_hi_score; - uint8_t year_prev_hi_score; + uint32_t month_last_hi_score : 4; + uint32_t year_last_hi_score : 6; + uint32_t unused : 9; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 6ec6476d0f6a27137b2955aec82f8979d4c98dbf Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 09:43:49 -0400 Subject: [PATCH 09/18] Refectored the state machine --- .../complication/endless_runner_face.c | 181 ++++++++++-------- 1 file changed, 98 insertions(+), 83 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 3016f885..31507c82 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -40,21 +40,21 @@ typedef enum { } RunnerCurrScreen; typedef enum { - DIFF_BABY = 0, // 0.5x speed; 4 0's min; jump is 3 frames - DIFF_EASY, // 1x speed; 4 0's min; jump is 3 frames - DIFF_NORM, // 1x speed; 4 0's min; jump is 2 frames - DIFF_HARD, // 1x speed; 3 0's min; jump is 2 frames + DIFF_BABY = 0, // FREQ_SLOW FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames + DIFF_EASY, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames + DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames + DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames DIFF_COUNT } RunnerDifficulty; -#define NUM_GRID 12 -#define FREQ 8 -#define FREQ_SLOW 4 -#define JUMP_FRAMES 2 -#define JUMP_FRAMES_EASY 3 -#define MIN_ZEROES 4 -#define MIN_ZEROES_HARD 3 -#define MAX_HI_SCORE 999 +#define NUM_GRID 12 // This the length that the obstacle track can be on +#define FREQ 8 // Frequency request for the game +#define FREQ_SLOW 4 // Frequency request for baby mode +#define JUMP_FRAMES 2 // Wait this many frames on difficulties above EASY before coming down from the jump button pressed +#define JUMP_FRAMES_EASY 3 // Wait this many frames on difficulties at or below EASY before coming down from the jump button pressed +#define MIN_ZEROES 4 // At minimum, we'll have this many spaces between obstacles +#define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode +#define MAX_HI_SCORE 999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -141,6 +141,19 @@ static void display_score(uint8_t score) { watch_display_string(buf, 2); } +static void check_and_reset_hi_score(endless_runner_state_t *state) { + // Resets the hi scroe at the beginning of each month. + watch_date_time date_time = watch_rtc_get_date_time(); + if ((state -> year_last_hi_score != date_time.unit.year) || + (state -> month_last_hi_score != date_time.unit.month)) + { + // The high score resets itself every new month. + state -> hi_score = 0; + state -> year_last_hi_score = date_time.unit.year; + state -> month_last_hi_score = date_time.unit.month; + } +} + static void display_difficulty(uint16_t difficulty) { switch (difficulty) { @@ -160,8 +173,27 @@ static void display_difficulty(uint16_t difficulty) { } } +static void change_difficulty(endless_runner_state_t *state) { + state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; + display_difficulty(state -> difficulty); + if (state -> soundOn) { + if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + } +} + +static void toggle_sound(endless_runner_state_t *state) { + state -> soundOn = !state -> soundOn; + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } +} + static void display_title(endless_runner_state_t *state) { - char buf[10]; uint16_t hi_score = state -> hi_score; uint8_t difficulty = state -> difficulty; bool sound_on = state -> soundOn; @@ -169,13 +201,17 @@ static void display_title(endless_runner_state_t *state) { game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second watch_set_colon(); - if (hi_score <= 99) - sprintf(buf, "ER HS%2d ", hi_score); - else if (hi_score <= MAX_HI_SCORE) - sprintf(buf, "ER HS%3d ", hi_score); - else - sprintf(buf, "ER HS-- ", hi_score); - watch_display_string(buf, 0); + if (hi_score > MAX_HI_SCORE) { + watch_display_string("ER HS-- ", 0); + } + else { + char buf[14]; + if (hi_score <= 99) + sprintf(buf, "ER HS%2d ", hi_score); + else if (hi_score <= MAX_HI_SCORE) + sprintf(buf, "ER HS%3d ", hi_score); + watch_display_string(buf, 0); + } display_difficulty(difficulty); } @@ -303,6 +339,43 @@ static bool display_obstacles(endless_runner_state_t *state) { return success_jump; } +static void update_game(endless_runner_state_t *state, uint8_t subsecond) { + bool success_jump = false; + uint8_t curr_jump_frame = 0; + if (game_state.sec_before_moves != 0) { + if (subsecond == 0) --game_state.sec_before_moves; + return; + } + success_jump = display_obstacles(state); + switch (game_state.jump_state) + { + case NOT_JUMPING: + break; + case JUMPING_FINAL_FRAME: + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + break; + default: + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. + break; + } + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { + delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. + display_lose_screen(state); + } +} + void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; @@ -321,21 +394,9 @@ void endless_runner_face_activate(movement_settings_t *settings, void *context) bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; - bool success_jump = false; - uint8_t curr_jump_frame = 0; - watch_date_time date_time; - switch (event.event_type) { case EVENT_ACTIVATE: - date_time = watch_rtc_get_date_time(); - if ((state -> year_last_hi_score != date_time.unit.year) || - (state -> month_last_hi_score != date_time.unit.month)) - { - // The high score resets itself every new month. - state -> hi_score = 0; - state -> year_last_hi_score = date_time.unit.year; - state -> month_last_hi_score = date_time.unit.month; - } + check_and_reset_hi_score(state); if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; @@ -346,38 +407,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case SCREEN_LOSE: break; default: - if (game_state.sec_before_moves != 0) { - if (event.subsecond == 0) --game_state.sec_before_moves; - break; - } - success_jump = display_obstacles(state); - switch (game_state.jump_state) - { - case NOT_JUMPING: - break; - case JUMPING_FINAL_FRAME: - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } - break; - default: - curr_jump_frame = game_state.jump_state - NOT_JUMPING; - if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) - game_state.jump_state = JUMPING_FINAL_FRAME; - else - game_state.jump_state++; - //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. - break; - } - if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { - delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. - display_lose_screen(state); - } + update_game(state, event.subsecond); break; } break; @@ -389,14 +419,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti display_title(state); break; case EVENT_LIGHT_LONG_PRESS: - if (game_state.curr_screen == SCREEN_TITLE) { - state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; - display_difficulty(state -> difficulty); - if (state -> soundOn) { - if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); - else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); - } - } + if (game_state.curr_screen == SCREEN_TITLE) + change_difficulty(state); break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: @@ -407,16 +431,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_ALARM_LONG_PRESS: if (game_state.curr_screen != SCREEN_PLAYING) - { - state -> soundOn = !state -> soundOn; - if (state -> soundOn){ - watch_buzzer_play_note(BUZZER_NOTE_C5, 30); - watch_set_indicator(WATCH_INDICATOR_BELL); - } - else { - watch_clear_indicator(WATCH_INDICATOR_BELL); - } - } + toggle_sound(state); break; case EVENT_TIMEOUT: movement_move_to_face(0); From 503fcd6ebcbc9b215c7fe87a10594b92e9e61387 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 23 Jul 2024 17:52:50 -0400 Subject: [PATCH 10/18] Added author in header --- movement/watch_faces/complication/endless_runner_face.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index 56d22a06..b9484dc3 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2024 <#author_name#> + * Copyright (c) 2024 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From c027b247b22cf720abe88bd88620542f5e31d813 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 23 Jul 2024 23:31:13 -0400 Subject: [PATCH 11/18] Changed hi score number offset and refactored some code --- .../complication/endless_runner_face.c | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 31507c82..1f35be9d 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -54,16 +54,17 @@ typedef enum { #define JUMP_FRAMES_EASY 3 // Wait this many frames on difficulties at or below EASY before coming down from the jump button pressed #define MIN_ZEROES 4 // At minimum, we'll have this many spaces between obstacles #define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode -#define MAX_HI_SCORE 999 // Max hi score to store and display on the title screen. +#define MAX_HI_SCORE 9999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { uint32_t obst_pattern; + uint16_t curr_score; uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; - uint16_t curr_score : 10; - uint16_t curr_screen : 4; + uint8_t curr_screen : 4; + bool success_jump; // Flag used for making a successful jumping sound. bool loc_2_on; bool loc_3_on; } game_state_t; @@ -141,6 +142,16 @@ static void display_score(uint8_t score) { watch_display_string(buf, 2); } +static void add_to_score(endless_runner_state_t *state) { + if (game_state.curr_score <= MAX_HI_SCORE) { + game_state.curr_score++; + if (game_state.curr_score > state -> hi_score) + state -> hi_score = game_state.curr_score; + } + game_state.success_jump = true; + display_score(game_state.curr_score); +} + static void check_and_reset_hi_score(endless_runner_state_t *state) { // Resets the hi scroe at the beginning of each month. watch_date_time date_time = watch_rtc_get_date_time(); @@ -202,14 +213,11 @@ static void display_title(endless_runner_state_t *state) { if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second watch_set_colon(); if (hi_score > MAX_HI_SCORE) { - watch_display_string("ER HS-- ", 0); + watch_display_string("ER HS --", 0); } else { char buf[14]; - if (hi_score <= 99) - sprintf(buf, "ER HS%2d ", hi_score); - else if (hi_score <= MAX_HI_SCORE) - sprintf(buf, "ER HS%3d ", hi_score); + sprintf(buf, "ER HS%4d ", hi_score); watch_display_string(buf, 0); } display_difficulty(difficulty); @@ -244,8 +252,19 @@ static void display_lose_screen(endless_runner_state_t *state) { delay_ms(600); } -static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { - bool success_jump = false; +static void stop_jumping(endless_runner_state_t *state) { + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (game_state.success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + game_state.success_jump = false; +} + +static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { switch (grid_loc) { case 2: @@ -265,18 +284,10 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - if (game_state.curr_score <= MAX_HI_SCORE) { - game_state.curr_score++; - if (game_state.curr_score > state -> hi_score) - state -> hi_score = game_state.curr_score; - } - display_score(game_state.curr_score); + add_to_score(state); } //fall through case 0: - if (obstacle) // If an obstacle is here, it means the ball cleared it - success_jump = true; - //fall through case 5: if (obstacle) watch_set_pixel(0, 18 + grid_loc); @@ -318,16 +329,14 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t default: break; } - return success_jump; } -static bool display_obstacles(endless_runner_state_t *state) { - bool success_jump = false; +static void display_obstacles(endless_runner_state_t *state) { for (int i = 0; i < NUM_GRID; i++) { // Use a bitmask to isolate each bit and shift it to the least significant position uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); - if (display_obstacle(obstacle, i, state)) success_jump = true; + display_obstacle(obstacle, i, state); } game_state.obst_pattern = game_state.obst_pattern << 1; @@ -336,30 +345,21 @@ static bool display_obstacles(endless_runner_state_t *state) { game_state.obst_indx = 0; game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); } - return success_jump; } static void update_game(endless_runner_state_t *state, uint8_t subsecond) { - bool success_jump = false; uint8_t curr_jump_frame = 0; if (game_state.sec_before_moves != 0) { if (subsecond == 0) --game_state.sec_before_moves; return; } - success_jump = display_obstacles(state); + display_obstacles(state); switch (game_state.jump_state) { case NOT_JUMPING: break; case JUMPING_FINAL_FRAME: - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } + stop_jumping(state); break; default: curr_jump_frame = game_state.jump_state - NOT_JUMPING; @@ -434,7 +434,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti toggle_sound(state); break; case EVENT_TIMEOUT: - movement_move_to_face(0); + if (game_state.curr_screen != SCREEN_TITLE) + display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: break; From 30363d408e211d8cd1781ad5e8bc4632c9cb66f3 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:03:54 -0400 Subject: [PATCH 12/18] Added fuel mode --- .../complication/endless_runner_face.c | 205 ++++++++++++++---- .../complication/endless_runner_face.h | 4 +- 2 files changed, 166 insertions(+), 43 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 1f35be9d..bf8e3ac8 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -36,6 +36,7 @@ typedef enum { SCREEN_TITLE = 0, SCREEN_PLAYING, SCREEN_LOSE, + SCREEN_TIME, SCREEN_COUNT } RunnerCurrScreen; @@ -44,6 +45,7 @@ typedef enum { DIFF_EASY, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames + DIFF_FUEL, // Mode where the top-right displays the amoount of fuel that you can be above the ground for, dodging obstacles. When on the ground, your fuel recharges. DIFF_COUNT } RunnerDifficulty; @@ -56,22 +58,45 @@ typedef enum { #define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode #define MAX_HI_SCORE 9999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 +#define JUMP_FRAMES_FUEL 30 // The max fuel that fuel that the fuel mode game will hold +#define JUMP_FRAMES_FUEL_RECHARGE 3 // How much fuel each frame on the ground adds +#define MAX_DISP_SCORE_FUEL 9 // Since the fuel mode displays the score in the weekday slot, two digits will display wonky data typedef struct { uint32_t obst_pattern; - uint16_t curr_score; uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; - uint8_t curr_screen : 4; - bool success_jump; // Flag used for making a successful jumping sound. + uint16_t curr_score : 10; + uint16_t curr_screen : 4; bool loc_2_on; bool loc_3_on; + bool success_jump; + bool fuel_mode; + uint8_t fuel; } game_state_t; static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; +static void print_binary(uint32_t value, int bits) { +#if __EMSCRIPTEN__ + for (int i = bits - 1; i >= 0; i--) { + // Print each bit + printf("%lu", (value >> i) & 1); + // Optional: add a space every 4 bits for readability + if (i % 4 == 0 && i != 0) { + printf(" "); + } + } + printf("\r\n"); +#else + (void) value; + (void) bits; +#endif + return; +} + static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ return rand() % max; @@ -80,6 +105,51 @@ static uint32_t get_random(uint32_t max) { #endif } +static uint32_t get_random_nonzero(uint32_t max) { + uint32_t random; + do + { + random = get_random(max); + } while (random == 0); + return random; +} + +static uint32_t get_random_kinda_nonzero(uint32_t max) { + // Returns a number that's between 1 and max, unless max is 0 or 1, then it returns 0 to max. + if (max == 0) return 0; + else if (max == 1) return get_random(max); + return get_random_nonzero(max); +} + +static uint32_t get_random_fuel(uint32_t prev_val) { + static uint8_t prev_rand_subset = 0; + uint32_t rand; + uint8_t max_ones, subset; + uint32_t rand_legal = 0; + prev_val = prev_val & ~0xFFFF; + + for (int i = 0; i < 2; i++) { + subset = 0; + max_ones = 8; + if (prev_rand_subset > 4) + max_ones -= prev_rand_subset; + rand = get_random_kinda_nonzero(max_ones); + if (rand > 5 && prev_rand_subset) rand = 5; // The gap of one or two is awkward + for (uint32_t j = 0; j < rand; j++) { + subset |= (1 << j); + } + if (prev_rand_subset >= 7) + subset = subset << 1; + subset &= 0xFF; + rand_legal |= subset << (8 * i); + prev_rand_subset = rand; + } + + rand_legal = prev_val | rand_legal; + print_binary(rand_legal, 32); + return rand_legal; +} + static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { /** @brief A legal random number starts with the previous number (which should be the 12 bits on the screen). * @param prev_val The previous value to tack onto. The return will have its first NUM_GRID MSBs be the same as prev_val, and the rest be new @@ -88,7 +158,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { */ uint8_t min_zeros = (difficulty == DIFF_HARD) ? MIN_ZEROES_HARD : MIN_ZEROES; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; - uint32_t rand = get_random(max); + uint32_t rand = get_random_nonzero(max); uint32_t rand_legal = 0; prev_val = prev_val & ~max; @@ -111,6 +181,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } } rand_legal = prev_val | rand_legal; + print_binary(rand_legal, 32); return rand_legal; } @@ -137,9 +208,16 @@ static void display_ball(bool jumping) { static void display_score(uint8_t score) { char buf[3]; - score %= (MAX_DISP_SCORE + 1); - sprintf(buf, "%2d", score); - watch_display_string(buf, 2); + if (game_state.fuel_mode) { + score %= (MAX_DISP_SCORE_FUEL + 1); + sprintf(buf, "%1d", score); + watch_display_string(buf, 0); + } + else { + score %= (MAX_DISP_SCORE + 1); + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); + } } static void add_to_score(endless_runner_state_t *state) { @@ -152,8 +230,14 @@ static void add_to_score(endless_runner_state_t *state) { display_score(game_state.curr_score); } +static void display_fuel(uint8_t subsecond, uint8_t difficulty) { + char buf[4]; + sprintf(buf, "%2d", game_state.fuel); + watch_display_string(buf, 2); +} + static void check_and_reset_hi_score(endless_runner_state_t *state) { - // Resets the hi scroe at the beginning of each month. + // Resets the hi score at the beginning of each month. watch_date_time date_time = watch_rtc_get_date_time(); if ((state -> year_last_hi_score != date_time.unit.year) || (state -> month_last_hi_score != date_time.unit.month)) @@ -169,19 +253,23 @@ static void display_difficulty(uint16_t difficulty) { switch (difficulty) { case DIFF_BABY: - watch_display_string("b", 3); + watch_display_string(" b", 2); break; case DIFF_EASY: - watch_display_string("E", 3); + watch_display_string(" E", 2); break; case DIFF_HARD: - watch_display_string("H", 3); + watch_display_string(" H", 2); + break; + case DIFF_FUEL: + watch_display_string(" F", 2); break; case DIFF_NORM: default: - watch_display_string("N", 3); + watch_display_string(" N", 2); break; } + game_state.fuel_mode = difficulty == DIFF_FUEL; } static void change_difficulty(endless_runner_state_t *state) { @@ -217,23 +305,30 @@ static void display_title(endless_runner_state_t *state) { } else { char buf[14]; - sprintf(buf, "ER HS%4d ", hi_score); + sprintf(buf, "ER HS%4d", hi_score); watch_display_string(buf, 0); } display_difficulty(difficulty); } static void begin_playing(endless_runner_state_t *state) { + uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; watch_clear_colon(); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); - watch_display_string("ER ", 0); + if (game_state.fuel_mode) { + watch_display_string(" ", 0); + game_state.obst_pattern = get_random_fuel(0); + if ((16 * JUMP_FRAMES_FUEL_RECHARGE) < JUMP_FRAMES_FUEL) // 16 frames of zeros at the start of a level + game_state.fuel = JUMP_FRAMES_FUEL - (16 * JUMP_FRAMES_FUEL_RECHARGE); // Have it below its max to show it counting up when starting. + if (game_state.fuel < JUMP_FRAMES_FUEL_RECHARGE) game_state.fuel = JUMP_FRAMES_FUEL_RECHARGE; + } + else { + watch_display_string(" ", 2); + game_state.obst_pattern = get_random_legal(0, difficulty); + } game_state.jump_state = NOT_JUMPING; display_ball(game_state.jump_state != NOT_JUMPING); - do // Avoid the first array of obstacles being a boring line of 0s - { - game_state.obst_pattern = get_random_legal(0, state -> difficulty); - } while (game_state.obst_pattern == 0); display_score( game_state.curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); @@ -252,27 +347,20 @@ static void display_lose_screen(endless_runner_state_t *state) { delay_ms(600); } -static void stop_jumping(endless_runner_state_t *state) { - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (game_state.success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } - game_state.success_jump = false; -} - static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { + static bool prev_obst_pos_two = 0; switch (grid_loc) { case 2: game_state.loc_2_on = obstacle; if (obstacle) watch_set_pixel(0, 20); - else if (game_state.jump_state != NOT_JUMPING) + else if (game_state.jump_state != NOT_JUMPING) { watch_clear_pixel(0, 20); + if (game_state.fuel_mode && prev_obst_pos_two) + add_to_score(state); + } + prev_obst_pos_two = obstacle; break; case 3: game_state.loc_3_on = obstacle; @@ -283,9 +371,8 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t break; case 1: - if (obstacle) { // If an obstacle is here, it means the ball cleared it + if (!game_state.fuel_mode && obstacle) // If an obstacle is here, it means the ball cleared it add_to_score(state); - } //fall through case 0: case 5: @@ -331,17 +418,34 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t } } +static void stop_jumping(endless_runner_state_t *state) { + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (game_state.success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + game_state.success_jump = false; +} + static void display_obstacles(endless_runner_state_t *state) { for (int i = 0; i < NUM_GRID; i++) { // Use a bitmask to isolate each bit and shift it to the least significant position uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); display_obstacle(obstacle, i, state); - } game_state.obst_pattern = game_state.obst_pattern << 1; game_state.obst_indx++; - if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { + if (game_state.fuel_mode) { + if (game_state.obst_indx >= (_num_bits_obst_pattern / 2)) { + game_state.obst_indx = 0; + game_state.obst_pattern = get_random_fuel(game_state.obst_pattern); + } + } + else if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { game_state.obst_indx = 0; game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); } @@ -357,23 +461,41 @@ static void update_game(endless_runner_state_t *state, uint8_t subsecond) { switch (game_state.jump_state) { case NOT_JUMPING: + if (game_state.fuel_mode) { + for (int i = 0; i < JUMP_FRAMES_FUEL_RECHARGE; i++) + { + if(game_state.fuel >= JUMP_FRAMES_FUEL) + break; + game_state.fuel++; + } + } break; case JUMPING_FINAL_FRAME: stop_jumping(state); break; default: - curr_jump_frame = game_state.jump_state - NOT_JUMPING; - if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) - game_state.jump_state = JUMPING_FINAL_FRAME; - else - game_state.jump_state++; - //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. + if (game_state.fuel_mode) { + if (!game_state.fuel) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.fuel--; + if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) stop_jumping(state); + } + else { + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + } break; } if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. display_lose_screen(state); } + else if (game_state.fuel_mode) + display_fuel(subsecond, state -> difficulty); } void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { @@ -425,6 +547,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + if (game_state.fuel_mode && !game_state.fuel) break; game_state.jump_state = JUMPING_START; display_ball(game_state.jump_state != NOT_JUMPING); } diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index b9484dc3..f9d6409b 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -39,11 +39,11 @@ typedef struct { // These are values that need saving between uses uint32_t hi_score : 10; - uint32_t difficulty : 2; + uint32_t difficulty : 3; bool soundOn; uint32_t month_last_hi_score : 4; uint32_t year_last_hi_score : 6; - uint32_t unused : 9; + uint32_t unused : 8; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 324942009ef8adaa7d3e3669b8420a3d1b9e35b2 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:08:35 -0400 Subject: [PATCH 13/18] Added second fuel mode where we don't recharge the fuel if it hits zero. --- .../watch_faces/complication/endless_runner_face.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index bf8e3ac8..60090155 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -46,6 +46,7 @@ typedef enum { DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames DIFF_FUEL, // Mode where the top-right displays the amoount of fuel that you can be above the ground for, dodging obstacles. When on the ground, your fuel recharges. + DIFF_FUEL_1, // Same as DIFF_FUEL, but if your fuel is 0, then you won't recharge DIFF_COUNT } RunnerDifficulty; @@ -232,6 +233,10 @@ static void add_to_score(endless_runner_state_t *state) { static void display_fuel(uint8_t subsecond, uint8_t difficulty) { char buf[4]; + if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % 4 == 0) { + watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled. + return; + } sprintf(buf, "%2d", game_state.fuel); watch_display_string(buf, 2); } @@ -264,12 +269,15 @@ static void display_difficulty(uint16_t difficulty) { case DIFF_FUEL: watch_display_string(" F", 2); break; + case DIFF_FUEL_1: + watch_display_string("1F", 2); + break; case DIFF_NORM: default: watch_display_string(" N", 2); break; } - game_state.fuel_mode = difficulty == DIFF_FUEL; + game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1; } static void change_difficulty(endless_runner_state_t *state) { @@ -464,7 +472,7 @@ static void update_game(endless_runner_state_t *state, uint8_t subsecond) { if (game_state.fuel_mode) { for (int i = 0; i < JUMP_FRAMES_FUEL_RECHARGE; i++) { - if(game_state.fuel >= JUMP_FRAMES_FUEL) + if(game_state.fuel >= JUMP_FRAMES_FUEL || (state -> difficulty == DIFF_FUEL_1 && !game_state.fuel)) break; game_state.fuel++; } From 28b14d36654998963a75bab687f4e02b645f1203 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:12:17 -0400 Subject: [PATCH 14/18] LE mode in the endless runner now displays the current time. --- .../complication/endless_runner_face.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 60090155..96dfa896 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -319,6 +319,37 @@ static void display_title(endless_runner_state_t *state) { display_difficulty(difficulty); } +static void display_time(watch_date_time date_time, bool clock_mode_24h) { + static watch_date_time previous_date_time; + char buf[6 + 1]; + + // If the hour needs updating + if (date_time.unit.hour != previous_date_time.unit.hour) { + uint8_t hour = date_time.unit.hour; + + if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + else { + if (hour >= 12) watch_set_indicator(WATCH_INDICATOR_PM); + hour %= 12; + if (hour == 0) hour = 12; + } + watch_set_colon(); + sprintf( buf, "%2d%02d ", hour, date_time.unit.minute); + watch_display_string(buf, 4); + } + // If both digits of the minute need updating + else if ((date_time.unit.minute / 10) != (previous_date_time.unit.minute / 10)) { + sprintf( buf, "%02d ", date_time.unit.minute); + watch_display_string(buf, 6); + } + // If only the ones-place of the minute needs updating. + else if (date_time.unit.minute != previous_date_time.unit.minute) { + sprintf( buf, "%d ", date_time.unit.minute % 10); + watch_display_string(buf, 7); + } + previous_date_time.reg = date_time.reg; +} + static void begin_playing(endless_runner_state_t *state) { uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; @@ -569,6 +600,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: + display_time(watch_rtc_get_date_time(), settings->bit.clock_mode_24h); break; default: return movement_default_loop_handler(event, settings); From e4a5121303c70d46773af8f99174386047ce86dc Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 26 Jul 2024 06:10:26 -0400 Subject: [PATCH 15/18] bug fix on displaying time in LE mode --- movement/watch_faces/complication/endless_runner_face.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 96dfa896..f24003fa 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -304,6 +304,7 @@ static void display_title(endless_runner_state_t *state) { uint16_t hi_score = state -> hi_score; uint8_t difficulty = state -> difficulty; bool sound_on = state -> soundOn; + game_state.curr_screen = SCREEN_TITLE; memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second @@ -323,9 +324,10 @@ static void display_time(watch_date_time date_time, bool clock_mode_24h) { static watch_date_time previous_date_time; char buf[6 + 1]; - // If the hour needs updating - if (date_time.unit.hour != previous_date_time.unit.hour) { + // If the hour needs updating or it's the first time displaying the time + if ((game_state.curr_screen != SCREEN_TIME) || (date_time.unit.hour != previous_date_time.unit.hour)) { uint8_t hour = date_time.unit.hour; + game_state.curr_screen = SCREEN_TIME; if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); else { From c74ed78d72c98433c5b57f2573da984b2d0a4149 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 27 Aug 2024 13:09:44 -0400 Subject: [PATCH 16/18] Changed U LOSE to LOSE --- movement/watch_faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index f24003fa..54cf31ee 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -381,7 +381,7 @@ static void begin_playing(endless_runner_state_t *state) { static void display_lose_screen(endless_runner_state_t *state) { game_state.curr_screen = SCREEN_LOSE; game_state.curr_score = 0; - watch_display_string(" U LOSE ", 0); + watch_display_string(" LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); else From 2e46aa0e2c5c3e406e90890e5a581b0257303fa9 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 16:25:05 -0400 Subject: [PATCH 17/18] got rid of hardcoding of half-second zero blink --- movement/watch_faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 54cf31ee..3509fc22 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -233,7 +233,7 @@ static void add_to_score(endless_runner_state_t *state) { static void display_fuel(uint8_t subsecond, uint8_t difficulty) { char buf[4]; - if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % 4 == 0) { + if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % (FREQ/2) == 0) { watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled. return; } From 118c07a3b63392fc5fb9c5b4b0dbfa052b254c9a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 17:13:59 -0400 Subject: [PATCH 18/18] Reduced struct memory per code review --- .../watch_faces/complication/endless_runner_face.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index f9d6409b..8c8fa215 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -37,13 +37,12 @@ */ typedef struct { - // These are values that need saving between uses - uint32_t hi_score : 10; - uint32_t difficulty : 3; - bool soundOn; - uint32_t month_last_hi_score : 4; - uint32_t year_last_hi_score : 6; - uint32_t unused : 8; + uint16_t hi_score : 10; + uint8_t difficulty : 3; + uint8_t month_last_hi_score : 4; + uint8_t year_last_hi_score : 6; + uint8_t soundOn : 1; + /* 24 bits, likely aligned to 32 bits = 4 bytes */ } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr);