From 07116222108a8bb3b602998acea4644c91642f68 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 8 Jul 2025 07:51:36 -0400 Subject: [PATCH] Add delays before registering btns, don't repeat words, cleaned up enum names --- movement_config.h | 1 + watch-faces/complication/wordle_face.c | 152 +++++++++++++++---------- watch-faces/complication/wordle_face.h | 50 +++++--- 3 files changed, 129 insertions(+), 74 deletions(-) diff --git a/movement_config.h b/movement_config.h index 0da8da43..c4cb8c36 100644 --- a/movement_config.h +++ b/movement_config.h @@ -29,6 +29,7 @@ const watch_face_t watch_faces[] = { clock_face, + wordle_face, world_clock_face, sunrise_sunset_face, moon_phase_face, diff --git a/watch-faces/complication/wordle_face.c b/watch-faces/complication/wordle_face.c index a93a0e0e..4873b5a1 100644 --- a/watch-faces/complication/wordle_face.c +++ b/watch-faces/complication/wordle_face.c @@ -35,7 +35,7 @@ static uint32_t get_random(uint32_t max) { #endif } -static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { +static uint8_t get_first_pos(wordle_letter_result *word_elements_result) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (word_elements_result[i] != WORDLE_LETTER_CORRECT) return i; @@ -43,7 +43,7 @@ static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { return 0; } -static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { +static uint8_t get_next_pos(uint8_t curr_pos, wordle_letter_result *word_elements_result) { for (size_t pos = curr_pos; pos < WORDLE_LENGTH;) { if (word_elements_result[++pos] != WORDLE_LETTER_CORRECT) return pos; @@ -51,7 +51,7 @@ static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ return WORDLE_LENGTH; } -static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { +static uint8_t get_prev_pos(uint8_t curr_pos, wordle_letter_result *word_elements_result) { if (curr_pos == 0) return 0; for (int8_t pos = curr_pos; pos >= 0;) { if (word_elements_result[--pos] != WORDLE_LETTER_CORRECT) @@ -99,13 +99,15 @@ static void display_all_letters(wordle_state_t *state) { #if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES static void display_not_in_dict(wordle_state_t *state) { - state->curr_screen = SCREEN_NO_DICT; + state->curr_screen = WORDLE_SCREEN_NO_DICT; watch_display_string("nodict", 4); + state->ignore_btn_ticks = WORDLE_TICK_BAD_GUESS; } static void display_already_guessed(wordle_state_t *state) { - state->curr_screen = SCREEN_ALREADY_GUESSED; + state->curr_screen = WORDLE_SCREEN_ALREADY_GUESSED; watch_display_string("GUESSD", 4); + state->ignore_btn_ticks = WORDLE_TICK_BAD_GUESS; } static uint32_t check_word_in_dict(uint8_t *word_elements) { @@ -164,8 +166,8 @@ static bool check_word(wordle_state_t *state) { return false; } -static void show_skip_wrong_letter_indicator(bool skipping, WordleScreen curr_screen) { - if (curr_screen >= SCREEN_PLAYING) return; +static void show_skip_wrong_letter_indicator(bool skipping, wordle_screen curr_screen) { + if (curr_screen >= WORDLE_SCREEN_PLAYING) return; if (skipping) watch_display_string("H", 3); else @@ -201,7 +203,7 @@ static void display_attempt(uint8_t attempt) { } static void display_playing(wordle_state_t *state) { - state->curr_screen = SCREEN_PLAYING; + state->curr_screen = WORDLE_SCREEN_PLAYING; display_attempt(state->attempt); display_all_letters(state); } @@ -230,9 +232,18 @@ static void reset_incorrect_elements(wordle_state_t *state) { } } +static bool is_in_do_not_use_list(uint16_t guess, const uint16_t *not_to_use, uint8_t max_repeats) { + for (size_t i = 0; i < max_repeats; i++) { + if (guess == not_to_use[i]) return true; + } + return false; +} + static void reset_board(wordle_state_t *state) { reset_all_elements(state); - state->curr_answer = get_random(WORDLE_NUM_WORDS); + do { + state->curr_answer = get_random(WORDLE_NUM_WORDS); + } while (is_in_do_not_use_list(state->curr_answer, state->not_to_use, WORDLE_MAX_BETWEEN_REPEATS)); watch_clear_colon(); state->position = get_first_pos(state->word_elements_result); display_playing(state); @@ -243,7 +254,7 @@ static void reset_board(wordle_state_t *state) { } static void display_title(wordle_state_t *state) { - state->curr_screen = SCREEN_TITLE; + state->curr_screen = WORDLE_SCREEN_TITLE; watch_display_string("WO WordLE", 0); show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } @@ -254,7 +265,7 @@ static void display_continue_result(bool continuing) { } static void display_continue(wordle_state_t *state) { - state->curr_screen = SCREEN_CONTINUE; + state->curr_screen = WORDLE_SCREEN_CONTINUE; watch_display_string("Cont ", 4); show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_continue_result(state->continuing); @@ -263,7 +274,7 @@ static void display_continue(wordle_state_t *state) { static void display_streak(wordle_state_t *state) { char buf[12]; - state->curr_screen = SCREEN_STREAK; + state->curr_screen = WORDLE_SCREEN_STREAK; #if WORDLE_USE_DAILY_STREAK == 2 if (state->streak > 99) sprintf(buf, "WO St--dy"); @@ -279,7 +290,7 @@ static void display_streak(wordle_state_t *state) { #if WORDLE_USE_DAILY_STREAK == 2 static void display_wait(wordle_state_t *state) { - state->curr_screen = SCREEN_WAIT; + state->curr_screen = WORDLE_SCREEN_WAIT; if (state->streak < 40) { char buf[13]; sprintf(buf,"WO%2d WaIt ", state->streak); @@ -302,14 +313,14 @@ static uint32_t get_day_unix_time(void) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; - sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); + sprintf(buf,"L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; - sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); + sprintf(buf,"W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -346,15 +357,16 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { watch_display_string(buf, 5); } -static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { +static bool act_on_btn(wordle_state_t *state, const wordle_pin_enum pin) { + if (state->ignore_btn_ticks > 0) return true; switch (state->curr_screen) { - case SCREEN_RESULT: + case WORDLE_SCREEN_RESULT: reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; - case SCREEN_TITLE: + case WORDLE_SCREEN_TITLE: #if WORDLE_USE_DAILY_STREAK == 2 if (state->day_last_game_started == get_day_unix_time()) { display_wait(state); @@ -372,26 +384,26 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { display_streak(state); #endif return true; - case SCREEN_STREAK: + case WORDLE_SCREEN_STREAK: state->day_last_game_started = get_day_unix_time(); reset_board(state); return true; - case SCREEN_WIN: - case SCREEN_LOSE: + case WORDLE_SCREEN_WIN: + case WORDLE_SCREEN_LOSE: display_title(state); return true; - case SCREEN_NO_DICT: - case SCREEN_ALREADY_GUESSED: + case WORDLE_SCREEN_NO_DICT: + case WORDLE_SCREEN_ALREADY_GUESSED: state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; #if WORDLE_USE_DAILY_STREAK == 2 - case SCREEN_WAIT: + case WORDLE_SCREEN_WAIT: (void) pin; display_title(state); return true; #else - case SCREEN_CONTINUE: + case WORDLE_SCREEN_CONTINUE: switch (pin) { case BTN_ALARM: @@ -407,6 +419,8 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { state->continuing = !state->continuing; display_continue_result(state->continuing); break; + default: + break; } return true; #endif @@ -416,6 +430,13 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { return false; } +static void win_lose_shared(wordle_state_t *state) { + reset_all_elements(state); + state->ignore_btn_ticks = WORDLE_TICK_WIN_LOSE; + state->not_to_use[state->not_to_use_position] = state->curr_answer; + state->not_to_use_position = (state->not_to_use_position + 1) % WORDLE_MAX_BETWEEN_REPEATS; +} + static void get_result(wordle_state_t *state) { #if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES // Check if it's in the dict @@ -437,8 +458,8 @@ static void get_result(wordle_state_t *state) { #endif bool exact_match = check_word(state); if (exact_match) { - reset_all_elements(state); - state->curr_screen = SCREEN_WIN; + state->curr_screen = WORDLE_SCREEN_WIN; + win_lose_shared(state); if (state->streak < 0x7F) state->streak++; #if WORDLE_USE_DAILY_STREAK == 2 @@ -447,13 +468,14 @@ static void get_result(wordle_state_t *state) { return; } if (++state->attempt >= WORDLE_MAX_ATTEMPTS) { - reset_all_elements(state); - state->curr_screen = SCREEN_LOSE; + state->curr_screen = WORDLE_SCREEN_LOSE; + win_lose_shared(state); state->streak = 0; return; } update_known_wrong_letters(state); - state->curr_screen = SCREEN_RESULT; + state->curr_screen = WORDLE_SCREEN_RESULT; + state->ignore_btn_ticks = WORDLE_TICKS_RESULT; return; } @@ -476,21 +498,7 @@ static void insert_random_guess(wordle_state_t *state) { } #endif -void wordle_face_setup(uint8_t watch_face_index, void ** context_ptr) { - (void) watch_face_index; - if (*context_ptr == NULL) { - *context_ptr = malloc(sizeof(wordle_state_t)); - memset(*context_ptr, 0, sizeof(wordle_state_t)); - wordle_state_t *state = (wordle_state_t *)*context_ptr; - state->curr_screen = SCREEN_TITLE; - state->skip_wrong_letter = false; - reset_all_elements(state); - } - // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. -} - -void wordle_face_activate(void *context) { - wordle_state_t *state = (wordle_state_t *)context; +static void _activate(wordle_state_t *state) { #if WORDLE_USE_DAILY_STREAK != 0 uint32_t now = get_day_unix_time(); uint32_t one_day = 60 *60 * 24; @@ -501,35 +509,58 @@ void wordle_face_activate(void *context) { } #endif state->using_random_guess = false; - if (is_playing(state) && state->curr_screen >= SCREEN_RESULT) { + if (is_playing(state) && state->curr_screen >= WORDLE_SCREEN_RESULT) { reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); } - movement_request_tick_frequency(2); + movement_request_tick_frequency(WORDLE_FREQ); + watch_clear_all_indicators(); + watch_clear_colon(); display_title(state); } +void wordle_face_setup(uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(wordle_state_t)); + memset(*context_ptr, 0, sizeof(wordle_state_t)); + wordle_state_t *state = (wordle_state_t *)*context_ptr; + state->curr_screen = WORDLE_SCREEN_TITLE; + state->skip_wrong_letter = true; + reset_all_elements(state); + memset(state->not_to_use, 0xff, sizeof(state->not_to_use)); + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void wordle_face_activate(void *context) { + wordle_state_t *state = (wordle_state_t *)context; + _activate(state); + +} + bool wordle_face_loop(movement_event_t event, void *context) { wordle_state_t *state = (wordle_state_t *)context; switch (event.event_type) { case EVENT_TICK: + if (state->ignore_btn_ticks > 0) state->ignore_btn_ticks--; switch (state->curr_screen) { - case SCREEN_PLAYING: + case WORDLE_SCREEN_PLAYING: if (event.subsecond % 2) { display_letter(state, true); } else { watch_display_string(" ", state->position + 5); } break; - case SCREEN_RESULT: + case WORDLE_SCREEN_RESULT: display_result(state, event.subsecond); break; - case SCREEN_LOSE: + case WORDLE_SCREEN_LOSE: display_lose(state, event.subsecond); break; - case SCREEN_WIN: + case WORDLE_SCREEN_WIN: display_win(state, event.subsecond); break; default: @@ -542,12 +573,12 @@ bool wordle_face_loop(movement_event_t event, void *context) { display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: - if (state->curr_screen < SCREEN_PLAYING) { + if (state->curr_screen < WORDLE_SCREEN_PLAYING) { state->skip_wrong_letter = !state->skip_wrong_letter; show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); break; } - if (state->curr_screen != SCREEN_PLAYING) break; + if (state->curr_screen != WORDLE_SCREEN_PLAYING) break; get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); display_letter(state, true); break; @@ -569,7 +600,7 @@ bool wordle_face_loop(movement_event_t event, void *context) { } break; case EVENT_ALARM_LONG_PRESS: - if (state->curr_screen != SCREEN_PLAYING) break; + if (state->curr_screen != WORDLE_SCREEN_PLAYING) break; display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); break; @@ -577,17 +608,24 @@ bool wordle_face_loop(movement_event_t event, void *context) { case EVENT_ACTIVATE: break; case EVENT_TIMEOUT: - if (state->curr_screen >= SCREEN_RESULT) { + if (state->curr_screen >= WORDLE_SCREEN_RESULT) { reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); display_title(state); } break; case EVENT_LOW_ENERGY_UPDATE: - if (state->curr_screen != SCREEN_TITLE) + if (state->curr_screen != WORDLE_SCREEN_TITLE) display_title(state); break; - default: + case EVENT_MODE_LONG_PRESS: + if (state->curr_screen >= WORDLE_SCREEN_PLAYING) { + _activate(state); + } else { + movement_move_to_face(0); + } + break; + default: return movement_default_loop_handler(event); } return true; diff --git a/watch-faces/complication/wordle_face.h b/watch-faces/complication/wordle_face.h index 5cc2f09a..e4f7d0f9 100644 --- a/watch-faces/complication/wordle_face.h +++ b/watch-faces/complication/wordle_face.h @@ -83,7 +83,15 @@ * 2 = Allow using a random guess of any value that can be an answer where all of its letters are unique * 3 = Allow using a random guess of any value that can be an answer, and it's considered one of the best initial choices. */ -#define WORDLE_USE_RANDOM_GUESS 2 +#define WORDLE_USE_RANDOM_GUESS 3 +#define WORDLE_FREQ 2 +// To avoid a button press immedietly skipping a screen, we wait this many ticks +#define WORDLE_TICKS_RESULT 4 +#define WORDLE_TICK_WIN_LOSE 2 +#define WORDLE_TICK_BAD_GUESS 0 + +// Store this many words in our list of words that were already used to avoid too much repetition of guesses +#define WORDLE_MAX_BETWEEN_REPEATS 50 #include "wordle_face_dict.h" #define WORDLE_NUM_WORDS (sizeof(_valid_words) / sizeof(_valid_words[0])) @@ -95,28 +103,34 @@ typedef enum { WORDLE_LETTER_WRONG_LOC, WORDLE_LETTER_CORRECT, WORDLE_LETTER_COUNT -} WordleLetterResult; +} wordle_letter_result; typedef enum { - SCREEN_TITLE = 0, - SCREEN_STREAK, - SCREEN_CONTINUE, + WORDLE_SCREEN_TITLE = 0, + WORDLE_SCREEN_STREAK, + WORDLE_SCREEN_CONTINUE, #if WORDLE_USE_DAILY_STREAK - SCREEN_WAIT, + WORDLE_SCREEN_WAIT, #endif - SCREEN_PLAYING, - SCREEN_RESULT, - SCREEN_WIN, - SCREEN_LOSE, - SCREEN_NO_DICT, - SCREEN_ALREADY_GUESSED, - SCREEN_COUNT -} WordleScreen; + WORDLE_SCREEN_PLAYING, + WORDLE_SCREEN_RESULT, + WORDLE_SCREEN_WIN, + WORDLE_SCREEN_LOSE, + WORDLE_SCREEN_NO_DICT, + WORDLE_SCREEN_ALREADY_GUESSED, + WORDLE_SCREEN_COUNT +} wordle_screen; + +typedef enum { + BTN_MODE = 0, + BTN_ALARM, + BTN_LIGHT, +} wordle_pin_enum; typedef struct { // Anything you need to keep track of, put it here! uint8_t word_elements[WORDLE_LENGTH]; - WordleLetterResult word_elements_result[WORDLE_LENGTH]; + wordle_letter_result word_elements_result[WORDLE_LENGTH]; #if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; #endif @@ -127,9 +141,12 @@ typedef struct { bool continuing : 1; bool skip_wrong_letter : 1; uint8_t streak; - WordleScreen curr_screen; + wordle_screen curr_screen; bool known_wrong_letters[WORDLE_NUM_VALID_LETTERS]; uint32_t day_last_game_started; + uint8_t ignore_btn_ticks; + uint16_t not_to_use[WORDLE_MAX_BETWEEN_REPEATS]; + uint8_t not_to_use_position; } wordle_state_t; void wordle_face_setup(uint8_t watch_face_index, void ** context_ptr); @@ -146,4 +163,3 @@ void wordle_face_resign(void *context); }) #endif // WORDLE_FACE_H_ -