From 647457f27ac361964ad47ba847825ae1ac5ecc46 Mon Sep 17 00:00:00 2001 From: Daniel Bergman Date: Sat, 9 Aug 2025 19:52:35 +0200 Subject: [PATCH 01/66] Add a defensive check for negative values - silences compiler warnings --- watch-faces/complication/pulsometer_face.c | 1 + 1 file changed, 1 insertion(+) diff --git a/watch-faces/complication/pulsometer_face.c b/watch-faces/complication/pulsometer_face.c index 42f445af..dcef39c9 100644 --- a/watch-faces/complication/pulsometer_face.c +++ b/watch-faces/complication/pulsometer_face.c @@ -80,6 +80,7 @@ static void pulsometer_display_measurement(pulsometer_state_t *pulsometer) { char buf[5]; int16_t value = pulsometer->pulses; + if (value < 0) value = 0; if (value > 9999) value = 9999; snprintf(buf, sizeof(buf), "%-4hd", value); From 2a714c74362937530aed750d26170919f8501ff9 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 11 Aug 2025 20:47:01 -0400 Subject: [PATCH 02/66] Moved face --- movement_faces.h | 1 + watch-faces.mk | 1 + .../complication/endless_runner_face.c | 0 .../complication/endless_runner_face.h | 0 4 files changed, 2 insertions(+) rename {legacy/watch_faces => watch-faces}/complication/endless_runner_face.c (100%) rename {legacy/watch_faces => watch-faces}/complication/endless_runner_face.h (100%) diff --git a/movement_faces.h b/movement_faces.h index fc43716d..bcd82330 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -73,4 +73,5 @@ #include "wareki_face.h" #include "deadline_face.h" #include "wordle_face.h" +#include "endless_runner_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 22e262cf..95d81875 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -48,4 +48,5 @@ SRCS += \ ./watch-faces/sensor/lis2dw_monitor_face.c \ ./watch-faces/complication/wareki_face.c \ ./watch-faces/complication/deadline_face.c \ + ./watch-faces/complication/endless_runner_face.c \ # New watch faces go above this line. diff --git a/legacy/watch_faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c similarity index 100% rename from legacy/watch_faces/complication/endless_runner_face.c rename to watch-faces/complication/endless_runner_face.c diff --git a/legacy/watch_faces/complication/endless_runner_face.h b/watch-faces/complication/endless_runner_face.h similarity index 100% rename from legacy/watch_faces/complication/endless_runner_face.h rename to watch-faces/complication/endless_runner_face.h From d9466cb2d1180057acbbd9a4d0c22be26dbba305 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 11 Aug 2025 21:05:04 -0400 Subject: [PATCH 03/66] Modified for custom display --- .../complication/endless_runner_face.c | 175 ++++++++---------- 1 file changed, 74 insertions(+), 101 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 1a173293..e1368707 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -77,6 +77,23 @@ typedef struct { uint8_t fuel; } game_state_t; +// always-on, left, right, bottom, jump-top, jump-left, jump-right +int8_t classic_ball_arr_com[] = {1, 0, 1, 0, 2, 1, 2}; +int8_t classic_ball_arr_seg[] = {20, 20, 21, 21, 20, 17, 21}; +int8_t custom_ball_arr_com[] = {2, 1, 1, 0, 3, 3, 2}; +int8_t custom_ball_arr_seg[] = {15, 15, 14, 15, 14, 15, 14}; + +// obstacle 0-11 +int8_t classic_obstacle_arr_com[] = {0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0}; +int8_t classic_obstacle_arr_seg[] = {18, 19, 20, 21, 22, 23, 0, 1, 2, 4, 5, 6}; +int8_t custom_obstacle_arr_com[] = {1, 1, 1, 1, 1, 0, 1, 0, 3, 0, 0, 2}; +int8_t custom_obstacle_arr_seg[] = {22, 16, 15, 14, 1, 2, 3, 4, 4, 5, 6, 7}; + +int8_t *ball_arr_com; +int8_t *ball_arr_seg; +int8_t *obstacle_arr_com; +int8_t *obstacle_arr_seg; + static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; @@ -84,7 +101,7 @@ 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); + printf("%u", (value >> i) & 1); // Optional: add a space every 4 bits for readability if (i % 4 == 0 && i != 0) { printf(" "); @@ -188,22 +205,22 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { static void display_ball(bool jumping) { if (!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); + watch_set_pixel(ball_arr_com[3], ball_arr_seg[3]); + watch_set_pixel(ball_arr_com[2], ball_arr_seg[2]); + watch_set_pixel(ball_arr_com[1], ball_arr_seg[1]); + watch_set_pixel(ball_arr_com[0], ball_arr_seg[0]); + watch_clear_pixel(ball_arr_com[6], ball_arr_seg[6]); + watch_clear_pixel(ball_arr_com[5], ball_arr_seg[5]); + watch_clear_pixel(ball_arr_com[4], ball_arr_seg[4]); } 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); + watch_clear_pixel(ball_arr_com[3], ball_arr_seg[3]); + watch_clear_pixel(ball_arr_com[2], ball_arr_seg[2]); + watch_clear_pixel(ball_arr_com[1], ball_arr_seg[1]); + watch_set_pixel(ball_arr_com[0], ball_arr_seg[0]); + watch_set_pixel(ball_arr_com[6], ball_arr_seg[6]); + watch_set_pixel(ball_arr_com[5], ball_arr_seg[5]); + watch_set_pixel(ball_arr_com[4], ball_arr_seg[4]); } } @@ -212,12 +229,12 @@ static void display_score(uint8_t score) { if (game_state.fuel_mode) { score %= (MAX_DISP_SCORE_FUEL + 1); sprintf(buf, "%1d", score); - watch_display_string(buf, 0); + watch_display_text(WATCH_POSITION_TOP_LEFT, buf); } else { score %= (MAX_DISP_SCORE + 1); sprintf(buf, "%2d", score); - watch_display_string(buf, 2); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); } } @@ -234,11 +251,11 @@ 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 % (FREQ/2) == 0) { - watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled. + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); // Blink the 0 fuel to show it cannot be refilled. return; } sprintf(buf, "%2d", game_state.fuel); - watch_display_string(buf, 2); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); } static void check_and_reset_hi_score(endless_runner_state_t *state) { @@ -255,28 +272,15 @@ static void check_and_reset_hi_score(endless_runner_state_t *state) { } static void display_difficulty(uint16_t difficulty) { - switch (difficulty) - { - case DIFF_BABY: - watch_display_string(" b", 2); - break; - case DIFF_EASY: - watch_display_string(" E", 2); - break; - case DIFF_HARD: - watch_display_string(" H", 2); - break; - 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; - } + static const char *labels[] = { + [DIFF_BABY] = " b", + [DIFF_EASY] = " E", + [DIFF_HARD] = " H", + [DIFF_FUEL] = " F", + [DIFF_FUEL_1] = "1F", + [DIFF_NORM] = " N" + }; + watch_display_text(WATCH_POSITION_TOP_RIGHT, labels[difficulty]); game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1; } @@ -309,13 +313,15 @@ 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(); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN", "ER"); + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); if (hi_score > MAX_HI_SCORE) { - watch_display_string("ER HS --", 0); + watch_display_text(WATCH_POSITION_BOTTOM, "HS --"); } else { - char buf[14]; - sprintf(buf, "ER HS%4d", hi_score); - watch_display_string(buf, 0); + char buf[10]; + sprintf(buf, "HS%4d", hi_score); + watch_display_text(WATCH_POSITION_BOTTOM, buf); } display_difficulty(difficulty); } @@ -337,17 +343,12 @@ static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { } watch_set_colon(); sprintf( buf, "%2d%02d ", hour, date_time.unit.minute); - watch_display_string(buf, 4); + watch_display_text(WATCH_POSITION_BOTTOM, buf); } - // 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); + // If only the minute need updating + else { + sprintf( buf, "%02d", date_time.unit.minute); + watch_display_text(WATCH_POSITION_MINUTES, buf); } previous_date_time.reg = date_time.reg; } @@ -358,14 +359,15 @@ static void begin_playing(endless_runner_state_t *state) { watch_clear_colon(); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); if (game_state.fuel_mode) { - watch_display_string(" ", 0); + watch_clear_display(); 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); + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); + watch_display_text(WATCH_POSITION_BOTTOM, " "); game_state.obst_pattern = get_random_legal(0, difficulty); } game_state.jump_state = NOT_JUMPING; @@ -381,7 +383,8 @@ 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(" LOSE ", 0); + watch_clear_display(); + watch_display_text(WATCH_POSITION_BOTTOM, " LOSE "); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); else @@ -395,9 +398,9 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 2: game_state.loc_2_on = obstacle; if (obstacle) - watch_set_pixel(0, 20); + watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); else if (game_state.jump_state != NOT_JUMPING) { - watch_clear_pixel(0, 20); + watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); if (game_state.fuel_mode && prev_obst_pos_two) add_to_score(state); } @@ -406,55 +409,20 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 3: game_state.loc_3_on = obstacle; if (obstacle) - watch_set_pixel(1, 21); + watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); else if (game_state.jump_state != NOT_JUMPING) - watch_clear_pixel(1, 21); + watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); break; case 1: 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: - 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: + if (obstacle) + watch_set_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); + else + watch_clear_pixel(obstacle_arr_com[grid_loc], obstacle_arr_seg[grid_loc]); break; } } @@ -551,6 +519,11 @@ void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr) { void endless_runner_face_activate(void *context) { (void) context; + bool is_custom_lcd = watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM; + ball_arr_com = is_custom_lcd ? custom_ball_arr_com : classic_ball_arr_com; + ball_arr_seg = is_custom_lcd ? custom_ball_arr_seg : classic_ball_arr_seg; + obstacle_arr_com = is_custom_lcd ? custom_obstacle_arr_com : classic_obstacle_arr_com; + obstacle_arr_seg = is_custom_lcd ? custom_obstacle_arr_seg : classic_obstacle_arr_seg; } bool endless_runner_face_loop(movement_event_t event, void *context) { From 89b58cbb795eed7082ab6446f547011fc1f98116 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 11 Aug 2025 21:12:27 -0400 Subject: [PATCH 04/66] Fixed time display --- watch-faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index e1368707..b7e78667 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -573,7 +573,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: - display_time(watch_rtc_get_date_time(), movement_clock_mode_24h()); + display_time(movement_get_local_date_time(), movement_clock_mode_24h()); break; default: return movement_default_loop_handler(event); From 553572db5f8c844b727ef19c1b415c69197fb3be Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 11 Aug 2025 21:15:44 -0400 Subject: [PATCH 05/66] included delay.h --- watch-faces/complication/endless_runner_face.c | 1 + 1 file changed, 1 insertion(+) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index b7e78667..d3985d40 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -25,6 +25,7 @@ #include #include #include "endless_runner_face.h" +#include "delay.h" typedef enum { JUMPING_FINAL_FRAME = 0, From 1b503336c523a9529c1a5561c299da235faa90dc Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 11 Aug 2025 22:03:15 -0400 Subject: [PATCH 06/66] Moved face --- .../complication/higher_lower_game_face.c | 0 .../complication/higher_lower_game_face.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {legacy/watch_faces => watch-faces}/complication/higher_lower_game_face.c (100%) rename {legacy/watch_faces => watch-faces}/complication/higher_lower_game_face.h (100%) diff --git a/legacy/watch_faces/complication/higher_lower_game_face.c b/watch-faces/complication/higher_lower_game_face.c similarity index 100% rename from legacy/watch_faces/complication/higher_lower_game_face.c rename to watch-faces/complication/higher_lower_game_face.c diff --git a/legacy/watch_faces/complication/higher_lower_game_face.h b/watch-faces/complication/higher_lower_game_face.h similarity index 100% rename from legacy/watch_faces/complication/higher_lower_game_face.h rename to watch-faces/complication/higher_lower_game_face.h From fc9e6c388e2794c32414ba6480d189c6bd33eb9f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 12 Aug 2025 18:57:15 -0400 Subject: [PATCH 07/66] Fixed segmapping --- movement_faces.h | 1 + watch-faces.mk | 1 + watch-faces/complication/higher_lower_game_face.c | 13 +++++++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/movement_faces.h b/movement_faces.h index fc43716d..24509a66 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -73,4 +73,5 @@ #include "wareki_face.h" #include "deadline_face.h" #include "wordle_face.h" +#include "higher_lower_game_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 22e262cf..11dab1d8 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -48,4 +48,5 @@ SRCS += \ ./watch-faces/sensor/lis2dw_monitor_face.c \ ./watch-faces/complication/wareki_face.c \ ./watch-faces/complication/deadline_face.c \ + ./watch-faces/complication/higher_lower_game_face.c \ # New watch faces go above this line. diff --git a/watch-faces/complication/higher_lower_game_face.c b/watch-faces/complication/higher_lower_game_face.c index 3491b5bd..260412d9 100755 --- a/watch-faces/complication/higher_lower_game_face.c +++ b/watch-faces/complication/higher_lower_game_face.c @@ -30,7 +30,7 @@ #include #include #include "higher_lower_game_face.h" -#include "watch_private_display.h" +#include "watch_common_display.h" #define TITLE_TEXT "Hi-Lo" #define GAME_BOARD_SIZE 6 @@ -151,9 +151,14 @@ static void init_game(void) { } static void set_segment_at_position(segment_t segment, uint8_t position) { - const uint64_t position_segment_data = (Segment_Map[position] >> (8 * (uint8_t) segment)) & 0xFF; - const uint8_t com_pin = position_segment_data >> 6; - const uint8_t seg = position_segment_data & 0x3F; + digit_mapping_t segmap; + if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) { + segmap = Custom_LCD_Display_Mapping[position]; + } else { + segmap = Classic_LCD_Display_Mapping[position]; + } + const uint8_t com_pin = segmap.segment[segment].address.com; + const uint8_t seg = segmap.segment[segment].address.seg; watch_set_pixel(com_pin, seg); } From f7e7482c491584966dbc77c2926a57b938100b78 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 12 Aug 2025 19:43:28 -0400 Subject: [PATCH 08/66] Moved away from watch_display_string --- .../complication/higher_lower_game_face.c | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/watch-faces/complication/higher_lower_game_face.c b/watch-faces/complication/higher_lower_game_face.c index 260412d9..cfbbb98e 100755 --- a/watch-faces/complication/higher_lower_game_face.c +++ b/watch-faces/complication/higher_lower_game_face.c @@ -37,8 +37,6 @@ #define MAX_BOARDS 40 #define GUESSES_PER_SCREEN 5 #define WIN_SCORE (MAX_BOARDS * GUESSES_PER_SCREEN) -#define STATUS_DISPLAY_START 0 -#define BOARD_SCORE_DISPLAY_START 2 #define BOARD_DISPLAY_START 4 #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 @@ -141,8 +139,8 @@ static void reset_board(bool first_round) { static void init_game(void) { watch_clear_display(); - watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); - watch_display_string("GA", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT); + watch_display_text(WATCH_POSITION_TOP_LEFT, "HL"); reset_deck(); reset_board(true); score = 0; @@ -214,16 +212,16 @@ static void render_board_count(void) { // Render completed boards (screens) char buf[3] = {0}; snprintf(buf, sizeof(buf), "%2hhu", completed_board_count); - watch_display_string(buf, BOARD_SCORE_DISPLAY_START); + watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); } static void render_final_score(void) { - watch_display_string("SC", STATUS_DISPLAY_START); + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "SCO", "SC"); char buf[7] = {0}; const uint8_t complete_boards = score / GUESSES_PER_SCREEN; snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score); watch_set_colon(); - watch_display_string(buf, BOARD_DISPLAY_START); + watch_display_text(WATCH_POSITION_BOTTOM, buf); } static guess_t get_answer(void) { @@ -256,13 +254,13 @@ static void do_game_loop(guess_t user_guess) { // Render answer indicator switch (answer) { case HL_GUESS_EQUAL: - watch_display_string("==", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_TOP_LEFT, "=="); break; case HL_GUESS_HIGHER: - watch_display_string("HI", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_TOP_LEFT, "HI"); break; case HL_GUESS_LOWER: - watch_display_string("LO", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_TOP_LEFT, "LO"); break; } @@ -273,7 +271,7 @@ static void do_game_loop(guess_t user_guess) { // No score for two consecutive identical cards } else { // Incorrect guess, game over - watch_display_string("GO", STATUS_DISPLAY_START); + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "End", "GO"); game_board[guess_position].revealed = true; render_board_position(guess_position); game_state = HL_GS_LOSE; @@ -282,9 +280,9 @@ static void do_game_loop(guess_t user_guess) { if (score >= WIN_SCORE) { // Win, perhaps some kind of animation sequence? - watch_display_string("WI", STATUS_DISPLAY_START); - watch_display_string(" ", BOARD_SCORE_DISPLAY_START); - watch_display_string("------", BOARD_DISPLAY_START); + watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "WIN", "WI"); + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); + watch_display_text(WATCH_POSITION_BOTTOM, "------"); game_state = HL_GS_WIN; return; } @@ -314,12 +312,12 @@ static void do_game_loop(guess_t user_guess) { break; case HL_GS_SHOW_SCORE: watch_clear_display(); - watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); - watch_display_string("GA", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT); + watch_display_text(WATCH_POSITION_TOP_LEFT, "HL"); game_state = HL_GS_TITLE_SCREEN; break; default: - watch_display_string("ERROR", BOARD_DISPLAY_START); + watch_display_text(WATCH_POSITION_BOTTOM, "ERROR"); break; } } @@ -358,8 +356,8 @@ bool higher_lower_game_face_loop(movement_event_t event, void *context) { switch (event.event_type) { case EVENT_ACTIVATE: // Show your initial UI here. - watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); - watch_display_string("GA", STATUS_DISPLAY_START); + watch_display_text(WATCH_POSITION_BOTTOM, TITLE_TEXT); + watch_display_text(WATCH_POSITION_TOP_LEFT, "HL"); break; case EVENT_TICK: // If needed, update your display here. From c803ba83b5ac6bd9672d37d0fc88b7417366cda1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 12 Aug 2025 20:07:03 -0400 Subject: [PATCH 09/66] Removed Aces; Added --- across screen when losing --- .../complication/higher_lower_game_face.c | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/watch-faces/complication/higher_lower_game_face.c b/watch-faces/complication/higher_lower_game_face.c index cfbbb98e..762c462a 100755 --- a/watch-faces/complication/higher_lower_game_face.c +++ b/watch-faces/complication/higher_lower_game_face.c @@ -32,6 +32,11 @@ #include "higher_lower_game_face.h" #include "watch_common_display.h" + +#define KING 12 +#define QUEEN 11 +#define JACK 10 + #define TITLE_TEXT "Hi-Lo" #define GAME_BOARD_SIZE 6 #define MAX_BOARDS 40 @@ -40,7 +45,7 @@ #define BOARD_DISPLAY_START 4 #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 -#define MAX_CARD_VALUE 14 +#define MAX_CARD_VALUE KING #define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1) #define CARD_SUIT_COUNT 4 #define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT) @@ -160,10 +165,12 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { watch_set_pixel(com_pin, seg); } +static inline size_t get_display_position(size_t board_position) { + return FLIP_BOARD_DIRECTION ? BOARD_DISPLAY_START + board_position : BOARD_DISPLAY_END - board_position; +} + static void render_board_position(size_t board_position) { - const size_t display_position = FLIP_BOARD_DIRECTION - ? BOARD_DISPLAY_START + board_position - : BOARD_DISPLAY_END - board_position; + const size_t display_position = get_display_position(board_position); const bool revealed = game_board[board_position].revealed; //// Current position indicator spot @@ -181,18 +188,18 @@ static void render_board_position(size_t board_position) { const uint8_t value = game_board[board_position].value; switch (value) { - case 14: // A (≡) + case KING: // K (≡) watch_display_character(' ', display_position); set_segment_at_position(A, display_position); set_segment_at_position(D, display_position); set_segment_at_position(G, display_position); break; - case 13: // K (=) + case QUEEN: // Q (=) watch_display_character(' ', display_position); set_segment_at_position(A, display_position); set_segment_at_position(D, display_position); break; - case 12: // Q (-) + case JACK: // J (-) watch_display_character('-', display_position); break; default: { @@ -216,7 +223,7 @@ static void render_board_count(void) { } static void render_final_score(void) { - watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "SCO", "SC"); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "SCORE", "SC "); char buf[7] = {0}; const uint8_t complete_boards = score / GUESSES_PER_SCREEN; snprintf(buf, sizeof(buf), "%2hu %03hu", complete_boards, score); @@ -273,7 +280,11 @@ static void do_game_loop(guess_t user_guess) { // Incorrect guess, game over watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "End", "GO"); game_board[guess_position].revealed = true; + watch_display_text(WATCH_POSITION_BOTTOM, "------"); + render_board_position(guess_position - 1); render_board_position(guess_position); + if (game_board[guess_position].value == JACK && guess_position < GAME_BOARD_SIZE) // Adds a space in case the revealed option is '-' + watch_display_character(' ', get_display_position(guess_position + 1)); game_state = HL_GS_LOSE; return; } @@ -282,7 +293,7 @@ static void do_game_loop(guess_t user_guess) { // Win, perhaps some kind of animation sequence? watch_display_text_with_fallback(WATCH_POSITION_TOP_LEFT, "WIN", "WI"); watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); - watch_display_text(WATCH_POSITION_BOTTOM, "------"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WINNER", "winnEr"); game_state = HL_GS_WIN; return; } From 9fbaadeaac81fa77b040a2bc99112d3b4a614ba8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 12 Aug 2025 21:22:58 -0400 Subject: [PATCH 10/66] Added lander face --- movement_faces.h | 1 + watch-faces.mk | 1 + watch-faces/complication/lander_face.c | 572 +++++++++++++++++++++++++ watch-faces/complication/lander_face.h | 152 +++++++ 4 files changed, 726 insertions(+) create mode 100644 watch-faces/complication/lander_face.c create mode 100644 watch-faces/complication/lander_face.h diff --git a/movement_faces.h b/movement_faces.h index fc43716d..7108c5e0 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -73,4 +73,5 @@ #include "wareki_face.h" #include "deadline_face.h" #include "wordle_face.h" +#include "lander_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 22e262cf..f60a827c 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -48,4 +48,5 @@ SRCS += \ ./watch-faces/sensor/lis2dw_monitor_face.c \ ./watch-faces/complication/wareki_face.c \ ./watch-faces/complication/deadline_face.c \ + ./watch-faces/complication/lander_face.c \ # New watch faces go above this line. diff --git a/watch-faces/complication/lander_face.c b/watch-faces/complication/lander_face.c new file mode 100644 index 00000000..1cf81039 --- /dev/null +++ b/watch-faces/complication/lander_face.c @@ -0,0 +1,572 @@ +/* + * MIT License + * + * Copyright (c) 2024 Klingon Jane + * + * 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. + */ + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include +#include "lander_face.h" + +#ifndef max +#define max(x, y) ((y) > (x) ? (y) : (x)) +#endif + +#ifndef min +#define min(x, y) ((x) > (y) ? (y) : (x)) +#endif + +#define LANDER_TICK_FREQUENCY 8 +#define MONSTER_DISPLAY_TICKS 9 +#define ENGINE_THRUST 11 +#define MODE_WAITING_TO_START 0 +#define MODE_DISPLAY_SKILL_LEVEL 1 +#define MODE_PLAYING 2 +#define MODE_TOUCHDOWN_BLANK 3 +#define MODE_DISPLAY_FINAL_STATUS 4 +#define MODE_MONSTER 5 +#define MODE_FIND_EARTH_MESSAGE 6 +#define CREWS_COMPLIMENT 13 +// Granularity is divisions per foot - height display +#define GRANUL 40 +// Next lines for repeat heroes only. +#define PROMOTION_INTERVAL 3 +#define LEVEL_ACE 8 +#define LEVEL_STARBUCK 11 +#define HARD_EARTH_INCREMENTS 11 +#define MAX_HARD_EARTH_CHANCE 6 + +// The gory final result calculations: +#define SPEED_FATALITY_ALL 41 +#define SPEED_FATALITY_NONE 26 +#define SPEED_NO_DAMAGE 21 +#define SPEED_LEVEL_INCREMENTS 2 +#define SPEED_MAJOR_CRASH 73 +#define MAJOR_CRASH_INCREMENTS 65 +#define SPEED_INJURY_NONE 20 +#define SPEED_INJURY_FULCRUM 32 +#define INJURY_FULCRUM_PROB 65 +#define FUEL_SCORE_GOOD 145 +#define FUEL_SCORE_GREAT 131 +#define FUEL_SCORE_FANTASTIC 125 + +// Joey Castillo to oversee storage allocation row +#define LANDER_STORAGE_ROW 2 +#define STORAGE_KEY_NUMBER 110 + +#define DIFFICULTY_LEVELS 3 +char lander_difficulty_names[DIFFICULTY_LEVELS][7] = { + "NOrMAL", "HArd ", "HArdEr" +}; +#define MONSTER_TYPES 4 +char lander_monster_names[MONSTER_TYPES][7] = { + "mOnStr", "6Erbil", "HAmStr", "Rabbit" +}; +#define MONSTER_ACTIONS 8 +char lander_monster_actions[MONSTER_ACTIONS][7] = { + "HUn6ry", " EAtS", "6Reedy", "annoYd", "nASty ", "SAVOry", "HO66SH", " pI66Y" +}; + +// -------------- +// Custom methods +// -------------- + + +static int gen_random_int (int16_t lower, int16_t upper) { + int range; + int retVal; + range = upper - lower + 1; + if ( range < 2 ) range = 2; + // Emulator: use rand. Hardware: use arc4random. + #if __EMSCRIPTEN__ + retVal = rand() % range; + #else + retVal = arc4random_uniform(range); + #endif + retVal += lower; + return retVal; +} + +static uint8_t assignProb ( uint8_t lowerProb, uint8_t upperProb, int16_t lowerSpeed, int16_t upperSpeed, int16_t actSpeed ) { + float probRange, speedRange; + float ratio, probFloat; + int probInt; + speedRange = upperSpeed - lowerSpeed; + if (speedRange<1.0) speedRange = 1.0; + probRange = upperProb - lowerProb; + ratio = ( (float) actSpeed - (float) lowerSpeed ) / speedRange; + probFloat = (float) lowerProb + ( ratio * probRange ); + probInt = (int) ( probFloat + 0.5 ); + probInt = min ( probInt, upperProb ); + probInt = max ( probInt, lowerProb ); + return (uint8_t) probInt; +} + +static void write_to_lander_EEPROM(lander_state_t *state) { + uint8_t output_array [ 3 ]; + output_array [ 0 ] = STORAGE_KEY_NUMBER; + output_array [ 1 ] = state->hero_counter; + output_array [ 2 ] = state->legend_counter; + watch_storage_erase ( LANDER_STORAGE_ROW ); + watch_storage_sync ( ); + watch_storage_write ( LANDER_STORAGE_ROW, 0, output_array, 3 ); +} + +// --------------------------- +// Standard watch face methods +// --------------------------- +void lander_face_setup(uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(lander_state_t)); + memset(*context_ptr, 0, sizeof(lander_state_t)); + lander_state_t *state = (lander_state_t *)*context_ptr; + state->led_enabled = false; + } + // Emulator only: Seed random number generator + #if __EMSCRIPTEN__ + srand(time(NULL)); + #endif +} + +void lander_face_activate(void *context) { + lander_state_t *state = (lander_state_t *)context; + char buf [ 7 ]; + state->mode = MODE_WAITING_TO_START; + state->led_active = false; + state->reset_counter = 0; + watch_clear_all_indicators ( ); + uint32_t offset = 0; + uint32_t size = 3; + uint8_t stored_data [ size ]; + // See if the hero_counter was ever written to EEPROM storage + watch_storage_read (LANDER_STORAGE_ROW, offset, stored_data, size); + if (stored_data[0] == STORAGE_KEY_NUMBER ) + { + state->hero_counter = stored_data [1]; // There's real data in there. + state->legend_counter = stored_data [2]; + } + else + { + state->hero_counter = 0; // Nope. Nothing there. + state->legend_counter = 0; + write_to_lander_EEPROM(state); // Initial EEPROM tracking data. + } + state->difficulty_level = state->hero_counter / PROMOTION_INTERVAL; + state->difficulty_level = min ( state->difficulty_level, DIFFICULTY_LEVELS - 1 ); // Upper limit + // Fancy intro + if ( state->legend_counter == 0 ) watch_display_string("LA", 0); + else watch_display_string("LE", 0); + if ( ( state->hero_counter == 0 ) || ( state->hero_counter >= 40 ) ) watch_display_string ( " ", 2); + else + { + sprintf ( buf, "%2d", state->hero_counter ); + watch_display_string(buf, 2); + } + if ( state->hero_counter >= 100 ) sprintf ( buf, "Str%3d", state->hero_counter ); + else if ( state->hero_counter >= 40 ) sprintf ( buf, "Strb%2d", state->hero_counter ); + else if ( state->hero_counter >= LEVEL_STARBUCK ) sprintf ( buf, "StrbUC" ); + else if ( state->hero_counter >= LEVEL_ACE ) sprintf ( buf, " ACE " ); // This human is good + else if ( state->difficulty_level == 0 ) sprintf ( buf, " " ); + else sprintf ( buf, "%s", lander_difficulty_names[state->difficulty_level] ); + watch_display_string ( buf, 4); + if (state->led_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); +} + +bool lander_face_loop(movement_event_t event, void *context) { + lander_state_t *state = (lander_state_t *)context; + char buf [ 20 ]; // [11] is more correct and works; compiler too helpful. + + switch (event.event_type) { + case EVENT_TICK: + state->tick_counter++; + if ( state->mode == MODE_PLAYING ) { + int16_t accel = state->gravity; + bool gas_pedal_on = HAL_GPIO_BTN_ALARM_read() || HAL_GPIO_BTN_LIGHT_read(); + if ( gas_pedal_on && ( state->fuel_remaining > 0 ) ) { + accel = ENGINE_THRUST + state->gravity; // Gravity is negative + state->fuel_remaining--; // Used 1 fuel unit + watch_set_indicator ( WATCH_INDICATOR_LAP ); + // Low fuel warning indicators + if ( state->fuel_remaining == ( 3 * LANDER_TICK_FREQUENCY ) ) { // 3 seconds of fuel left + watch_set_indicator ( WATCH_INDICATOR_SIGNAL ); + watch_set_indicator ( WATCH_INDICATOR_BELL ); + watch_set_indicator ( WATCH_INDICATOR_PM ); + watch_set_indicator ( WATCH_INDICATOR_24H ); + } + else if ( state->fuel_remaining == 0 ) { // 0 seconds of fuel left, empty! + watch_clear_all_indicators ( ); + } + } + else { + watch_clear_indicator ( WATCH_INDICATOR_LAP ); + } + state->speed += accel; + state->height += state->speed; + if ( state->height > 971 * 80 ) { // Escape height + watch_clear_all_indicators (); + watch_display_string ( "ESCAPE", 4 ); + state->tick_counter = 0; + state->mode = MODE_WAITING_TO_START; + } + else if ( state->height <= 0 ) { // Touchdown + state->tick_counter = 0; + state->mode = MODE_TOUCHDOWN_BLANK; + } + else { + // Update height display + sprintf ( buf, "%4d", (int) ( state->height / GRANUL ) ); + watch_display_string ( buf, 4 ); + } + } + else if ( state->mode == MODE_TOUCHDOWN_BLANK ) { + // Blank display on touchdown + if ( state->tick_counter == 1 ) { + watch_clear_all_indicators (); + watch_display_string ( " ", 4 ); + + // Also calc fuel score now. + float fuel_score_float; + uint16_t fuel_used; + fuel_used = state->fuel_start - state->fuel_remaining; + fuel_score_float = (float) fuel_used / (float) state->fuel_tpl; + state->fuel_score = (int) (fuel_score_float * 100.0 + 0.5); + if ( state->legend_counter == 0 ) state->fuel_score -= 8; // First Earth is easier + // Monitor reset_counter + if ( fuel_used >= 1 ) state->reset_counter = 0; + else state->reset_counter++; + if ( state->reset_counter >= 3 ) { + state->hero_counter = 0; + state->difficulty_level = 0; + if ( state->reset_counter >= 6 ) state->legend_counter = 0; + watch_display_string ( "rESET ", 4 ); + write_to_lander_EEPROM(state); + } + } + // Wait until time for next display + if ( state->tick_counter >= ( 1 * LANDER_TICK_FREQUENCY ) ) { + state->tick_counter = 0; + state->mode = MODE_DISPLAY_FINAL_STATUS; + } + } + else if ( state->mode == MODE_DISPLAY_FINAL_STATUS ) { + bool last_pass = false; + if ( state->tick_counter >= LANDER_TICK_FREQUENCY ) last_pass = true; + + // Show final status + if ( state->tick_counter == 1 ) { + // Calculate many attributes + // 1) Major crash: bug, crater, vaporized (gone). + // 2) Rank ship's health 0 to 8 + // 3) Crew fatalities and injuries + // 4) Special conditions: hero + // 5) Set fuel conservation indicators as appropriate + // 6) Set coffee maker OK indicator as appropriate + // 7) Green light if ship intact + // 8) Set standard display if not preempted. + bool allDone; + int16_t finalSpeed, boostedSpeed, levelsDamage; + int8_t shipsHealth, myRand; + uint8_t fatalities, probFatal, probInjury; + uint8_t i; + + allDone = false; + // Easiest implementation for difficulty_level is to increase touchdown speed above actual. + finalSpeed = abs ( state->speed ) + state->difficulty_level * 4; + // First Earth is a bit easier than all the others + if ( state->legend_counter == 0 ) finalSpeed -= 2; + + // 1) Major crash: bug, crater, vaporized (gone). + if ( finalSpeed >= SPEED_MAJOR_CRASH ) { + allDone = true; + shipsHealth = -1; + if ( finalSpeed >= ( SPEED_MAJOR_CRASH + 2 * MAJOR_CRASH_INCREMENTS ) ) sprintf ( buf, "GOnE " ); + else if ( finalSpeed >= ( SPEED_MAJOR_CRASH + MAJOR_CRASH_INCREMENTS ) ) sprintf ( buf, " CrAtr" ); + else sprintf ( buf, " bU6" ); + } + // 2) Rank ship's health 0 to 8 + if (!allDone) { + boostedSpeed = finalSpeed + SPEED_LEVEL_INCREMENTS - 1; + levelsDamage = (int) ( ( boostedSpeed - SPEED_NO_DAMAGE ) / SPEED_LEVEL_INCREMENTS ); + shipsHealth = 8 - levelsDamage; + shipsHealth = min ( shipsHealth, 8 ); // Keep between 0 and 8 + shipsHealth = max ( shipsHealth, 0 ); + } + state->ships_health = shipsHealth; // Remember ships health + // 3) Crew fatalities and injuries + if (!allDone) { + // Fatalies + probFatal = assignProb ( 0, 92, SPEED_FATALITY_NONE, SPEED_FATALITY_ALL, finalSpeed ); + // Injuries + if ( finalSpeed <= SPEED_INJURY_FULCRUM ) { + probInjury = assignProb ( 0, INJURY_FULCRUM_PROB, SPEED_INJURY_NONE, SPEED_INJURY_FULCRUM, finalSpeed ); + } else { + probInjury = assignProb ( INJURY_FULCRUM_PROB, 96, SPEED_INJURY_FULCRUM, SPEED_FATALITY_ALL, finalSpeed ); + } + fatalities = 0; + state->injured = 0; + for ( i = 0; i < CREWS_COMPLIMENT; i++ ) { + myRand = gen_random_int ( 1, 100 ); + if ( myRand <= probFatal ) fatalities++; + else if ( myRand <= probInjury ) state->injured++; + } + state->uninjured = CREWS_COMPLIMENT - fatalities - state->injured; + } + // 4) Special conditions: hero + if (!allDone) { + if ( (shipsHealth>=8) && ( state->fuel_score <= FUEL_SCORE_FANTASTIC ) ) { + state->hero_counter++; + if ( state->hero_counter==1 ) sprintf ( buf, "HErO " ); + else if ( state->hero_counter == LEVEL_ACE ) sprintf ( buf, " ACE " ); + else if ( state->hero_counter == LEVEL_STARBUCK ) sprintf ( buf, "STrbUC" ); + else if ( state->hero_counter>99 ) sprintf ( buf, "HEr%3d", state->hero_counter ); + else sprintf ( buf, "HErO%2d", state->hero_counter ); // Typical case + allDone = true; + // Two rule sets for finding Earth. Alternate between easy and hard. + int8_t my_odds, temp; + if ( state->legend_counter %2 == 0 ) my_odds = (int8_t) state->hero_counter - LEVEL_STARBUCK; // Easy + else { + temp = ( state->hero_counter - LEVEL_STARBUCK ) + HARD_EARTH_INCREMENTS - 1; + my_odds = temp / HARD_EARTH_INCREMENTS; + my_odds = min ( my_odds, MAX_HARD_EARTH_CHANCE ); + } + // Display odds in weekday region if positive value + if ( my_odds > 0 ) { + char buff3 [ 5 ]; + sprintf ( buff3, "%2d", my_odds ); + watch_display_string ( buff3, 2 ); + } else watch_display_string ( " ", 2 ); + if ( my_odds >= gen_random_int ( 1, 200 ) ) { // EARTH!!!! The final objective. + sprintf ( buf, "EArTH " ); // 17% within 8, 50% by 16, 79% by 24, 94% by 32 <- easy mode + state->hero_counter = 0; + state->legend_counter++; + } + // Recalculate difficulty level base on new hero_counter. + state->difficulty_level = state->hero_counter / PROMOTION_INTERVAL; + state->difficulty_level = min ( state->difficulty_level, DIFFICULTY_LEVELS - 1 ); // Upper limit + // Write to EEPROM + write_to_lander_EEPROM(state); + } + } + // 5) Set fuel conservation indicators as appropriate + if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_FANTASTIC ) ) watch_set_indicator ( WATCH_INDICATOR_LAP ); + if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_GREAT ) ) watch_set_indicator ( WATCH_INDICATOR_24H ); + if ( shipsHealth >= 1 && ( state->fuel_score <= FUEL_SCORE_GOOD ) ) watch_set_indicator ( WATCH_INDICATOR_PM ); + // 6) Set coffee maker OK indicator as appropriate + if ( shipsHealth >= 5 || ( shipsHealth >= 0 && ( gen_random_int ( 0, 3 ) != 1 ) ) ){ + watch_set_indicator ( WATCH_INDICATOR_SIGNAL ); + } + // 7) Green light if ship intact + if ( shipsHealth >= 8 && state->led_enabled) { + watch_set_led_green ( ); + state->led_active = true; + } + // 8) Set standard display if not preempted. + if (!allDone) { + if ( ( state->injured > 0 ) || ( state->uninjured == 0 ) ) { + sprintf ( buf, "%d %2d%2d", shipsHealth, state->uninjured, state->injured ); + } + else { + sprintf ( buf, "%d %2d ", shipsHealth, state->uninjured ); + } + } + // Display final status. + watch_display_string ( buf, 4 ); + } // End if tick_counter == 1 + + // Major crash - ship burning with red LED. + if ( state->ships_health < 0 && state->led_enabled) { + if ( ( gen_random_int ( 0, 1 ) != 1 ) && !last_pass ) { // Always off on last pass + // Turn on red LED. + watch_set_led_red ( ); + state->led_active = true; + } else { + watch_set_led_off ( ); + } + } + // Wait long enough, then allow waiting for next game. + if ( last_pass ) { + watch_set_led_off ( ); + // No change to display text, allow new game to start. + state->mode = MODE_WAITING_TO_START; + // Unless it's time for monsters + uint8_t survivors = state->injured + state->uninjured; + if ( ( state->ships_health >= 0 ) && ( survivors > 0 ) && + ( gen_random_int ( -1, 3 ) >= state->ships_health ) ) { + state->mode = MODE_MONSTER; + state->tick_counter = 0; + state->monster_type = gen_random_int ( 0, MONSTER_TYPES - 1 ); + } + } + } // End if MODE_DISPLAY_FINAL_STATUS + else if ( state->mode == MODE_DISPLAY_SKILL_LEVEL ) { + // Display skill level + if ( state->tick_counter == 1 ) { + sprintf ( buf, " %d %d ", state->skill_level, state->skill_level ); + watch_display_string ( buf, 2 ); + } + // Wait long enough, then start game. + if ( state->tick_counter >= ( 2.0 * LANDER_TICK_FREQUENCY ) ) { + state->tick_counter = 0; + // Houston, WE ARE LAUNCHING NOW.... + state->mode = MODE_PLAYING; + } + } + else if ( state->mode == MODE_FIND_EARTH_MESSAGE ) { + // Display "Find" then "Earth" + if ( state->tick_counter == 1 ) { + sprintf ( buf, " FInd " ); + watch_display_string ( buf, 2 ); + } + if ( state->tick_counter == (int) ( 1.5 * LANDER_TICK_FREQUENCY + 1 ) ) { + sprintf ( buf, " EArTH " ); + watch_display_string ( buf, 2 ); + } + // Wait long enough, then display skill level. + if ( state->tick_counter >= ( 3 * LANDER_TICK_FREQUENCY ) ) { + state->tick_counter = 0; + state->mode = MODE_DISPLAY_SKILL_LEVEL; + } + } + else if ( state->mode == MODE_MONSTER ) { + if ( state->tick_counter == 1 ) watch_display_string ( lander_monster_names[state->monster_type], 4 ); + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS + 1 ) { + uint8_t my_rand; + my_rand = gen_random_int ( 0 , MONSTER_ACTIONS - 1 ); + watch_display_string ( lander_monster_actions[my_rand], 4 ); + } + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 ) { // Display 1st monster character + sprintf ( buf, "%s", lander_monster_names[state->monster_type] ); + buf [1] = 0; + watch_display_string(buf,4); + } + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 1 ) { // Display current population, close mouth + sprintf ( buf, "c%2d%2d", state->uninjured, state->injured ); + watch_display_string ( buf, 5 ); + } + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 3 ) watch_display_string ( "C", 5 ); // Open mouth + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 5 ) { + // Decision to: continue loop, end loop or eat astronaut + uint8_t survivors = state->injured + state->uninjured; + uint8_t myRand = gen_random_int ( 0, 16 ); + if ( survivors == 0 ) state->mode = MODE_WAITING_TO_START; + else if ( myRand <= 1 ) { // Leave loop with survivors + sprintf ( buf, "%d %2d%2d", state->ships_health, state->uninjured, state->injured ); + watch_display_string ( buf, 4 ); + state->mode = MODE_WAITING_TO_START; + } else if ( myRand <= 11 ) state->tick_counter = MONSTER_DISPLAY_TICKS * 2; // Do nothing, loop continues + else { // Eat an astronaut - welcome to the space program! + if ( state->injured > 0 && state->uninjured > 0 ) { + if ( gen_random_int ( 0,1 ) == 0 ) state->injured--; + else state->uninjured--; + } + else if ( state->injured > 0 ) state->injured--; + else state->uninjured--; + state->tick_counter = MONSTER_DISPLAY_TICKS * 2; // Re-display + } + } + else if ( state->tick_counter >= MONSTER_DISPLAY_TICKS * 4 ) state->mode = MODE_WAITING_TO_START; // Safety + } // End if MODE_MONSTER + break; // End case EVENT_TICK + case EVENT_ALARM_BUTTON_DOWN: + if ( state->mode == MODE_WAITING_TO_START ) { + // That was the go signal - start a new game!! + float numerator, denominator, timeSquared; + int16_t gravity, thrust; + float myTime, distToTop, fuel_mult; + uint8_t skill_level; + int32_t tplTop; // Top lander height for TPL calculations + movement_request_tick_frequency(LANDER_TICK_FREQUENCY); + watch_set_led_off ( ); // Safety + watch_clear_all_indicators ( ); + // Randomize starting parameters + state->height = gen_random_int ( 131, 181 ) * 80; + // Per line below; see Mars Orbiter September 23, 1999 + if ( gen_random_int ( 0, 8 ) == 5 ) state->height = gen_random_int ( 240, 800 ) * 80; + state->speed = gen_random_int ( -120, 35 ); // Positive is up + state->gravity = gen_random_int ( -3, -2 ) * 2; // negative downwards value + skill_level = gen_random_int ( 1, 4 ); // Precursor to fuel allocation + // Theoretical Perfect Landing (TPL) calculations start here. + myTime = (float) state->speed / (float) state->gravity; // How long to reach this speed? Don't care which way sign is. + distToTop = fabs ( 0.5 * state->gravity * myTime * myTime ); + tplTop = (int) ( state->height + distToTop + 0.5 ); // Theoretical highest point based on all of speed, height and gravity. + // Time squared = ( 2 * grav * height ) / ( t*t + g*t ), where t is net acceleration with thrust on. + gravity = abs ( state->gravity ); + thrust = ENGINE_THRUST + state->gravity; + numerator = 2.0 * (float) gravity * (float) tplTop; + denominator = thrust * thrust + thrust * gravity; + timeSquared = numerator / denominator; + state->fuel_tpl = (int) ( sqrt ( timeSquared ) + 0.5 ); // Fuel required for theoretical perfect landing (TPL). + if ( skill_level == 1 ) fuel_mult = 4.0; // TPL + 300% + else if ( skill_level == 2 ) fuel_mult = 2.5; // TPL + 150% + else if ( skill_level == 3 ) fuel_mult = 1.6; // TPL + 60% + else fuel_mult = 1.3; // TPL + 30% + state->fuel_start = state->fuel_tpl * fuel_mult; + state->fuel_remaining = state->fuel_start; + state->skill_level = skill_level; + state->tick_counter = 0; + if ( gen_random_int ( 1, 109 ) != 37 ) { + // Houston, approaching launch.... + state->mode = MODE_DISPLAY_SKILL_LEVEL; + } + else state->mode = MODE_FIND_EARTH_MESSAGE; + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + if ( state->mode == MODE_WAITING_TO_START ) { + // Display difficulty level + watch_display_string ( lander_difficulty_names [state->difficulty_level], 4 ); + } + break; + case EVENT_LIGHT_LONG_PRESS: + if ( state->mode != MODE_WAITING_TO_START ) break; + state->led_enabled = !state->led_enabled; + if (state->led_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + break; + case EVENT_LIGHT_LONG_UP: + if ( ( state->mode == MODE_WAITING_TO_START ) && ( state->legend_counter > 0 ) ) { + if ( state->legend_counter > 9 ) sprintf (buf,"EArt%2d", state->legend_counter ); + else sprintf (buf,"EArth%d", state->legend_counter ); + // Display legend counter + watch_display_string ( buf, 4 ); + } + break; + + default: + movement_default_loop_handler(event); + break; + } + if ( !state->led_active ) return true; + else return false; +} + +void lander_face_resign(void *context) { + (void) context; + watch_set_led_off ( ); +} \ No newline at end of file diff --git a/watch-faces/complication/lander_face.h b/watch-faces/complication/lander_face.h new file mode 100644 index 00000000..5e4c344f --- /dev/null +++ b/watch-faces/complication/lander_face.h @@ -0,0 +1,152 @@ +/* + * MIT License + * + * Copyright (c) 2024 Klingon Jane + * + * 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 LANDER_FACE_H_ +#define LANDER_FACE_H_ + +#include "movement.h" + +/* + +My remake of a classic planet landing game. + +Objective: Safely land the Cringeworthy. +Use your limited fuel supply to achieve a soft touch-down. + +End scenarios and ship's health: + +Hero They name this planet after you. +8 Life is very cozy. +7 +6 +5 Life is tolerable, plus some creature comforts +4 +3 Marooned. +2 +1 +0 Ship destroyed. Life is harsh, no shelter. Giant hamsters are cute. ** +Bug As in squished. +Crater They name this crater after you. +Gone As in vapourized. + +Landing display format is: +Ship's health, intact crewmen, injured crewmen. + +Additional data: +Crew's compliment: 13. +Low fuel warning icons: activates when 3 seconds of full thrust remains. +** Yes, hamsters are very cute. However; some eating of astronauts may occur. + +Starting velocity, height and gravity are randomized each scenario. +Fuel levels randomly assigned from 1 to 4 (hardest) to match starting parameters. + +A safe landing is always possible. + +End of game icons: +LAP - Fantastic budgeting of fuel supply ( Required for heroic landing status. ) +24H - Great budgeting of fuel supply +PM - Good budgeting of fuel supply +SIGNAL - The combination coffee and tea maker survived + +Landings get progressively harder with the number of heroic landings made. +Number of heroic landings are remembered. + +Heroic +Landings Status + 0 Normal + 3 Hard ( first difficulty increase ) + 6 Harder ( final difficulty increase ) + 8 Ace + 11 ?????? + +Save yourself. Save the coffee maker. + +END of standard training manual + +*/ + + +/* + +What is really going on here? +The fleet is lost. You are a newbie pilot making a name for yourself. + +Objective: Find Earth. + +After reaching ?????? status, future heroic sorties will have 'some' chance in 200 +of finding Earth. + +Your chances improve by 1 chance in 200 for each subsequent Heroic Landing (HL). + +Completing HL 12 will give you 1 chance in 200, for that landing. +HL 13 will give you 2 chances in 200, for that landing. +HL 14 will give you 3 chances in 200, for that landing. +HL 20 will give you 9 chances in 200, for that landing, and so on. + +At these higher levels, your chances in 200 are displayed in the upper right corner on a heroic landing. + +For wannabe pilots only: The HL counter can be reset by crashing three consecutive +missions without touching the thrust button. ( 6 to reset Earth-found counter ) + +Find Earth. Save Humanity. + +*/ + +typedef struct { + int32_t height; + int16_t speed; // Positive is up + uint16_t tick_counter; // For minimum delays + uint16_t fuel_start; + uint16_t fuel_remaining; + uint16_t fuel_tpl; // Fuel required for theoretical perfect landing + uint16_t fuel_score; // 100 is perfect; higher is less perfect + int8_t gravity; // negative downwards value + bool led_enabled; // Can the led be turned on? + bool led_active; // Did we use it this scenario? + uint8_t mode; // 0 Pre-launch waiting, 1 show level, 2 playing, 3 touchdown blank, 4 final display, 5 monster + uint8_t skill_level; // 1 thru 4. Dictates fuel alloted + int8_t ships_health; // 0 thru 8. -1 = major crash + uint8_t hero_counter; // Total heroic landings ever + uint8_t legend_counter; // Historic events counter ( Earth ) + uint8_t difficulty_level; // Based on hero_counter + uint8_t reset_counter; // Can reset hero_counter by crashing using zero fuel several consecutive scenarios + uint8_t monster_type; // Which monster is hungry? + uint8_t uninjured; // OK survivors + uint8_t injured; // Hurt survivors +} lander_state_t; + +void lander_face_setup(uint8_t watch_face_index, void ** context_ptr); +void lander_face_activate(void *context); +bool lander_face_loop(movement_event_t event, void *context); +void lander_face_resign(void *context); + +#define lander_face ((const watch_face_t){ \ + lander_face_setup, \ + lander_face_activate, \ + lander_face_loop, \ + lander_face_resign, \ + NULL, \ +}) + +#endif // LANDER_FACE_H_ From b8260718540cee021a26940d60558f9d78220cef Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 12 Aug 2025 22:08:28 -0400 Subject: [PATCH 11/66] Changed the text logic to watch_display_text --- watch-faces/complication/lander_face.c | 59 ++++++++++++++------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/watch-faces/complication/lander_face.c b/watch-faces/complication/lander_face.c index 1cf81039..8381ff0a 100644 --- a/watch-faces/complication/lander_face.c +++ b/watch-faces/complication/lander_face.c @@ -31,6 +31,7 @@ #include #include #include "lander_face.h" +#include "watch_common_display.h" #ifndef max #define max(x, y) ((y) > (x) ? (y) : (x)) @@ -179,13 +180,13 @@ void lander_face_activate(void *context) { state->difficulty_level = state->hero_counter / PROMOTION_INTERVAL; state->difficulty_level = min ( state->difficulty_level, DIFFICULTY_LEVELS - 1 ); // Upper limit // Fancy intro - if ( state->legend_counter == 0 ) watch_display_string("LA", 0); - else watch_display_string("LE", 0); - if ( ( state->hero_counter == 0 ) || ( state->hero_counter >= 40 ) ) watch_display_string ( " ", 2); + if ( state->legend_counter == 0 ) watch_display_text(WATCH_POSITION_TOP_LEFT, "LA"); + else watch_display_text(WATCH_POSITION_TOP_LEFT, "LE"); + if ( ( state->hero_counter == 0 ) || ( state->hero_counter >= 40 ) ) watch_display_text ( WATCH_POSITION_TOP_RIGHT, " "); else { sprintf ( buf, "%2d", state->hero_counter ); - watch_display_string(buf, 2); + watch_display_text ( WATCH_POSITION_TOP_RIGHT, buf); } if ( state->hero_counter >= 100 ) sprintf ( buf, "Str%3d", state->hero_counter ); else if ( state->hero_counter >= 40 ) sprintf ( buf, "Strb%2d", state->hero_counter ); @@ -193,7 +194,7 @@ void lander_face_activate(void *context) { else if ( state->hero_counter >= LEVEL_ACE ) sprintf ( buf, " ACE " ); // This human is good else if ( state->difficulty_level == 0 ) sprintf ( buf, " " ); else sprintf ( buf, "%s", lander_difficulty_names[state->difficulty_level] ); - watch_display_string ( buf, 4); + watch_display_text ( WATCH_POSITION_BOTTOM, buf); if (state->led_enabled) watch_set_indicator(WATCH_INDICATOR_SIGNAL); else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); } @@ -230,7 +231,7 @@ bool lander_face_loop(movement_event_t event, void *context) { state->height += state->speed; if ( state->height > 971 * 80 ) { // Escape height watch_clear_all_indicators (); - watch_display_string ( "ESCAPE", 4 ); + watch_display_text( WATCH_POSITION_BOTTOM, "ESCAPE" ); state->tick_counter = 0; state->mode = MODE_WAITING_TO_START; } @@ -241,14 +242,14 @@ bool lander_face_loop(movement_event_t event, void *context) { else { // Update height display sprintf ( buf, "%4d", (int) ( state->height / GRANUL ) ); - watch_display_string ( buf, 4 ); + watch_display_text( WATCH_POSITION_BOTTOM, buf ); } } else if ( state->mode == MODE_TOUCHDOWN_BLANK ) { // Blank display on touchdown if ( state->tick_counter == 1 ) { watch_clear_all_indicators (); - watch_display_string ( " ", 4 ); + watch_display_text( WATCH_POSITION_BOTTOM, " " ); // Also calc fuel score now. float fuel_score_float; @@ -264,7 +265,7 @@ bool lander_face_loop(movement_event_t event, void *context) { state->hero_counter = 0; state->difficulty_level = 0; if ( state->reset_counter >= 6 ) state->legend_counter = 0; - watch_display_string ( "rESET ", 4 ); + watch_display_text(WATCH_POSITION_BOTTOM, "rESET "); write_to_lander_EEPROM(state); } } @@ -359,8 +360,8 @@ bool lander_face_loop(movement_event_t event, void *context) { if ( my_odds > 0 ) { char buff3 [ 5 ]; sprintf ( buff3, "%2d", my_odds ); - watch_display_string ( buff3, 2 ); - } else watch_display_string ( " ", 2 ); + watch_display_text( WATCH_POSITION_TOP_RIGHT, buff3 ); + } else watch_display_text( WATCH_POSITION_TOP_RIGHT, " " ); if ( my_odds >= gen_random_int ( 1, 200 ) ) { // EARTH!!!! The final objective. sprintf ( buf, "EArTH " ); // 17% within 8, 50% by 16, 79% by 24, 94% by 32 <- easy mode state->hero_counter = 0; @@ -396,7 +397,7 @@ bool lander_face_loop(movement_event_t event, void *context) { } } // Display final status. - watch_display_string ( buf, 4 ); + watch_display_text(WATCH_POSITION_BOTTOM, buf ); } // End if tick_counter == 1 // Major crash - ship burning with red LED. @@ -427,8 +428,10 @@ bool lander_face_loop(movement_event_t event, void *context) { else if ( state->mode == MODE_DISPLAY_SKILL_LEVEL ) { // Display skill level if ( state->tick_counter == 1 ) { - sprintf ( buf, " %d %d ", state->skill_level, state->skill_level ); - watch_display_string ( buf, 2 ); + sprintf ( buf, " %d", state->skill_level ); + watch_display_text ( WATCH_POSITION_TOP_RIGHT, buf ); + sprintf ( buf, " %d ", state->skill_level ); + watch_display_text ( WATCH_POSITION_BOTTOM, buf ); } // Wait long enough, then start game. if ( state->tick_counter >= ( 2.0 * LANDER_TICK_FREQUENCY ) ) { @@ -440,12 +443,14 @@ bool lander_face_loop(movement_event_t event, void *context) { else if ( state->mode == MODE_FIND_EARTH_MESSAGE ) { // Display "Find" then "Earth" if ( state->tick_counter == 1 ) { - sprintf ( buf, " FInd " ); - watch_display_string ( buf, 2 ); + sprintf ( buf, " FInd " ); + watch_display_text ( WATCH_POSITION_TOP_RIGHT, " " ); + watch_display_text ( WATCH_POSITION_BOTTOM, buf ); } if ( state->tick_counter == (int) ( 1.5 * LANDER_TICK_FREQUENCY + 1 ) ) { - sprintf ( buf, " EArTH " ); - watch_display_string ( buf, 2 ); + sprintf ( buf, "EArTH " ); + watch_display_text ( WATCH_POSITION_TOP_RIGHT, " " ); + watch_display_text ( WATCH_POSITION_BOTTOM, buf ); } // Wait long enough, then display skill level. if ( state->tick_counter >= ( 3 * LANDER_TICK_FREQUENCY ) ) { @@ -454,22 +459,22 @@ bool lander_face_loop(movement_event_t event, void *context) { } } else if ( state->mode == MODE_MONSTER ) { - if ( state->tick_counter == 1 ) watch_display_string ( lander_monster_names[state->monster_type], 4 ); + if ( state->tick_counter == 1 ) watch_display_text ( WATCH_POSITION_BOTTOM, lander_monster_names[state->monster_type] ); else if ( state->tick_counter == MONSTER_DISPLAY_TICKS + 1 ) { uint8_t my_rand; my_rand = gen_random_int ( 0 , MONSTER_ACTIONS - 1 ); - watch_display_string ( lander_monster_actions[my_rand], 4 ); + watch_display_text ( WATCH_POSITION_BOTTOM, lander_monster_actions[my_rand] ); } else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 ) { // Display 1st monster character sprintf ( buf, "%s", lander_monster_names[state->monster_type] ); buf [1] = 0; - watch_display_string(buf,4); + watch_display_text(WATCH_POSITION_BOTTOM, buf); } else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 1 ) { // Display current population, close mouth - sprintf ( buf, "c%2d%2d", state->uninjured, state->injured ); - watch_display_string ( buf, 5 ); + sprintf ( buf, " c%2d%2d", state->uninjured, state->injured ); + watch_display_text ( WATCH_POSITION_BOTTOM, buf ); } - else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 3 ) watch_display_string ( "C", 5 ); // Open mouth + else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 3 ) watch_display_character ( 'C', 5 ); // Open mouth else if ( state->tick_counter == MONSTER_DISPLAY_TICKS * 2 + 5 ) { // Decision to: continue loop, end loop or eat astronaut uint8_t survivors = state->injured + state->uninjured; @@ -477,7 +482,7 @@ bool lander_face_loop(movement_event_t event, void *context) { if ( survivors == 0 ) state->mode = MODE_WAITING_TO_START; else if ( myRand <= 1 ) { // Leave loop with survivors sprintf ( buf, "%d %2d%2d", state->ships_health, state->uninjured, state->injured ); - watch_display_string ( buf, 4 ); + watch_display_text ( WATCH_POSITION_BOTTOM, buf); state->mode = MODE_WAITING_TO_START; } else if ( myRand <= 11 ) state->tick_counter = MONSTER_DISPLAY_TICKS * 2; // Do nothing, loop continues else { // Eat an astronaut - welcome to the space program! @@ -540,7 +545,7 @@ bool lander_face_loop(movement_event_t event, void *context) { case EVENT_LIGHT_BUTTON_DOWN: if ( state->mode == MODE_WAITING_TO_START ) { // Display difficulty level - watch_display_string ( lander_difficulty_names [state->difficulty_level], 4 ); + watch_display_text(WATCH_POSITION_BOTTOM, lander_difficulty_names [state->difficulty_level]); } break; case EVENT_LIGHT_LONG_PRESS: @@ -554,7 +559,7 @@ bool lander_face_loop(movement_event_t event, void *context) { if ( state->legend_counter > 9 ) sprintf (buf,"EArt%2d", state->legend_counter ); else sprintf (buf,"EArth%d", state->legend_counter ); // Display legend counter - watch_display_string ( buf, 4 ); + watch_display_text(WATCH_POSITION_BOTTOM, buf); } break; From 47c62f66fdd1687bc71037c056246a4f45dc0f20 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 17 Aug 2025 12:54:33 -0400 Subject: [PATCH 12/66] Added tapping to endless runner --- watch-faces/complication/endless_runner_face.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index d3985d40..6084ef61 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -557,6 +557,10 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { if (game_state.curr_screen == SCREEN_TITLE) change_difficulty(state); break; + case EVENT_SINGLE_TAP: + case EVENT_DOUBLE_TAP: + if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes + //fall through case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ From 4fe6a7bbb183271763c757d4cabf372a38de1bb1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 18 Aug 2025 22:10:49 -0400 Subject: [PATCH 13/66] Moved Simon face --- {legacy/watch_faces => watch-faces}/complication/simon_face.c | 0 {legacy/watch_faces => watch-faces}/complication/simon_face.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {legacy/watch_faces => watch-faces}/complication/simon_face.c (100%) rename {legacy/watch_faces => watch-faces}/complication/simon_face.h (100%) diff --git a/legacy/watch_faces/complication/simon_face.c b/watch-faces/complication/simon_face.c similarity index 100% rename from legacy/watch_faces/complication/simon_face.c rename to watch-faces/complication/simon_face.c diff --git a/legacy/watch_faces/complication/simon_face.h b/watch-faces/complication/simon_face.h similarity index 100% rename from legacy/watch_faces/complication/simon_face.h rename to watch-faces/complication/simon_face.h From eeeb9865ac4692d40ec876215fb3990e02e11d40 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 06:59:59 -0400 Subject: [PATCH 14/66] Refactored text display --- movement_faces.h | 1 + watch-faces.mk | 1 + watch-faces/complication/simon_face.c | 42 ++++++++++++++------------- watch-faces/complication/simon_face.h | 4 +-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/movement_faces.h b/movement_faces.h index fc43716d..d86e72d4 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -73,4 +73,5 @@ #include "wareki_face.h" #include "deadline_face.h" #include "wordle_face.h" +#include "simon_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 22e262cf..3f1f8946 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -48,4 +48,5 @@ SRCS += \ ./watch-faces/sensor/lis2dw_monitor_face.c \ ./watch-faces/complication/wareki_face.c \ ./watch-faces/complication/deadline_face.c \ + ./watch-faces/complication/simon_face.c \ # New watch faces go above this line. diff --git a/watch-faces/complication/simon_face.c b/watch-faces/complication/simon_face.c index 41bc5c3d..55f484bf 100644 --- a/watch-faces/complication/simon_face.c +++ b/watch-faces/complication/simon_face.c @@ -47,18 +47,19 @@ static inline uint8_t _simon_get_rand_num(uint8_t num_values) { } static void _simon_clear_display(simon_state_t *state) { - if (state->playing_state == SIMON_NOT_PLAYING) { - watch_display_string(" ", 0); - } else { - sprintf(_simon_display_buf, " %2d ", state->sequence_length); - watch_display_string(_simon_display_buf, 0); + watch_clear_display(); + if (state->playing_state != SIMON_NOT_PLAYING) { + sprintf(_simon_display_buf, "%2d", state->sequence_length); + watch_display_text(WATCH_POSITION_TOP_RIGHT, _simon_display_buf); } } static void _simon_not_playing_display(simon_state_t *state) { _simon_clear_display(state); - sprintf(_simon_display_buf, "SI %d", state->best_score); + watch_display_text(WATCH_POSITION_TOP, "SI"); + sprintf(_simon_display_buf, "%d", state->best_score); + watch_display_text(WATCH_POSITION_BOTTOM, _simon_display_buf); if (!state->soundOff) watch_set_indicator(WATCH_INDICATOR_BELL); else @@ -67,14 +68,13 @@ static void _simon_not_playing_display(simon_state_t *state) { watch_set_indicator(WATCH_INDICATOR_SIGNAL); else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); - watch_display_string(_simon_display_buf, 0); switch (state->mode) { case SIMON_MODE_EASY: - watch_display_string("E", 9); + watch_display_text(WATCH_POSITION_SECONDS, " E"); break; case SIMON_MODE_HARD: - watch_display_string("H", 9); + watch_display_text(WATCH_POSITION_SECONDS, " H"); break; default: break; @@ -90,24 +90,27 @@ static void _simon_reset(simon_state_t *state) { static void _simon_display_note(SimonNote note, simon_state_t *state) { - char *ndtemplate = NULL; - + watch_clear_display(); + if (note == SIMON_WRONG_NOTE) { + watch_display_text(WATCH_POSITION_TOP_LEFT, "OH"); + watch_display_text(WATCH_POSITION_BOTTOM, "NOOOOO"); + return; + } + sprintf(_simon_display_buf, "%2d", state->sequence_length); + watch_display_text(WATCH_POSITION_TOP_RIGHT, _simon_display_buf); switch (note) { case SIMON_LED_NOTE: - ndtemplate = "LI%2d "; + watch_display_text(WATCH_POSITION_TOP_LEFT, "LI"); break; case SIMON_ALARM_NOTE: - ndtemplate = " %2d AL"; + watch_display_text(WATCH_POSITION_SECONDS, "AL"); break; case SIMON_MODE_NOTE: - ndtemplate = " %2dDE "; + watch_display_text(WATCH_POSITION_HOURS, DE"); + break; + default: break; - case SIMON_WRONG_NOTE: - ndtemplate = "OH NOOOOO"; } - - sprintf(_simon_display_buf, ndtemplate, state->sequence_length); - watch_display_string(_simon_display_buf, 0); } static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_rest) { @@ -220,7 +223,6 @@ void simon_face_setup(uint8_t watch_face_index, } void simon_face_activate(void *context) { - (void) settings; (void) context; simon_state_t *state = (simon_state_t *)context; _simon_change_speed(state); diff --git a/watch-faces/complication/simon_face.h b/watch-faces/complication/simon_face.h index 63e83895..a3dd743b 100644 --- a/watch-faces/complication/simon_face.h +++ b/watch-faces/complication/simon_face.h @@ -95,8 +95,8 @@ void simon_face_activate(void *context); bool simon_face_loop(movement_event_t event, void *context); void simon_face_resign(void *context); -#define simon_face \ - ((const watch_face_t){ \ +#define simon_face \ + ((const watch_face_t){ \ simon_face_setup, \ simon_face_activate, \ simon_face_loop, \ From 3220ab4a3abd11dc493b5eb83dd4e1b8f7c532c5 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 07:00:35 -0400 Subject: [PATCH 15/66] Added fallbacks on custom dispaly texts --- watch-faces/complication/simon_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watch-faces/complication/simon_face.c b/watch-faces/complication/simon_face.c index 55f484bf..ef8b0095 100644 --- a/watch-faces/complication/simon_face.c +++ b/watch-faces/complication/simon_face.c @@ -57,7 +57,7 @@ static void _simon_clear_display(simon_state_t *state) { static void _simon_not_playing_display(simon_state_t *state) { _simon_clear_display(state); - watch_display_text(WATCH_POSITION_TOP, "SI"); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "SIMON", "SI"); sprintf(_simon_display_buf, "%d", state->best_score); watch_display_text(WATCH_POSITION_BOTTOM, _simon_display_buf); if (!state->soundOff) @@ -106,7 +106,7 @@ static void _simon_display_note(SimonNote note, simon_state_t *state) { watch_display_text(WATCH_POSITION_SECONDS, "AL"); break; case SIMON_MODE_NOTE: - watch_display_text(WATCH_POSITION_HOURS, DE"); + watch_display_text_with_fallback(WATCH_POSITION_HOURS, "Md", "DE"); break; default: break; From fb6978fde87a2634827f02d6860c68f5212c9ff6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 08:41:18 -0400 Subject: [PATCH 16/66] Added default temp of 25C in simulator --- movement.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/movement.c b/movement.c index 0046a489..976d811d 100644 --- a/movement.c +++ b/movement.c @@ -587,6 +587,9 @@ bool movement_set_accelerometer_motion_threshold(uint8_t new_threshold) { } float movement_get_temperature(void) { +#if __EMSCRIPTEN__ + return 25; +#endif float temperature_c = (float)0xFFFFFFFF; if (movement_state.has_thermistor) { From 4ee1c9ec2e3979aa1a1b687a2750c797ea2f60c0 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 18 Aug 2025 20:42:22 -0400 Subject: [PATCH 17/66] Moon face and Activity face loop --- watch-faces/complication/moon_phase_face.c | 15 ++++++++++++--- watch-faces/sensor/activity_logging_face.c | 7 +++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/moon_phase_face.c b/watch-faces/complication/moon_phase_face.c index 9e9591d3..c7a550dc 100644 --- a/watch-faces/complication/moon_phase_face.c +++ b/watch-faces/complication/moon_phase_face.c @@ -184,10 +184,19 @@ bool moon_phase_face_loop(movement_event_t event, void *context) { state->offset += 86400; _update(state, state->offset); break; - case EVENT_ALARM_LONG_PRESS: - state->offset = 0; + case EVENT_ALARM_LONG_PRESS: + state->offset = 0; _update(state, state->offset); - break; + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + state->offset -= 86400; + _update(state, state->offset); + break; + case EVENT_LIGHT_LONG_PRESS: + movement_illuminate_led(); + break; case EVENT_TIMEOUT: // QUESTION: Should timeout reset offset to 0? break; diff --git a/watch-faces/sensor/activity_logging_face.c b/watch-faces/sensor/activity_logging_face.c index 3d5eb1cf..65d717f1 100644 --- a/watch-faces/sensor/activity_logging_face.c +++ b/watch-faces/sensor/activity_logging_face.c @@ -86,6 +86,13 @@ void activity_logging_face_activate(void *context) { bool activity_logging_face_loop(movement_event_t event, void *context) { activity_logging_state_t *state = (activity_logging_state_t *)context; switch (event.event_type) { + case EVENT_LIGHT_LONG_PRESS: + movement_illuminate_led(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->display_index = (state->display_index + ACTIVITY_LOGGING_NUM_DAYS - 1) % ACTIVITY_LOGGING_NUM_DAYS; + _activity_logging_face_update_display(state); + break; case EVENT_ALARM_BUTTON_DOWN: state->display_index = (state->display_index + 1) % ACTIVITY_LOGGING_NUM_DAYS; // fall through From e5c5d9f067ccc3a7337d35c788fdef74a0b2af29 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 08:46:11 -0400 Subject: [PATCH 18/66] Documented UI changes --- watch-faces/complication/moon_phase_face.h | 3 +++ watch-faces/sensor/activity_logging_face.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/watch-faces/complication/moon_phase_face.h b/watch-faces/complication/moon_phase_face.h index b0209829..2a32ed6b 100644 --- a/watch-faces/complication/moon_phase_face.h +++ b/watch-faces/complication/moon_phase_face.h @@ -47,6 +47,9 @@ * each button press, and both the text and the graphical representation will * display the moon phase for that day. Try pressing the Alarm button 27 times * now, just to visualize what the moon will look like over the next month. + * Pressing the Light button will move back in time. + * + * Holding the Light button will illuminate the display. */ #include "movement.h" diff --git a/watch-faces/sensor/activity_logging_face.h b/watch-faces/sensor/activity_logging_face.h index 6c5b74fb..f4f58b6e 100644 --- a/watch-faces/sensor/activity_logging_face.h +++ b/watch-faces/sensor/activity_logging_face.h @@ -40,6 +40,8 @@ * * A short press of the Alarm button moves backwards in the data log, showing yesterday's active minutes, * then the day before, etc. going back 14 days. + * A short press of the Light button moves forward in the data log, looping around if we're on the most-recent day. + * Holding the Light button will illuminate the display. * */ From 95df9a66838ae2ae3dd6dc492a0bd3ffb05a18ad Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 20:16:43 -0400 Subject: [PATCH 19/66] Enable tap functionality --- watch-faces/complication/endless_runner_face.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 6084ef61..c749cc9d 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -525,6 +525,7 @@ void endless_runner_face_activate(void *context) { ball_arr_seg = is_custom_lcd ? custom_ball_arr_seg : classic_ball_arr_seg; obstacle_arr_com = is_custom_lcd ? custom_obstacle_arr_com : classic_obstacle_arr_com; obstacle_arr_seg = is_custom_lcd ? custom_obstacle_arr_seg : classic_obstacle_arr_seg; + movement_enable_tap_detection_if_available(); } bool endless_runner_face_loop(movement_event_t event, void *context) { @@ -588,5 +589,6 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { void endless_runner_face_resign(void *context) { (void) context; + movement_disable_tap_detection_if_available(); } From 7b4f491db6d62ec6c17edcc67e9edf6e16577bf4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 20:18:20 -0400 Subject: [PATCH 20/66] Able to start runner with a tap --- watch-faces/complication/endless_runner_face.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index c749cc9d..1f245e8d 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -547,6 +547,10 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { break; } break; + case EVENT_SINGLE_TAP: + case EVENT_DOUBLE_TAP: + if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes + //fall through case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: if (game_state.curr_screen == SCREEN_TITLE) From 0e13674a79e717cbf6eeb7eb5ae17beceb38e2f3 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 21:47:51 -0400 Subject: [PATCH 21/66] Fixed duplicate cases --- watch-faces/complication/endless_runner_face.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 1f245e8d..7f6837ba 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -547,10 +547,6 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { break; } break; - case EVENT_SINGLE_TAP: - case EVENT_DOUBLE_TAP: - if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes - //fall through case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: if (game_state.curr_screen == SCREEN_TITLE) @@ -565,6 +561,15 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { case EVENT_SINGLE_TAP: case EVENT_DOUBLE_TAP: if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes + // Allow starting a new game by tapping. + if (game_state.curr_screen == SCREEN_TITLE) { + begin_playing(state); + break; + } + else if (game_state.curr_screen == SCREEN_LOSE) { + display_title(state); + break; + } //fall through case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: From 7ec68ad2189035401b7f59cd0411ed77686c265b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 19 Aug 2025 22:00:03 -0400 Subject: [PATCH 22/66] Included delay library --- watch-faces/complication/simon_face.c | 1 + 1 file changed, 1 insertion(+) diff --git a/watch-faces/complication/simon_face.c b/watch-faces/complication/simon_face.c index ef8b0095..642b32a2 100644 --- a/watch-faces/complication/simon_face.c +++ b/watch-faces/complication/simon_face.c @@ -23,6 +23,7 @@ */ #include "simon_face.h" +#include "delay.h" #include #include #include From c0c78411df515cf44bc68a6efb4b29977248fe6f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 23 Aug 2025 07:37:24 -0400 Subject: [PATCH 23/66] Tap enabe and disable added; fixed soundOn icon --- .../complication/endless_runner_face.c | 31 +++++++++++++++---- .../complication/endless_runner_face.h | 5 ++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 7f6837ba..390f54c7 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -305,6 +305,20 @@ static void toggle_sound(endless_runner_state_t *state) { } } +static void enable_tap_control(endless_runner_state_t *state) { + if (!state->tap_control_on) { + movement_enable_tap_detection_if_available(); + state->tap_control_on = true; + } +} + +static void disable_tap_control(endless_runner_state_t *state) { + if (state->tap_control_on) { + movement_disable_tap_detection_if_available(); + state->tap_control_on = false; + } +} + static void display_title(endless_runner_state_t *state) { uint16_t hi_score = state -> hi_score; uint8_t difficulty = state -> difficulty; @@ -325,6 +339,7 @@ static void display_title(endless_runner_state_t *state) { watch_display_text(WATCH_POSITION_BOTTOM, buf); } display_difficulty(difficulty); + if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); } static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { @@ -532,8 +547,8 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; switch (event.event_type) { case EVENT_ACTIVATE: + disable_tap_control(state); check_and_reset_hi_score(state); - if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; case EVENT_TICK: @@ -549,10 +564,13 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (game_state.curr_screen == SCREEN_TITLE) + if (game_state.curr_screen == SCREEN_TITLE) { + enable_tap_control(state); begin_playing(state); - else if (game_state.curr_screen == SCREEN_LOSE) + } + else if (game_state.curr_screen == SCREEN_LOSE) { display_title(state); + } break; case EVENT_LIGHT_LONG_PRESS: if (game_state.curr_screen == SCREEN_TITLE) @@ -580,10 +598,11 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { } break; case EVENT_ALARM_LONG_PRESS: - if (game_state.curr_screen != SCREEN_PLAYING) + if (game_state.curr_screen == SCREEN_TITLE) toggle_sound(state); break; case EVENT_TIMEOUT: + disable_tap_control(state); if (game_state.curr_screen != SCREEN_TITLE) display_title(state); break; @@ -597,7 +616,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { } void endless_runner_face_resign(void *context) { - (void) context; - movement_disable_tap_detection_if_available(); + endless_runner_state_t *state = (endless_runner_state_t *)context; + disable_tap_control(state); } diff --git a/watch-faces/complication/endless_runner_face.h b/watch-faces/complication/endless_runner_face.h index 3cfa6814..3a17a611 100644 --- a/watch-faces/complication/endless_runner_face.h +++ b/watch-faces/complication/endless_runner_face.h @@ -33,6 +33,8 @@ 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. + If the accelerometer is installed, you can tap the screen to jump and move through the menus after using the + buttons to go into the first game. High-score is displayed on the top-right on the title screen. During a game, the current score is displayed. */ @@ -42,7 +44,8 @@ typedef struct { 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 */ + uint8_t tap_control_on : 1; + uint8_t unused : 7; } endless_runner_state_t; void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr); From a769b2968775cae34b3fba875ea101e4d4fe7a92 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 23 Aug 2025 07:55:12 -0400 Subject: [PATCH 24/66] Fixed up start chime --- watch-faces/complication/endless_runner_face.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 390f54c7..4588c2a3 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -369,10 +369,18 @@ static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { previous_date_time.reg = date_time.reg; } +int8_t start_tune[] = { + BUZZER_NOTE_C5, 15, + BUZZER_NOTE_E5, 15, + BUZZER_NOTE_G5, 15, + 0 +}; + static void begin_playing(endless_runner_state_t *state) { uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; watch_clear_colon(); + watch_clear_indicator(WATCH_INDICATOR_BELL); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); if (game_state.fuel_mode) { watch_clear_display(); @@ -390,9 +398,7 @@ static void begin_playing(endless_runner_state_t *state) { display_ball(game_state.jump_state != NOT_JUMPING); 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); - watch_buzzer_play_note(BUZZER_NOTE_G5, 200); + watch_buzzer_play_sequence(start_tune, NULL); } } From 3dada3e9b5d551612658ad23ea57822be4b153cc Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 23 Aug 2025 08:04:09 -0400 Subject: [PATCH 25/66] Referenicng local time function --- watch-faces/complication/endless_runner_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 4588c2a3..d4aebf69 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -261,7 +261,7 @@ static void display_fuel(uint8_t subsecond, uint8_t difficulty) { static void check_and_reset_hi_score(endless_runner_state_t *state) { // Resets the hi score at the beginning of each month. - watch_date_time_t date_time = watch_rtc_get_date_time(); + watch_date_time_t date_time = movement_get_local_date_time(); if ((state -> year_last_hi_score != date_time.unit.year) || (state -> month_last_hi_score != date_time.unit.month)) { @@ -536,6 +536,7 @@ void endless_runner_face_setup(uint8_t watch_face_index, void ** context_ptr) { memset(*context_ptr, 0, sizeof(endless_runner_state_t)); endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr; state->difficulty = DIFF_NORM; + state->tap_control_on = false; } } @@ -576,7 +577,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { } else if (game_state.curr_screen == SCREEN_LOSE) { display_title(state); - } + } break; case EVENT_LIGHT_LONG_PRESS: if (game_state.curr_screen == SCREEN_TITLE) @@ -625,4 +626,3 @@ void endless_runner_face_resign(void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; disable_tap_control(state); } - From a71d48d5d3681259b5f00a5967e9be8113175c5f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 23 Aug 2025 09:41:42 -0400 Subject: [PATCH 26/66] Corrected obstacle on classic face --- watch-faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index d4aebf69..aff3fd69 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -85,7 +85,7 @@ int8_t custom_ball_arr_com[] = {2, 1, 1, 0, 3, 3, 2}; int8_t custom_ball_arr_seg[] = {15, 15, 14, 15, 14, 15, 14}; // obstacle 0-11 -int8_t classic_obstacle_arr_com[] = {0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0}; +int8_t classic_obstacle_arr_com[] = {0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1}; int8_t classic_obstacle_arr_seg[] = {18, 19, 20, 21, 22, 23, 0, 1, 2, 4, 5, 6}; int8_t custom_obstacle_arr_com[] = {1, 1, 1, 1, 1, 0, 1, 0, 3, 0, 0, 2}; int8_t custom_obstacle_arr_seg[] = {22, 16, 15, 14, 1, 2, 3, 4, 4, 5, 6, 7}; From 877d9721903916680d42e6dd21a0b4cd9534eec6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 23 Aug 2025 11:45:53 -0400 Subject: [PATCH 27/66] Temperature now can be set by simulator --- movement.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/movement.c b/movement.c index 976d811d..dac3c08c 100644 --- a/movement.c +++ b/movement.c @@ -588,7 +588,10 @@ bool movement_set_accelerometer_motion_threshold(uint8_t new_threshold) { float movement_get_temperature(void) { #if __EMSCRIPTEN__ - return 25; +#include + return EM_ASM_DOUBLE({ + return temp_c || 25.0; + }); #endif float temperature_c = (float)0xFFFFFFFF; From 018c94f23d9407cc99bae030137bc0391aa11966 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 24 Aug 2025 08:58:49 -0400 Subject: [PATCH 28/66] movement_get_temperature no longer returns early in case logic after reading will ever be needed --- movement.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/movement.c b/movement.c index dac3c08c..cf64f90e 100644 --- a/movement.c +++ b/movement.c @@ -587,13 +587,13 @@ bool movement_set_accelerometer_motion_threshold(uint8_t new_threshold) { } float movement_get_temperature(void) { + float temperature_c = (float)0xFFFFFFFF; #if __EMSCRIPTEN__ #include - return EM_ASM_DOUBLE({ + temperature_c = EM_ASM_DOUBLE({ return temp_c || 25.0; }); -#endif - float temperature_c = (float)0xFFFFFFFF; +#else if (movement_state.has_thermistor) { thermistor_driver_enable(); @@ -604,6 +604,7 @@ float movement_get_temperature(void) { val = val >> 4; temperature_c = 25 + (float)val / 16.0; } +#endif return temperature_c; } From d5eb32bc7466495fcb5e873213a9dd6c3c105d81 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 07:42:54 -0400 Subject: [PATCH 29/66] Removed duplicate includes --- legacy/watch_faces/clock/world_clock2_face.c | 1 - movement.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/legacy/watch_faces/clock/world_clock2_face.c b/legacy/watch_faces/clock/world_clock2_face.c index e79f2df7..e47bc44d 100644 --- a/legacy/watch_faces/clock/world_clock2_face.c +++ b/legacy/watch_faces/clock/world_clock2_face.c @@ -28,7 +28,6 @@ #include "world_clock2_face.h" #include "watch.h" #include "watch_utility.h" -#include "watch_utility.h" static bool refresh_face; diff --git a/movement.c b/movement.c index ca0b0f36..4b4a1676 100644 --- a/movement.c +++ b/movement.c @@ -27,9 +27,7 @@ #include #include #include -#include #include -#include #include "app.h" #include "watch.h" #include "watch_utility.h" From 6374045c690fe2d244eb85cac2966712b9050423 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 07:44:59 -0400 Subject: [PATCH 30/66] Removed duplicate include --- movement.c | 1 - 1 file changed, 1 deletion(-) diff --git a/movement.c b/movement.c index cf64f90e..b35621f7 100644 --- a/movement.c +++ b/movement.c @@ -589,7 +589,6 @@ bool movement_set_accelerometer_motion_threshold(uint8_t new_threshold) { float movement_get_temperature(void) { float temperature_c = (float)0xFFFFFFFF; #if __EMSCRIPTEN__ -#include temperature_c = EM_ASM_DOUBLE({ return temp_c || 25.0; }); From ba98aab4bd3dd73ae2d7aa23412826727e874087 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 08:06:52 -0400 Subject: [PATCH 31/66] Blink colon on showing time in custom display --- watch-faces/complication/endless_runner_face.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index aff3fd69..0849c27b 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -346,6 +346,9 @@ static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { static watch_date_time_t previous_date_time; char buf[6 + 1]; + if (!watch_sleep_animation_is_running()) { + watch_start_indicator_blink_if_possible(WATCH_INDICATOR_COLON, 500); + } // 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; @@ -358,7 +361,7 @@ static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { if (hour == 0) hour = 12; } watch_set_colon(); - sprintf( buf, "%2d%02d ", hour, date_time.unit.minute); + sprintf( buf, movement_clock_mode_24h() == MOVEMENT_CLOCK_MODE_024H ? "%02d%02d " : "%2d%02d ", hour, date_time.unit.minute); watch_display_text(WATCH_POSITION_BOTTOM, buf); } // If only the minute need updating From 61b65c1d1768692592ca304f68a9fa4e0c69b773 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 08:45:16 -0400 Subject: [PATCH 32/66] MOVEMENT_CLOCK_MODE_12H works on the time display --- .../complication/endless_runner_face.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 0849c27b..6fb9b9c5 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -342,26 +342,27 @@ static void display_title(endless_runner_state_t *state) { if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); } -static void display_time(watch_date_time_t date_time, bool clock_mode_24h) { +static void display_time(void) { static watch_date_time_t previous_date_time; + watch_date_time_t date_time = movement_get_local_date_time(); + movement_clock_mode_t clock_mode_24h = movement_clock_mode_24h(); char buf[6 + 1]; - if (!watch_sleep_animation_is_running()) { - watch_start_indicator_blink_if_possible(WATCH_INDICATOR_COLON, 500); - } // 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); + if (!watch_sleep_animation_is_running()) { + watch_set_colon(); + watch_start_indicator_blink_if_possible(WATCH_INDICATOR_COLON, 500); + } + if (clock_mode_24h != MOVEMENT_CLOCK_MODE_12H) 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, movement_clock_mode_24h() == MOVEMENT_CLOCK_MODE_024H ? "%02d%02d " : "%2d%02d ", hour, date_time.unit.minute); + sprintf( buf, clock_mode_24h == MOVEMENT_CLOCK_MODE_024H ? "%02d%02d " : "%2d%02d ", hour, date_time.unit.minute); watch_display_text(WATCH_POSITION_BOTTOM, buf); } // If only the minute need updating @@ -617,7 +618,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: - display_time(movement_get_local_date_time(), movement_clock_mode_24h()); + display_time(); break; default: return movement_default_loop_handler(event); From 71d7b4495b5063ff37c52a414b978dc379569468 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 09:03:31 -0400 Subject: [PATCH 33/66] Stop blink animation when leaving sleep mode --- watch-faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 6fb9b9c5..f43a831b 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -551,7 +551,6 @@ void endless_runner_face_activate(void *context) { ball_arr_seg = is_custom_lcd ? custom_ball_arr_seg : classic_ball_arr_seg; obstacle_arr_com = is_custom_lcd ? custom_obstacle_arr_com : classic_obstacle_arr_com; obstacle_arr_seg = is_custom_lcd ? custom_obstacle_arr_seg : classic_obstacle_arr_seg; - movement_enable_tap_detection_if_available(); } bool endless_runner_face_loop(movement_event_t event, void *context) { @@ -559,6 +558,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { switch (event.event_type) { case EVENT_ACTIVATE: disable_tap_control(state); + clock_stop_tick_tock_animation(); check_and_reset_hi_score(state); display_title(state); break; From 2770b8b924cb46308f17114cdc4c4aebad8a1d3f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 09:09:44 -0400 Subject: [PATCH 34/66] Fixed proper usage of blinkng --- watch-faces/complication/endless_runner_face.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index f43a831b..a3f7e371 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -558,7 +558,9 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { switch (event.event_type) { case EVENT_ACTIVATE: disable_tap_control(state); - clock_stop_tick_tock_animation(); + if (watch_sleep_animation_is_running()) { + watch_stop_blink(); + } check_and_reset_hi_score(state); display_title(state); break; From db2d5cbf68e3585342ff361e3f277d4a6c9a3bb1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 27 Aug 2025 17:17:11 -0400 Subject: [PATCH 35/66] Moved sleep animation check into the function rather than the switch statement --- watch-faces/complication/endless_runner_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index a3f7e371..a0dc85db 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -551,6 +551,9 @@ void endless_runner_face_activate(void *context) { ball_arr_seg = is_custom_lcd ? custom_ball_arr_seg : classic_ball_arr_seg; obstacle_arr_com = is_custom_lcd ? custom_obstacle_arr_com : classic_obstacle_arr_com; obstacle_arr_seg = is_custom_lcd ? custom_obstacle_arr_seg : classic_obstacle_arr_seg; + if (watch_sleep_animation_is_running()) { + watch_stop_blink(); + } } bool endless_runner_face_loop(movement_event_t event, void *context) { @@ -558,9 +561,6 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { switch (event.event_type) { case EVENT_ACTIVATE: disable_tap_control(state); - if (watch_sleep_animation_is_running()) { - watch_stop_blink(); - } check_and_reset_hi_score(state); display_title(state); break; From d24ebce9da1d38957be37321bffe7cd9e1c03649 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 30 Aug 2025 10:45:06 -0400 Subject: [PATCH 36/66] Added lose_tune --- .../complication/endless_runner_face.c | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index a0dc85db..cd4f7249 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -98,6 +98,20 @@ int8_t *obstacle_arr_seg; static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; +int8_t start_tune[] = { + BUZZER_NOTE_C5, 15, + BUZZER_NOTE_E5, 15, + BUZZER_NOTE_G5, 15, + 0 +}; + +int8_t lose_tune[] = { + BUZZER_NOTE_D3, 10, + BUZZER_NOTE_C3SHARP_D3FLAT, 10, + BUZZER_NOTE_C3, 10, + 0 +}; + static void print_binary(uint32_t value, int bits) { #if __EMSCRIPTEN__ for (int i = bits - 1; i >= 0; i--) { @@ -373,13 +387,6 @@ static void display_time(void) { previous_date_time.reg = date_time.reg; } -int8_t start_tune[] = { - BUZZER_NOTE_C5, 15, - BUZZER_NOTE_E5, 15, - BUZZER_NOTE_G5, 15, - 0 -}; - static void begin_playing(endless_runner_state_t *state) { uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; @@ -412,7 +419,7 @@ static void display_lose_screen(endless_runner_state_t *state) { watch_clear_display(); watch_display_text(WATCH_POSITION_BOTTOM, " LOSE "); if (state -> soundOn) - watch_buzzer_play_note(BUZZER_NOTE_A1, 600); + watch_buzzer_play_sequence(lose_tune, NULL); else delay_ms(600); } From 8fbb2d48b0686f33a95dcb4eed584fbca7f3ba9d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 30 Aug 2025 10:03:40 -0400 Subject: [PATCH 37/66] Added title screen to endless runner --- .../complication/endless_runner_face.c | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index cd4f7249..9f951d31 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -35,6 +35,7 @@ typedef enum { typedef enum { SCREEN_TITLE = 0, + SCREEN_SCORE, SCREEN_PLAYING, SCREEN_LOSE, SCREEN_TIME, @@ -334,11 +335,19 @@ static void disable_tap_control(endless_runner_state_t *state) { } static void display_title(endless_runner_state_t *state) { + game_state.curr_screen = SCREEN_TITLE; + watch_clear_colon(); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "ENdLS", "ER "); + watch_display_text(WATCH_POSITION_BOTTOM, "RUNNER"); + if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); +} + +static void display_score_screen(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.curr_screen = SCREEN_SCORE; 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(); @@ -353,7 +362,7 @@ static void display_title(endless_runner_state_t *state) { watch_display_text(WATCH_POSITION_BOTTOM, buf); } display_difficulty(difficulty); - if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); + if (sound_on) watch_set_indicator(WATCH_INDICATOR_BELL); } static void display_time(void) { @@ -575,6 +584,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { switch (game_state.curr_screen) { case SCREEN_TITLE: + case SCREEN_SCORE: case SCREEN_LOSE: break; default: @@ -584,28 +594,28 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (game_state.curr_screen == SCREEN_TITLE) { + if (game_state.curr_screen == SCREEN_SCORE) { enable_tap_control(state); begin_playing(state); } - else if (game_state.curr_screen == SCREEN_LOSE) { - display_title(state); + else if (game_state.curr_screen == SCREEN_TITLE || game_state.curr_screen == SCREEN_LOSE) { + display_score_screen(state); } break; case EVENT_LIGHT_LONG_PRESS: - if (game_state.curr_screen == SCREEN_TITLE) + if (game_state.curr_screen == SCREEN_SCORE) change_difficulty(state); break; case EVENT_SINGLE_TAP: case EVENT_DOUBLE_TAP: if (state->difficulty > DIFF_HARD) break; // Don't do this on fuel modes // Allow starting a new game by tapping. - if (game_state.curr_screen == SCREEN_TITLE) { + if (game_state.curr_screen == SCREEN_SCORE) { begin_playing(state); break; } else if (game_state.curr_screen == SCREEN_LOSE) { - display_title(state); + display_score_screen(state); break; } //fall through @@ -618,13 +628,13 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { } break; case EVENT_ALARM_LONG_PRESS: - if (game_state.curr_screen == SCREEN_TITLE) + if (game_state.curr_screen == SCREEN_TITLE || game_state.curr_screen == SCREEN_SCORE) toggle_sound(state); break; case EVENT_TIMEOUT: disable_tap_control(state); - if (game_state.curr_screen != SCREEN_TITLE) - display_title(state); + if (game_state.curr_screen != SCREEN_SCORE) + display_score_screen(state); break; case EVENT_LOW_ENERGY_UPDATE: display_time(); From 8c5921538502c86b88e81503a6676d123407943b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 31 Aug 2025 09:35:36 -0400 Subject: [PATCH 38/66] Better enterring of time mode and sound indicator during game --- .../complication/endless_runner_face.c | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 9f951d31..4b22262f 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -309,14 +309,19 @@ static void change_difficulty(endless_runner_state_t *state) { } } +static void display_sound_indicator(bool soundOn) { + if (soundOn){ + watch_set_indicator(WATCH_INDICATOR_BELL); + } else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } +} + static void toggle_sound(endless_runner_state_t *state) { state -> soundOn = !state -> soundOn; + display_sound_indicator(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); } } @@ -339,7 +344,7 @@ static void display_title(endless_runner_state_t *state) { watch_clear_colon(); watch_display_text_with_fallback(WATCH_POSITION_TOP, "ENdLS", "ER "); watch_display_text(WATCH_POSITION_BOTTOM, "RUNNER"); - if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); + display_sound_indicator(state -> soundOn); } static void display_score_screen(endless_runner_state_t *state) { @@ -351,8 +356,7 @@ static void display_score_screen(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(); - watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN", "ER"); - watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN ", "ER "); if (hi_score > MAX_HI_SCORE) { watch_display_text(WATCH_POSITION_BOTTOM, "HS --"); } @@ -362,7 +366,7 @@ static void display_score_screen(endless_runner_state_t *state) { watch_display_text(WATCH_POSITION_BOTTOM, buf); } display_difficulty(difficulty); - if (sound_on) watch_set_indicator(WATCH_INDICATOR_BELL); + display_sound_indicator(sound_on); } static void display_time(void) { @@ -400,7 +404,7 @@ static void begin_playing(endless_runner_state_t *state) { uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; watch_clear_colon(); - watch_clear_indicator(WATCH_INDICATOR_BELL); + display_sound_indicator(state -> soundOn); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); if (game_state.fuel_mode) { watch_clear_display(); @@ -586,6 +590,7 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { case SCREEN_TITLE: case SCREEN_SCORE: case SCREEN_LOSE: + case SCREEN_TIME: break; default: update_game(state, event.subsecond); @@ -637,6 +642,11 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { display_score_screen(state); break; case EVENT_LOW_ENERGY_UPDATE: + if (game_state.curr_screen != SCREEN_TIME) { + watch_display_text_with_fallback(WATCH_POSITION_TOP, "RUN ", "ER "); + display_sound_indicator(state -> soundOn); + display_difficulty(state->difficulty); + } display_time(); break; default: From e6b8be467630137d9890f0c096f00c869a8f1d24 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 31 Aug 2025 09:44:46 -0400 Subject: [PATCH 39/66] Lose chime plays fully when sound is on --- watch-faces/complication/endless_runner_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 4b22262f..5d479b9a 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -431,10 +431,10 @@ static void display_lose_screen(endless_runner_state_t *state) { game_state.curr_score = 0; watch_clear_display(); watch_display_text(WATCH_POSITION_BOTTOM, " LOSE "); - if (state -> soundOn) + if (state -> soundOn) { watch_buzzer_play_sequence(lose_tune, NULL); - else delay_ms(600); + } } static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { From 2bce9bba65e1bb75b0b94e2961064cce31c9a37d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 31 Aug 2025 16:28:59 -0400 Subject: [PATCH 40/66] Enable endless runner tapping right after leaving the title screen --- watch-faces/complication/endless_runner_face.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/watch-faces/complication/endless_runner_face.c b/watch-faces/complication/endless_runner_face.c index 5d479b9a..8a4b7a3b 100644 --- a/watch-faces/complication/endless_runner_face.c +++ b/watch-faces/complication/endless_runner_face.c @@ -599,12 +599,18 @@ bool endless_runner_face_loop(movement_event_t event, void *context) { break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (game_state.curr_screen == SCREEN_SCORE) { - enable_tap_control(state); - begin_playing(state); - } - else if (game_state.curr_screen == SCREEN_TITLE || game_state.curr_screen == SCREEN_LOSE) { - display_score_screen(state); + switch (game_state.curr_screen) { + case SCREEN_SCORE: + enable_tap_control(state); + begin_playing(state); + break; + case SCREEN_TITLE: + enable_tap_control(state); + // fall through + case SCREEN_TIME: + case SCREEN_LOSE: + watch_clear_display(); + display_score_screen(state); } break; case EVENT_LIGHT_LONG_PRESS: From 838aa1e6f701e2d8b832f64c083261aae749a368 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 2 Sep 2025 21:34:29 -0400 Subject: [PATCH 41/66] initial commit of working code --- movement_faces.h | 1 + watch-faces.mk | 1 + watch-faces/complication/blackjack_face.c | 326 ++++++++++++++++++++++ watch-faces/complication/blackjack_face.h | 55 ++++ 4 files changed, 383 insertions(+) create mode 100755 watch-faces/complication/blackjack_face.c create mode 100755 watch-faces/complication/blackjack_face.h diff --git a/movement_faces.h b/movement_faces.h index fc43716d..fab9ee31 100644 --- a/movement_faces.h +++ b/movement_faces.h @@ -73,4 +73,5 @@ #include "wareki_face.h" #include "deadline_face.h" #include "wordle_face.h" +#include "blackjack_face.h" // New includes go above this line. diff --git a/watch-faces.mk b/watch-faces.mk index 22e262cf..52919f53 100644 --- a/watch-faces.mk +++ b/watch-faces.mk @@ -48,4 +48,5 @@ SRCS += \ ./watch-faces/sensor/lis2dw_monitor_face.c \ ./watch-faces/complication/wareki_face.c \ ./watch-faces/complication/deadline_face.c \ + ./watch-faces/complication/blackjack_face.c \ # New watch faces go above this line. diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c new file mode 100755 index 00000000..ee3d274a --- /dev/null +++ b/watch-faces/complication/blackjack_face.c @@ -0,0 +1,326 @@ +/* + * MIT License + * + * Copyright (c) 2025 David Volovskiy + * + * 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. + */ + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include "blackjack_face.h" +#include "watch_common_display.h" + +#define KING 12 +#define QUEEN 11 +#define JACK 10 + +#define MIN_CARD_VALUE 1 +#define MAX_CARD_VALUE KING +#define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1) +#define CARD_SUIT_COUNT 4 +#define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT) + +#define BLACKJACK_MAX_HAND_SIZE 11 // 4*1 + 4*2 + 3*3 = 21; 11 cards total +#define MAX_PLAYER_CARDS_DISPLAY 4 +#define BOARD_DISPLAY_START 4 + +uint8_t score_player = 0; +uint8_t score_dealer = 0; +uint8_t hand_player[BLACKJACK_MAX_HAND_SIZE] = {0xFF}; +uint8_t hand_dealer[BLACKJACK_MAX_HAND_SIZE] = {0xFF}; +uint8_t idx_player = 0; +uint8_t idx_dealer = 0; + +typedef enum { + BJ_HIT, + BJ_STAND, + BJ_BUST, +} guess_t; + +typedef enum { + BJ_TITLE_SCREEN, + BJ_PLAYING, + BJ_DEALER_PLAYING, + BJ_RESULT, +} game_state_t; + +static game_state_t game_state = BJ_TITLE_SCREEN; +static uint8_t deck[DECK_SIZE] = {0}; +static uint8_t current_card = 0; + +static uint8_t generate_random_number(uint8_t num_values) { + // Emulator: use rand. Hardware: use arc4random. +#if __EMSCRIPTEN__ + return rand() % num_values; +#else + return arc4random_uniform(num_values); +#endif +} + +static void stack_deck(void) { + for (size_t i = 0; i < CARD_RANK_COUNT; i++) { + for (size_t j = 0; j < CARD_SUIT_COUNT; j++) + deck[(i * CARD_SUIT_COUNT) + j] = MIN_CARD_VALUE + i; + } +} + +static void shuffle_deck(void) { + // Randomize shuffle with Fisher Yates + size_t i; + size_t j; + uint8_t tmp; + + for (i = DECK_SIZE - 1; i > 0; i--) { + j = generate_random_number(0xFF) % (i + 1); + tmp = deck[j]; + deck[j] = deck[i]; + deck[i] = tmp; + } +} + +static void reset_deck(void) { + current_card = 0; + stack_deck(); + shuffle_deck(); +} + +static uint8_t get_next_card(void) { + if (current_card >= DECK_SIZE) + reset_deck(); + uint8_t card = deck[current_card++]; + if (card > 10) return 10; + return card; +} + +static void reset_hands(void) { + score_player = 0; + score_dealer = 0; + idx_player = 0; + idx_dealer = 0; + memset(hand_player, 0xFF, sizeof(hand_player)); + memset(hand_dealer, 0xFF, sizeof(hand_dealer)); + reset_deck(); +} + +static void give_player_card(void) { + uint8_t card = get_next_card(); + hand_player[idx_player++] = card; + score_player += card; +} + +static void give_dealer_card(void) { + uint8_t card = get_next_card(); + hand_dealer[idx_dealer++] = card; + score_dealer += card; +} + +static void display_player_hand(void) { + uint8_t cards_to_display = idx_player > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : idx_player; + for (uint8_t i=0; i 21) { + display_bust(); + } else { + display_player_hand(); + display_player_score(); + } +} + +static void perform_stand(void) { + game_state = BJ_DEALER_PLAYING; + watch_display_text(WATCH_POSITION_BOTTOM, "Stnd"); + display_player_score(); +} + +static void dealer_performs_hits(void) { + give_dealer_card(); + display_dealer_hand(); + if (score_dealer > 21) { + display_win(); + } else if (score_dealer > score_player) { + display_lose(); + } else { + display_dealer_hand(); + display_dealer_score(); + } +} + +static void see_if_dealer_hits(void) { + if (score_dealer > 16) { + if (score_dealer > score_player) { + display_lose(); + } else if (score_dealer < score_player) { + display_win(); + } else { + display_tie(); + } + } else { + dealer_performs_hits(); + } +} + +void handle_button_presses(bool hit) { + switch (game_state) + { + case BJ_TITLE_SCREEN: + begin_playing(); + break; + case BJ_PLAYING: + if (hit) { + perform_hit(); + } else { + perform_stand(); + } + break; + case BJ_DEALER_PLAYING: + see_if_dealer_hits(); + break; + case BJ_RESULT: + display_title(); + break; + } +} + +void blackjack_face_setup(uint8_t watch_face_index, void **context_ptr) { + (void) watch_face_index; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(blackjack_face_state_t)); + memset(*context_ptr, 0, sizeof(blackjack_face_state_t)); + } +} + +void blackjack_face_activate(void *context) { + blackjack_face_state_t *state = (blackjack_face_state_t *) context; + (void) state; + display_title(); +} + +bool blackjack_face_loop(movement_event_t event, void *context) { + blackjack_face_state_t *state = (blackjack_face_state_t *) context; + (void) state; + + switch (event.event_type) { + case EVENT_ACTIVATE: + break; + case EVENT_TICK: + if (game_state == BJ_DEALER_PLAYING) { + see_if_dealer_hits(); + } + break; + case EVENT_LIGHT_BUTTON_UP: + handle_button_presses(false); + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_ALARM_BUTTON_UP: + handle_button_presses(true); + break; + case EVENT_TIMEOUT: + break; + default: + return movement_default_loop_handler(event); + } + return true; +} + +void blackjack_face_resign(void *context) { + (void) context; +} diff --git a/watch-faces/complication/blackjack_face.h b/watch-faces/complication/blackjack_face.h new file mode 100755 index 00000000..36eb4425 --- /dev/null +++ b/watch-faces/complication/blackjack_face.h @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2023 Chris Ellis + * + * 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 BLACKJACK_FACE_H_ +#define BLACKJACK_FACE_H_ + +#include "movement.h" + +/* + * Blackjack face + * ====================== + * + */ + + + +typedef struct { + // Anything you need to keep track of, put it here! +} blackjack_face_state_t; + +void blackjack_face_setup(uint8_t watch_face_index, void ** context_ptr); +void blackjack_face_activate(void *context); +bool blackjack_face_loop(movement_event_t event, void *context); +void blackjack_face_resign(void *context); + +#define blackjack_face ((const watch_face_t){ \ + blackjack_face_setup, \ + blackjack_face_activate, \ + blackjack_face_loop, \ + blackjack_face_resign, \ + NULL, \ +}) + +#endif // blackjack_FACE_H_ From 3760aeb947306de9cf70d8b74b25db3d53b43c4c Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 2 Sep 2025 21:57:42 -0400 Subject: [PATCH 42/66] Changed order of how cards are displayed for player --- watch-faces/complication/blackjack_face.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index ee3d274a..b0f78b09 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -53,12 +53,6 @@ uint8_t hand_dealer[BLACKJACK_MAX_HAND_SIZE] = {0xFF}; uint8_t idx_player = 0; uint8_t idx_dealer = 0; -typedef enum { - BJ_HIT, - BJ_STAND, - BJ_BUST, -} guess_t; - typedef enum { BJ_TITLE_SCREEN, BJ_PLAYING, @@ -66,7 +60,7 @@ typedef enum { BJ_RESULT, } game_state_t; -static game_state_t game_state = BJ_TITLE_SCREEN; +static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; @@ -138,11 +132,11 @@ static void give_dealer_card(void) { static void display_player_hand(void) { uint8_t cards_to_display = idx_player > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : idx_player; - for (uint8_t i=0; i0; i--) { + uint8_t card = hand_player[idx_player-i]; if (card == 10) card = 0; const char display_char = card + '0'; - watch_display_character(display_char, BOARD_DISPLAY_START + i); + watch_display_character(display_char, BOARD_DISPLAY_START + cards_to_display - i); } } @@ -167,7 +161,7 @@ static void display_dealer_score(void) { static void display_win(void) { game_state = BJ_RESULT; - watch_display_text(WATCH_POSITION_BOTTOM, " WIN"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WlN ", " WIN"); display_player_score(); display_dealer_score(); } @@ -181,13 +175,13 @@ static void display_lose(void) { static void display_tie(void) { game_state = BJ_RESULT; - watch_display_text(WATCH_POSITION_BOTTOM, " TIE"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "TlE ", " TIE"); display_player_score(); } static void display_bust(void) { game_state = BJ_RESULT; - watch_display_text(WATCH_POSITION_BOTTOM, "BUST"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST ", " BUST"); display_player_score(); } From 8a831f5cfded756822c51a25870eac12746fd72a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 2 Sep 2025 22:15:06 -0400 Subject: [PATCH 43/66] Added tap controls --- watch-faces/complication/blackjack_face.c | 28 +++++++++++++++++++++-- watch-faces/complication/blackjack_face.h | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index b0f78b09..9b0de5a5 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -273,12 +273,28 @@ void handle_button_presses(bool hit) { } } +static void toggle_tap_control(blackjack_face_state_t *state) { + if (state->tap_control_on) { + movement_disable_tap_detection_if_available(); + state->tap_control_on = false; + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + } else { + bool tap_could_enable = movement_enable_tap_detection_if_available(); + if (tap_could_enable) { + state->tap_control_on = true; + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } + } +} + void blackjack_face_setup(uint8_t watch_face_index, void **context_ptr) { (void) watch_face_index; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(blackjack_face_state_t)); memset(*context_ptr, 0, sizeof(blackjack_face_state_t)); + blackjack_face_state_t *state = (blackjack_face_state_t *)*context_ptr; + state->tap_control_on = false; } } @@ -290,10 +306,11 @@ void blackjack_face_activate(void *context) { bool blackjack_face_loop(movement_event_t event, void *context) { blackjack_face_state_t *state = (blackjack_face_state_t *) context; - (void) state; - switch (event.event_type) { case EVENT_ACTIVATE: + if (state->tap_control_on) { + movement_enable_tap_detection_if_available(); + } break; case EVENT_TICK: if (game_state == BJ_DEALER_PLAYING) { @@ -301,12 +318,19 @@ bool blackjack_face_loop(movement_event_t event, void *context) { } break; case EVENT_LIGHT_BUTTON_UP: + case EVENT_DOUBLE_TAP: handle_button_presses(false); case EVENT_LIGHT_BUTTON_DOWN: break; case EVENT_ALARM_BUTTON_UP: + case EVENT_SINGLE_TAP: handle_button_presses(true); break; + case EVENT_ALARM_LONG_PRESS: + if (game_state == BJ_TITLE_SCREEN) { + toggle_tap_control(state); + } + break; case EVENT_TIMEOUT: break; default: diff --git a/watch-faces/complication/blackjack_face.h b/watch-faces/complication/blackjack_face.h index 36eb4425..ff52d79f 100755 --- a/watch-faces/complication/blackjack_face.h +++ b/watch-faces/complication/blackjack_face.h @@ -36,7 +36,7 @@ typedef struct { - // Anything you need to keep track of, put it here! + bool tap_control_on; } blackjack_face_state_t; void blackjack_face_setup(uint8_t watch_face_index, void ** context_ptr); From 3b3ecffd3e55064a81fde9a71675d3e187756e2d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 2 Sep 2025 22:23:36 -0400 Subject: [PATCH 44/66] Fixed a few warnings. --- watch-faces/complication/blackjack_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 9b0de5a5..5234d02a 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -148,13 +148,13 @@ static void display_dealer_hand(void) { } static void display_player_score(void) { - char buf[3]; + char buf[4]; sprintf(buf, "%2d", score_player); watch_display_text(WATCH_POSITION_SECONDS, buf); } static void display_dealer_score(void) { - char buf[3]; + char buf[4]; sprintf(buf, "%2d", score_dealer); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); } @@ -251,7 +251,7 @@ static void see_if_dealer_hits(void) { } } -void handle_button_presses(bool hit) { +static void handle_button_presses(bool hit) { switch (game_state) { case BJ_TITLE_SCREEN: From b66e10406e44152c38307cf1d94da4389db0a80b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 06:45:14 -0400 Subject: [PATCH 45/66] Made hand info into a struct --- watch-faces/complication/blackjack_face.c | 53 +++++++++++------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 5234d02a..c403579a 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -46,12 +46,11 @@ #define MAX_PLAYER_CARDS_DISPLAY 4 #define BOARD_DISPLAY_START 4 -uint8_t score_player = 0; -uint8_t score_dealer = 0; -uint8_t hand_player[BLACKJACK_MAX_HAND_SIZE] = {0xFF}; -uint8_t hand_dealer[BLACKJACK_MAX_HAND_SIZE] = {0xFF}; -uint8_t idx_player = 0; -uint8_t idx_dealer = 0; +typedef struct { + uint8_t score; + uint8_t idx_hand; + uint8_t hand[BLACKJACK_MAX_HAND_SIZE]; +} hand_info_t; typedef enum { BJ_TITLE_SCREEN, @@ -63,6 +62,8 @@ typedef enum { static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; +hand_info_t player; +hand_info_t dealer; static uint8_t generate_random_number(uint8_t num_values) { // Emulator: use rand. Hardware: use arc4random. @@ -109,31 +110,27 @@ static uint8_t get_next_card(void) { } static void reset_hands(void) { - score_player = 0; - score_dealer = 0; - idx_player = 0; - idx_dealer = 0; - memset(hand_player, 0xFF, sizeof(hand_player)); - memset(hand_dealer, 0xFF, sizeof(hand_dealer)); + memset(&player, 0, sizeof(player)); + memset(&dealer, 0, sizeof(dealer)); reset_deck(); } static void give_player_card(void) { uint8_t card = get_next_card(); - hand_player[idx_player++] = card; - score_player += card; + player.hand[player.idx_hand++] = card; + player.score += card; } static void give_dealer_card(void) { uint8_t card = get_next_card(); - hand_dealer[idx_dealer++] = card; - score_dealer += card; + dealer.hand[dealer.idx_hand++] = card; + dealer.score += card; } static void display_player_hand(void) { - uint8_t cards_to_display = idx_player > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : idx_player; + uint8_t cards_to_display = player.idx_hand > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : player.idx_hand; for (uint8_t i=cards_to_display; i>0; i--) { - uint8_t card = hand_player[idx_player-i]; + uint8_t card = player.hand[player.idx_hand-i]; if (card == 10) card = 0; const char display_char = card + '0'; watch_display_character(display_char, BOARD_DISPLAY_START + cards_to_display - i); @@ -141,7 +138,7 @@ static void display_player_hand(void) { } static void display_dealer_hand(void) { - uint8_t card = hand_dealer[idx_dealer - 1]; + uint8_t card = dealer.hand[dealer.idx_hand - 1]; if (card == 10) card = 0; const char display_char = card + '0'; watch_display_character(display_char, 0); @@ -149,13 +146,13 @@ static void display_dealer_hand(void) { static void display_player_score(void) { char buf[4]; - sprintf(buf, "%2d", score_player); + sprintf(buf, "%2d", player.score); watch_display_text(WATCH_POSITION_SECONDS, buf); } static void display_dealer_score(void) { char buf[4]; - sprintf(buf, "%2d", score_dealer); + sprintf(buf, "%2d", dealer.score); watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); } @@ -206,11 +203,11 @@ static void begin_playing(void) { } static void perform_hit(void) { - if (score_player == 21) { + if (player.score == 21) { return; // Assume hitting on 21 is a mistake and ignore } give_player_card(); - if (score_player > 21) { + if (player.score > 21) { display_bust(); } else { display_player_hand(); @@ -227,9 +224,9 @@ static void perform_stand(void) { static void dealer_performs_hits(void) { give_dealer_card(); display_dealer_hand(); - if (score_dealer > 21) { + if (dealer.score > 21) { display_win(); - } else if (score_dealer > score_player) { + } else if (dealer.score > player.score) { display_lose(); } else { display_dealer_hand(); @@ -238,10 +235,10 @@ static void dealer_performs_hits(void) { } static void see_if_dealer_hits(void) { - if (score_dealer > 16) { - if (score_dealer > score_player) { + if (dealer.score > 16) { + if (dealer.score > player.score) { display_lose(); - } else if (score_dealer < score_player) { + } else if (dealer.score < player.score) { display_win(); } else { display_tie(); From 1d40c384cb63bd81e7f950a05e8f2f6514c8e42a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 07:07:51 -0400 Subject: [PATCH 46/66] Added logic for supporting Ace --- watch-faces/complication/blackjack_face.c | 55 +++++++++++++++-------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index c403579a..beb14446 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -32,12 +32,13 @@ #include "blackjack_face.h" #include "watch_common_display.h" +#define ACE 13 #define KING 12 #define QUEEN 11 #define JACK 10 -#define MIN_CARD_VALUE 1 -#define MAX_CARD_VALUE KING +#define MIN_CARD_VALUE 2 +#define MAX_CARD_VALUE ACE #define CARD_RANK_COUNT (MAX_CARD_VALUE - MIN_CARD_VALUE + 1) #define CARD_SUIT_COUNT 4 #define DECK_SIZE (CARD_SUIT_COUNT * CARD_RANK_COUNT) @@ -49,6 +50,7 @@ typedef struct { uint8_t score; uint8_t idx_hand; + int8_t high_aces_in_hand; uint8_t hand[BLACKJACK_MAX_HAND_SIZE]; } hand_info_t; @@ -101,30 +103,45 @@ static void reset_deck(void) { shuffle_deck(); } -static uint8_t get_next_card(void) { +static uint8_t get_next_card(hand_info_t *hand_info) { if (current_card >= DECK_SIZE) reset_deck(); uint8_t card = deck[current_card++]; - if (card > 10) return 10; + switch (card) + { + case ACE: + card = 11; + hand_info->high_aces_in_hand++; + break; + case KING: + case QUEEN: + case JACK: + card = 10; + + default: + break; + } return card; } +static void modify_score_from_aces(hand_info_t *hand_info) { + while (hand_info->score > 21 && hand_info->high_aces_in_hand > 0) { + hand_info->score -= 10; + hand_info->high_aces_in_hand--; + } +} + static void reset_hands(void) { memset(&player, 0, sizeof(player)); memset(&dealer, 0, sizeof(dealer)); reset_deck(); } -static void give_player_card(void) { - uint8_t card = get_next_card(); - player.hand[player.idx_hand++] = card; - player.score += card; -} - -static void give_dealer_card(void) { - uint8_t card = get_next_card(); - dealer.hand[dealer.idx_hand++] = card; - dealer.score += card; +static void give_card(hand_info_t *hand_info) { + uint8_t card = get_next_card(hand_info); + hand_info->hand[hand_info->idx_hand++] = card; + hand_info->score += card; + modify_score_from_aces(hand_info); } static void display_player_hand(void) { @@ -193,11 +210,11 @@ static void begin_playing(void) { game_state = BJ_PLAYING; reset_hands(); // Give player their first 2 cards - give_player_card(); - give_player_card(); + give_card(&player); + give_card(&player); display_player_hand(); display_player_score(); - give_dealer_card(); + give_card(&dealer); display_dealer_hand(); display_dealer_score(); } @@ -206,7 +223,7 @@ static void perform_hit(void) { if (player.score == 21) { return; // Assume hitting on 21 is a mistake and ignore } - give_player_card(); + give_card(&player); if (player.score > 21) { display_bust(); } else { @@ -222,7 +239,7 @@ static void perform_stand(void) { } static void dealer_performs_hits(void) { - give_dealer_card(); + give_card(&dealer); display_dealer_hand(); if (dealer.score > 21) { display_win(); From 9ac62e8e53a39aab692212396fd2df9663158c44 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 07:28:38 -0400 Subject: [PATCH 47/66] Display of face cards and Ace --- watch-faces/complication/blackjack_face.c | 102 +++++++++++++++------- 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index beb14446..ee490426 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -61,6 +61,10 @@ typedef enum { BJ_RESULT, } game_state_t; +typedef enum { + A, B, C, D, E, F, G +} segment_t; + static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; @@ -106,22 +110,23 @@ static void reset_deck(void) { static uint8_t get_next_card(hand_info_t *hand_info) { if (current_card >= DECK_SIZE) reset_deck(); - uint8_t card = deck[current_card++]; + return deck[current_card++]; +} + +static uint8_t get_card_value(uint8_t card) { + uint8_t card_value; switch (card) { case ACE: - card = 11; - hand_info->high_aces_in_hand++; + return 11; break; case KING: case QUEEN: case JACK: card = 10; - default: - break; + return card; } - return card; } static void modify_score_from_aces(hand_info_t *hand_info) { @@ -139,64 +144,95 @@ static void reset_hands(void) { static void give_card(hand_info_t *hand_info) { uint8_t card = get_next_card(hand_info); + if (card == ACE) hand_info->high_aces_in_hand++; hand_info->hand[hand_info->idx_hand++] = card; - hand_info->score += card; + uint8_t card_value = get_card_value(card); + hand_info->score += card_value; modify_score_from_aces(hand_info); } +static void set_segment_at_position(segment_t segment, uint8_t position) { + digit_mapping_t segmap; + if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) { + segmap = Custom_LCD_Display_Mapping[position]; + } else { + segmap = Classic_LCD_Display_Mapping[position]; + } + const uint8_t com_pin = segmap.segment[segment].address.com; + const uint8_t seg = segmap.segment[segment].address.seg; + watch_set_pixel(com_pin, seg); +} + +static void display_card_at_position(uint8_t card, uint8_t display_position) { + switch (card) { + case KING: + watch_display_character(' ', display_position); + set_segment_at_position(A, display_position); + set_segment_at_position(D, display_position); + set_segment_at_position(G, display_position); + break; + case QUEEN: + watch_display_character(' ', display_position); + set_segment_at_position(A, display_position); + set_segment_at_position(D, display_position); + break; + case JACK: + watch_display_character('-', display_position); + break; + case ACE: + watch_display_character('A', display_position); + break; + default: { + const char display_char = card + '0'; + watch_display_character(display_char, display_position); + break; + } + } +} + static void display_player_hand(void) { uint8_t cards_to_display = player.idx_hand > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : player.idx_hand; for (uint8_t i=cards_to_display; i>0; i--) { uint8_t card = player.hand[player.idx_hand-i]; - if (card == 10) card = 0; - const char display_char = card + '0'; - watch_display_character(display_char, BOARD_DISPLAY_START + cards_to_display - i); + display_card_at_position(card, BOARD_DISPLAY_START + cards_to_display - i); } } static void display_dealer_hand(void) { uint8_t card = dealer.hand[dealer.idx_hand - 1]; - if (card == 10) card = 0; - const char display_char = card + '0'; - watch_display_character(display_char, 0); + display_card_at_position(card, 0); } -static void display_player_score(void) { +display_score(uint8_t score, watch_position_t pos) { char buf[4]; - sprintf(buf, "%2d", player.score); - watch_display_text(WATCH_POSITION_SECONDS, buf); -} - -static void display_dealer_score(void) { - char buf[4]; - sprintf(buf, "%2d", dealer.score); - watch_display_text(WATCH_POSITION_TOP_RIGHT, buf); + sprintf(buf, "%2d", score); + watch_display_text(pos, buf); } static void display_win(void) { game_state = BJ_RESULT; watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WlN ", " WIN"); - display_player_score(); - display_dealer_score(); + display_score(player.score, WATCH_POSITION_SECONDS); + display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); } static void display_lose(void) { game_state = BJ_RESULT; watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "LOSE", "lOSE"); - display_player_score(); - display_dealer_score(); + display_score(player.score, WATCH_POSITION_SECONDS); + display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); } static void display_tie(void) { game_state = BJ_RESULT; watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "TlE ", " TIE"); - display_player_score(); + display_score(player.score, WATCH_POSITION_SECONDS); } static void display_bust(void) { game_state = BJ_RESULT; watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST ", " BUST"); - display_player_score(); + display_score(player.score, WATCH_POSITION_SECONDS); } static void display_title(void) { @@ -213,10 +249,10 @@ static void begin_playing(void) { give_card(&player); give_card(&player); display_player_hand(); - display_player_score(); + display_score(player.score, WATCH_POSITION_SECONDS); give_card(&dealer); display_dealer_hand(); - display_dealer_score(); + display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); } static void perform_hit(void) { @@ -228,14 +264,14 @@ static void perform_hit(void) { display_bust(); } else { display_player_hand(); - display_player_score(); + display_score(player.score, WATCH_POSITION_SECONDS); } } static void perform_stand(void) { game_state = BJ_DEALER_PLAYING; watch_display_text(WATCH_POSITION_BOTTOM, "Stnd"); - display_player_score(); + display_score(player.score, WATCH_POSITION_SECONDS); } static void dealer_performs_hits(void) { @@ -247,7 +283,7 @@ static void dealer_performs_hits(void) { display_lose(); } else { display_dealer_hand(); - display_dealer_score(); + display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); } } From 93a2a5a5eef056d40c142ff7495181ac83397cc5 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 07:46:38 -0400 Subject: [PATCH 48/66] Documented face --- watch-faces/complication/blackjack_face.c | 13 ++++++---- watch-faces/complication/blackjack_face.h | 29 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index ee490426..c9c7a3cd 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -32,10 +32,10 @@ #include "blackjack_face.h" #include "watch_common_display.h" -#define ACE 13 -#define KING 12 -#define QUEEN 11 -#define JACK 10 +#define ACE 14 +#define KING 13 +#define QUEEN 12 +#define JACK 11 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE ACE @@ -180,7 +180,10 @@ static void display_card_at_position(uint8_t card, uint8_t display_position) { watch_display_character('-', display_position); break; case ACE: - watch_display_character('A', display_position); + watch_display_character(watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM ? 'A' : 'a', display_position); + break; + case 10: + watch_display_character('0', display_position); break; default: { const char display_char = card + '0'; diff --git a/watch-faces/complication/blackjack_face.h b/watch-faces/complication/blackjack_face.h index ff52d79f..8771561c 100755 --- a/watch-faces/complication/blackjack_face.h +++ b/watch-faces/complication/blackjack_face.h @@ -31,6 +31,35 @@ * Blackjack face * ====================== * + * Simple blackjack game. + * + * Aces are 11 unless you'd but, and if so, they become 1. + * King, Queen, and jack are all 10 points. + * Dealer deals to themselves until they get at least 17. + * The game plays with one shuffled deck that gets reshuffled with every game. + * + * Press either ALARM or LIGHT to begin playing. + * Your score is in the Seconds position. + * The dealer's score is in the Top-Right position. + * The dealer's last-shown card is in the Top-Left position. + * Your cards are in the Bottom row. From left to right, they are oldest to newest. Up to four cards will be dislayed. + * + * To hit, press the ALARM button. + * To stand, press the LIGHT button. + * If you're at 21, you cannoy hit, since we just assume it's a mispress on the button. + * + * Once you stand, the dealer will deal out to themselves once per second (or immidietly when you press the LIGHT or ALARM buttons). + * The game results are: + * WIN: You have a higher score than the dealer, but no more than 21. Or the dealer's score is over 21. + * LOSE: Your score is lower than the dealer's. + * BUST: Your score is above 21. + * TIE: Your score matches the dealer's final score + * + * | Cards | | + * |---------|--------------------------| + * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| + * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|a| + * If you're using a custom display, Ace will display as 'A', not 'a' */ From d6e4a1ad441f86ffb586c62db2dea4ff88b91515 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 07:52:18 -0400 Subject: [PATCH 49/66] Added delay before showing Bust --- watch-faces/complication/blackjack_face.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index c9c7a3cd..78270bf0 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -58,6 +58,7 @@ typedef enum { BJ_TITLE_SCREEN, BJ_PLAYING, BJ_DEALER_PLAYING, + BJ_BUST, BJ_RESULT, } game_state_t; @@ -206,7 +207,7 @@ static void display_dealer_hand(void) { display_card_at_position(card, 0); } -display_score(uint8_t score, watch_position_t pos) { +static void display_score(uint8_t score, watch_position_t pos) { char buf[4]; sprintf(buf, "%2d", score); watch_display_text(pos, buf); @@ -235,7 +236,6 @@ static void display_tie(void) { static void display_bust(void) { game_state = BJ_RESULT; watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST ", " BUST"); - display_score(player.score, WATCH_POSITION_SECONDS); } static void display_title(void) { @@ -264,11 +264,10 @@ static void perform_hit(void) { } give_card(&player); if (player.score > 21) { - display_bust(); - } else { - display_player_hand(); - display_score(player.score, WATCH_POSITION_SECONDS); + game_state = BJ_BUST; } + display_player_hand(); + display_score(player.score, WATCH_POSITION_SECONDS); } static void perform_stand(void) { @@ -320,6 +319,8 @@ static void handle_button_presses(bool hit) { case BJ_DEALER_PLAYING: see_if_dealer_hits(); break; + case BJ_BUST: + display_bust(); case BJ_RESULT: display_title(); break; @@ -369,6 +370,9 @@ bool blackjack_face_loop(movement_event_t event, void *context) { if (game_state == BJ_DEALER_PLAYING) { see_if_dealer_hits(); } + else if (game_state == BJ_BUST) { + display_bust(); + } break; case EVENT_LIGHT_BUTTON_UP: case EVENT_DOUBLE_TAP: From 20bb59f02f44ecdc337b576a48993ef8b7300633 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 07:56:33 -0400 Subject: [PATCH 50/66] Compiler warning fixes --- watch-faces/complication/blackjack_face.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 78270bf0..2545fd7a 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -108,23 +108,21 @@ static void reset_deck(void) { shuffle_deck(); } -static uint8_t get_next_card(hand_info_t *hand_info) { +static uint8_t get_next_card(void) { if (current_card >= DECK_SIZE) reset_deck(); return deck[current_card++]; } static uint8_t get_card_value(uint8_t card) { - uint8_t card_value; switch (card) { case ACE: return 11; - break; case KING: case QUEEN: case JACK: - card = 10; + return 10; default: return card; } @@ -144,7 +142,7 @@ static void reset_hands(void) { } static void give_card(hand_info_t *hand_info) { - uint8_t card = get_next_card(hand_info); + uint8_t card = get_next_card(); if (card == ACE) hand_info->high_aces_in_hand++; hand_info->hand[hand_info->idx_hand++] = card; uint8_t card_value = get_card_value(card); @@ -321,6 +319,7 @@ static void handle_button_presses(bool hit) { break; case BJ_BUST: display_bust(); + break; case BJ_RESULT: display_title(); break; From eb1b551e7bd86e5e4e2aebf3fd0162f25c332127 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 08:06:03 -0400 Subject: [PATCH 51/66] Added documentation on tap controls and retained the tap indicator --- watch-faces/complication/blackjack_face.c | 20 ++++++++++++++------ watch-faces/complication/blackjack_face.h | 5 +++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 2545fd7a..1497b7d6 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -242,8 +242,11 @@ static void display_title(void) { watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, " JACK ", "BLaKJK"); } -static void begin_playing(void) { +static void begin_playing(bool tap_control_on) { watch_clear_display(); + if (tap_control_on) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } game_state = BJ_PLAYING; reset_hands(); // Give player their first 2 cards @@ -301,11 +304,11 @@ static void see_if_dealer_hits(void) { } } -static void handle_button_presses(bool hit) { +static void handle_button_presses(bool tap_control_on, bool hit) { switch (game_state) { case BJ_TITLE_SCREEN: - begin_playing(); + begin_playing(tap_control_on); break; case BJ_PLAYING: if (hit) { @@ -362,7 +365,12 @@ bool blackjack_face_loop(movement_event_t event, void *context) { switch (event.event_type) { case EVENT_ACTIVATE: if (state->tap_control_on) { - movement_enable_tap_detection_if_available(); + bool tap_could_enable = movement_enable_tap_detection_if_available(); + if (tap_could_enable) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } else { + state->tap_control_on = false; + } } break; case EVENT_TICK: @@ -375,12 +383,12 @@ bool blackjack_face_loop(movement_event_t event, void *context) { break; case EVENT_LIGHT_BUTTON_UP: case EVENT_DOUBLE_TAP: - handle_button_presses(false); + handle_button_presses(state->tap_control_on, false); case EVENT_LIGHT_BUTTON_DOWN: break; case EVENT_ALARM_BUTTON_UP: case EVENT_SINGLE_TAP: - handle_button_presses(true); + handle_button_presses(state->tap_control_on, true); break; case EVENT_ALARM_LONG_PRESS: if (game_state == BJ_TITLE_SCREEN) { diff --git a/watch-faces/complication/blackjack_face.h b/watch-faces/complication/blackjack_face.h index 8771561c..84041517 100755 --- a/watch-faces/complication/blackjack_face.h +++ b/watch-faces/complication/blackjack_face.h @@ -55,6 +55,11 @@ * BUST: Your score is above 21. * TIE: Your score matches the dealer's final score * + * On a watch that has the accelerometer, long-pressing the ALARM button will turn on the ability to play by tapping. + * The SIGNAL indicator will display when tapping is enabled. + * Tapping once will behave like the ALARM button and hit. + * Tapping twice behave like the LIGHT button and stand. + * * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| From a2c400ff8711f5bc90eb9e2c6c620079f7e55eec Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 3 Sep 2025 18:18:03 -0400 Subject: [PATCH 52/66] Added Win percentage face --- watch-faces/complication/blackjack_face.c | 62 +++++++++++++++++++++-- watch-faces/complication/blackjack_face.h | 2 + 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 1497b7d6..3d549dca 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -60,6 +60,7 @@ typedef enum { BJ_DEALER_PLAYING, BJ_BUST, BJ_RESULT, + BJ_WIN_RATIO, } game_state_t; typedef enum { @@ -69,6 +70,8 @@ typedef enum { static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; +static bool add_to_games_played = false; +static bool add_to_games_won = false; hand_info_t player; hand_info_t dealer; @@ -193,10 +196,15 @@ static void display_card_at_position(uint8_t card, uint8_t display_position) { } static void display_player_hand(void) { - uint8_t cards_to_display = player.idx_hand > MAX_PLAYER_CARDS_DISPLAY ? MAX_PLAYER_CARDS_DISPLAY : player.idx_hand; - for (uint8_t i=cards_to_display; i>0; i--) { - uint8_t card = player.hand[player.idx_hand-i]; - display_card_at_position(card, BOARD_DISPLAY_START + cards_to_display - i); + uint8_t card; + if (player.idx_hand <= MAX_PLAYER_CARDS_DISPLAY) { + card = player.hand[player.idx_hand - 1]; + display_card_at_position(card, BOARD_DISPLAY_START + player.idx_hand - 1); + } else { + for (uint8_t i=0; igames_played > 0) { // Avoid dividing by zero + win_ratio = (uint8_t)(100 * state->games_won) / state->games_played; + } + watch_display_text_with_fallback(WATCH_POSITION_TOP, "WINS ", "WR "); + sprintf(buf, "%3dPct", win_ratio); + watch_display_text(WATCH_POSITION_BOTTOM, buf); +} + static void begin_playing(bool tap_control_on) { watch_clear_display(); if (tap_control_on) { @@ -251,6 +276,7 @@ static void begin_playing(bool tap_control_on) { reset_hands(); // Give player their first 2 cards give_card(&player); + display_player_hand(); give_card(&player); display_player_hand(); display_score(player.score, WATCH_POSITION_SECONDS); @@ -304,6 +330,25 @@ static void see_if_dealer_hits(void) { } } +static void add_to_game_scores(blackjack_face_state_t *state) { + if (add_to_games_played) { + add_to_games_played = false; + state->games_played++; + if (state->games_played == 0) { + // Overflow + state->games_won = 0; + } + } + if (add_to_games_won) { + add_to_games_won = false; + state->games_won++; + if (state->games_won == 0) { + // Overflow + state->games_played = 0; + } + } +} + static void handle_button_presses(bool tap_control_on, bool hit) { switch (game_state) { @@ -324,6 +369,7 @@ static void handle_button_presses(bool tap_control_on, bool hit) { display_bust(); break; case BJ_RESULT: + case BJ_WIN_RATIO: display_title(); break; } @@ -362,6 +408,9 @@ void blackjack_face_activate(void *context) { bool blackjack_face_loop(movement_event_t event, void *context) { blackjack_face_state_t *state = (blackjack_face_state_t *) context; + if (game_state == BJ_RESULT) { + add_to_game_scores(state); + } switch (event.event_type) { case EVENT_ACTIVATE: if (state->tap_control_on) { @@ -390,6 +439,11 @@ bool blackjack_face_loop(movement_event_t event, void *context) { case EVENT_SINGLE_TAP: handle_button_presses(state->tap_control_on, true); break; + case EVENT_LIGHT_LONG_PRESS: + if (game_state == BJ_TITLE_SCREEN) { + display_win_ratio(state); + } + break; case EVENT_ALARM_LONG_PRESS: if (game_state == BJ_TITLE_SCREEN) { toggle_tap_control(state); diff --git a/watch-faces/complication/blackjack_face.h b/watch-faces/complication/blackjack_face.h index 84041517..c93143b9 100755 --- a/watch-faces/complication/blackjack_face.h +++ b/watch-faces/complication/blackjack_face.h @@ -71,6 +71,8 @@ typedef struct { bool tap_control_on; + uint16_t games_played; + uint16_t games_won; } blackjack_face_state_t; void blackjack_face_setup(uint8_t watch_face_index, void ** context_ptr); From a7c2cede060577ce716a3650aebc2bb0f593ffb8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 4 Sep 2025 18:29:07 -0400 Subject: [PATCH 53/66] Typo fix --- watch-faces/complication/blackjack_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 3d549dca..9db6db88 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -246,7 +246,7 @@ static void display_tie(void) { static void display_bust(void) { game_state = BJ_RESULT; add_to_games_played = true; - watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST ", " BUST"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST", " BUST"); } static void display_title(void) { From 30c378a3ce2950f39532838a15427bfac220d213 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 5 Sep 2025 17:07:24 -0400 Subject: [PATCH 54/66] Text fixes for classic display --- watch-faces/complication/blackjack_face.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 9db6db88..1ff1c2ed 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -246,12 +246,13 @@ static void display_tie(void) { static void display_bust(void) { game_state = BJ_RESULT; add_to_games_played = true; - watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST", " BUST"); + watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST", "BUST"); } static void display_title(void) { game_state = BJ_TITLE_SCREEN; - watch_display_text_with_fallback(WATCH_POSITION_TOP, "BLACK ", "21 "); + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "BLACK ", "21"); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, " JACK ", "BLaKJK"); } @@ -262,7 +263,8 @@ static void display_win_ratio(blackjack_face_state_t *state) { if (state->games_played > 0) { // Avoid dividing by zero win_ratio = (uint8_t)(100 * state->games_won) / state->games_played; } - watch_display_text_with_fallback(WATCH_POSITION_TOP, "WINS ", "WR "); + watch_display_text(WATCH_POSITION_TOP_RIGHT, " "); + watch_display_text_with_fallback(WATCH_POSITION_TOP, "WINS ", "WR"); sprintf(buf, "%3dPct", win_ratio); watch_display_text(WATCH_POSITION_BOTTOM, buf); } From 2c4d3d7f503a26b32effb680bcbddde97f875519 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 6 Sep 2025 12:07:17 -0400 Subject: [PATCH 55/66] Can now turn on LED with long press --- watch-faces/complication/blackjack_face.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 1ff1c2ed..4f7c89db 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -444,6 +444,8 @@ bool blackjack_face_loop(movement_event_t event, void *context) { case EVENT_LIGHT_LONG_PRESS: if (game_state == BJ_TITLE_SCREEN) { display_win_ratio(state); + } else { + movement_illuminate_led(); } break; case EVENT_ALARM_LONG_PRESS: From 1b805a273709b9db241e6f37364f230740b11cfc Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 7 Sep 2025 08:48:42 -0400 Subject: [PATCH 56/66] No longer remake deck before each shuffle --- watch-faces/complication/higher_lower_game_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/higher_lower_game_face.c b/watch-faces/complication/higher_lower_game_face.c index 762c462a..d47e1f61 100755 --- a/watch-faces/complication/higher_lower_game_face.c +++ b/watch-faces/complication/higher_lower_game_face.c @@ -114,7 +114,6 @@ static void shuffle_deck(void) { static void reset_deck(void) { current_card = 0; - stack_deck(); shuffle_deck(); } @@ -358,6 +357,7 @@ void higher_lower_game_face_activate(void *context) { (void) state; // Handle any tasks related to your watch face coming on screen. game_state = HL_GS_TITLE_SCREEN; + stack_deck(); } bool higher_lower_game_face_loop(movement_event_t event, void *context) { From 9764c0f84dc8d23ebc7f09b070295f6a9815b0e4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 7 Sep 2025 08:51:47 -0400 Subject: [PATCH 57/66] We only stack the deck on activate and not before every new shuffle --- watch-faces/complication/blackjack_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 4f7c89db..7b538e6b 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -107,7 +107,6 @@ static void shuffle_deck(void) { static void reset_deck(void) { current_card = 0; - stack_deck(); shuffle_deck(); } @@ -406,6 +405,7 @@ void blackjack_face_activate(void *context) { blackjack_face_state_t *state = (blackjack_face_state_t *) context; (void) state; display_title(); + stack_deck(); } bool blackjack_face_loop(movement_event_t event, void *context) { From 320e7c13f97e90c80c651c9c47d4c7b1b82cfc92 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 7 Sep 2025 15:10:35 -0400 Subject: [PATCH 58/66] Set time face isn't blank at the beginning of displaying it --- watch-faces/settings/set_time_face.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/watch-faces/settings/set_time_face.c b/watch-faces/settings/set_time_face.c index 63fc34fd..5d1a8abe 100644 --- a/watch-faces/settings/set_time_face.c +++ b/watch-faces/settings/set_time_face.c @@ -91,6 +91,8 @@ bool set_time_face_loop(movement_event_t event, void *context) { watch_date_time_t date_time = movement_get_local_date_time(); switch (event.event_type) { + case EVENT_ACTIVATE: + break; case EVENT_TICK: if (_quick_ticks_running) { if (HAL_GPIO_BTN_ALARM_read()) _handle_alarm_button(date_time, current_page); From 639f42c5d8d843fd91279d7828ab55916fb56cf4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 8 Sep 2025 19:44:14 -0400 Subject: [PATCH 59/66] Tapping turned off when not being used since tap control uses 90uA --- watch-faces/complication/blackjack_face.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 7b538e6b..ff4d73a4 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -67,6 +67,7 @@ typedef enum { A, B, C, D, E, F, G } segment_t; +static bool tap_turned_on = false; static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; @@ -354,6 +355,9 @@ static void handle_button_presses(bool tap_control_on, bool hit) { switch (game_state) { case BJ_TITLE_SCREEN: + if (!tap_turned_on && tap_control_on) { + if (movement_enable_tap_detection_if_available()) tap_turned_on = true; + } begin_playing(tap_control_on); break; case BJ_PLAYING: @@ -415,14 +419,7 @@ bool blackjack_face_loop(movement_event_t event, void *context) { } switch (event.event_type) { case EVENT_ACTIVATE: - if (state->tap_control_on) { - bool tap_could_enable = movement_enable_tap_detection_if_available(); - if (tap_could_enable) { - watch_set_indicator(WATCH_INDICATOR_SIGNAL); - } else { - state->tap_control_on = false; - } - } + if (state->tap_control_on) watch_set_indicator(WATCH_INDICATOR_SIGNAL); break; case EVENT_TICK: if (game_state == BJ_DEALER_PLAYING) { @@ -454,6 +451,10 @@ bool blackjack_face_loop(movement_event_t event, void *context) { } break; case EVENT_TIMEOUT: + case EVENT_LOW_ENERGY_UPDATE: + if (tap_turned_on) { + movement_disable_tap_detection_if_available(); + } break; default: return movement_default_loop_handler(event); @@ -463,4 +464,8 @@ bool blackjack_face_loop(movement_event_t event, void *context) { void blackjack_face_resign(void *context) { (void) context; + if (tap_turned_on) { + tap_turned_on = false; + movement_disable_tap_detection_if_available(); + } } From 796f83a19f95dddcac565e23d1f9129d90a08cae Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 26 Sep 2025 10:02:20 -0400 Subject: [PATCH 60/66] Attempt at fixing blackjack win rate counter --- watch-faces/complication/blackjack_face.c | 51 ++++++++++------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index ff4d73a4..654b883e 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -71,8 +71,7 @@ static bool tap_turned_on = false; static game_state_t game_state; static uint8_t deck[DECK_SIZE] = {0}; static uint8_t current_card = 0; -static bool add_to_games_played = false; -static bool add_to_games_won = false; +static blackjack_face_state_t *g_state = NULL; hand_info_t player; hand_info_t dealer; @@ -219,10 +218,27 @@ static void display_score(uint8_t score, watch_position_t pos) { watch_display_text(pos, buf); } +static void add_to_game_scores(bool add_to_wins) { + g_state->games_played++; + if (g_state->games_played == 0) { + // Overflow + g_state->games_played = 1; + g_state->games_won = add_to_wins ? 1 : 0; + return; + } + if (add_to_wins) { + g_state->games_won++; + if (g_state->games_won == 0) { + // Overflow + g_state->games_played = 1; + g_state->games_won = 1; + } + } +} + static void display_win(void) { game_state = BJ_RESULT; - add_to_games_played = true; - add_to_games_won = true; + add_to_game_scores(true); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "WlN ", " WIN"); display_score(player.score, WATCH_POSITION_SECONDS); display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); @@ -230,7 +246,7 @@ static void display_win(void) { static void display_lose(void) { game_state = BJ_RESULT; - add_to_games_played = true; + add_to_game_scores(false); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "LOSE", "lOSE"); display_score(player.score, WATCH_POSITION_SECONDS); display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); @@ -245,7 +261,7 @@ static void display_tie(void) { static void display_bust(void) { game_state = BJ_RESULT; - add_to_games_played = true; + add_to_game_scores(false); watch_display_text_with_fallback(WATCH_POSITION_BOTTOM, "8UST", "BUST"); } @@ -332,25 +348,6 @@ static void see_if_dealer_hits(void) { } } -static void add_to_game_scores(blackjack_face_state_t *state) { - if (add_to_games_played) { - add_to_games_played = false; - state->games_played++; - if (state->games_played == 0) { - // Overflow - state->games_won = 0; - } - } - if (add_to_games_won) { - add_to_games_won = false; - state->games_won++; - if (state->games_won == 0) { - // Overflow - state->games_played = 0; - } - } -} - static void handle_button_presses(bool tap_control_on, bool hit) { switch (game_state) { @@ -403,6 +400,7 @@ void blackjack_face_setup(uint8_t watch_face_index, void **context_ptr) { blackjack_face_state_t *state = (blackjack_face_state_t *)*context_ptr; state->tap_control_on = false; } + g_state = (blackjack_face_state_t *)*context_ptr; } void blackjack_face_activate(void *context) { @@ -414,9 +412,6 @@ void blackjack_face_activate(void *context) { bool blackjack_face_loop(movement_event_t event, void *context) { blackjack_face_state_t *state = (blackjack_face_state_t *) context; - if (game_state == BJ_RESULT) { - add_to_game_scores(state); - } switch (event.event_type) { case EVENT_ACTIVATE: if (state->tap_control_on) watch_set_indicator(WATCH_INDICATOR_SIGNAL); From 6609b3f79a6a45ace613f9d803bf6b3d686a5f47 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 15 Nov 2025 12:48:16 -0500 Subject: [PATCH 61/66] LIS2DW tap mode uses low-power mode --- movement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement.c b/movement.c index ca0b0f36..0a41b306 100644 --- a/movement.c +++ b/movement.c @@ -521,7 +521,7 @@ bool movement_enable_tap_detection_if_available(void) { // ramp data rate up to 400 Hz and high performance mode lis2dw_set_low_noise_mode(true); lis2dw_set_data_rate(LIS2DW_DATA_RATE_HP_400_HZ); - lis2dw_set_mode(LIS2DW_MODE_HIGH_PERFORMANCE); + lis2dw_set_mode(LIS2DW_MODE_LOW_POWER); // Settling time (1 sample duration, i.e. 1/400Hz) delay_ms(3); From 437e513d1f416b2df2901e636263bd7d5488fec4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 16 Nov 2025 08:28:46 -0500 Subject: [PATCH 62/66] Stand on 21 if we ask for a hit --- watch-faces/complication/blackjack_face.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/watch-faces/complication/blackjack_face.c b/watch-faces/complication/blackjack_face.c index 654b883e..375e0435 100755 --- a/watch-faces/complication/blackjack_face.c +++ b/watch-faces/complication/blackjack_face.c @@ -303,9 +303,16 @@ static void begin_playing(bool tap_control_on) { display_score(dealer.score, WATCH_POSITION_TOP_RIGHT); } +static void perform_stand(void) { + game_state = BJ_DEALER_PLAYING; + watch_display_text(WATCH_POSITION_BOTTOM, "Stnd"); + display_score(player.score, WATCH_POSITION_SECONDS); +} + static void perform_hit(void) { if (player.score == 21) { - return; // Assume hitting on 21 is a mistake and ignore + perform_stand(); + return; // Assume hitting on 21 is a mistake and stand } give_card(&player); if (player.score > 21) { @@ -315,12 +322,6 @@ static void perform_hit(void) { display_score(player.score, WATCH_POSITION_SECONDS); } -static void perform_stand(void) { - game_state = BJ_DEALER_PLAYING; - watch_display_text(WATCH_POSITION_BOTTOM, "Stnd"); - display_score(player.score, WATCH_POSITION_SECONDS); -} - static void dealer_performs_hits(void) { give_card(&dealer); display_dealer_hand(); From 6f08545e127d353ecdfec2d7415b40cf8f7001ee Mon Sep 17 00:00:00 2001 From: Daniel Bergman Date: Sat, 9 Aug 2025 20:20:10 +0200 Subject: [PATCH 63/66] Fix compiler warnings: missing prototype, unused variables, implicit declaration --- watch-faces/complication/simple_coin_flip_face.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/watch-faces/complication/simple_coin_flip_face.c b/watch-faces/complication/simple_coin_flip_face.c index 446832be..635c70ff 100644 --- a/watch-faces/complication/simple_coin_flip_face.c +++ b/watch-faces/complication/simple_coin_flip_face.c @@ -26,6 +26,7 @@ #include #include #include "simple_coin_flip_face.h" +#include "delay.h" void simple_coin_flip_face_setup(uint8_t watch_face_index, void ** context_ptr) { (void) watch_face_index; @@ -36,7 +37,7 @@ void simple_coin_flip_face_setup(uint8_t watch_face_index, void ** context_ptr) } void simple_coin_flip_face_activate(void *context) { - simple_coin_flip_face_state_t *state = (simple_coin_flip_face_state_t *)context; + (void) context; } static uint32_t get_random(uint32_t max) { @@ -48,7 +49,7 @@ static uint32_t get_random(uint32_t max) { } -void draw_start_face() { +static void draw_start_face(void) { watch_clear_display(); if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) { watch_display_text(WATCH_POSITION_BOTTOM, " Flip"); @@ -57,7 +58,7 @@ void draw_start_face() { } } -void set_pixels(int pixels[3][4][2], int j_len) { +static void set_pixels(int pixels[3][4][2], int j_len) { for(int loopruns = 0; loopruns<2; loopruns++) { for(int i = 0; i<3; i++) { watch_clear_display(); @@ -69,7 +70,7 @@ void set_pixels(int pixels[3][4][2], int j_len) { } } -void load_animation() { +static void load_animation(void) { if (watch_get_lcd_type() == WATCH_LCD_TYPE_CLASSIC) { int j_len = 2; int pixels[3][4][2] = { @@ -114,6 +115,7 @@ void load_animation() { } static void _blink_face_update_lcd(simple_coin_flip_face_state_t *state) { + (void) state; watch_clear_display(); load_animation(); watch_clear_display(); From 3e4c6f23637d113df4fd9e2c1a0ebd95bb03f646 Mon Sep 17 00:00:00 2001 From: Ante Vukorepa Date: Fri, 14 Nov 2025 19:32:51 +0100 Subject: [PATCH 64/66] Make deadline face respect button volumes Just blindly replaced watch_buzzer_play_note with watch_buzzer_play_note_with_volume --- watch-faces/complication/deadline_face.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/watch-faces/complication/deadline_face.c b/watch-faces/complication/deadline_face.c index 41240e76..d3204f20 100644 --- a/watch-faces/complication/deadline_face.c +++ b/watch-faces/complication/deadline_face.c @@ -161,19 +161,19 @@ static inline void _beep(beep_type_t beep_type) switch (beep_type) { case BEEP_BUTTON: - watch_buzzer_play_note(BUZZER_NOTE_C7, 50); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C7, 50, movement_button_volume()); break; case BEEP_ENABLE: - watch_buzzer_play_note(BUZZER_NOTE_G7, 50); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_G7, 50, movement_button_volume()); watch_buzzer_play_note(BUZZER_NOTE_REST, 75); - watch_buzzer_play_note(BUZZER_NOTE_C8, 75); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C8, 50, movement_button_volume()); break; case BEEP_DISABLE: - watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_C8, 50, movement_button_volume()); watch_buzzer_play_note(BUZZER_NOTE_REST, 75); - watch_buzzer_play_note(BUZZER_NOTE_G7, 75); + watch_buzzer_play_note_with_volume(BUZZER_NOTE_G7, 50, movement_button_volume()); break; } } From 998a1350788908ae7c5cc5f727c78e0433e91ae2 Mon Sep 17 00:00:00 2001 From: michael-lachmann <43858037+michael-lachmann@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:28:41 -0700 Subject: [PATCH 65/66] Avoid out-of-range character When character is out of range, this will access illegal memory areas. In watch_display_text() it also makes sense to stop on char='\0' even when in position 0. --- watch-library/shared/watch/watch_common_display.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/watch-library/shared/watch/watch_common_display.c b/watch-library/shared/watch/watch_common_display.c index 86ab352d..1e24a5b1 100644 --- a/watch-library/shared/watch/watch_common_display.c +++ b/watch-library/shared/watch/watch_common_display.c @@ -43,6 +43,8 @@ uint8_t IndicatorSegments[8] = { }; void watch_display_character(uint8_t character, uint8_t position) { + if((character-0x20 < 0) | (character-0x20 >= sizeof(Classic_LCD_Character_Set)) return ; + if (watch_get_lcd_type() == WATCH_LCD_TYPE_CUSTOM) { if (character == 'R' && position > 1 && position < 8) character = 'r'; // We can't display uppercase R in these positions else if (character == 'T' && position > 1 && position < 8) character = 't'; // lowercase t is the only option for these positions @@ -125,9 +127,9 @@ void watch_display_character(uint8_t character, uint8_t position) { void watch_display_character_lp_seconds(uint8_t character, uint8_t position) { // Will only work for digits and for positions 8 and 9 - but less code & checks to reduce power consumption - digit_mapping_t segmap; uint8_t segdata; + if(character < 20) return ; /// TODO: See optimization note above. @@ -169,6 +171,7 @@ void watch_display_string(const char *string, uint8_t position) { } void watch_display_text(watch_position_t location, const char *string) { + if(!string[0]) return ; switch (location) { case WATCH_POSITION_TOP: case WATCH_POSITION_TOP_LEFT: From a139dc81b100040908dbcaf2b8d0bafbf26453f7 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 22 Nov 2025 07:44:36 -0500 Subject: [PATCH 66/66] Resized baby kick buffers to get rid of truncation warnings --- watch-faces/complication/baby_kicks_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watch-faces/complication/baby_kicks_face.c b/watch-faces/complication/baby_kicks_face.c index bfbc0756..cc0dd513 100644 --- a/watch-faces/complication/baby_kicks_face.c +++ b/watch-faces/complication/baby_kicks_face.c @@ -210,7 +210,7 @@ static inline void _display_counts(baby_kicks_state_t *state) { watch_display_text(WATCH_POSITION_BOTTOM, "baby "); watch_clear_colon(); } else { - char buf[7]; + char buf[9]; snprintf( buf, @@ -259,7 +259,7 @@ static void _display_elapsed_minutes(baby_kicks_state_t *state) { * the total elapsed minutes. */ - char buf[3]; + char buf[5]; uint32_t elapsed_minutes = _elapsed_minutes(state); uint8_t multiple = elapsed_minutes / 30; uint8_t remainder = elapsed_minutes % 30;