From a831ed33364edb9b94f0ce43365a3956fa45c903 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 25 Jun 2023 20:43:01 +0100 Subject: [PATCH 001/220] Hi-lo: Initial game face commit --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/higher_lower_game_face.c | 371 ++++++++++++++++++ .../complication/higher_lower_game_face.h | 55 +++ 4 files changed, 428 insertions(+) create mode 100755 movement/watch_faces/complication/higher_lower_game_face.c create mode 100755 movement/watch_faces/complication/higher_lower_game_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 8b056a0..c761c8f 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -116,6 +116,7 @@ SRCS += \ ../watch_faces/complication/geomancy_face.c \ ../watch_faces/clock/simple_clock_bin_led_face.c \ ../watch_faces/complication/flashlight_face.c \ + ../watch_faces/complication/higher_lower_game_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index bf63732..4b510da 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -93,6 +93,7 @@ #include "dual_timer_face.h" #include "simple_clock_bin_led_face.h" #include "flashlight_face.h" +#include "higher_lower_game_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c new file mode 100755 index 0000000..3c35bc2 --- /dev/null +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -0,0 +1,371 @@ +/* + * 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. + */ + +// TODO: Win animation? +// TODO: Save highscore? +// TODO: Add sounds? +// Add sound option +// TODO: Flip board direction? + +// Future Ideas: +// - Use lap indicator for larger score improvement? + +// Emulator only: need time() to seed the random number generator. +#if __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include "higher_lower_game_face.h" +#include "watch_private_display.h" + +#define TITLE_TEXT "Hi-Lo" +#define GAME_BOARD_SIZE 6 +#define MAX_BOARDS 3 //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 +#define MAX_CARD_VALUE 14 + +typedef struct card_t { + uint8_t value; + bool revealed; +} card_t; + +typedef enum { + A, B, C, D, E, F, G +} segment_t; + +typedef enum { + HL_GUESS_EQUAL, + HL_GUESS_HIGHER, + HL_GUESS_LOWER +} guess_t; + +typedef enum { + HL_GS_TITLE_SCREEN, + HL_GS_GUESSING, + HL_GS_WIN, + HL_GS_LOSE, + HL_GS_SHOW_SCORE, +} game_state_t; + +static game_state_t game_state = HL_GS_TITLE_SCREEN; +static card_t game_board[GAME_BOARD_SIZE] = {0}; +static uint8_t guess_position = 0; +static uint8_t score = 0; +static uint8_t completed_board_count = 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 reset_board(bool first_round) { + // First card is random on the first board, and carried over from the last position on subsequent boards + const uint8_t first_card_value = first_round + ? generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE + : game_board[GAME_BOARD_SIZE - 1].value; + + game_board[0].value = first_card_value; + game_board[0].revealed = true; // Always reveal first card + + // Fill remainder of board + for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { + game_board[i] = (card_t) { + //.value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, + .value = i + MIN_CARD_VALUE, + .revealed = false + }; + } +} + +static void init_game(void) { + watch_clear_display(); + watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); + watch_display_string("GA", STATUS_DISPLAY_START); + reset_board(true); + score = 0; + completed_board_count = 0; + guess_position = 1; +} + +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; + watch_set_pixel(com_pin, seg); +} + +static void render_board_position(size_t board_position) { + const size_t display_position = BOARD_DISPLAY_END - board_position; + const bool revealed = game_board[board_position].revealed; + + //if (board_position == guess_position) { + // // Current spot + // watch_display_character('-', display_position); + // return; + //} + + if (!revealed) { + // Higher or lower indicator (currently just an empty space) + watch_display_character(' ', display_position); + //set_segment_at_position(F, display_position); + return; + } + + const uint8_t value = game_board[board_position].value; + switch (value) { + case 14: // A + watch_display_character('H', display_position); + break; + case 13: // 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 12: // Q (=) + watch_display_character(' ', display_position); + set_segment_at_position(A, display_position); + set_segment_at_position(D, display_position); + break; + case 11: // J (-) + watch_display_character('-', display_position); + break; + case 10: // 10 (0) + watch_display_character('0', display_position); + break; + default: { + const char display_char = value + '0'; + watch_display_character(display_char, display_position); + } + } +} + +static void render_board() { + for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) { + render_board_position(i); + } +} + +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); +} + +static void render_final_score(void) { + watch_display_string("SC", STATUS_DISPLAY_START); + 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); +} + +static guess_t get_answer() { + if (guess_position < 1 || guess_position > GAME_BOARD_SIZE) + return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this. + + game_board[guess_position].revealed = true; + const uint8_t previous_value = game_board[guess_position - 1].value; + const uint8_t current_value = game_board[guess_position].value; + + if (current_value > previous_value) + return HL_GUESS_HIGHER; + else if (current_value < previous_value) + return HL_GUESS_LOWER; + else + return HL_GUESS_EQUAL; +} + +static void do_game_loop(guess_t user_guess) { + switch (game_state) { + case HL_GS_TITLE_SCREEN: + init_game(); + render_board(); + render_board_count(); + game_state = HL_GS_GUESSING; + break; + case HL_GS_GUESSING: { + const guess_t answer = get_answer(); + + // Render answer indicator + switch (answer) { + case HL_GUESS_EQUAL: + watch_display_string("==", STATUS_DISPLAY_START); + break; + case HL_GUESS_HIGHER: + watch_display_string("HI", STATUS_DISPLAY_START); + break; + case HL_GUESS_LOWER: + watch_display_string("LO", STATUS_DISPLAY_START); + break; + } + + // Scoring + if (answer == user_guess) { + score++; + } else if (answer == HL_GUESS_EQUAL) { + // No score for two consecutive identical cards + } else { + // Incorrect guess, game over + watch_display_string("GO", STATUS_DISPLAY_START); + game_board[guess_position].revealed = true; + render_board_position(guess_position); + game_state = HL_GS_LOSE; + return; + } + + 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); + game_state = HL_GS_WIN; + return; + } + + // Next guess position + const bool final_board_guess = guess_position == GAME_BOARD_SIZE - 1; + if (final_board_guess) { + // Seed new board + completed_board_count++; + render_board_count(); + guess_position = 1; + reset_board(false); + render_board(); + } else { + guess_position++; + render_board_position(guess_position - 1); + render_board_position(guess_position); + } + break; + } + case HL_GS_WIN: + case HL_GS_LOSE: + // Show score screen on button press from either state + watch_clear_display(); + render_final_score(); + game_state = HL_GS_SHOW_SCORE; + break; + case HL_GS_SHOW_SCORE: + watch_clear_display(); + watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); + watch_display_string("GA", STATUS_DISPLAY_START); + game_state = HL_GS_TITLE_SCREEN; + break; + default: + watch_display_string("ERROR", BOARD_DISPLAY_START); + break; + } +} + +static void light_button_handler(void) { + do_game_loop(HL_GUESS_HIGHER); +} + +static void alarm_button_handler(void) { + do_game_loop(HL_GUESS_LOWER); +} + +void higher_lower_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { + (void) settings; + (void) watch_face_index; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(higher_lower_game_face_state_t)); + memset(*context_ptr, 0, sizeof(higher_lower_game_face_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + memset(game_board, 0, sizeof(game_board)); + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void higher_lower_game_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context; + (void) state; + // Handle any tasks related to your watch face coming on screen. + game_state = HL_GS_TITLE_SCREEN; +} + +bool higher_lower_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + higher_lower_game_face_state_t *state = (higher_lower_game_face_state_t *) context; + (void) state; + + 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); + break; + case EVENT_TICK: + // If needed, update your display here. + break; + case EVENT_LIGHT_BUTTON_UP: + light_button_handler(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + // Don't trigger light + break; + case EVENT_ALARM_BUTTON_UP: + alarm_button_handler(); + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, + // you may uncomment this line to move back to the first watch face in the list: + // movement_move_to_face(0); + break; + default: + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void higher_lower_game_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h new file mode 100755 index 0000000..1cef05d --- /dev/null +++ b/movement/watch_faces/complication/higher_lower_game_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 HIGHER_LOWER_GAME_FACE_H_ +#define HIGHER_LOWER_GAME_FACE_H_ + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +typedef struct { + // Anything you need to keep track of, put it here! +} higher_lower_game_face_state_t; + +void higher_lower_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void higher_lower_game_face_activate(movement_settings_t *settings, void *context); +bool higher_lower_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void higher_lower_game_face_resign(movement_settings_t *settings, void *context); + +#define higher_lower_game_face ((const watch_face_t){ \ + higher_lower_game_face_setup, \ + higher_lower_game_face_activate, \ + higher_lower_game_face_loop, \ + higher_lower_game_face_resign, \ + NULL, \ +}) + +#endif // HIGHER_LOWER_GAME_FACE_H_ + From 739ad64cc10d8889c0564e75cf3faa378726d3ba Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 2 Jul 2023 11:40:11 +0100 Subject: [PATCH 002/220] Hi-lo: Allow flipped board rendering option --- movement/watch_faces/complication/higher_lower_game_face.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index 3c35bc2..1edcaa0 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -52,6 +52,7 @@ #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE 14 +#define FLIP_BOARD_DIRECTION false typedef struct card_t { uint8_t value; @@ -128,11 +129,13 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { } static void render_board_position(size_t board_position) { - const size_t display_position = BOARD_DISPLAY_END - board_position; + size_t display_position = FLIP_BOARD_DIRECTION + ? BOARD_DISPLAY_START + board_position + : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; + //// Current position indicator spot //if (board_position == guess_position) { - // // Current spot // watch_display_character('-', display_position); // return; //} From b147ac0c672f95a04a72fd1254b4e686d47e43f0 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 9 Jul 2023 15:52:54 +0100 Subject: [PATCH 003/220] Hi-lo: Update face description - Remove test code --- .../complication/higher_lower_game_face.c | 17 ++---- .../complication/higher_lower_game_face.h | 54 +++++++++++++++++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index 1edcaa0..aed6eee 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -22,15 +22,6 @@ * SOFTWARE. */ -// TODO: Win animation? -// TODO: Save highscore? -// TODO: Add sounds? -// Add sound option -// TODO: Flip board direction? - -// Future Ideas: -// - Use lap indicator for larger score improvement? - // Emulator only: need time() to seed the random number generator. #if __EMSCRIPTEN__ #include @@ -43,7 +34,7 @@ #define TITLE_TEXT "Hi-Lo" #define GAME_BOARD_SIZE 6 -#define MAX_BOARDS 3 //40 +#define MAX_BOARDS 40 #define GUESSES_PER_SCREEN 5 #define WIN_SCORE MAX_BOARDS * GUESSES_PER_SCREEN #define STATUS_DISPLAY_START 0 @@ -104,8 +95,7 @@ static void reset_board(bool first_round) { // Fill remainder of board for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { game_board[i] = (card_t) { - //.value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, - .value = i + MIN_CARD_VALUE, + .value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, .revealed = false }; } @@ -129,7 +119,7 @@ static void set_segment_at_position(segment_t segment, uint8_t position) { } static void render_board_position(size_t board_position) { - size_t display_position = FLIP_BOARD_DIRECTION + const size_t display_position = FLIP_BOARD_DIRECTION ? BOARD_DISPLAY_START + board_position : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; @@ -371,4 +361,3 @@ void higher_lower_game_face_resign(movement_settings_t *settings, void *context) // handle any cleanup before your watch face goes off-screen. } - diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index 1cef05d..d093680 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -28,10 +28,59 @@ #include "movement.h" /* - * A DESCRIPTION OF YOUR WATCH FACE + * Higher-Lower game face + * ====================== * - * and a description of how use it + * A game face based on the "higher-lower" card game where the objective is to correctly guess if the next card will + * be higher or lower than the last revealed cards. * + * Game Flow: + * - When the face is selected, the "Hi-Lo" "Title" screen will be displayed, and the status indicator will display "GA" for game + * - Pressing `ALARM` or `LIGHT` will start the game and proceed to the "Guessing" screen + * - The first card will be revealed and the player must now make a guess + * - A player can guess `Higher` by pressing the `LIGHT` button, and `Lower` by pressing the `ALARM` button + * - The status indicator will show the result of the guess: HI (Higher), LO (Lower), or == (Equal) + * - There are five guesses to make on each game screen, once the end of the screen is reached, a new screen + * will be started, with the last revealed card carried over + * - The number of completed screens is displayed in the top right (see Scoring) + * - If the player has guessed correctly, the score is updated and play continues (see Scoring) + * - If the player has guessed incorrectly, the status will change to GO (Game Over) + * - The current card will be revealed + * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen + * - If the game is won, the status indicator will display "WI" and the "Win" screen will be displayed + * - Pressing `ALARM` or `LIGHT` will transition to the "Score" screen + * - The status indicator will change to "SC" when the final score is displayed + * - The number of completed game screens will be displayed on using the first two digits + * - The number of correct guesses will be displayed using the final three digits + * - E.g. "13: 063" represents 13 completed screens, with 63 correct guesses + * - Pressing `ALARM` or `LIGHT` while on the "Score" screen will transition to back to the "Title" screen + * + * Scoring: + * - If the player guesses correctly (HI/LO) a point is gained + * - If the player guesses incorrectly the game ends + * - Unless the revealed card is equal (==) to the last card, in which case play continues, but no point is gained + * - If the player completes 40 screens full of cards, the game ends and a win screen is displayed + * + * Misc: + * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at + * the following mapping of card values to screen display, but am open to better suggestions: + * + * | 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|-|=|≡|H| + * + * The following may more legible choice: + * | Cards | | + * |---------|--------------------------| + * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| + * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| + * + * Future Ideas: + * - Add sounds + * - Save/Display high score + * - Add a "Win" animation + * - Consider using lap indicator for larger score limit */ typedef struct { @@ -52,4 +101,3 @@ void higher_lower_game_face_resign(movement_settings_t *settings, void *context) }) #endif // HIGHER_LOWER_GAME_FACE_H_ - From 43e94ca0f2317ec105d2638c9616ca1c9ef46375 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Wed, 9 Aug 2023 22:34:01 +0200 Subject: [PATCH 004/220] Watch face for tracking deadlines. You can enter and monitor up to four different deadlines by providing their respective date and time. The watch face displays the remaining time at matching granularity, ranging from years to seconds. --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/deadline_face.c | 531 ++++++++++++++++++ .../watch_faces/complication/deadline_face.h | 61 ++ 4 files changed, 594 insertions(+) create mode 100644 movement/watch_faces/complication/deadline_face.c create mode 100644 movement/watch_faces/complication/deadline_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 625c772..74c630b 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -118,6 +118,7 @@ SRCS += \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ + ../watch_faces/complication/deadline_face.c # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index ff34c06..b96bfbb 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -95,6 +95,7 @@ #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" +#include "deadline_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c new file mode 100644 index 0000000..01ddc12 --- /dev/null +++ b/movement/watch_faces/complication/deadline_face.c @@ -0,0 +1,531 @@ +/* + * MIT License + * + * Copyright (c) 2023 Konrad Rieck + * + * 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. + */ + +/* + * # Deadline Face + * + * This is a watch face for tracking deadlines. It draws inspiration from + * other watch faces of the project but focuses on keeping track of + * deadlines. You can enter and monitor up to four different deadlines by + * providing their respective date and time. The face has two modes: + * *running mode* and *settings mode*. + * + * ## Running Mode + * + * When the watch face is activated, it defaults to running mode. The top + * right corner shows the current deadline number, and the main display + * presents the time left until the deadline. The format of the display + * varies depending on the remaining time. + * + * - When less than a day is left, the display shows the remaining hours, + * minutes, and seconds in the form `HH:MM:SS`. + * + * - When less than a month is left, the display shows the remaining days + * and hours in the form `DD:HH` with the unit `dy` for days. + * + * - When less than a year is left, the display shows the remaining months + * and days in the form `MM:DD` with the unit `mo` for months. + * + * - When more than a year is left, the years and months are displayed in + * the form `YY:MM` with the unit `yr` for years. + * + * - When a deadline has passed in the last 24 hours, the display shows + * `over` to indicate that the deadline has just recently been reached. + * + * - When no deadline is set for a particular slot, or if a deadline has + * already passed by more than 24 hours, `--:--` is displayed. + * + * The user can navigate in running mode using the following buttons: + * + * - The *alarm button* moves the next deadline. There are currently four + * slots available for deadlines. When the last slot has been reached, + * pressing the button moves to the first slot. + * + * - A *long press* on the *alarm button* activates settings mode and + * enables configuring the currently selected deadline. + * + * ## Settings Mode + * + * In settings mode, the currently selected slot for a deadline can be + * configured by providing the date and the time. Like running mode, the + * top right corner of the display indicates the current deadline number. + * The main display shows the date and, on the next page, the time to be + * configured. + * + * The user can use the following buttons in settings mode. + * + * - The *light button* navigates through the different date and time + * settings, going from year, month, day, hour, to minute. The selected + * position is blinking. + * + * - A *long press* on the light button resets the date and time to the next + * day at midnight. This is the default deadline. + * + * - The *alarm button* increments the currently selected position. A *long + * press* on the *alarm button* changes the value faster. + * + * - The *mode button* exists setting mode and returns to *running mode*. + * Here the selected deadline slot can be changed. + * + */ + +#include +#include +#include "deadline_face.h" +#include "watch.h" +#include "watch_utility.h" + +#define SETTINGS_NUM (5) +const char settings_titles[SETTINGS_NUM][3] = { "YR", "MO", "DA", "HR", "M1" }; + +static uint8_t tick_freq; + +/* Local functions */ +static void _running_init(movement_settings_t *settings, deadline_state_t * state); +static bool _running_loop(movement_event_t event, movement_settings_t *settings, void *context); +static void _running_display(movement_event_t event, movement_settings_t *settings, deadline_state_t * state); +static void _setting_init(movement_settings_t *settings, deadline_state_t * state); +static bool _setting_loop(movement_event_t event, movement_settings_t *settings, void *context); +static void _setting_display(movement_event_t event, movement_settings_t *settings, deadline_state_t * state, watch_date_time date); + +/* Utility functions */ +static void _increment_date(movement_settings_t *settings, deadline_state_t * state, watch_date_time date_time); +static inline int32_t _get_tz_offset(movement_settings_t *settings); +static inline void _change_tick_freq(uint8_t freq); +static inline bool _is_leap(int16_t y); +static inline int _days_in_month(int16_t mpnth, int16_t y); +static inline unsigned int _mod(int a, int b); +static inline void _beep_button(movement_settings_t *settings); +static inline void _beep_enable(movement_settings_t *settings); +static inline void _beep_disable(movement_settings_t *settings); +static inline void _reset_deadline(movement_settings_t *settings, deadline_state_t * state); + +/* Check for leap year */ +static inline bool _is_leap(int16_t y) +{ + y += 1900; + return !(y % 4) && ((y % 100) || !(y % 400)); +} + +/* Modulo function */ +static inline unsigned int _mod(int a, int b) +{ + int r = a % b; + return r < 0 ? r + b : r; +} + +/* Return days in month */ +static inline int _days_in_month(int16_t month, int16_t year) +{ + uint8_t days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + month = _mod(month - 1, 12); + + if (month == 1 && _is_leap(year)) { + return days[month] + 1; + } else { + return days[month]; + } +} + +/* Calculate time zone offset */ +static inline int32_t _get_tz_offset(movement_settings_t *settings) +{ + return movement_timezone_offsets[settings->bit.time_zone] * 60; +} + +/* Beep for a button press*/ +static inline void _beep_button(movement_settings_t *settings) +{ + // play a beep as confirmation for a button press (if applicable) + if (!settings->bit.button_should_sound) + return; + + watch_buzzer_play_note(BUZZER_NOTE_C7, 50); +} + +/* Beep for entering settings */ +static inline void _beep_enable(movement_settings_t *settings) +{ + if (!settings->bit.button_should_sound) + return; + + watch_buzzer_play_note(BUZZER_NOTE_G7, 50); + watch_buzzer_play_note(BUZZER_NOTE_REST, 75); + watch_buzzer_play_note(BUZZER_NOTE_C8, 75); +} + +/* Beep for leaving settings */ +static inline void _beep_disable(movement_settings_t *settings) +{ + if (!settings->bit.button_should_sound) + return; + + watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + watch_buzzer_play_note(BUZZER_NOTE_REST, 75); + watch_buzzer_play_note(BUZZER_NOTE_G7, 75); +} + +/* Change tick frequency */ +static inline void _change_tick_freq(uint8_t freq) +{ + if (tick_freq != freq) { + movement_request_tick_frequency(freq); + tick_freq = freq; + } +} + +/* Reset deadline to tomorrow */ +static inline void _reset_deadline(movement_settings_t *settings, deadline_state_t *state) +{ + /* Get current time and reset hours/minutes/seconds */ + watch_date_time date_time = watch_rtc_get_date_time(); + date_time.unit.second = 0; + date_time.unit.minute = 0; + date_time.unit.hour = 0; + + /* Add 24 hours to obtain first second of tomorrow */ + uint32_t ts = watch_utility_date_time_to_unix_time(date_time, _get_tz_offset(settings)); + ts += 24 * 60 * 60; + + state->deadlines[state->current_index] = ts; +} + +/* Increment date in settings mode. Function taken from `set_time_face.c` */ +static void _increment_date(movement_settings_t *settings, deadline_state_t *state, watch_date_time date_time) +{ + const uint8_t days_in_month[12] = { 31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31 }; + + switch (state->current_page) { + case 0: + /* Only 10 years covered. Fix this sometime next decade */ + date_time.unit.year = ((date_time.unit.year % 10) + 1); + break; + case 1: + date_time.unit.month = (date_time.unit.month % 12) + 1; + break; + case 2: + date_time.unit.day = date_time.unit.day + 1; + + /* Check for leap years */ + int8_t days = days_in_month[date_time.unit.month - 1]; + if (date_time.unit.month == 2 && _is_leap(date_time.unit.year)) + days++; + + if (date_time.unit.day > days) + date_time.unit.day = 1; + break; + case 3: + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + break; + case 4: + date_time.unit.minute = (date_time.unit.minute + 1) % 60; + break; + } + + uint32_t ts = watch_utility_date_time_to_unix_time(date_time, _get_tz_offset(settings)); + state->deadlines[state->current_index] = ts; +} + +/* Update display in running mode */ +static void _running_display(movement_event_t event, movement_settings_t *settings, deadline_state_t *state) +{ + (void) event; + (void) settings; + + /* seconds, minutes, hours, days, months, years */ + int16_t unit[] = { 0, 0, 0, 0, 0, 0 }; + uint8_t i, range[] = { 60, 60, 24, 30, 12, 0 }; + char buf[16]; + + watch_date_time now = watch_rtc_get_date_time(); + uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + + /* Deadline expired */ + if (state->deadlines[state->current_index] < now_ts) { + if (state->deadlines[state->current_index] + 24 * 60 * 60 > now_ts) + sprintf(buf, "DL%2dOVER ", state->current_index + 1); + else + sprintf(buf, "DL%2d---- ", state->current_index + 1); + + //watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 0); + return; + } + + /* Get date time structs */ + watch_date_time deadline = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], _get_tz_offset(settings) + ); + + /* Calculate naive difference of dates */ + unit[0] = deadline.unit.second - now.unit.second; + unit[1] = deadline.unit.minute - now.unit.minute; + unit[2] = deadline.unit.hour - now.unit.hour; + unit[3] = deadline.unit.day - now.unit.day; + unit[4] = deadline.unit.month - now.unit.month; + unit[5] = deadline.unit.year - now.unit.year; + + /* Correct errors of naive difference */ + for (i = 0; i < 6; i++) { + if (unit[i] < 0) { + /* Correct remaining units */ + if (i == 3) + unit[i] += _days_in_month(deadline.unit.month - 1, deadline.unit.year); + else + unit[i] += range[i]; + + /* Carry over change to next unit if non-zero */ + if (i < 5 && unit[i + 1] != 0) + unit[i + 1] -= 1; + } + } + + /* Set range */ + i = state->current_index + 1; + if (unit[5] > 0) { + /* years:months */ + sprintf(buf, "DL%2d%02d%02dYR", i, unit[5] % 100, unit[4] % 12); + } else if (unit[4] > 0) { + /* months:days */ + sprintf(buf, "DL%2d%02d%02dMO", i, (unit[5] * 12 + unit[4]) % 100, unit[3] % 32); + } else if (unit[3] > 0) { + /* days:hours */ + sprintf(buf, "DL%2d%02d%02ddY", i, unit[3] % 32, unit[2] % 24); + } else { + /* hours:minutes:seconds */ + sprintf(buf, "DL%2d%02d%02d%02d", i, unit[2] % 24, unit[1] % 60, unit[0] % 60); + } + + //watch_set_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 0); +} + +/* Init running mode */ +static void _running_init(movement_settings_t *settings, deadline_state_t *state) +{ + (void) settings; + (void) state; + + watch_clear_indicator(WATCH_INDICATOR_24H); + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_set_colon(); +} + +/* Loop of running mode */ +static bool _running_loop(movement_event_t event, movement_settings_t *settings, void *context) +{ + deadline_state_t *state = (deadline_state_t *) context; + _running_display(event, settings, state); + + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + _beep_button(settings); + state->current_index = (state->current_index + 1) % DEADLINE_FACE_DATES; + _running_display(event, settings, state); + break; + case EVENT_ALARM_LONG_PRESS: + _beep_enable(settings); + _setting_init(settings, state); + state->mode = DEADLINE_FACE_SETTING; + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + return false; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + + /* Slow down frequency after first loop for snappiness */ + _change_tick_freq(1); + return true; +} + +/* Update display in settings mode */ +static void _setting_display(movement_event_t event, movement_settings_t *settings, deadline_state_t *state, watch_date_time date_time) +{ + char buf[11]; + + int i = state->current_index + 1; + if (state->current_page > 2) { + watch_set_colon(); + if (settings->bit.clock_mode_24h) { + watch_set_indicator(WATCH_INDICATOR_24H); + sprintf(buf, "%s%2d%2d%02d ", settings_titles[state->current_page], i, date_time.unit.hour, date_time.unit.minute); + } else { + sprintf(buf, "%s%2d%2d%02d ", settings_titles[state->current_page], i, (date_time.unit.hour % 12) ? (date_time.unit.hour % 12) : 12, + date_time.unit.minute); + if (date_time.unit.hour < 12) + watch_clear_indicator(WATCH_INDICATOR_PM); + else + watch_set_indicator(WATCH_INDICATOR_PM); + } + } else { + watch_clear_colon(); + watch_clear_indicator(WATCH_INDICATOR_24H); + watch_clear_indicator(WATCH_INDICATOR_PM); + sprintf(buf, "%s%2d%2d%02d%02d", settings_titles[state->current_page], i, date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); + } + + /* Blink up the parameter we are setting */ + if (event.subsecond % 2) { + switch (state->current_page) { + case 0: + case 3: + buf[4] = buf[5] = ' '; + break; + case 1: + case 4: + buf[6] = buf[7] = ' '; + break; + case 2: + buf[8] = buf[9] = ' '; + break; + } + } + + watch_display_string(buf, 0); +} + +/* Init setting mode */ +static void _setting_init(movement_settings_t *settings, deadline_state_t *state) +{ + _change_tick_freq(4); + state->current_page = 0; + + /* Init fresh deadline to next day */ + if (state->deadlines[state->current_index] == 0) { + _reset_deadline(settings, state); + } +} + +/* Loop of setting mode */ +static bool _setting_loop(movement_event_t event, movement_settings_t *settings, void *context) +{ + deadline_state_t *state = (deadline_state_t *) context; + watch_date_time date_time; + date_time = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], _get_tz_offset(settings)); + + _setting_display(event, settings, state, date_time); + + switch (event.event_type) { + case EVENT_TICK: + if (tick_freq == 8) { + if (watch_get_pin_level(BTN_ALARM)) { + _increment_date(settings, state, date_time); + _setting_display(event, settings, state, date_time); + } else { + _change_tick_freq(4); + } + } + break; + case EVENT_ALARM_LONG_PRESS: + _change_tick_freq(8); + break; + case EVENT_ALARM_LONG_UP: + _change_tick_freq(4); + break; + case EVENT_LIGHT_LONG_PRESS: + _beep_button(settings); + _reset_deadline(settings, state); + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + state->current_page = (state->current_page + 1) % SETTINGS_NUM; + _setting_display(event, settings, state, date_time); + break; + case EVENT_ALARM_BUTTON_UP: + _change_tick_freq(4); + _increment_date(settings, state, date_time); + _setting_display(event, settings, state, date_time); + break; + case EVENT_TIMEOUT: + _beep_button(settings); + _change_tick_freq(1); + movement_move_to_face(0); + break; + case EVENT_MODE_BUTTON_UP: + _beep_disable(settings); + _running_init(settings, state); + state->mode = DEADLINE_FACE_RUNNING; + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +/* Setup face */ +void deadline_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) +{ + (void) settings; + (void) watch_face_index; + if (*context_ptr != NULL) + return; /* Skip setup if context available */ + + *context_ptr = malloc(sizeof(deadline_state_t)); + memset(*context_ptr, 0, sizeof(deadline_state_t)); +} + +/* Activate face */ +void deadline_face_activate(movement_settings_t *settings, void *context) +{ + (void) settings; + deadline_state_t *state = (deadline_state_t *) context; + + /* Set display options */ + _running_init(settings, state); + state->mode = DEADLINE_FACE_RUNNING; +} + +/* Loop face */ +bool deadline_face_loop(movement_event_t event, movement_settings_t *settings, void *context) +{ + (void) settings; + deadline_state_t *state = (deadline_state_t *) context; + switch (state->mode) { + case DEADLINE_FACE_SETTING: + _setting_loop(event, settings, context); + break; + default: + case DEADLINE_FACE_RUNNING: + _running_loop(event, settings, context); + break; + } + + return true; +} + +/* Resign face */ +void deadline_face_resign(movement_settings_t *settings, void *context) +{ + (void) settings; + (void) context; +} diff --git a/movement/watch_faces/complication/deadline_face.h b/movement/watch_faces/complication/deadline_face.h new file mode 100644 index 0000000..b560ab2 --- /dev/null +++ b/movement/watch_faces/complication/deadline_face.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2023 Konrad Rieck + * + * 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 DEADLINE_FACE_H_ +#define DEADLINE_FACE_H_ + +#include "movement.h" + +/* Modes of face */ +typedef enum { + DEADLINE_FACE_RUNNING = 0, + DEADLINE_FACE_SETTING +} deadline_mode_t; + +/* Number of deadline dates */ +#define DEADLINE_FACE_DATES (4) + +/* Deadline configuration */ +typedef struct { + deadline_mode_t mode:1; + uint8_t current_page:3; + uint8_t current_index:2; + uint32_t deadlines[DEADLINE_FACE_DATES]; +} deadline_state_t; + +void deadline_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); +void deadline_face_activate(movement_settings_t *settings, void *context); +bool deadline_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void deadline_face_resign(movement_settings_t *settings, void *context); + +#define deadline_face ((const watch_face_t){ \ + deadline_face_setup, \ + deadline_face_activate, \ + deadline_face_loop, \ + deadline_face_resign, \ + NULL, \ +}) + +#endif // DEADLINE_FACE_H_ From f3c28ede9613dcbc9ad3616634ce98c592d0a8ba Mon Sep 17 00:00:00 2001 From: "R. Alex Barbieri" <> Date: Sat, 2 Sep 2023 14:52:37 -0500 Subject: [PATCH 005/220] add a manual dst toggle Uses a simplistic set of jump tables to toggle daylight savings on and off. --- movement/movement.c | 107 +++++++++++++++++- movement/movement.h | 5 +- movement/watch_faces/settings/set_time_face.c | 20 +++- 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 0a5ac2e..cf2eb27 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -122,6 +122,107 @@ const int16_t movement_timezone_offsets[] = { -60, // 40 : -1:00:00 (Azores Standard Time) }; +/* These are approximate equivalent DST timezones for each + * timezone in the offset table. Unlike the full tzinfo file, + * the time-offsets used above are incomplete, so there are + * cases below where an approximate DST timezone is proposed + * for a timezone where no one observes DST, and cases + * where we can't propose an equivaent DST timezone since + * there isn't an appropriate one in the offset table. + * + * However, this should be good enough for anyone living in + * a DST-observing region to manually toggle DST without + * having to separately change the hour and timezone info + * in the time set face. + */ +const uint8_t movement_dst_jump_table[] = { + 1, // 0 UTC + 1 = CET + 2, // 1 CET + 1 = SAST + 3, // 2 SAST + 1 = AST + 5, // 3 AST + 1 = GST + 6, // 4 IST + 1 = AT + 7, // 5 GST + 1 = PST + 8, // 6 AT + 1 = IST + 10, // 7 PST + 1 = KT + 11, // 8 IST + 1 = MT + 9, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway + 12, // 10 KT + 1 = TST + 11, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway + 13, // 12 TST + 1 = CST + 15, // 13 CST + 1 = JST + 14, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway + 17, // 15 JST + 1 = AEST + 18, // 16 ACST + 1 = LHST + 19, // 17 AEST + 1 = SIT + 18, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway + 20, // 19 SIT + 1 = NZST + 22, // 20 NZST + 1 = TT + 23, // 21 CST + 1 = CDT + 24, // 22 TT + 1 = LIT + 23, // 23 CDT is already a daylight timezone + 24, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway + 26, // 25 BIT + 1 = NT + 27, // 26 NT + 1 = HAST + 29, // 27 HAST + 1 = AST + 28, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway + 30, // 29 AST + 1 = PST + 31, // 30 PST + 1 = MST + 32, // 31 MST + 1 = CST + 33, // 32 CST + 1 = EST + 35, // 33 EST + 1 = AST + 36, // 34 VST + 1 = NST + 37, // 35 AST + 1 = BT + 38, // 36 NST + 1 = NDT + 39, // 37 BT + 1 = 39 + 38, // 38 NDT is already a daylight timezone + 40, // 39 FNT + 1 = AST + 0 // 40 AST + 1 = UTC +}; + +const uint8_t movement_dst_inverse_jump_table[] = { + 40, // 0 + 0, // 1 + 1, // 2 + 2, // 3 + 4, // 4 + 3, // 5 + 4, // 6 + 5, // 7 + 6, // 8 + 9, // 9 + 7, // 10 + 8, // 11 + 10, // 12 + 12, // 13 + 14, // 14 + 13, // 15 + 16, // 16 + 15, // 17 + 16, // 18 + 17, // 19 + 19, // 20 + 21, // 21 + 20, // 22 + 21, // 23 + 24, // 24 + 25, // 25 + 25, // 26 + 26, // 27 + 28, // 28 + 27, // 29 + 29, // 30 + 30, // 31 + 31, // 32 + 32, // 33 + 34, // 34 + 33, // 35 + 34, // 36 + 35, // 37 + 36, // 38 + 37, // 39 + 39 // 40 +}; + const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789"; const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378"; @@ -613,13 +714,13 @@ void cb_fast_tick(void) { // Notice: is it possible that two or more buttons have an identical timestamp? In this case // only one of these buttons would receive the long press event. Don't bother for now... if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) event.event_type = EVENT_LIGHT_LONG_PRESS; if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) event.event_type = EVENT_MODE_LONG_PRESS; if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) event.event_type = EVENT_ALARM_LONG_PRESS; // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. diff --git a/movement/movement.h b/movement/movement.h index 66bf6af..d61d510 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -62,7 +62,8 @@ typedef union { bool clock_mode_24h : 1; // indicates whether clock should use 12 or 24 hour mode. bool use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial. bool alarm_enabled : 1; // indicates whether there is at least one alarm enabled. - uint8_t reserved : 6; // room for more preferences if needed. + bool dst_active : 1; // indicates whether daylight savings time is active + uint8_t reserved : 5; // room for more preferences if needed. } bit; uint32_t reg; } movement_settings_t; @@ -128,6 +129,8 @@ typedef struct { } movement_event_t; extern const int16_t movement_timezone_offsets[]; +extern const uint8_t movement_dst_jump_table[]; +extern const uint8_t movement_dst_inverse_jump_table[]; extern const char movement_valid_position_0_chars[]; extern const char movement_valid_position_1_chars[]; diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 84a4d18..16011e3 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -26,8 +26,8 @@ #include "set_time_face.h" #include "watch.h" -#define SET_TIME_FACE_NUM_SETTINGS (7) -const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; +#define SET_TIME_FACE_NUM_SETTINGS (8) +const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO", "DS"}; static bool _quick_ticks_running; @@ -66,6 +66,16 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time settings->bit.time_zone++; if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; + case 7: // daylight savings time + if (settings->bit.dst_active) { // deactivate DST + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + settings->bit.time_zone = movement_dst_inverse_jump_table[settings->bit.time_zone]; + } else { // activate DST + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + settings->bit.time_zone = movement_dst_jump_table[settings->bit.time_zone]; + } + settings->bit.dst_active = !settings->bit.dst_active; + break; } watch_rtc_set_date_time(date_time); } @@ -146,7 +156,7 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); - } else { + } else if (current_page < 7) { // zone if (event.subsecond % 2) { watch_clear_colon(); sprintf(buf, "%s ", set_time_face_titles[current_page]); @@ -154,6 +164,10 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_set_colon(); sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (movement_timezone_offsets[settings->bit.time_zone] / 60), (int8_t) (movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); } + } else { // daylight savings + watch_clear_colon(); + if (settings->bit.dst_active) sprintf(buf, "%s dsT y", set_time_face_titles[current_page]); + else sprintf(buf, "%s dsT n", set_time_face_titles[current_page]); } // blink up the parameter we're setting From 56f76b8c6a74ed67f2f82f803a7bae89643d0696 Mon Sep 17 00:00:00 2001 From: CarpeNoctem Date: Wed, 6 Sep 2023 00:20:55 +1000 Subject: [PATCH 006/220] Initial commit of French Revolutionary (Decimal) Time face. (french_revolutionary_face) --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../clock/french_revolutionary_face.c | 243 ++++++++++++++++++ .../clock/french_revolutionary_face.h | 84 ++++++ 4 files changed, 329 insertions(+) create mode 100644 movement/watch_faces/clock/french_revolutionary_face.c create mode 100644 movement/watch_faces/clock/french_revolutionary_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 625c772..1d8ccfe 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -118,6 +118,7 @@ SRCS += \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ + ../watch_faces/clock/french_revolutionary_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index ff34c06..9ee8737 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -95,6 +95,7 @@ #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" +#include "french_revolutionary_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/clock/french_revolutionary_face.c b/movement/watch_faces/clock/french_revolutionary_face.c new file mode 100644 index 0000000..9801b9c --- /dev/null +++ b/movement/watch_faces/clock/french_revolutionary_face.c @@ -0,0 +1,243 @@ +/* + * MIT License + * + * Copyright (c) 2023 CarpeNoctem + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "french_revolutionary_face.h" + +void french_revolutionary_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(french_revolutionary_state_t)); + memset(*context_ptr, 0, sizeof(french_revolutionary_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + french_revolutionary_state_t *state = (french_revolutionary_state_t *)*context_ptr; + state->use_am_pm = false; + state->show_seconds = true; + state->display_type = 0; + state->colon_set_after_splash = false; + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void french_revolutionary_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + french_revolutionary_state_t *state = (french_revolutionary_state_t *)context; + + // Handle any tasks related to your watch face coming on screen. + state->colon_set_after_splash = false; +} + +bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + french_revolutionary_state_t *state = (french_revolutionary_state_t *)context; + + char buf[11]; + watch_date_time date_time; + fr_decimal_time decimal_time; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Initial UI - Show a quick "splash screen" + watch_clear_display(); + watch_display_string("FR dECimL", 0); + break; + case EVENT_TICK: + case EVENT_LOW_ENERGY_UPDATE: + + date_time = watch_rtc_get_date_time(); + + decimal_time = get_decimal_time(&date_time); + + set_display_buffer(buf, state, &decimal_time, &date_time); + + // If we're in low-energy mode, don't write out the seconds. Also start the LE tick animation if it's not already going. + if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { + buf[8] = ' '; + buf[9] = ' '; + if (!watch_tick_animation_is_running()) { watch_start_tick_animation(500); } + } + + // Update the display with our decimal time + watch_display_string(buf, 0); + + // Oh, and a one-off to set the colon after the "splash screen" + if (!state->colon_set_after_splash) { + watch_set_colon(); + state->colon_set_after_splash = true; + } + break; + case EVENT_ALARM_BUTTON_UP: + state->display_type += 1 ; // cycle through the display types + if (state->display_type > 2) { state->display_type = 0; } // but return to 0 after 2 + break; + case EVENT_ALARM_LONG_PRESS: + // I originally had chiming on the decimal-hour enabled, and this would enable/disable that chime, just like on + // the simple clock and decimal time faces. But because decimal seconds don't always line up with normal seconds, + // I assume the (decimal-)hourly chime could sometimes be missed. Additionally, I need this button for other purposes, + // now that I added seconds on/off toggle and upper normal-time with the ability to toggle that between 12/24hr format. + state->show_seconds = !state->show_seconds; + if (!state->show_seconds) { watch_display_string(" ", 8); } + else { watch_display_string("--", 8); } + break; + case EVENT_LIGHT_LONG_PRESS: + // In case anyone really wants that upper time in 12-hour format. I thought about using the global setting (settings->bit.clock_mode_24h) + // for this preference, but thought someone who prefers 12-hour format normally, might prefer 24hr when compared to a 10hr decimal day, + // so this is separate for now. + state->use_am_pm = !state->use_am_pm; + if (state->use_am_pm) { + watch_clear_indicator(WATCH_INDICATOR_24H); + if (date_time.unit.hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); } + else { watch_set_indicator(WATCH_INDICATOR_PM); } + } else { + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_set_indicator(WATCH_INDICATOR_24H); + } + break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void french_revolutionary_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + +// Calculate decimal time from normal (24hr) time +fr_decimal_time get_decimal_time(watch_date_time *date_time) { + uint32_t current_24hr_secs, current_decimal_seconds; + fr_decimal_time decimal_time; + // Current 24-hr time in seconds (There are 86400 of these in a day.) + current_24hr_secs = date_time->unit.hour * 3600 + date_time->unit.minute * 60 + date_time->unit.second; + + // Current Decimal Time in seconds. There are 100000 seconds in a 10-hr decimal-time day. + // current_decimal_seconds = current_24hr_seconds * 100000 / 86400, or = current_24_seconds * 1000 / 864; + // By chopping the extra zeros off the end, we can use uint32 instead of uint64. + current_decimal_seconds = current_24hr_secs * 1000 / 864; + + decimal_time.hour = current_decimal_seconds / 10000; + // Remove the hours from total seconds and keep the remainder for below. + current_decimal_seconds = current_decimal_seconds - decimal_time.hour * 10000; + + decimal_time.minute = current_decimal_seconds / 100; + // Remove the minutes from total seconds and keep the remaining seconds + // Note: I think I used an extra seconds variable here because sprintf or movement weren't liking a uint32... + decimal_time.second = current_decimal_seconds - decimal_time.hour * 100; + return decimal_time; +} + +// Fills in the display buffer, depending on the currently-selected display option (and sub-options): +// - Decimal-time only +// - Decimal-time with date in top-right +// - Decimal-time with normal time in the top (minutes first, then hours, due to display limitations) +// Note: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here. +// I'll try to add that optimization could be added in a future commit. +void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time *date_time) { + switch (state->display_type) { + // Decimal time only + case 0: + // Originally I had the day slot set to "FR" (French Revolutionary time), but my brain kept thinking "Friday" whenever I saw it, + // so I changed it to dT (Decimal Time) to avoid that confusion. Apologies to anyone who has the other decimal_time face and this one + // installed concurrently. Maybe the splash screen will help a little. + sprintf( buf, "dT %2d%02d%02d", decimal_time->hour, decimal_time->minute, decimal_time->second ); + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_clear_indicator(WATCH_INDICATOR_24H); + break; + // Decimal time and date + case 1: + sprintf( buf, "dT%2d%2d%02d%02d", date_time->unit.day, decimal_time->hour, decimal_time->minute, decimal_time->second ); + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_clear_indicator(WATCH_INDICATOR_24H); + break; + // Decimal time on bottom, normal time above + case 2: + if (state->use_am_pm) { + // if we are in 12 hour mode, do some cleanup. + watch_clear_indicator(WATCH_INDICATOR_24H); + if (date_time->unit.hour < 12) { + watch_clear_indicator(WATCH_INDICATOR_PM); + } else { + watch_set_indicator(WATCH_INDICATOR_PM); + } + date_time->unit.hour %= 12; + if (date_time->unit.hour == 0) date_time->unit.hour = 12; + } else { + watch_clear_indicator(WATCH_INDICATOR_PM); + watch_set_indicator(WATCH_INDICATOR_24H); + } + // Note, the date digits don't display a leading zero well, so we don't use it. + sprintf( buf, "%02d%2d%2d%02d%02d", date_time->unit.minute, date_time->unit.hour, decimal_time->hour, decimal_time->minute, decimal_time->second ); + + // Make the second character of the Day area more readable + buf[1] = fix_character_one(buf[1]); + break; + } + // Finally, if show_seconds is disabled, trim those off. + if (!state->show_seconds) { + buf[8] = ' '; + buf[9] = ' '; + } +} + +// Sadly, the second character of the Day field cannot show all numbers, so we make some replacements. +// See https://www.sensorwatch.net/docs/wig/display/#limitations-of-the-weekday-digits +char fix_character_one(char digit) { + char return_char = digit; // We don't need to update this for 0, 1, 3, 7 and 8. + switch(digit) { + case '2': + // Roman numeral / tally representation of 2 + return_char = '|'; // Thanks, Joey, for already having this in the character set. + break; + case '4': + // Looks almost like a 4 - just missing the top-left segment. + // 0b01000110 + return_char = '&'; // Slight hack - I want 0b01000110, but 0b01000100 is already in the character set and will do, since B and C segments are linked in this position. + break; + case '5': + return_char = 'F'; // F for Five + break; + case '6': + return_char = 'E'; // Looks almost like a 6 - just missing the bottom-right segment. Not super happy with it, but liked it best of the options I tried. + break; + case '9': + return_char = 'N'; // N for Nine + break; + } + return return_char; +} \ No newline at end of file diff --git a/movement/watch_faces/clock/french_revolutionary_face.h b/movement/watch_faces/clock/french_revolutionary_face.h new file mode 100644 index 0000000..f18bb1f --- /dev/null +++ b/movement/watch_faces/clock/french_revolutionary_face.h @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2023 CarpeNoctem + * + * 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 FRENCH_REVOLUTIONARY_FACE_H_ +#define FRENCH_REVOLUTIONARY_FACE_H_ + +#include "movement.h" + +/* + * French Revolutionary Decimal Time + * + * Similar to the Decimal Time face, but with the day divided into ten hours instead of twenty four. + * Each hour is divided into one hundred minutes, and those minutes are divided into 100 seconds. + * I came across this one the Svalbard watch site here: https://svalbard.watch/pages/about_decimal_time.html + * More info here as well: https://en.wikipedia.org/wiki/Decimal_time + * + * By default, the face just displays the current decimal time. Pressing the alarm button will toggle through other display options: + * 1) Just decimal time (with dT indicator at top) + * 2) Decimal time, with dT indicator and date above. + * 3) Decimal time, with 24-hr time above (where Day and Date would normally be displayed), BUT minutes first then hours. + * Sadly, the first character of the date area only goes up to 3 (see https://www.sensorwatch.net/docs/wig/display/#the-day-digits) + * I was going to begrudgindly leave this display option out when I realized that, but thought it would be better to have this backwards + * representation of the "normal" time than not at all. + * + * A long-press of the light button will toggle the upper time between 12-hr AM/PM and 24-hr mode. I thought of reading the main setting for this, + * but thought that a person could normally prefer 12hr time, but next to a 10hr day want to see the normal time in the 24hr format. + * + * A long-press of the alarm button will toggle the seconds off and on. + * + */ + +typedef struct { + bool use_am_pm; // Use 12-hr AM/PM for upper display instead of 24-hr? (Default is 24-hr) + bool show_seconds; + bool colon_set_after_splash; + uint8_t display_type : 2; +} french_revolutionary_state_t; + +typedef struct { + uint8_t second : 6; // 0-99 + uint8_t minute : 6; // 0-99 + uint8_t hour : 5; // 0-10 +} fr_decimal_time; + +void french_revolutionary_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void french_revolutionary_face_activate(movement_settings_t *settings, void *context); +bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void french_revolutionary_face_resign(movement_settings_t *settings, void *context); +char fix_character_one(char digit); +fr_decimal_time get_decimal_time(watch_date_time *date_time); +void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time *date_time); + + +#define french_revolutionary_face ((const watch_face_t){ \ + french_revolutionary_face_setup, \ + french_revolutionary_face_activate, \ + french_revolutionary_face_loop, \ + french_revolutionary_face_resign, \ + NULL, \ +}) + +#endif // FRENCH_REVOLUTIONARY_FACE_H_ + From 82ed355aba8a367765c1d4ae4dd2b052777910b7 Mon Sep 17 00:00:00 2001 From: CarpeNoctem Date: Wed, 6 Sep 2023 01:21:26 +1000 Subject: [PATCH 007/220] fix two-part bug with decimal seconds introduced during big refactor. --- movement/watch_faces/clock/french_revolutionary_face.c | 4 ++-- movement/watch_faces/clock/french_revolutionary_face.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/clock/french_revolutionary_face.c b/movement/watch_faces/clock/french_revolutionary_face.c index 9801b9c..519cfc3 100644 --- a/movement/watch_faces/clock/french_revolutionary_face.c +++ b/movement/watch_faces/clock/french_revolutionary_face.c @@ -158,7 +158,7 @@ fr_decimal_time get_decimal_time(watch_date_time *date_time) { decimal_time.minute = current_decimal_seconds / 100; // Remove the minutes from total seconds and keep the remaining seconds // Note: I think I used an extra seconds variable here because sprintf or movement weren't liking a uint32... - decimal_time.second = current_decimal_seconds - decimal_time.hour * 100; + decimal_time.second = current_decimal_seconds - decimal_time.minute * 100; return decimal_time; } @@ -166,7 +166,7 @@ fr_decimal_time get_decimal_time(watch_date_time *date_time) { // - Decimal-time only // - Decimal-time with date in top-right // - Decimal-time with normal time in the top (minutes first, then hours, due to display limitations) -// Note: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here. +// TODO: There is some power-saving stuff that simple clock does here around not redrawing characters that haven't changed, but we're not doing that here. // I'll try to add that optimization could be added in a future commit. void set_display_buffer(char *buf, french_revolutionary_state_t *state, fr_decimal_time *decimal_time, watch_date_time *date_time) { switch (state->display_type) { diff --git a/movement/watch_faces/clock/french_revolutionary_face.h b/movement/watch_faces/clock/french_revolutionary_face.h index f18bb1f..294f422 100644 --- a/movement/watch_faces/clock/french_revolutionary_face.h +++ b/movement/watch_faces/clock/french_revolutionary_face.h @@ -58,8 +58,8 @@ typedef struct { } french_revolutionary_state_t; typedef struct { - uint8_t second : 6; // 0-99 - uint8_t minute : 6; // 0-99 + uint8_t second : 8; // 0-99 + uint8_t minute : 8; // 0-99 uint8_t hour : 5; // 0-10 } fr_decimal_time; From 57e9f11a0c898e6d174d71e33f1b32efb904eb2b Mon Sep 17 00:00:00 2001 From: Dennisman219 Date: Wed, 27 Sep 2023 22:14:19 +0200 Subject: [PATCH 008/220] Minimal clock face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/clock/minimal_clock_face.c | 115 ++++++++++++++++++ .../watch_faces/clock/minimal_clock_face.h | 56 +++++++++ 4 files changed, 173 insertions(+) create mode 100644 movement/watch_faces/clock/minimal_clock_face.c create mode 100644 movement/watch_faces/clock/minimal_clock_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 625c772..56c21a6 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -118,6 +118,7 @@ SRCS += \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ + ../watch_faces/clock/minimal_clock_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index ff34c06..92b82fb 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -95,6 +95,7 @@ #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" +#include "minimal_clock_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/clock/minimal_clock_face.c b/movement/watch_faces/clock/minimal_clock_face.c new file mode 100644 index 0000000..195a3ee --- /dev/null +++ b/movement/watch_faces/clock/minimal_clock_face.c @@ -0,0 +1,115 @@ +/* + * MIT License + * + * Copyright (c) 2023 Dennisman219 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "minimal_clock_face.h" + +static void _minimal_clock_face_update_display(movement_settings_t *settings) { + watch_date_time date_time = watch_rtc_get_date_time(); + char buffer[11]; + + if (!settings->bit.clock_mode_24h) { + date_time.unit.hour %= 12; + } + + sprintf(buffer, "%2d%02d ", date_time.unit.hour, date_time.unit.minute); + watch_display_string(buffer, 4); +} + +void minimal_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(minimal_clock_state_t)); + memset(*context_ptr, 0, sizeof(minimal_clock_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void minimal_clock_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + // Handle any tasks related to your watch face coming on screen. + watch_set_colon(); +} + +bool minimal_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + _minimal_clock_face_update_display(settings); + break; + case EVENT_TICK: + // If needed, update your display here. + _minimal_clock_face_update_display(settings); + break; + case EVENT_LIGHT_BUTTON_UP: + // You can use the Light button for your own purposes. Note that by default, Movement will also + // illuminate the LED in response to EVENT_LIGHT_BUTTON_DOWN; to suppress that behavior, add an + // empty case for EVENT_LIGHT_BUTTON_DOWN. + break; + case EVENT_ALARM_BUTTON_UP: + // Just in case you have need for another button. + break; + case EVENT_TIMEOUT: + // Your watch face will receive this event after a period of inactivity. If it makes sense to resign, + // you may uncomment this line to move back to the first watch face in the list: + // movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute. + // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds. + // You should also consider starting the tick animation, to show the wearer that this is sleep mode: + // watch_start_tick_animation(500); + _minimal_clock_face_update_display(settings); + break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void minimal_clock_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/clock/minimal_clock_face.h b/movement/watch_faces/clock/minimal_clock_face.h new file mode 100644 index 0000000..1978689 --- /dev/null +++ b/movement/watch_faces/clock/minimal_clock_face.h @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2023 Dennisman219 + * + * 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 MINIMAL_CLOCK_FACE_H_ +#define MINIMAL_CLOCK_FACE_H_ + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +typedef struct { + // Anything you need to keep track of, put it here! + uint8_t unused; +} minimal_clock_state_t; + +void minimal_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void minimal_clock_face_activate(movement_settings_t *settings, void *context); +bool minimal_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void minimal_clock_face_resign(movement_settings_t *settings, void *context); + +#define minimal_clock_face ((const watch_face_t){ \ + minimal_clock_face_setup, \ + minimal_clock_face_activate, \ + minimal_clock_face_loop, \ + minimal_clock_face_resign, \ + NULL, \ +}) + +#endif // MINIMAL_CLOCK_FACE_H_ + From c15b01cd4c44f01f93561ae7081ba98282c2c290 Mon Sep 17 00:00:00 2001 From: Dennisman219 Date: Fri, 29 Sep 2023 14:16:51 +0200 Subject: [PATCH 009/220] Added missing description to header file --- movement/watch_faces/clock/minimal_clock_face.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/clock/minimal_clock_face.h b/movement/watch_faces/clock/minimal_clock_face.h index 1978689..d1f6ddf 100644 --- a/movement/watch_faces/clock/minimal_clock_face.h +++ b/movement/watch_faces/clock/minimal_clock_face.h @@ -28,9 +28,10 @@ #include "movement.h" /* - * A DESCRIPTION OF YOUR WATCH FACE + * MINIMAL CLOCK FACE * - * and a description of how use it + * A minimal clock face that just shows hours and minutes. + * There is nothing to configure. The face follows the 12h/24h setting * */ From 21645934844053c62a349ae827c0eabd27fad1b9 Mon Sep 17 00:00:00 2001 From: Dennisman219 Date: Fri, 29 Sep 2023 14:17:30 +0200 Subject: [PATCH 010/220] Leading 0 for hours <12 in 24h mode --- movement/watch_faces/clock/minimal_clock_face.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/clock/minimal_clock_face.c b/movement/watch_faces/clock/minimal_clock_face.c index 195a3ee..fa4880e 100644 --- a/movement/watch_faces/clock/minimal_clock_face.c +++ b/movement/watch_faces/clock/minimal_clock_face.c @@ -32,9 +32,11 @@ static void _minimal_clock_face_update_display(movement_settings_t *settings) { if (!settings->bit.clock_mode_24h) { date_time.unit.hour %= 12; + sprintf(buffer, "%2d%02d ", date_time.unit.hour, date_time.unit.minute); + } else { + sprintf(buffer, "%02d%02d ", date_time.unit.hour, date_time.unit.minute); } - sprintf(buffer, "%2d%02d ", date_time.unit.hour, date_time.unit.minute); watch_display_string(buffer, 4); } From 30195a2619fe04a9c27ed03a3d116f2c3921688d Mon Sep 17 00:00:00 2001 From: Wesley Aptekar-Cassels Date: Fri, 29 Sep 2023 22:10:14 -0400 Subject: [PATCH 011/220] movement: Add "instant" led_duration option. This illuminates the LED only for the time that the button is pressed, and turns it off as soon as it's released, which is the behaviour of the original watch. I chose a led_duration of zero to represent "instant" and all bits set to represent "no LED", which is arbitrary but seemed more sensible to me. --- movement/movement.c | 23 +++++++++++++++---- movement/movement.h | 4 ++-- .../watch_faces/settings/preferences_face.c | 11 ++++++--- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index f086841..17011e0 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -213,14 +213,24 @@ void movement_request_tick_frequency(uint8_t freq) { } void movement_illuminate_led(void) { - if (movement_state.settings.bit.led_duration) { + if (movement_state.settings.bit.led_duration != 0b111) { watch_set_led_color(movement_state.settings.bit.led_red_color ? (0xF | movement_state.settings.bit.led_red_color << 4) : 0, movement_state.settings.bit.led_green_color ? (0xF | movement_state.settings.bit.led_green_color << 4) : 0); - movement_state.light_ticks = (movement_state.settings.bit.led_duration * 2 - 1) * 128; + if (movement_state.settings.bit.led_duration == 0) { + movement_state.light_ticks = 1; + } else { + movement_state.light_ticks = (movement_state.settings.bit.led_duration * 2 - 1) * 128; + } _movement_enable_fast_tick_if_needed(); } } +static void _movement_led_off(void) { + watch_set_led_off(); + movement_state.light_ticks = -1; + _movement_disable_fast_tick_if_possible(); +} + bool movement_default_loop_handler(movement_event_t event, movement_settings_t *settings) { (void)settings; @@ -231,6 +241,11 @@ bool movement_default_loop_handler(movement_event_t event, movement_settings_t * case EVENT_LIGHT_BUTTON_DOWN: movement_illuminate_led(); break; + case EVENT_LIGHT_BUTTON_UP: + if (movement_state.settings.bit.led_duration == 0) { + _movement_led_off(); + } + break; case EVENT_MODE_LONG_PRESS: if (MOVEMENT_SECONDARY_FACE_INDEX && movement_state.current_watch_face == 0) { movement_move_to_face(MOVEMENT_SECONDARY_FACE_INDEX); @@ -465,9 +480,7 @@ bool app_loop(void) { if (watch_get_pin_level(BTN_LIGHT)) { movement_state.light_ticks = 1; } else { - watch_set_led_off(); - movement_state.light_ticks = -1; - _movement_disable_fast_tick_if_possible(); + _movement_led_off(); } } diff --git a/movement/movement.h b/movement/movement.h index 5f30dfb..87fdeba 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -50,7 +50,7 @@ typedef union { uint8_t to_interval : 2; // an inactivity interval for asking the active face to resign. bool to_always : 1; // if true, always time out from the active face to face 0. otherwise only faces that time out will resign (the default). uint8_t le_interval : 3; // 0 to disable low energy mode, or an inactivity interval for going into low energy mode. - uint8_t led_duration : 2; // how many seconds to shine the LED for (x2), or 0 to disable it. + uint8_t led_duration : 3; // how many seconds to shine the LED for (x2), 0 to shine only while the button is depressed, or all bits set to disable the LED altogether. uint8_t led_red_color : 4; // for general purpose illumination, the red LED value (0-15) uint8_t led_green_color : 4; // for general purpose illumination, the green LED value (0-15) uint8_t time_zone : 6; // an integer representing an index in the time zone table. @@ -62,7 +62,7 @@ typedef union { bool clock_mode_24h : 1; // indicates whether clock should use 12 or 24 hour mode. bool use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial. bool alarm_enabled : 1; // indicates whether there is at least one alarm enabled. - uint8_t reserved : 6; // room for more preferences if needed. + uint8_t reserved : 5; // room for more preferences if needed. } bit; uint32_t reg; } movement_settings_t; diff --git a/movement/watch_faces/settings/preferences_face.c b/movement/watch_faces/settings/preferences_face.c index b0e328b..68041d2 100644 --- a/movement/watch_faces/settings/preferences_face.c +++ b/movement/watch_faces/settings/preferences_face.c @@ -84,6 +84,9 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings break; case 4: settings->bit.led_duration = settings->bit.led_duration + 1; + if (settings->bit.led_duration > 3) { + settings->bit.led_duration = 0b111; + } break; case 5: settings->bit.led_green_color = settings->bit.led_green_color + 1; @@ -159,11 +162,13 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings } break; case 4: - if (settings->bit.led_duration) { + if (settings->bit.led_duration == 0) { + watch_display_string("instnt", 4); + } else if (settings->bit.led_duration == 0b111) { + watch_display_string("no LEd", 4); + } else { sprintf(buf, " %1d SeC", settings->bit.led_duration * 2 - 1); watch_display_string(buf, 4); - } else { - watch_display_string("no LEd", 4); } break; case 5: From 4763568900d35003cec03f091203f58132d6b958 Mon Sep 17 00:00:00 2001 From: Wesley Aptekar-Cassels Date: Fri, 29 Sep 2023 22:13:29 -0400 Subject: [PATCH 012/220] movement: default to "instant" led_duration. This is a matter of personal preference, but "instant" is the behaviour of the original watch, and seems like the thing more people would expect. Feel free not to take this commit if you disagree, though. --- movement/movement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/movement.c b/movement/movement.c index 17011e0..577f199 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -364,7 +364,7 @@ void app_init(void) { movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR; movement_state.settings.bit.button_should_sound = true; movement_state.settings.bit.le_interval = 1; - movement_state.settings.bit.led_duration = 1; + movement_state.settings.bit.led_duration = 0; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; movement_state.next_available_backup_register = 4; From f633b7634b80d0bf08e491606eab3f10f85d6ece Mon Sep 17 00:00:00 2001 From: Jonathan Glines Date: Sun, 24 Sep 2023 14:00:13 -0400 Subject: [PATCH 013/220] Support leading zero representation for 24h clock Toggle between default behavior and leading zero with long-press of alarm button on page with 24h setting. --- movement/movement.h | 3 ++- .../clock/repetition_minute_face.c | 7 ++++++- .../clock/simple_clock_bin_led_face.c | 7 ++++++- .../watch_faces/clock/simple_clock_face.c | 7 ++++++- .../watch_faces/clock/weeknumber_clock_face.c | 7 ++++++- .../watch_faces/clock/world_clock2_face.c | 9 +++++++-- movement/watch_faces/clock/world_clock_face.c | 7 ++++++- .../watch_faces/complication/activity_face.c | 11 ++++++++--- .../watch_faces/complication/alarm_face.c | 7 +++++++ .../complication/planetary_hours_face.c | 7 ++++++- .../complication/planetary_time_face.c | 8 +++++++- .../complication/sunrise_sunset_face.c | 12 +++++++++++- movement/watch_faces/complication/wake_face.c | 11 ++++++++--- .../watch_faces/demo/lis2dw_logging_face.c | 11 ++++++++--- .../sensor/thermistor_logging_face.c | 19 ++++++++++++------- .../watch_faces/settings/preferences_face.c | 14 ++++++++++++-- movement/watch_faces/settings/set_time_face.c | 8 +++++++- .../settings/set_time_hackwatch_face.c | 8 +++++++- 18 files changed, 132 insertions(+), 31 deletions(-) diff --git a/movement/movement.h b/movement/movement.h index 5f30dfb..c9e955c 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -60,9 +60,10 @@ typedef union { // time-oriented complication like a sunrise/sunset timer, and a simple locale preference could tell an // altimeter to display feet or meters as easily as it tells a thermometer to display degrees in F or C. bool clock_mode_24h : 1; // indicates whether clock should use 12 or 24 hour mode. + bool clock_24h_leading_zero : 1; // indicates whether clock should leading zero to indicate 24 hour mode. bool use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial. bool alarm_enabled : 1; // indicates whether there is at least one alarm enabled. - uint8_t reserved : 6; // room for more preferences if needed. + uint8_t reserved : 5; // room for more preferences if needed. } bit; uint32_t reg; } movement_settings_t; diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c index fc78b2d..e992cdf 100644 --- a/movement/watch_faces/clock/repetition_minute_face.c +++ b/movement/watch_faces/clock/repetition_minute_face.c @@ -68,7 +68,7 @@ void repetition_minute_face_activate(movement_settings_t *settings, void *contex if (watch_tick_animation_is_running()) watch_stop_tick_animation(); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -112,6 +112,7 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se // ...and set the LAP indicator if low. if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + bool set_leading_zero = false; if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); @@ -132,6 +133,8 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se } date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -142,6 +145,8 @@ bool repetition_minute_face_loop(movement_event_t event, movement_settings_t *se } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); // handle alarm indicator if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); break; diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.c b/movement/watch_faces/clock/simple_clock_bin_led_face.c index cf39c18..371f1e1 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.c +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.c @@ -60,7 +60,7 @@ void simple_clock_bin_led_face_activate(movement_settings_t *settings, void *con if (watch_tick_animation_is_running()) watch_stop_tick_animation(); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -138,6 +138,7 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t // ...and set the LAP indicator if low. if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + bool set_leading_zero = false; if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); @@ -158,6 +159,8 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t } date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -168,6 +171,8 @@ bool simple_clock_bin_led_face_loop(movement_event_t event, movement_settings_t } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); // handle alarm indicator if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); } diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index ac9a97b..6f67c36 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -51,7 +51,7 @@ void simple_clock_face_activate(movement_settings_t *settings, void *context) { if (watch_tick_animation_is_running()) watch_stop_tick_animation(); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -95,6 +95,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting // ...and set the LAP indicator if low. if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + bool set_leading_zero = false; if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. watch_display_character_lp_seconds('0' + date_time.unit.second / 10, 8); @@ -115,6 +116,8 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting } date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -125,6 +128,8 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); // handle alarm indicator if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); break; diff --git a/movement/watch_faces/clock/weeknumber_clock_face.c b/movement/watch_faces/clock/weeknumber_clock_face.c index 81df584..ed694e6 100644 --- a/movement/watch_faces/clock/weeknumber_clock_face.c +++ b/movement/watch_faces/clock/weeknumber_clock_face.c @@ -50,7 +50,7 @@ void weeknumber_clock_face_activate(movement_settings_t *settings, void *context if (watch_tick_animation_is_running()) watch_stop_tick_animation(); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -94,6 +94,7 @@ bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *set // ...and set the LAP indicator if low. if (state->battery_low) watch_set_indicator(WATCH_INDICATOR_LAP); + bool set_leading_zero = false; if ((date_time.reg >> 12) == (previous_date_time >> 12) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before minutes is the same. pos = 6; @@ -109,6 +110,8 @@ bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *set } date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -119,6 +122,8 @@ bool weeknumber_clock_face_loop(movement_event_t event, movement_settings_t *set } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); // handle alarm indicator if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); break; diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 0077f63..0d4acd0 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -247,7 +247,7 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, if (refresh_face) { watch_clear_indicator(WATCH_INDICATOR_SIGNAL); watch_set_colon(); - if (settings->bit.clock_mode_24h) + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); state->previous_date_time = REFRESH_TIME; @@ -261,6 +261,7 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; + bool set_leading_zero = false; if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { /* Everything before seconds is the same, don't waste cycles setting those segments. */ pos = 8; @@ -281,7 +282,9 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; - } + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; + } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -303,6 +306,8 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); break; case EVENT_ALARM_BUTTON_UP: state->current_zone = find_selected_zone(state, FORWARD); diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index b12d9cd..6b731e8 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -60,7 +60,7 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se watch_date_time date_time; switch (event.event_type) { case EVENT_ACTIVATE: - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); watch_set_colon(); state->previous_date_time = 0xFFFFFFFF; // fall through @@ -72,6 +72,7 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; + bool set_leading_zero = false; if ((date_time.reg >> 6) == (previous_date_time >> 6) && event.event_type != EVENT_LOW_ENERGY_UPDATE) { // everything before seconds is the same, don't waste cycles setting those segments. pos = 8; @@ -91,6 +92,8 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se } date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + set_leading_zero = true; } pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { @@ -112,6 +115,8 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se } } watch_display_string(buf, pos); + if (set_leading_zero) + watch_display_string("0", 4); break; case EVENT_ALARM_LONG_PRESS: movement_request_tick_frequency(4); diff --git a/movement/watch_faces/complication/activity_face.c b/movement/watch_faces/complication/activity_face.c index f92984c..b26fbeb 100644 --- a/movement/watch_faces/complication/activity_face.c +++ b/movement/watch_faces/complication/activity_face.c @@ -293,6 +293,7 @@ static void _activity_update_logging_screen(movement_settings_t *settings, activ } // Briefly, show time without seconds else { + bool set_leading_zero = false; watch_clear_indicator(WATCH_INDICATOR_LAP); watch_date_time now = watch_rtc_get_date_time(); uint8_t hour = now.unit.hour; @@ -304,14 +305,18 @@ static void _activity_update_logging_screen(movement_settings_t *settings, activ watch_set_indicator(WATCH_INDICATOR_PM); hour %= 12; if (hour == 0) hour = 12; - } - else { - watch_set_indicator(WATCH_INDICATOR_24H); + } else { watch_clear_indicator(WATCH_INDICATOR_PM); + if (!settings->bit.clock_24h_leading_zero) + watch_set_indicator(WATCH_INDICATOR_24H); + else if (hour < 10) + set_leading_zero = true; } sprintf(activity_buf, "%2d%02d ", hour, now.unit.minute); watch_set_colon(); watch_display_string(activity_buf, 4); + if (set_leading_zero) + watch_display_string("0", 4); } } diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index 3b7d1e3..6817222 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -99,6 +99,7 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state i = state->alarm[state->alarm_idx].day + 1; } //handle am/pm for hour display + bool set_leading_zero = false; uint8_t h = state->alarm[state->alarm_idx].hour; if (!settings->bit.clock_mode_24h) { if (h >= 12) { @@ -108,6 +109,10 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state watch_clear_indicator(WATCH_INDICATOR_PM); } if (h == 0) h = 12; + } else if (!settings->bit.clock_24h_leading_zero) { + watch_set_indicator(WATCH_INDICATOR_24H); + } else if (h < 10) { + set_leading_zero = true; } sprintf(buf, "%c%c%2d%2d%02d ", _dow_strings[i][0], _dow_strings[i][1], @@ -119,6 +124,8 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state buf[_blink_idx[state->setting_state]] = buf[_blink_idx2[state->setting_state]] = ' '; } watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); if (state->is_setting) { // draw pitch level indicator diff --git a/movement/watch_faces/complication/planetary_hours_face.c b/movement/watch_faces/complication/planetary_hours_face.c index acded91..e13466b 100644 --- a/movement/watch_faces/complication/planetary_hours_face.c +++ b/movement/watch_faces/complication/planetary_hours_face.c @@ -228,6 +228,7 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat uint8_t weekday, planet, planetary_hour; uint32_t current_hour_epoch; watch_date_time scratch_time; + bool set_leading_zero = false; // check if we have a location. If not, display error if ( state->no_location ) { @@ -253,7 +254,7 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat return; } - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // roll over hour iterator if ( state->hour < 0 ) state->hour = 23; @@ -313,6 +314,8 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat } scratch_time.unit.hour %= 12; if (scratch_time.unit.hour == 0) scratch_time.unit.hour = 12; + } else if (settings->bit.clock_24h_leading_zero && scratch_time.unit.hour < 10) { + set_leading_zero = true; } // planetary ruler of the hour @@ -328,6 +331,8 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat watch_set_colon(); watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); if ( state->ruler == 2 ) _planetary_icon(planet); } diff --git a/movement/watch_faces/complication/planetary_time_face.c b/movement/watch_faces/complication/planetary_time_face.c index 56a18cf..7e7ac6c 100644 --- a/movement/watch_faces/complication/planetary_time_face.c +++ b/movement/watch_faces/complication/planetary_time_face.c @@ -206,6 +206,7 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting double night_hour_count = 0.0; uint8_t weekday, planet, planetary_hour; double hour_duration, current_hour, current_minute, current_second; + bool set_leading_zero = false; watch_set_colon(); @@ -218,7 +219,7 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting return; } - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); // PM for night hours, otherwise the night hours are counted from 13 if ( state->night ) { @@ -246,6 +247,9 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting state->scratch.unit.minute = floor(current_minute); state->scratch.unit.second = (uint8_t)floor(current_second) % 60; + if (settings->bit.clock_mode_24h && settings->bit.clock_24h_leading_zero && state->scratch.unit.hour < 10) + set_leading_zero = true; + // what weekday is it (0 - 6) weekday = watch_utility_get_iso8601_weekday_number(state->scratch.unit.year, state->scratch.unit.month, state->scratch.unit.day) - 1; @@ -263,6 +267,8 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting else sprintf(buf, "%s h%2d%02d%02d", ruler, state->scratch.unit.hour, state->scratch.unit.minute, state->scratch.unit.second); watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); if ( state->ruler == 2 ) _planetary_icon(planet); diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 82de9c6..65dbc55 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -85,7 +85,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s } watch_set_colon(); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); rise += hours_from_utc; set += hours_from_utc; @@ -105,12 +105,17 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s if (date_time.reg < scratch_time.reg || show_next_match) { if (state->rise_index == 0 || show_next_match) { + bool set_leading_zero = false; if (!settings->bit.clock_mode_24h) { if (watch_utility_convert_to_12_hour(&scratch_time)) watch_set_indicator(WATCH_INDICATOR_PM); else watch_clear_indicator(WATCH_INDICATOR_PM); + } else if (settings->bit.clock_24h_leading_zero && scratch_time.unit.hour < 10) { + set_leading_zero = true; } sprintf(buf, "rI%2d%2d%02d ", scratch_time.unit.day, scratch_time.unit.hour, scratch_time.unit.minute); watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); return; } else { show_next_match = true; @@ -132,12 +137,17 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s if (date_time.reg < scratch_time.reg || show_next_match) { if (state->rise_index == 0 || show_next_match) { + bool set_leading_zero = false; if (!settings->bit.clock_mode_24h) { if (watch_utility_convert_to_12_hour(&scratch_time)) watch_set_indicator(WATCH_INDICATOR_PM); else watch_clear_indicator(WATCH_INDICATOR_PM); + } else if (settings->bit.clock_24h_leading_zero && scratch_time.unit.hour < 10) { + set_leading_zero = true; } sprintf(buf, "SE%2d%2d%02d ", scratch_time.unit.day, scratch_time.unit.hour, scratch_time.unit.minute); watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); return; } else { show_next_match = true; diff --git a/movement/watch_faces/complication/wake_face.c b/movement/watch_faces/complication/wake_face.c index 5c5f86b..99a7f94 100644 --- a/movement/watch_faces/complication/wake_face.c +++ b/movement/watch_faces/complication/wake_face.c @@ -50,12 +50,15 @@ void _wake_face_update_display(movement_settings_t *settings, wake_face_state_t uint8_t hour = state->hour; watch_clear_display(); - if ( settings->bit.clock_mode_24h ) - watch_set_indicator(WATCH_INDICATOR_24H); - else { + bool set_leading_zero = false; + if ( !settings->bit.clock_mode_24h ) { if ( hour >= 12 ) watch_set_indicator(WATCH_INDICATOR_PM); hour = hour % 12 ? hour % 12 : 12; + } else if ( !settings->bit.clock_24h_leading_zero ) { + watch_set_indicator(WATCH_INDICATOR_24H); + } else if ( hour < 10 ) { + set_leading_zero = true; } if ( state->mode ) @@ -66,6 +69,8 @@ void _wake_face_update_display(movement_settings_t *settings, wake_face_state_t watch_set_colon(); watch_display_string(lcdbuf, 0); + if ( set_leading_zero ) + watch_display_string("0", 4); } // diff --git a/movement/watch_faces/demo/lis2dw_logging_face.c b/movement/watch_faces/demo/lis2dw_logging_face.c index 0a957bb..cbae54b 100644 --- a/movement/watch_faces/demo/lis2dw_logging_face.c +++ b/movement/watch_faces/demo/lis2dw_logging_face.c @@ -41,6 +41,7 @@ static void _lis2dw_logging_face_update_display(movement_settings_t *settings, l char time_indication_character; int8_t pos; watch_date_time date_time; + bool set_leading_zero = false; if (logger_state->log_ticks) { pos = (logger_state->data_points - 1 - logger_state->display_index) % LIS2DW_LOGGING_NUM_DATA_POINTS; @@ -50,12 +51,14 @@ static void _lis2dw_logging_face_update_display(movement_settings_t *settings, l } else { date_time = logger_state->data[pos].timestamp; watch_set_colon(); - if (settings->bit.clock_mode_24h) { - watch_set_indicator(WATCH_INDICATOR_24H); - } else { + if (!settings->bit.clock_mode_24h) { if (date_time.unit.hour > 11) watch_set_indicator(WATCH_INDICATOR_PM); date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (!settings->bit.clock_24h_leading_zero) { + watch_set_indicator(WATCH_INDICATOR_24H); + } else if (date_time.unit.hour < 10) { + set_leading_zero = true; } switch (logger_state->axis_index) { case 0: @@ -89,6 +92,8 @@ static void _lis2dw_logging_face_update_display(movement_settings_t *settings, l logger_state->interrupts[2]); } watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); } static void _lis2dw_logging_face_log_data(lis2dw_logger_state_t *logger_state) { diff --git a/movement/watch_faces/sensor/thermistor_logging_face.c b/movement/watch_faces/sensor/thermistor_logging_face.c index 64f605e..18625cc 100644 --- a/movement/watch_faces/sensor/thermistor_logging_face.c +++ b/movement/watch_faces/sensor/thermistor_logging_face.c @@ -40,9 +40,10 @@ static void _thermistor_logging_face_log_data(thermistor_logger_state_t *logger_ thermistor_driver_disable(); } -static void _thermistor_logging_face_update_display(thermistor_logger_state_t *logger_state, bool in_fahrenheit, bool clock_mode_24h) { +static void _thermistor_logging_face_update_display(thermistor_logger_state_t *logger_state, bool in_fahrenheit, bool clock_mode_24h, bool clock_24h_leading_zero) { int8_t pos = (logger_state->data_points - 1 - logger_state->display_index) % THERMISTOR_LOGGING_NUM_DATA_POINTS; char buf[14]; + bool set_leading_zero = false; watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); @@ -53,12 +54,14 @@ static void _thermistor_logging_face_update_display(thermistor_logger_state_t *l } else if (logger_state->ts_ticks) { watch_date_time date_time = logger_state->data[pos].timestamp; watch_set_colon(); - if (clock_mode_24h) { - watch_set_indicator(WATCH_INDICATOR_24H); - } else { + if (!clock_mode_24h) { if (date_time.unit.hour > 11) watch_set_indicator(WATCH_INDICATOR_PM); date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; + } else if (!clock_24h_leading_zero) { + watch_set_indicator(WATCH_INDICATOR_24H); + } else if (date_time.unit.hour < 10) { + set_leading_zero = true; } sprintf(buf, "AT%2d%2d%02d%02d", date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); } else { @@ -70,6 +73,8 @@ static void _thermistor_logging_face_update_display(thermistor_logger_state_t *l } watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); } void thermistor_logging_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { @@ -100,18 +105,18 @@ bool thermistor_logging_face_loop(movement_event_t event, movement_settings_t *s break; case EVENT_LIGHT_BUTTON_DOWN: logger_state->ts_ticks = 2; - _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h); + _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h, settings->bit.clock_24h_leading_zero); break; case EVENT_ALARM_BUTTON_DOWN: logger_state->display_index = (logger_state->display_index + 1) % THERMISTOR_LOGGING_NUM_DATA_POINTS; logger_state->ts_ticks = 0; // fall through case EVENT_ACTIVATE: - _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h); + _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h, settings->bit.clock_24h_leading_zero); break; case EVENT_TICK: if (logger_state->ts_ticks && --logger_state->ts_ticks == 0) { - _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h); + _thermistor_logging_face_update_display(logger_state, settings->bit.use_imperial_units, settings->bit.clock_mode_24h, settings->bit.clock_24h_leading_zero); } break; case EVENT_BACKGROUND_TASK: diff --git a/movement/watch_faces/settings/preferences_face.c b/movement/watch_faces/settings/preferences_face.c index b0e328b..1cbffc1 100644 --- a/movement/watch_faces/settings/preferences_face.c +++ b/movement/watch_faces/settings/preferences_face.c @@ -93,6 +93,14 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings break; } break; + case EVENT_ALARM_LONG_PRESS: + switch (current_page) { + case 0: + if (settings->bit.clock_mode_24h) + settings->bit.clock_24h_leading_zero = !(settings->bit.clock_24h_leading_zero); + break; + } + break; case EVENT_TIMEOUT: movement_move_to_face(0); break; @@ -107,8 +115,10 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings char buf[8]; switch (current_page) { case 0: - if (settings->bit.clock_mode_24h) watch_display_string("24h", 4); - else watch_display_string("12h", 4); + if (settings->bit.clock_mode_24h) { + if (settings->bit.clock_24h_leading_zero) watch_display_string("024h", 4); + else watch_display_string("24h", 4); + } else watch_display_string("12h", 4); break; case 1: if (settings->bit.button_should_sound) watch_display_string("y", 9); diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 84a4d18..9308071 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -131,10 +131,14 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v } char buf[11]; + bool set_leading_zero = false; if (current_page < 3) { watch_set_colon(); if (settings->bit.clock_mode_24h) { - watch_set_indicator(WATCH_INDICATOR_24H); + if (!settings->bit.clock_24h_leading_zero) + watch_set_indicator(WATCH_INDICATOR_24H); + else if (date_time.unit.hour < 10) + set_leading_zero = true; sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.hour, date_time.unit.minute, date_time.unit.second); } else { sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], (date_time.unit.hour % 12) ? (date_time.unit.hour % 12) : 12, date_time.unit.minute, date_time.unit.second); @@ -175,6 +179,8 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v } watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); return true; } diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index c65e2ff..863dcc2 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -209,10 +209,14 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } char buf[11]; + bool set_leading_zero = false; if (current_page < 3) { watch_set_colon(); if (settings->bit.clock_mode_24h) { - watch_set_indicator(WATCH_INDICATOR_24H); + if (!settings->bit.clock_24h_leading_zero) + watch_set_indicator(WATCH_INDICATOR_24H); + else if (date_time_settings.unit.hour < 10) + set_leading_zero = true; sprintf(buf, "%s %2d%02d%02d", set_time_hackwatch_face_titles[current_page], @@ -278,6 +282,8 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } watch_display_string(buf, 0); + if (set_leading_zero) + watch_display_string("0", 4); return true; } From 7eb725a5f60d4a64058ca19ed655e912e58d14b7 Mon Sep 17 00:00:00 2001 From: PrimmR Date: Fri, 24 Nov 2023 18:58:07 +0000 Subject: [PATCH 014/220] Periodic Table Face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/periodic_face.c | 357 ++++++++++++++++++ .../watch_faces/complication/periodic_face.h | 61 +++ 4 files changed, 420 insertions(+) create mode 100644 movement/watch_faces/complication/periodic_face.c create mode 100644 movement/watch_faces/complication/periodic_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index c294b06..2e623e6 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -121,6 +121,7 @@ SRCS += \ ../watch_faces/complication/couch_to_5k_face.c \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ + ../watch_faces/complication/periodic_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index cb58612..23c5c35 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -98,6 +98,7 @@ #include "couch_to_5k_face.h" #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" +#include "periodic_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c new file mode 100644 index 0000000..6746c26 --- /dev/null +++ b/movement/watch_faces/complication/periodic_face.c @@ -0,0 +1,357 @@ +/* + * MIT License + * + * Copyright (c) 2023 PrimmR + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "periodic_face.h" + +void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) +{ + (void)settings; + (void)watch_face_index; + if (*context_ptr == NULL) + { + *context_ptr = malloc(sizeof(periodic_state_t)); + memset(*context_ptr, 0, sizeof(periodic_state_t)); + } +} + +void periodic_face_activate(movement_settings_t *settings, void *context) +{ + (void)settings; + periodic_state_t *state = (periodic_state_t *)context; + + state->atomic_num = 1; + state->mode = 0; + state->selection_index = 0; + + movement_request_tick_frequency(2); +} + +typedef struct +{ + char name[3]; + uint16_t atomic_mass; + char group[3]; +} element; + +// Comments on the table denote symbols that cannot be displayed +#define MAX_ELEMENT 118 +const element table[MAX_ELEMENT] = { + {"H ", 1, " "}, + {"He", 4, " 0"}, + {"Li", 7, " 1"}, + {"Be", 9, " 2"}, + {"B ", 11, " 3"}, + {"C ", 12, " 4"}, + {"N ", 14, " 5"}, + {"O ", 16, " 6"}, + {"F ", 19, " 7"}, + {"Ne", 20, " 0"}, + {"Na", 23, " 1"}, + {"Mg", 24, " 2"}, // + {"Al", 27, " 3"}, + {"Si", 28, " 4"}, + {"P ", 31, " 5"}, + {"S ", 32, " 6"}, + {"Cl", 355, " 7"}, + {"Ar", 40, " 0"}, + {"K ", 39, " 1"}, + {"Ca", 40, " 2"}, + {"Sc", 45, " T"}, + {"Ti", 48, " T"}, + {" W", 51, " T"}, // "V" + {"Cr", 52, " T"}, + {"Mn", 55, " T"}, + {"Fe", 56, " T"}, + {"Co", 59, " T"}, + {"Ni", 59, " T"}, + {"Cu", 635, " T"}, + {"Zn", 65, " T"}, + {"Ga", 70, " 3"}, + {"Ge", 73, " 4"}, + {"As", 75, " 5"}, // + {"Se", 79, " 6"}, + {"Br", 80, " 7"}, + {"Kr", 84, " 0"}, + {"Rb", 85, " 1"}, + {"Sr", 88, " 2"}, + {"Y ", 89, " T"}, + {"Zr", 91, " T"}, + {"Nb", 93, " T"}, + {"Mo", 96, " T"}, + {"Tc", 97, " T"}, + {"Ru", 101, " T"}, + {"Rh", 103, " T"}, + {"Pd", 106, " T"}, + {"Ag", 108, " T"}, // + {"Cd", 112, " T"}, + {"In", 115, " 3"}, + {"Sn", 119, " 4"}, + {"Sb", 122, " 5"}, + {"Te", 128, " 6"}, + {"I ", 127, " 7"}, + {"Xe", 131, " 0"}, + {"CS", 133, " 1"}, + {"Ba", 137, " 2"}, + {"La", 139, "1a"}, // La + {"Ce", 140, "1a"}, + {"Pr", 141, "1a"}, + {"Nd", 144, "1a"}, + {"Pm", 145, "1a"}, + {"Sm", 150, "1a"}, + {"Eu", 152, "1a"}, + {"Gd", 157, "1a"}, + {"Tb", 159, "1a"}, + {"Dy", 163, "1a"}, // .5 Rounded up due to space constraints + {"Ho", 165, "1a"}, + {"Er", 167, "1a"}, + {"Tm", 169, "1a"}, + {"Yb", 173, "1a"}, + {"Lu", 175, "1a"}, + {"Hf", 179, " T"}, + {"Ta", 181, " T"}, + {"W ", 184, " T"}, + {"Re", 186, " T"}, + {"OS", 190, " T"}, + {"Ir", 192, " T"}, + {"Pt", 195, " T"}, + {"Au", 197, " T"}, + {"Hg", 201, " T"}, // + {"Tl", 204, " 3"}, + {"Pb", 207, " 4"}, + {"Bi", 209, " 5"}, + {"Po", 209, " 6"}, + {"At", 210, " 7"}, + {"Rn", 222, " 0"}, + {"Fr", 223, " 1"}, + {"Ra", 226, " 2"}, + {"Ac", 227, "Ac"}, + {"Th", 232, "Ac"}, + {"Pa", 231, "Ac"}, + {"U ", 238, "Ac"}, + {"Np", 237, "Ac"}, // + {"Pu", 244, "Ac"}, + {"Am", 243, "Ac"}, + {"Cm", 247, "Ac"}, + {"Bk", 247, "Ac"}, // + {"Cf", 251, "Ac"}, + {"Es", 252, "Ac"}, // + {"Fm", 257, "Ac"}, + {"Md", 258, "Ac"}, + {"No", 259, "Ac"}, + {"Lr", 262, "Ac"}, + {"Rf", 267, " T"}, + {"Db", 262, " T"}, + {"Sg", 269, " T"}, // + {"Bh", 264, " T"}, + {"Hs", 269, " T"}, + {"Mt", 278, " T"}, + {"Ds", 281, " T"}, // + {"Rg", 282, " T"}, // + {"Cn", 285, " T"}, + {"Nh", 286, " 3"}, + {"Fl", 289, " 4"}, + {"Mc", 289, " 5"}, + {"LW", 293, " 6"}, // Lv + {"Ts", 294, " 7"}, // + {"Og", 294, " 0"}, // +}; + +// Warning light for symbols that can't be displayed +static void _warning(periodic_state_t *state) +{ + char second_char = table[state->atomic_num - 1].name[1]; + if (second_char == 'p' || second_char == 'g' || second_char == 'y' || second_char == 's') + { + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else + { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } +} + +// Regular mode display +static void _periodic_face_update_lcd(periodic_state_t *state) +{ + // Colon as a decimal for Cl & Cu + if (state->atomic_num == 17 || state->atomic_num == 29) + { + watch_set_colon(); + } + else + { + watch_clear_colon(); + } + + _warning(state); + + char buf[11]; + sprintf(buf, "%s%s%-3d%3d", table[state->atomic_num - 1].name, table[state->atomic_num - 1].group, table[state->atomic_num - 1].atomic_mass, state->atomic_num); + watch_display_string(buf, 0); +} + +// Selection mode logic +static void _periodic_face_selection_increment(periodic_state_t *state) +{ + uint8_t digit0 = (state->atomic_num / 100) % 10; + uint8_t digit1 = (state->atomic_num / 10) % 10; + uint8_t digit2 = (state->atomic_num) % 10; + + // Increment the selected digit by 1 + switch (state->selection_index) + { + case 0: + digit0 ^= 1; + break; + case 1: + if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10) + digit1 = 0; + else + digit1 = (digit1 + 1) % 10; + break; + case 2: + if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10 && digit2 == MAX_ELEMENT % 10) + digit2 = 0; + else + digit2 = (digit2 + 1) % 10; + break; + } + + // Prevent 000 + if (digit0 == 0 && digit1 == 0 && digit2 == 0) { + digit2 = 1; + } + + // Prevent Overflow + if (digit0 == (MAX_ELEMENT / 100) % 10 && digit1 > (MAX_ELEMENT / 10) % 10) + { + digit2 = MAX_ELEMENT % 10; + digit1 = (MAX_ELEMENT / 10) % 10; + } + + state->atomic_num = digit0 * 100 + digit1 * 10 + digit2; +} + +// Selection mode display +static void _periodic_face_selection(periodic_state_t *state, uint8_t subsec) +{ + uint8_t digit0 = (state->atomic_num / 100) % 10; + uint8_t digit1 = (state->atomic_num / 10) % 10; + uint8_t digit2 = (state->atomic_num) % 10; + + watch_display_string(" ", 0); + + char buf[2] = {'\0'}; + + buf[0] = (state->selection_index == 0 && subsec == 0) ? ' ' : digit0 + '0'; + watch_display_string(buf, 5); + + buf[0] = (state->selection_index == 1 && subsec == 0) ? ' ' : digit1 + '0'; + watch_display_string(buf, 6); + + buf[0] = (state->selection_index == 2 && subsec == 0) ? ' ' : digit2 + '0'; + watch_display_string(buf, 7); + + char buf2[3]; + sprintf(buf2, "%s", table[state->atomic_num - 1].name); + watch_display_string(buf2, 0); + + _warning(state); +} + +bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, void *context) +{ + periodic_state_t *state = (periodic_state_t *)context; + + switch (event.event_type) + { + case EVENT_ACTIVATE: + _periodic_face_update_lcd(state); + break; + case EVENT_TICK: + if (state->mode != 0) + { + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_LIGHT_BUTTON_UP: + // Only light LED when in regular mode + if (state->mode != MODE_VIEW) + { + state->selection_index = (state->selection_index + 1) % 3; + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + if (state->mode != MODE_SELECT) + movement_illuminate_led(); + break; + case EVENT_ALARM_BUTTON_UP: + if (state->mode == MODE_VIEW) + { + state->atomic_num = (state->atomic_num % MAX_ELEMENT) + 1; // Wraps back to 1 + _periodic_face_update_lcd(state); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); + } + else + { + _periodic_face_selection_increment(state); + _periodic_face_selection(state, event.subsecond % 2); + } + break; + case EVENT_ALARM_LONG_PRESS: + // Toggle between selection mode and regular + if (state->mode == MODE_VIEW) + { + state->mode = MODE_SELECT; + _periodic_face_selection(state, event.subsecond % 2); + } + else + { + state->mode = MODE_VIEW; + _periodic_face_update_lcd(state); + } + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C8, 50); + + break; + case EVENT_TIMEOUT: + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void periodic_face_resign(movement_settings_t *settings, void *context) +{ + (void)settings; + (void)context; + + // handle any cleanup before your watch face goes off-screen. +} diff --git a/movement/watch_faces/complication/periodic_face.h b/movement/watch_faces/complication/periodic_face.h new file mode 100644 index 0000000..56476ca --- /dev/null +++ b/movement/watch_faces/complication/periodic_face.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2023 PrimmR + * + * 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 PERIODIC_FACE_H_ +#define PERIODIC_FACE_H_ + +#include "movement.h" + +/* + * Periodic Table Face + * + * Elements can be viewed sequentially with a short press of the alarm button, + * or the atomic number can be input directly after holding down the alarm button. + * + */ + +#define MODE_VIEW 0 +#define MODE_SELECT 1 + +typedef struct { + uint8_t atomic_num; + uint8_t mode; + uint8_t selection_index; +} periodic_state_t; + +void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void periodic_face_activate(movement_settings_t *settings, void *context); +bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void periodic_face_resign(movement_settings_t *settings, void *context); + +#define periodic_face ((const watch_face_t){ \ + periodic_face_setup, \ + periodic_face_activate, \ + periodic_face_loop, \ + periodic_face_resign, \ + NULL, \ +}) + +#endif // PERIODIC_FACE_H_ + From 0e70adf4d4430a106a34b90c707630c6c6f1a519 Mon Sep 17 00:00:00 2001 From: Wesley Black Date: Wed, 20 Mar 2024 17:53:45 -0300 Subject: [PATCH 015/220] Update movement_faces.h --- movement/movement_faces.h | 1 + 1 file changed, 1 insertion(+) diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..76bf023 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "beeps_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ From 69f25f10165b7d0521f6605991b5d94a49db7332 Mon Sep 17 00:00:00 2001 From: Wesley Black Date: Wed, 20 Mar 2024 17:54:23 -0300 Subject: [PATCH 016/220] Add files via upload --- movement/watch_faces/demo/beeps_face.c | 249 +++++++++++++++++++++++++ movement/watch_faces/demo/beeps_face.h | 61 ++++++ 2 files changed, 310 insertions(+) create mode 100644 movement/watch_faces/demo/beeps_face.c create mode 100644 movement/watch_faces/demo/beeps_face.h diff --git a/movement/watch_faces/demo/beeps_face.c b/movement/watch_faces/demo/beeps_face.c new file mode 100644 index 0000000..07df627 --- /dev/null +++ b/movement/watch_faces/demo/beeps_face.c @@ -0,0 +1,249 @@ +/* + * MIT License + * + * Copyright (c) 2024 Wesley + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "beeps_face.h" + +void beeps_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(beeps_state_t)); + memset(*context_ptr, 0, sizeof(beeps_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } +} + +void beeps_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + beeps_state_t *state = (beeps_state_t *)context; +} + +static void _beep_face_update_lcd(beeps_state_t *state) { + char buf[11]; + const char buzzernote[][7] = {" 5500", " 5827", " 6174"," 6541"," 6930"," 7342"," 7778"," 8241"," 8731"," 9250"," 9800"," 10383"," 11000"," 11654"," 12347"," 13081"," 13859"," 14683"," 15556"," 16481"," 17461"," 18500"," 19600"," 20765"," 22000"," 23308"," 24694"," 26163"," 27718"," 29366"," 31113"," 32963"," 34923"," 36999"," 39200"," 41530"," 44000"," 46616"," 49388"," 52325"," 55437"," 58733"," 62225"," 65925"," 69846"," 73999"," 78399"," 83061"," 88000"," 93233"," 98777"," 104650"," 110873"," 117466"," 124451"," 131851"," 139691"," 147998"," 156798"," 166122"," 176000"," 186466"," 197553"," 209300"," 221746"," 234932"," 248902"," 263702"," 279383"," 295996"," 313596"," 332244"," 352000"," 372931"," 395107"," 418601"," 443492"," 469863"," 497803"," 527404"," 558765"," 591991"," 627193"," 664488"," 704000"," 745862"," 790213"}; + sprintf(buf, "HZ %s", buzzernote[state->frequency]); + watch_display_string(buf, 0); +} + +bool beeps_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + beeps_state_t *state = (beeps_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + _beep_face_update_lcd(state); + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->frequency = (state->frequency + 1) % 87; + _beep_face_update_lcd(state); + break; + case EVENT_ALARM_BUTTON_DOWN: + if (state->frequency == 0) { + watch_buzzer_play_note(BUZZER_NOTE_A1, 500); + } else if (state->frequency == 1) { + watch_buzzer_play_note(BUZZER_NOTE_A1SHARP_B1FLAT, 500); + } else if (state->frequency == 2) { + watch_buzzer_play_note(BUZZER_NOTE_B1, 500); + } else if (state->frequency == 3) { + watch_buzzer_play_note(BUZZER_NOTE_C2, 500); + } else if (state->frequency == 4) { + watch_buzzer_play_note(BUZZER_NOTE_C2SHARP_D2FLAT, 500); + } else if (state->frequency == 5) { + watch_buzzer_play_note(BUZZER_NOTE_D2, 500); + } else if (state->frequency == 6) { + watch_buzzer_play_note(BUZZER_NOTE_D2SHARP_E2FLAT, 500); + } else if (state->frequency == 7) { + watch_buzzer_play_note(BUZZER_NOTE_E2, 500); + } else if (state->frequency == 8) { + watch_buzzer_play_note(BUZZER_NOTE_F2, 500); + } else if (state->frequency == 9) { + watch_buzzer_play_note(BUZZER_NOTE_F2SHARP_G2FLAT, 500); + } else if (state->frequency == 10) { + watch_buzzer_play_note(BUZZER_NOTE_G2, 500); + } else if (state->frequency == 11) { + watch_buzzer_play_note(BUZZER_NOTE_G2SHARP_A2FLAT, 500); + } else if (state->frequency == 12) { + watch_buzzer_play_note(BUZZER_NOTE_A2, 500); + } else if (state->frequency == 13) { + watch_buzzer_play_note(BUZZER_NOTE_A2SHARP_B2FLAT, 500); + } else if (state->frequency == 14) { + watch_buzzer_play_note(BUZZER_NOTE_B2, 500); + } else if (state->frequency == 15) { + watch_buzzer_play_note(BUZZER_NOTE_C3, 500); + } else if (state->frequency == 16) { + watch_buzzer_play_note(BUZZER_NOTE_C3SHARP_D3FLAT, 500); + } else if (state->frequency == 17) { + watch_buzzer_play_note(BUZZER_NOTE_D3, 500); + } else if (state->frequency == 18) { + watch_buzzer_play_note(BUZZER_NOTE_D3SHARP_E3FLAT, 500); + } else if (state->frequency == 19) { + watch_buzzer_play_note(BUZZER_NOTE_E3, 500); + } else if (state->frequency == 20) { + watch_buzzer_play_note(BUZZER_NOTE_F3, 500); + } else if (state->frequency == 21) { + watch_buzzer_play_note(BUZZER_NOTE_F3SHARP_G3FLAT, 500); + } else if (state->frequency == 22) { + watch_buzzer_play_note(BUZZER_NOTE_G3, 500); + } else if (state->frequency == 23) { + watch_buzzer_play_note(BUZZER_NOTE_G3SHARP_A3FLAT, 500); + } else if (state->frequency == 24) { + watch_buzzer_play_note(BUZZER_NOTE_A3, 500); + } else if (state->frequency == 25) { + watch_buzzer_play_note(BUZZER_NOTE_A3SHARP_B3FLAT, 500); + } else if (state->frequency == 26) { + watch_buzzer_play_note(BUZZER_NOTE_B3, 500); + } else if (state->frequency == 27) { + watch_buzzer_play_note(BUZZER_NOTE_C4, 500); + } else if (state->frequency == 28) { + watch_buzzer_play_note(BUZZER_NOTE_C4SHARP_D4FLAT, 500); + } else if (state->frequency == 29) { + watch_buzzer_play_note(BUZZER_NOTE_D4, 500); + } else if (state->frequency == 30) { + watch_buzzer_play_note(BUZZER_NOTE_D4SHARP_E4FLAT, 500); + } else if (state->frequency == 31) { + watch_buzzer_play_note(BUZZER_NOTE_E4, 500); + } else if (state->frequency == 32) { + watch_buzzer_play_note(BUZZER_NOTE_F4, 500); + } else if (state->frequency == 33) { + watch_buzzer_play_note(BUZZER_NOTE_F4SHARP_G4FLAT, 500); + } else if (state->frequency == 34) { + watch_buzzer_play_note(BUZZER_NOTE_G4, 500); + } else if (state->frequency == 35) { + watch_buzzer_play_note(BUZZER_NOTE_G4SHARP_A4FLAT, 500); + } else if (state->frequency == 36) { + watch_buzzer_play_note(BUZZER_NOTE_A4, 500); + } else if (state->frequency == 37) { + watch_buzzer_play_note(BUZZER_NOTE_A4SHARP_B4FLAT, 500); + } else if (state->frequency == 38) { + watch_buzzer_play_note(BUZZER_NOTE_B4, 500); + } else if (state->frequency == 39) { + watch_buzzer_play_note(BUZZER_NOTE_C5, 500); + } else if (state->frequency == 40) { + watch_buzzer_play_note(BUZZER_NOTE_C5SHARP_D5FLAT, 500); + } else if (state->frequency == 41) { + watch_buzzer_play_note(BUZZER_NOTE_D5, 500); + } else if (state->frequency == 42) { + watch_buzzer_play_note(BUZZER_NOTE_D5SHARP_E5FLAT, 500); + } else if (state->frequency == 43) { + watch_buzzer_play_note(BUZZER_NOTE_E5, 500); + } else if (state->frequency == 44) { + watch_buzzer_play_note(BUZZER_NOTE_F5, 500); + } else if (state->frequency == 45) { + watch_buzzer_play_note(BUZZER_NOTE_F5SHARP_G5FLAT, 500); + } else if (state->frequency == 46) { + watch_buzzer_play_note(BUZZER_NOTE_G5, 500); + } else if (state->frequency == 47) { + watch_buzzer_play_note(BUZZER_NOTE_G5SHARP_A5FLAT, 500); + } else if (state->frequency == 48) { + watch_buzzer_play_note(BUZZER_NOTE_A5, 500); + } else if (state->frequency == 49) { + watch_buzzer_play_note(BUZZER_NOTE_A5SHARP_B5FLAT, 500); + } else if (state->frequency == 50) { + watch_buzzer_play_note(BUZZER_NOTE_B5, 500); + } else if (state->frequency == 51) { + watch_buzzer_play_note(BUZZER_NOTE_C6, 500); + } else if (state->frequency == 52) { + watch_buzzer_play_note(BUZZER_NOTE_C6SHARP_D6FLAT, 500); + } else if (state->frequency == 53) { + watch_buzzer_play_note(BUZZER_NOTE_D6, 500); + } else if (state->frequency == 54) { + watch_buzzer_play_note(BUZZER_NOTE_D6SHARP_E6FLAT, 500); + } else if (state->frequency == 55) { + watch_buzzer_play_note(BUZZER_NOTE_E6, 500); + } else if (state->frequency == 56) { + watch_buzzer_play_note(BUZZER_NOTE_F6, 500); + } else if (state->frequency == 57) { + watch_buzzer_play_note(BUZZER_NOTE_F6SHARP_G6FLAT, 500); + } else if (state->frequency == 58) { + watch_buzzer_play_note(BUZZER_NOTE_G6, 500); + } else if (state->frequency == 59) { + watch_buzzer_play_note(BUZZER_NOTE_G6SHARP_A6FLAT, 500); + } else if (state->frequency == 60) { + watch_buzzer_play_note(BUZZER_NOTE_A6, 500); + } else if (state->frequency == 61) { + watch_buzzer_play_note(BUZZER_NOTE_A6SHARP_B6FLAT, 500); + } else if (state->frequency == 62) { + watch_buzzer_play_note(BUZZER_NOTE_B6, 500); + } else if (state->frequency == 63) { + watch_buzzer_play_note(BUZZER_NOTE_C7, 500); + } else if (state->frequency == 64) { + watch_buzzer_play_note(BUZZER_NOTE_C7SHARP_D7FLAT, 500); + } else if (state->frequency == 65) { + watch_buzzer_play_note(BUZZER_NOTE_D7, 500); + } else if (state->frequency == 66) { + watch_buzzer_play_note(BUZZER_NOTE_D7SHARP_E7FLAT, 500); + } else if (state->frequency == 67) { + watch_buzzer_play_note(BUZZER_NOTE_E7, 500); + } else if (state->frequency == 68) { + watch_buzzer_play_note(BUZZER_NOTE_F7, 500); + } else if (state->frequency == 69) { + watch_buzzer_play_note(BUZZER_NOTE_F7SHARP_G7FLAT, 500); + } else if (state->frequency == 70) { + watch_buzzer_play_note(BUZZER_NOTE_G7, 500); + } else if (state->frequency == 71) { + watch_buzzer_play_note(BUZZER_NOTE_G7SHARP_A7FLAT, 500); + } else if (state->frequency == 72) { + watch_buzzer_play_note(BUZZER_NOTE_A7, 500); + } else if (state->frequency == 73) { + watch_buzzer_play_note(BUZZER_NOTE_A7SHARP_B7FLAT, 500); + } else if (state->frequency == 74) { + watch_buzzer_play_note(BUZZER_NOTE_B7, 500); + } else if (state->frequency == 75) { + watch_buzzer_play_note(BUZZER_NOTE_C8, 500); + } else if (state->frequency == 76) { + watch_buzzer_play_note(BUZZER_NOTE_C8SHARP_D8FLAT, 500); + } else if (state->frequency == 77) { + watch_buzzer_play_note(BUZZER_NOTE_D8, 500); + } else if (state->frequency == 78) { + watch_buzzer_play_note(BUZZER_NOTE_D8SHARP_E8FLAT, 500); + } else if (state->frequency == 79) { + watch_buzzer_play_note(BUZZER_NOTE_E8, 500); + } else if (state->frequency == 80) { + watch_buzzer_play_note(BUZZER_NOTE_F8, 500); + } else if (state->frequency == 81) { + watch_buzzer_play_note(BUZZER_NOTE_F8SHARP_G8FLAT, 500); + } else if (state->frequency == 82) { + watch_buzzer_play_note(BUZZER_NOTE_G8, 500); + } else if (state->frequency == 83) { + watch_buzzer_play_note(BUZZER_NOTE_G8SHARP_A8FLAT, 500); + } else if (state->frequency == 84) { + watch_buzzer_play_note(BUZZER_NOTE_A8, 500); + } else if (state->frequency == 85) { + watch_buzzer_play_note(BUZZER_NOTE_A8SHARP_B8FLAT, 500); + } else if (state->frequency == 86) { + watch_buzzer_play_note(BUZZER_NOTE_B8, 500); + } + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void beeps_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/demo/beeps_face.h b/movement/watch_faces/demo/beeps_face.h new file mode 100644 index 0000000..73606fd --- /dev/null +++ b/movement/watch_faces/demo/beeps_face.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2024 Wesley + * + * 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 BEEPS_FACE_H_ +#define BEEPS_FACE_H_ + +#include "movement.h" + +/* + * A simple watch face to test the different Buzzer Notes. + * + * Press the Light button to play a sound. + * Press the Alarm button to change the frequency. + * + * The watch face displays the frequency of the buzzer it will play + * this allows you to reference the watch_buzzer.h file to find the + * corresponding note. + * + * The watch_buzzer.h file is found at watch-library/shared/watch/watch_buzzer.h + */ + +typedef struct { + uint8_t frequency; +} beeps_state_t; + +void beeps_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void beeps_face_activate(movement_settings_t *settings, void *context); +bool beeps_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void beeps_face_resign(movement_settings_t *settings, void *context); + +#define beeps_face ((const watch_face_t){ \ + beeps_face_setup, \ + beeps_face_activate, \ + beeps_face_loop, \ + beeps_face_resign, \ + NULL, \ +}) + +#endif // BEEPS_FACE_H_ + From 524098b9258232964d7756d4d44f16b006d340bf Mon Sep 17 00:00:00 2001 From: madhogs Date: Mon, 18 Mar 2024 18:01:05 +0000 Subject: [PATCH 017/220] add the ability to set location and birthdate in movement config --- movement/movement.c | 34 ++++++++++++++++++++++++++++++++++ movement/movement.h | 2 ++ movement/movement_config.h | 30 +++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 0b292a1..ec14838 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -95,6 +95,31 @@ #define MOVEMENT_DEFAULT_LED_DURATION 1 #endif +// Default to no set location latitude +#ifndef MOVEMENT_DEFAULT_LATITUDE +#define MOVEMENT_DEFAULT_LATITUDE 0 +#endif + +// Default to no set location longitude +#ifndef MOVEMENT_DEFAULT_LONGITUDE +#define MOVEMENT_DEFAULT_LONGITUDE 0 +#endif + +// Default to no set birthdate year +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_YEAR +#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 +#endif + +// Default to no set birthdate month +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_MONTH +#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 +#endif + +// Default to no set birthdate day +#ifndef MOVEMENT_DEFAULT_BIRTHDATE_DAY +#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 +#endif + #if __EMSCRIPTEN__ #include #endif @@ -384,6 +409,11 @@ void app_init(void) { movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL; movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL; movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; + movement_state.location.bit.latitude = MOVEMENT_DEFAULT_LATITUDE; + movement_state.location.bit.longitude = MOVEMENT_DEFAULT_LONGITUDE; + movement_state.birthdate.bit.year = MOVEMENT_DEFAULT_BIRTHDATE_YEAR; + movement_state.birthdate.bit.month = MOVEMENT_DEFAULT_BIRTHDATE_MONTH; + movement_state.birthdate.bit.day = MOVEMENT_DEFAULT_BIRTHDATE_DAY; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; movement_state.next_available_backup_register = 4; @@ -406,10 +436,14 @@ void app_init(void) { void app_wake_from_backup(void) { movement_state.settings.reg = watch_get_backup_data(0); + movement_state.location.reg = watch_get_backup_data(1); + movement_state.birthdate.reg = watch_get_backup_data(2); } void app_setup(void) { watch_store_backup_data(movement_state.settings.reg, 0); + watch_store_backup_data(movement_state.location.reg, 1); + watch_store_backup_data(movement_state.birthdate.reg, 2); static bool is_first_launch = true; diff --git a/movement/movement.h b/movement/movement.h index 1dabfbc..3560009 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -242,6 +242,8 @@ typedef struct { typedef struct { // properties stored in BACKUP register movement_settings_t settings; + movement_location_t location; + movement_birthdate_t birthdate; // transient properties int16_t current_face_idx; diff --git a/movement/movement_config.h b/movement/movement_config.h index 10a30af..abceacf 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -76,15 +76,15 @@ const watch_face_t watch_faces[] = { /* Set the timeout before switching to low energy mode * Valid values are: * 0: Never - * 1: 1 hour - * 2: 2 hours - * 3: 6 hours - * 4: 12 hours - * 5: 1 day - * 6: 2 days + * 1: 10 mins + * 2: 1 hour + * 3: 2 hours + * 4: 6 hours + * 5: 12 hours + * 6: 1 day * 7: 7 days */ -#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1 +#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2 /* Set the led duration * Valid values are: @@ -95,4 +95,20 @@ const watch_face_t watch_faces[] = { */ #define MOVEMENT_DEFAULT_LED_DURATION 1 +/* The latitude and longitude used for the wearers location + * Set signed values in 1/100ths of a degree + */ +#define MOVEMENT_DEFAULT_LATITUDE 0 +#define MOVEMENT_DEFAULT_LONGITUDE 0 + +/* The wearers birthdate + * Valid values: + * Year: 1 - 4095 + * Month: 1 - 12 + * Day: 1 - 31 + */ +#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 +#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 +#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 + #endif // MOVEMENT_CONFIG_H_ From 2ce07f9539eaa2b20c69d078052a9b90e1ae1f67 Mon Sep 17 00:00:00 2001 From: "R. Alex Barbieri" <> Date: Sat, 27 Apr 2024 13:33:32 -0500 Subject: [PATCH 018/220] add blinking to DST toggle in settings page --- movement/watch_faces/settings/set_time_face.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 16011e3..fb0c806 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -185,6 +185,9 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v case 5: buf[8] = buf[9] = ' '; break; + case 7: + buf[9] = ' '; + break; } } From 9794f86430d8d824b30af3e7e348d9573d8cbb03 Mon Sep 17 00:00:00 2001 From: Matt Greer Date: Sun, 5 May 2024 09:47:47 -0400 Subject: [PATCH 019/220] simon_face: Simon game complication --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/simon_face.c | 247 ++++++++++++++++++ .../watch_faces/complication/simon_face.h | 97 +++++++ 4 files changed, 346 insertions(+) create mode 100644 movement/watch_faces/complication/simon_face.c create mode 100644 movement/watch_faces/complication/simon_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..09fdb33 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/simon_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..72aae1b 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "simon_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/simon_face.c b/movement/watch_faces/complication/simon_face.c new file mode 100644 index 0000000..99fc160 --- /dev/null +++ b/movement/watch_faces/complication/simon_face.c @@ -0,0 +1,247 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "simon_face.h" +#include +#include +#include + +// Emulator only: need time() to seed the random number generator +#if __EMSCRIPTEN__ +#include +#endif + +static char _simon_display_buf[12]; + +static inline uint8_t _simon_get_rand_num(uint8_t num_values) { +#if __EMSCRIPTEN__ + return rand() % num_values; +#else + return arc4random_uniform(num_values); +#endif +} + +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); + } +} + +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_string(_simon_display_buf, 0); +} + +static void _simon_reset(simon_state_t *state) { + state->playing_state = SIMON_NOT_PLAYING; + state->listen_index = 0; + state->sequence_length = 0; + _simon_not_playing_display(state); +} + + +static void _simon_display_note(SimonNote note, simon_state_t *state) { + char *ndtemplate = NULL; + + switch (note) { + case SIMON_LED_NOTE: + ndtemplate = "LI%2d "; + break; + case SIMON_ALARM_NOTE: + ndtemplate = " %2d AL"; + break; + case SIMON_MODE_NOTE: + ndtemplate = " %2dDE "; + 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) { + _simon_display_note(note, state); + switch (note) { + case SIMON_LED_NOTE: + watch_set_led_yellow(); + watch_buzzer_play_note(BUZZER_NOTE_D3, 300); + break; + case SIMON_MODE_NOTE: + watch_set_led_red(); + watch_buzzer_play_note(BUZZER_NOTE_E4, 300); + break; + case SIMON_ALARM_NOTE: + watch_set_led_green(); + watch_buzzer_play_note(BUZZER_NOTE_C3, 300); + break; + case SIMON_WRONG_NOTE: + watch_buzzer_play_note(BUZZER_NOTE_A1, 800); + break; + } + watch_set_led_off(); + + if (note != SIMON_WRONG_NOTE) { + _simon_clear_display(state); + if (!skip_rest) { + watch_buzzer_play_note(BUZZER_NOTE_REST, 200); + } + } +} + + +static void _simon_setup_next_note(simon_state_t *state) { + if (state->sequence_length > state->best_score) { + state->best_score = state->sequence_length; + } + + _simon_clear_display(state); + state->playing_state = SIMON_TEACHING; + state->sequence[state->sequence_length] = _simon_get_rand_num(3) + 1; + state->sequence_length = state->sequence_length + 1; + state->teaching_index = 0; + state->listen_index = 0; +} + +static void _simon_listen(SimonNote note, simon_state_t *state) { + if (state->sequence[state->listen_index] == note) { + _simon_play_note(note, state, true); + state->listen_index++; + + if (state->listen_index == state->sequence_length) { + state->playing_state = SIMON_READY_FOR_NEXT_NOTE; + } + } else { + _simon_play_note(SIMON_WRONG_NOTE, state, true); + _simon_reset(state); + } +} + +static void _simon_begin_listening(simon_state_t *state) { + state->playing_state = SIMON_LISTENING_BACK; + state->listen_index = 0; +} + +void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, + void **context_ptr) { + (void)settings; + (void)watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(simon_state_t)); + memset(*context_ptr, 0, sizeof(simon_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens + // only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch + // wakes from deep sleep. +#if __EMSCRIPTEN__ + // simulator only: seed the randon number generator + time_t t; + srand((unsigned)time(&t)); +#endif +} + +void simon_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool simon_face_loop(movement_event_t event, movement_settings_t *settings, + void *context) { + simon_state_t *state = (simon_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + // Show your initial UI here. + _simon_reset(state); + break; + case EVENT_TICK: + if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE) { + _simon_setup_next_note(state); + } else if (state->playing_state == SIMON_TEACHING) { + SimonNote note = state->sequence[state->teaching_index]; + // if this is the final note in the sequence, don't play the rest to let + // the player jump in faster + _simon_play_note(note, state, state->teaching_index == (state->sequence_length - 1)); + state->teaching_index++; + + if (state->teaching_index == state->sequence_length) { + _simon_begin_listening(state); + } + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_BUTTON_UP: + if (state->playing_state == SIMON_NOT_PLAYING) { + state->sequence_length = 0; + _simon_setup_next_note(state); + } else if (state->playing_state == SIMON_LISTENING_BACK) { + _simon_listen(SIMON_LED_NOTE, state); + } + break; + case EVENT_MODE_LONG_PRESS: + if (state->playing_state == SIMON_NOT_PLAYING) { + movement_move_to_face(0); + } else { + state->playing_state = SIMON_NOT_PLAYING; + _simon_reset(state); + } + break; + case EVENT_MODE_BUTTON_UP: + if (state->playing_state == SIMON_NOT_PLAYING) { + movement_move_to_next_face(); + } else if (state->playing_state == SIMON_LISTENING_BACK) { + _simon_listen(SIMON_MODE_NOTE, state); + } + break; + case EVENT_ALARM_BUTTON_UP: + if (state->playing_state == SIMON_LISTENING_BACK) { + _simon_listen(SIMON_ALARM_NOTE, state); + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void simon_face_resign(movement_settings_t *settings, void *context) { + (void)settings; + (void)context; + watch_set_led_off(); + watch_set_buzzer_off(); +} diff --git a/movement/watch_faces/complication/simon_face.h b/movement/watch_faces/complication/simon_face.h new file mode 100644 index 0000000..b59d56c --- /dev/null +++ b/movement/watch_faces/complication/simon_face.h @@ -0,0 +1,97 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SIMON_FACE_H_ +#define SIMON_FACE_H_ + +#include "movement.h" + +/* + * simon_face + * ----------- + * The classic electronic game, Simon, reduced to be played on a Sensor-Watch + * + * How to play: + * + * When first arriving at the face, it will show your best score. + * + * Press the light button to start the game. + * + * A sequence will be played, starting with length 1. The sequence can be + * made up of tones corresponding to any of the three buttons. + * + * light button: "LI" will display at the top of the screen, the LED will be yellow, and a high D will play + * mode button: "DE" will display at the left of the screen, the LED will be red, and a high E will play + * alarm button: "AL" will display on the right of the screen, the LED will be green, and a high C will play + * + * Once the sequence has finished, press the same buttons to recreate the sequence. + * + * If correct, the sequence will get one tone longer and play again. See how long of a sequence you can get. + * + * If you recreate the sequence incorrectly, a low note will play with "OH NOOOOO" displayed and the game is over. + * Press light to play again. + * + * Once playing, long press the mode button when it is your turn to exit the game early. + */ + +#define MAX_SEQUENCE 99 + +typedef enum SimonNote { + SIMON_LED_NOTE = 1, + SIMON_MODE_NOTE, + SIMON_ALARM_NOTE, + SIMON_WRONG_NOTE +} SimonNote; + +typedef enum SimonPlayingState { + SIMON_NOT_PLAYING = 0, + SIMON_TEACHING, + SIMON_LISTENING_BACK, + SIMON_READY_FOR_NEXT_NOTE +} SimonPlayingState; + +typedef struct { + uint8_t best_score; + SimonNote sequence[MAX_SEQUENCE]; + uint8_t sequence_length; + uint8_t teaching_index; + uint8_t listen_index; + SimonPlayingState playing_state; +} simon_state_t; + +void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); +void simon_face_activate(movement_settings_t *settings, void *context); +bool simon_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void simon_face_resign(movement_settings_t *settings, void *context); + +#define simon_face \ + ((const watch_face_t){ \ + simon_face_setup, \ + simon_face_activate, \ + simon_face_loop, \ + simon_face_resign, \ + NULL, \ + }) + +#endif // SIMON_FACE_H_ From fa35b8bb77f687fdd0eba09a6d8e3b49e72ab272 Mon Sep 17 00:00:00 2001 From: Ruben Nic Date: Mon, 6 May 2024 20:32:59 -0400 Subject: [PATCH 020/220] Add close enough clock face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../clock/close_enough_clock_face.c | 199 ++++++++++++++++++ .../clock/close_enough_clock_face.h | 61 ++++++ 4 files changed, 262 insertions(+) create mode 100644 movement/watch_faces/clock/close_enough_clock_face.c create mode 100644 movement/watch_faces/clock/close_enough_clock_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..690ab81 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -52,6 +52,7 @@ SRCS += \ ../shell.c \ ../shell_cmd_list.c \ ../watch_faces/clock/simple_clock_face.c \ + ../watch_faces/clock/close_enough_clock_face.c \ ../watch_faces/clock/clock_face.c \ ../watch_faces/clock/world_clock_face.c \ ../watch_faces/clock/beats_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..c6b2958 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -26,6 +26,7 @@ #define MOVEMENT_FACES_H_ #include "simple_clock_face.h" +#include "close_enough_clock_face.h" #include "clock_face.h" #include "world_clock_face.h" #include "preferences_face.h" diff --git a/movement/watch_faces/clock/close_enough_clock_face.c b/movement/watch_faces/clock/close_enough_clock_face.c new file mode 100644 index 0000000..58db37a --- /dev/null +++ b/movement/watch_faces/clock/close_enough_clock_face.c @@ -0,0 +1,199 @@ +/* + * MIT License + * + * Copyright (c) 2024 Ruben Nic + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "close_enough_clock_face.h" +#include "watch.h" +#include "watch_utility.h" +#include "watch_private_display.h" + +static const char first_words[12][3] = { + " ", // "HH OC", + " 5", // " 5 past HH", + "10", // "10 past HH", + "15", // "15 past HH", + "20", // "20 past HH", + "25", // "25 past HH", + "30", // "30 past HH", + "35", // "35 past HH", + "40", // "20 two HH+1", + "15", // "15 two HH+1", + "10", // "10 two HH+1", + " 5" // " 5 two HH+1" +}; + +static const char second_words[12][3] = { + "OC", // "HH OC", + " P", // " 5 past HH", + " P", // "10 past HH", + " P", // "15 past HH", + " P", // "20 past HH", + " P", // "25 past HH", + " P", // "30 past HH", + " P", // "35 past HH", + " 2", // "20 two HH+1", + " 2", // "15 two HH+1", + " 2", // "10 two HH+1", + " 2" // " 5 two HH+1" +}; + +static const int hour_switch_index = 8; + +static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) { + state->alarm_enabled = settings_alarm_enabled; + if (state->alarm_enabled) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } else { + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + }; +} + +void close_enough_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(close_enough_clock_state_t)); + } +} + +void close_enough_clock_face_activate(movement_settings_t *settings, void *context) { + close_enough_clock_state_t *state = (close_enough_clock_state_t *)context; + + if (watch_tick_animation_is_running()) { + watch_stop_tick_animation(); + } + + if (settings->bit.clock_mode_24h) { + watch_set_indicator(WATCH_INDICATOR_24H); + } + + // show alarm indicator if there is an active alarm + _update_alarm_indicator(settings->bit.alarm_enabled, state); + + // this ensures that none of the five_minute_periods will match, so we always rerender when the face activates + state->prev_five_minute_period = -1; +} + +bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + close_enough_clock_state_t *state = (close_enough_clock_state_t *)context; + char buf[11]; + + watch_date_time date_time; + int prev_five_minute_period; + switch (event.event_type) { + case EVENT_ACTIVATE: + case EVENT_TICK: + case EVENT_LOW_ENERGY_UPDATE: + date_time = watch_rtc_get_date_time(); + prev_five_minute_period = state->prev_five_minute_period; + + // check the battery voltage once a day... + if (date_time.unit.day != state->last_battery_check) { + state->last_battery_check = date_time.unit.day; + watch_enable_adc(); + uint16_t voltage = watch_get_vcc_voltage(); + watch_disable_adc(); + // 2.2 volts will happen when the battery has maybe 5-10% remaining? + // we can refine this later. + state->battery_low = (voltage < 2200); + } + + // ...and set the LAP indicator if low. + if (state->battery_low) { + watch_set_indicator(WATCH_INDICATOR_LAP); + } + + int five_minute_period = (date_time.unit.minute / 5) % 12; + + // same five_minute_period, skip update + if (five_minute_period == prev_five_minute_period) { + break; + } + + // move from "x mins past y" to "x mins to y+1" + if (five_minute_period >= hour_switch_index) { + date_time.unit.hour += 1; + date_time.unit.hour %= 24; + } + + if (!settings->bit.clock_mode_24h) { + // if we are in 12 hour mode, do some cleanup. + if (date_time.unit.hour < 12) { + watch_clear_indicator(WATCH_INDICATOR_PM); + } else { + watch_set_indicator(WATCH_INDICATOR_PM); + } + + date_time.unit.hour %= 12; + if (date_time.unit.hour == 0) { + date_time.unit.hour = 12; + } + } + + char first_word[3]; + char second_word[3]; + char third_word[3]; + if (five_minute_period == 0) { + sprintf(first_word, "%2d", date_time.unit.hour); + strncpy(second_word, first_words[five_minute_period], 3); + strncpy(third_word, second_words[five_minute_period], 3); + } else { + strncpy(first_word, first_words[five_minute_period], 3); + strncpy(second_word, second_words[five_minute_period], 3); + sprintf(third_word, "%2d", date_time.unit.hour); + } + + sprintf( + buf, + "%s%2d%s%s%s", + watch_utility_get_weekday(date_time), + date_time.unit.day, + first_word, + second_word, + third_word + ); + + watch_display_string(buf, 0); + state->prev_five_minute_period = five_minute_period; + + // handle alarm indicator + if (state->alarm_enabled != settings->bit.alarm_enabled) { + _update_alarm_indicator(settings->bit.alarm_enabled, state); + } + + break; + + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void close_enough_clock_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} diff --git a/movement/watch_faces/clock/close_enough_clock_face.h b/movement/watch_faces/clock/close_enough_clock_face.h new file mode 100644 index 0000000..341357b --- /dev/null +++ b/movement/watch_faces/clock/close_enough_clock_face.h @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2024 Ruben Nic + * + * 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 CLOSE_ENOUGH_CLOCK_FACE_H_ +#define CLOSE_ENOUGH_CLOCK_FACE_H_ + +/* + * CLOSE ENOUGH CLOCK FACE + * + * Displays the current time; but only in periods of 5. + * Just in the in the formats of: + * - "10 past 5" + * - "15 to 7" + * - "6 o'clock" + * + */ + +#include "movement.h" + +typedef struct { + int prev_five_minute_period; + uint8_t last_battery_check; + bool battery_low; + bool alarm_enabled; +} close_enough_clock_state_t; + +void close_enough_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void close_enough_clock_face_activate(movement_settings_t *settings, void *context); +bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void close_enough_clock_face_resign(movement_settings_t *settings, void *context); + +#define close_enough_clock_face ((const watch_face_t){ \ + close_enough_clock_face_setup, \ + close_enough_clock_face_activate, \ + close_enough_clock_face_loop, \ + close_enough_clock_face_resign, \ + NULL, \ +}) + +#endif // CLOSE_ENOUGH_CLOCK_FACE_H_ From 53f11cbd1e42f888c4de7af19075133f77346b9d Mon Sep 17 00:00:00 2001 From: Ruben Nic Date: Wed, 15 May 2024 12:30:00 -0400 Subject: [PATCH 021/220] have close enough to update less and clean up code --- .../clock/close_enough_clock_face.c | 101 ++++++++++-------- .../clock/close_enough_clock_face.h | 1 + 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/movement/watch_faces/clock/close_enough_clock_face.c b/movement/watch_faces/clock/close_enough_clock_face.c index 58db37a..19bde69 100644 --- a/movement/watch_faces/clock/close_enough_clock_face.c +++ b/movement/watch_faces/clock/close_enough_clock_face.c @@ -24,42 +24,27 @@ #include #include +#include #include "close_enough_clock_face.h" #include "watch.h" #include "watch_utility.h" -#include "watch_private_display.h" -static const char first_words[12][3] = { - " ", // "HH OC", - " 5", // " 5 past HH", - "10", // "10 past HH", - "15", // "15 past HH", - "20", // "20 past HH", - "25", // "25 past HH", - "30", // "30 past HH", - "35", // "35 past HH", - "40", // "20 two HH+1", - "15", // "15 two HH+1", - "10", // "10 two HH+1", - " 5" // " 5 two HH+1" +const char *words[12][2] = { + {" ", "OC"}, // "HH OC", + {" 5", " P"}, // " 5 past HH", + {"10", " P"}, // "10 past HH", + {"15", " P"}, // "15 past HH", + {"20", " P"}, // "20 past HH", + {"25", " P"}, // "25 past HH", + {"30", " P"}, // "30 past HH", + {"35", " P"}, // "35 past HH", + {"40", " P"}, // "40 past HH", + {"15", " 2"}, // "15 two HH+1", + {"10", " 2"}, // "10 two HH+1", + {" 5", " 2"}, // " 5 two HH+1", }; -static const char second_words[12][3] = { - "OC", // "HH OC", - " P", // " 5 past HH", - " P", // "10 past HH", - " P", // "15 past HH", - " P", // "20 past HH", - " P", // "25 past HH", - " P", // "30 past HH", - " P", // "35 past HH", - " 2", // "20 two HH+1", - " 2", // "15 two HH+1", - " 2", // "10 two HH+1", - " 2" // " 5 two HH+1" -}; - -static const int hour_switch_index = 8; +static const int hour_switch_index = 9; static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) { state->alarm_enabled = settings_alarm_enabled; @@ -95,20 +80,26 @@ void close_enough_clock_face_activate(movement_settings_t *settings, void *conte // this ensures that none of the five_minute_periods will match, so we always rerender when the face activates state->prev_five_minute_period = -1; + state->prev_min_checked = -1; } bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { close_enough_clock_state_t *state = (close_enough_clock_state_t *)context; - char buf[11]; + char buf[11]; watch_date_time date_time; + bool show_next_hour = false; int prev_five_minute_period; + int prev_min_checked; + int close_enough_hour; + switch (event.event_type) { case EVENT_ACTIVATE: case EVENT_TICK: case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); prev_five_minute_period = state->prev_five_minute_period; + prev_min_checked = state->prev_min_checked; // check the battery voltage once a day... if (date_time.unit.day != state->last_battery_check) { @@ -126,27 +117,51 @@ bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *s watch_set_indicator(WATCH_INDICATOR_LAP); } + // same minute, skip update + if (date_time.unit.minute == prev_min_checked) { + break; + } else { + state->prev_min_checked = date_time.unit.minute; + } + int five_minute_period = (date_time.unit.minute / 5) % 12; + // If we are 60% to the next 5 interval, move up to the next period + if (fmodf(date_time.unit.minute / 5.0f, 1.0f) > 0.5f) { + // If we are on the last 5 interval and moving to the next period we need to display the next hour because we are wrapping around + if (five_minute_period == 11) { + show_next_hour = true; + } + + five_minute_period = (five_minute_period + 1) % 12; + } + // same five_minute_period, skip update if (five_minute_period == prev_five_minute_period) { break; } - // move from "x mins past y" to "x mins to y+1" - if (five_minute_period >= hour_switch_index) { - date_time.unit.hour += 1; - date_time.unit.hour %= 24; + // we don't want to modify date_time.unit.hour just in case other watch faces use it + close_enough_hour = date_time.unit.hour; + + // move from "MM(mins) P HH" to "MM(mins) 2 HH+1" + if (five_minute_period >= hour_switch_index || show_next_hour) { + close_enough_hour = (close_enough_hour + 1) % 24; } if (!settings->bit.clock_mode_24h) { // if we are in 12 hour mode, do some cleanup. - if (date_time.unit.hour < 12) { + if (close_enough_hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); } else { watch_set_indicator(WATCH_INDICATOR_PM); } + close_enough_hour %= 12; + if (close_enough_hour == 0) { + close_enough_hour = 12; + } + date_time.unit.hour %= 12; if (date_time.unit.hour == 0) { date_time.unit.hour = 12; @@ -156,14 +171,14 @@ bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *s char first_word[3]; char second_word[3]; char third_word[3]; - if (five_minute_period == 0) { - sprintf(first_word, "%2d", date_time.unit.hour); - strncpy(second_word, first_words[five_minute_period], 3); - strncpy(third_word, second_words[five_minute_period], 3); + if (five_minute_period == 0) { // "HH OC", + sprintf(first_word, "%2d", close_enough_hour); + strncpy(second_word, words[five_minute_period][0], 3); + strncpy(third_word, words[five_minute_period][1], 3); } else { - strncpy(first_word, first_words[five_minute_period], 3); - strncpy(second_word, second_words[five_minute_period], 3); - sprintf(third_word, "%2d", date_time.unit.hour); + strncpy(first_word, words[five_minute_period][0], 3); + strncpy(second_word, words[five_minute_period][1], 3); + sprintf(third_word, "%2d", close_enough_hour); } sprintf( diff --git a/movement/watch_faces/clock/close_enough_clock_face.h b/movement/watch_faces/clock/close_enough_clock_face.h index 341357b..736a6c6 100644 --- a/movement/watch_faces/clock/close_enough_clock_face.h +++ b/movement/watch_faces/clock/close_enough_clock_face.h @@ -40,6 +40,7 @@ typedef struct { int prev_five_minute_period; + int prev_min_checked; uint8_t last_battery_check; bool battery_low; bool alarm_enabled; From af0f8d273259e8898ff4dafdcaff8e067ecb0c53 Mon Sep 17 00:00:00 2001 From: Ruben Nic Date: Sat, 18 May 2024 10:44:43 -0400 Subject: [PATCH 022/220] If the alarm is enabled show bell not signal --- movement/watch_faces/clock/close_enough_clock_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/clock/close_enough_clock_face.c b/movement/watch_faces/clock/close_enough_clock_face.c index 19bde69..341942e 100644 --- a/movement/watch_faces/clock/close_enough_clock_face.c +++ b/movement/watch_faces/clock/close_enough_clock_face.c @@ -49,9 +49,9 @@ static const int hour_switch_index = 9; static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) { state->alarm_enabled = settings_alarm_enabled; if (state->alarm_enabled) { - watch_set_indicator(WATCH_INDICATOR_SIGNAL); + watch_set_indicator(WATCH_INDICATOR_BELL); } else { - watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + watch_clear_indicator(WATCH_INDICATOR_BELL); }; } From 7a2ecad334319acd4ea5ae585ad82b92990a1fca Mon Sep 17 00:00:00 2001 From: Ruben Nic Date: Sat, 25 May 2024 18:16:50 -0400 Subject: [PATCH 023/220] Custom setting of switch from past to to index --- .../clock/close_enough_clock_face.c | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/movement/watch_faces/clock/close_enough_clock_face.c b/movement/watch_faces/clock/close_enough_clock_face.c index 341942e..cbd62e2 100644 --- a/movement/watch_faces/clock/close_enough_clock_face.c +++ b/movement/watch_faces/clock/close_enough_clock_face.c @@ -29,22 +29,28 @@ #include "watch.h" #include "watch_utility.h" -const char *words[12][2] = { - {" ", "OC"}, // "HH OC", - {" 5", " P"}, // " 5 past HH", - {"10", " P"}, // "10 past HH", - {"15", " P"}, // "15 past HH", - {"20", " P"}, // "20 past HH", - {"25", " P"}, // "25 past HH", - {"30", " P"}, // "30 past HH", - {"35", " P"}, // "35 past HH", - {"40", " P"}, // "40 past HH", - {"15", " 2"}, // "15 two HH+1", - {"10", " 2"}, // "10 two HH+1", - {" 5", " 2"}, // " 5 two HH+1", +const char *words[12] = { + " ", + " 5", + "10", + "15", + "20", + "25", + "30", + "35", + "40", + "45", + "50", + "55", }; -static const int hour_switch_index = 9; +static const char *past_word = " P"; +static const char *to_word = " 2"; +static const char *oclock_word = "OC"; + +// sets when in the five minute period we switch +// from "X past HH" to "X to HH+1" +static const int hour_switch_index = 8; static void _update_alarm_indicator(bool settings_alarm_enabled, close_enough_clock_state_t *state) { state->alarm_enabled = settings_alarm_enabled; @@ -173,11 +179,24 @@ bool close_enough_clock_face_loop(movement_event_t event, movement_settings_t *s char third_word[3]; if (five_minute_period == 0) { // "HH OC", sprintf(first_word, "%2d", close_enough_hour); - strncpy(second_word, words[five_minute_period][0], 3); - strncpy(third_word, words[five_minute_period][1], 3); + strncpy(second_word, words[five_minute_period], 3); + strncpy(third_word, oclock_word, 3); } else { - strncpy(first_word, words[five_minute_period][0], 3); - strncpy(second_word, words[five_minute_period][1], 3); + int words_length = sizeof(words) / sizeof(words[0]); + + strncpy( + first_word, + five_minute_period >= hour_switch_index ? + words[words_length - five_minute_period] : + words[five_minute_period], + 3 + ); + strncpy( + second_word, + five_minute_period >= hour_switch_index ? + to_word : past_word, + 3 + ); sprintf(third_word, "%2d", close_enough_hour); } From 3eaf807590539e6f182bac9204b46f5b6681fccc Mon Sep 17 00:00:00 2001 From: voloved <36523934+voloved@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:23:31 -0400 Subject: [PATCH 024/220] Added Timeout; Ability to turn off LED and Sound; Added doublespeed mode. (#1) * Check that color is valid Instead of merely checking that COLOR is set, check that it is one of RED, BLUE or GREEN * Added ability to turn off sound and timer with modes * Added enum for mode --------- Co-authored-by: Wesley Ellis --- make.mk | 6 + .../watch_faces/complication/simon_face.c | 110 ++++++++++++++++-- .../watch_faces/complication/simon_face.h | 14 +++ 3 files changed, 119 insertions(+), 11 deletions(-) diff --git a/make.mk b/make.mk index bb2d153..00933d0 100644 --- a/make.mk +++ b/make.mk @@ -213,6 +213,12 @@ ifndef COLOR $(error Set the COLOR variable to RED, BLUE, or GREEN depending on what board you have.) endif +COLOR_VALID := $(filter $(COLOR),RED BLUE GREEN) + +ifeq ($(COLOR_VALID),) +$(error COLOR must be RED, BLUE, or GREEN) +endif + ifeq ($(COLOR), BLUE) CFLAGS += -DWATCH_IS_BLUE_BOARD endif diff --git a/movement/watch_faces/complication/simon_face.c b/movement/watch_faces/complication/simon_face.c index 99fc160..eb08f17 100644 --- a/movement/watch_faces/complication/simon_face.c +++ b/movement/watch_faces/complication/simon_face.c @@ -33,6 +33,10 @@ #endif static char _simon_display_buf[12]; +static uint8_t _timer; +static uint16_t _delay_beep; +static uint16_t _timeout; +static uint8_t _secSub; static inline uint8_t _simon_get_rand_num(uint8_t num_values) { #if __EMSCRIPTEN__ @@ -55,7 +59,26 @@ static void _simon_not_playing_display(simon_state_t *state) { _simon_clear_display(state); sprintf(_simon_display_buf, "SI %d", state->best_score); + if (!state->soundOff) + watch_set_indicator(WATCH_INDICATOR_BELL); + else + watch_clear_indicator(WATCH_INDICATOR_BELL); + if (!state->lightOff) + 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); + break; + case SIMON_MODE_HARD: + watch_display_string("H", 9); + break; + default: + break; + } } static void _simon_reset(simon_state_t *state) { @@ -91,19 +114,31 @@ static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_res _simon_display_note(note, state); switch (note) { case SIMON_LED_NOTE: - watch_set_led_yellow(); - watch_buzzer_play_note(BUZZER_NOTE_D3, 300); + if (!state->lightOff) watch_set_led_yellow(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep); break; case SIMON_MODE_NOTE: - watch_set_led_red(); - watch_buzzer_play_note(BUZZER_NOTE_E4, 300); + if (!state->lightOff) watch_set_led_red(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_E4, _delay_beep); break; case SIMON_ALARM_NOTE: - watch_set_led_green(); - watch_buzzer_play_note(BUZZER_NOTE_C3, 300); + if (!state->lightOff) watch_set_led_green(); + if (state->soundOff) + delay_ms(_delay_beep); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, _delay_beep); break; case SIMON_WRONG_NOTE: - watch_buzzer_play_note(BUZZER_NOTE_A1, 800); + if (state->soundOff) + delay_ms(800); + else + watch_buzzer_play_note(BUZZER_NOTE_A1, 800); break; } watch_set_led_off(); @@ -111,7 +146,7 @@ static void _simon_play_note(SimonNote note, simon_state_t *state, bool skip_res if (note != SIMON_WRONG_NOTE) { _simon_clear_display(state); if (!skip_rest) { - watch_buzzer_play_note(BUZZER_NOTE_REST, 200); + watch_buzzer_play_note(BUZZER_NOTE_REST, (_delay_beep * 2)/3); } } } @@ -134,6 +169,7 @@ static void _simon_listen(SimonNote note, simon_state_t *state) { if (state->sequence[state->listen_index] == note) { _simon_play_note(note, state, true); state->listen_index++; + _timer = 0; if (state->listen_index == state->sequence_length) { state->playing_state = SIMON_READY_FOR_NEXT_NOTE; @@ -149,6 +185,22 @@ static void _simon_begin_listening(simon_state_t *state) { state->listen_index = 0; } +static void _simon_change_speed(simon_state_t *state){ + switch (state->mode) + { + case SIMON_MODE_HARD: + _delay_beep = DELAY_FOR_TONE_MS / 2; + _secSub = SIMON_FACE_FREQUENCY / 2; + _timeout = (TIMER_MAX * SIMON_FACE_FREQUENCY) / 2; + break; + default: + _delay_beep = DELAY_FOR_TONE_MS; + _secSub = SIMON_FACE_FREQUENCY; + _timeout = TIMER_MAX * SIMON_FACE_FREQUENCY; + break; + } +} + void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { (void)settings; @@ -171,6 +223,10 @@ void simon_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void simon_face_activate(movement_settings_t *settings, void *context) { (void) settings; (void) context; + simon_state_t *state = (simon_state_t *)context; + _simon_change_speed(state); + movement_request_tick_frequency(SIMON_FACE_FREQUENCY); + _timer = 0; } bool simon_face_loop(movement_event_t event, movement_settings_t *settings, @@ -183,9 +239,16 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, _simon_reset(state); break; case EVENT_TICK: - if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE) { - _simon_setup_next_note(state); - } else if (state->playing_state == SIMON_TEACHING) { + if (state->playing_state == SIMON_LISTENING_BACK && state->mode != SIMON_MODE_EASY) + { + _timer++; + if(_timer >= (_timeout)){ + _timer = 0; + _simon_play_note(SIMON_WRONG_NOTE, state, true); + _simon_reset(state); + } + } + else if (state->playing_state == SIMON_TEACHING && event.subsecond == 0) { SimonNote note = state->sequence[state->teaching_index]; // if this is the final note in the sequence, don't play the rest to let // the player jump in faster @@ -196,12 +259,32 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, _simon_begin_listening(state); } } + else if (state->playing_state == SIMON_READY_FOR_NEXT_NOTE && (event.subsecond % _secSub) == 0) { + _timer = 0; + _simon_setup_next_note(state); + } break; case EVENT_LIGHT_BUTTON_DOWN: break; + case EVENT_LIGHT_LONG_PRESS: + if (state->playing_state == SIMON_NOT_PLAYING) { + state->lightOff = !state->lightOff; + _simon_not_playing_display(state); + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state->playing_state == SIMON_NOT_PLAYING) { + state->soundOff = !state->soundOff; + _simon_not_playing_display(state); + if (!state->soundOff) + watch_buzzer_play_note(BUZZER_NOTE_D3, _delay_beep); + } + break; case EVENT_LIGHT_BUTTON_UP: if (state->playing_state == SIMON_NOT_PLAYING) { state->sequence_length = 0; + watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); _simon_setup_next_note(state); } else if (state->playing_state == SIMON_LISTENING_BACK) { _simon_listen(SIMON_LED_NOTE, state); @@ -226,6 +309,11 @@ bool simon_face_loop(movement_event_t event, movement_settings_t *settings, if (state->playing_state == SIMON_LISTENING_BACK) { _simon_listen(SIMON_ALARM_NOTE, state); } + else if (state->playing_state == SIMON_NOT_PLAYING){ + state->mode = (state->mode + 1) % SIMON_MODE_TOTAL; + _simon_change_speed(state); + _simon_not_playing_display(state); + } break; case EVENT_TIMEOUT: movement_move_to_face(0); diff --git a/movement/watch_faces/complication/simon_face.h b/movement/watch_faces/complication/simon_face.h index b59d56c..44bc9f3 100644 --- a/movement/watch_faces/complication/simon_face.h +++ b/movement/watch_faces/complication/simon_face.h @@ -71,12 +71,22 @@ typedef enum SimonPlayingState { SIMON_READY_FOR_NEXT_NOTE } SimonPlayingState; +typedef enum SimonMode { + SIMON_MODE_NORMAL = 0, // 5 Second timeout if nothing is input + SIMON_MODE_EASY, // There is no timeout in this mode + SIMON_MODE_HARD, // The speed of the teaching is doubled and th etimeout is halved + SIMON_MODE_TOTAL +} SimonMode; + typedef struct { uint8_t best_score; SimonNote sequence[MAX_SEQUENCE]; uint8_t sequence_length; uint8_t teaching_index; uint8_t listen_index; + bool soundOff; + bool lightOff; + uint8_t mode:6; SimonPlayingState playing_state; } simon_state_t; @@ -94,4 +104,8 @@ void simon_face_resign(movement_settings_t *settings, void *context); NULL, \ }) +#define TIMER_MAX 5 +#define SIMON_FACE_FREQUENCY 8 +#define DELAY_FOR_TONE_MS 300 + #endif // SIMON_FACE_H_ From b923d506526382c74149c5f49e6395279d56bd3f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 8 Jul 2024 18:32:41 -0400 Subject: [PATCH 025/220] CLOCK_FACE_24H_ONLY hides the preference to change the setting and defaults the mode to 24Hr mode --- movement/movement.c | 4 ++++ movement/watch_faces/clock/clock_face.c | 5 ----- movement/watch_faces/settings/preferences_face.c | 4 +++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index cb3dcf7..2ca2eb9 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -377,7 +377,11 @@ void app_init(void) { memset(&movement_state, 0, sizeof(movement_state)); +#ifdef CLOCK_FACE_24H_ONLY + movement_state.settings.bit.clock_mode_24h = true; +#else movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE; +#endif movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR; movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR; movement_state.settings.bit.button_should_sound = MOVEMENT_DEFAULT_BUTTON_SOUND; diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index eab5cd8..0e3a595 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -42,10 +42,6 @@ #define CLOCK_FACE_LOW_BATTERY_VOLTAGE_THRESHOLD 2200 #endif -#ifndef CLOCK_FACE_24H_ONLY -#define CLOCK_FACE_24H_ONLY 0 -#endif - typedef struct { struct { watch_date_time previous; @@ -57,7 +53,6 @@ typedef struct { } clock_state_t; static bool clock_is_in_24h_mode(movement_settings_t *settings) { - if (CLOCK_FACE_24H_ONLY) { return true; } return settings->bit.clock_mode_24h; } diff --git a/movement/watch_faces/settings/preferences_face.c b/movement/watch_faces/settings/preferences_face.c index c96e8d1..2d4de9d 100644 --- a/movement/watch_faces/settings/preferences_face.c +++ b/movement/watch_faces/settings/preferences_face.c @@ -99,7 +99,9 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings default: return movement_default_loop_handler(event, settings); } - +#ifdef CLOCK_FACE_24H_ONLY + if (current_page == 0) current_page++; // Skips past 12/24HR mode +#endif watch_display_string((char *)preferences_face_titles[current_page], 0); // blink active setting on even-numbered quarter-seconds From 7f6a9e5c9bdb04fa23a780da173594a4f7153bea Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 8 Jul 2024 18:33:17 -0400 Subject: [PATCH 026/220] Typo fix on PREFERENCES_FACE_NUM_PREFERENCES --- movement/watch_faces/settings/preferences_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/settings/preferences_face.c b/movement/watch_faces/settings/preferences_face.c index 2d4de9d..2297977 100644 --- a/movement/watch_faces/settings/preferences_face.c +++ b/movement/watch_faces/settings/preferences_face.c @@ -26,8 +26,8 @@ #include "preferences_face.h" #include "watch.h" -#define PREFERENCES_FACE_NUM_PREFEFENCES (7) -const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFEFENCES][11] = { +#define PREFERENCES_FACE_NUM_PREFERENCES (7) +const char preferences_face_titles[PREFERENCES_FACE_NUM_PREFERENCES][11] = { "CL ", // Clock: 12 or 24 hour "BT Beep ", // Buttons: should they beep? "TO ", // Timeout: how long before we snap back to the clock face? @@ -65,7 +65,7 @@ bool preferences_face_loop(movement_event_t event, movement_settings_t *settings movement_move_to_next_face(); return false; case EVENT_LIGHT_BUTTON_DOWN: - current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFEFENCES; + current_page = (current_page + 1) % PREFERENCES_FACE_NUM_PREFERENCES; *((uint8_t *)context) = current_page; break; case EVENT_ALARM_BUTTON_UP: From 2afc2c6721a526d2b5beebb9b266aef79ea04a20 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 10 Jul 2024 07:22:55 -0400 Subject: [PATCH 027/220] isolating this bit of complexity in movement function; Add ifdefs in clock faces for DCE --- movement/movement.c | 15 +++++++++------ movement/watch_faces/clock/clock_face.c | 4 ++++ movement/watch_faces/clock/simple_clock_face.c | 6 ++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 2ca2eb9..7363a8b 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -328,6 +328,14 @@ static void end_buzzing_and_disable_buzzer(void) { watch_disable_buzzer(); } +static void set_initial_clock_mode(void) { +#ifdef CLOCK_FACE_24H_ONLY + movement_state.settings.bit.clock_mode_24h = true; +#else + movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE; +#endif +} + void movement_play_signal(void) { void *maybe_disable_buzzer = end_buzzing_and_disable_buzzer; if (watch_is_buzzer_or_led_enabled()) { @@ -376,12 +384,7 @@ void app_init(void) { #endif memset(&movement_state, 0, sizeof(movement_state)); - -#ifdef CLOCK_FACE_24H_ONLY - movement_state.settings.bit.clock_mode_24h = true; -#else - movement_state.settings.bit.clock_mode_24h = MOVEMENT_DEFAULT_24H_MODE; -#endif + set_initial_clock_mode(); movement_state.settings.bit.led_red_color = MOVEMENT_DEFAULT_RED_COLOR; movement_state.settings.bit.led_green_color = MOVEMENT_DEFAULT_GREEN_COLOR; movement_state.settings.bit.button_should_sound = MOVEMENT_DEFAULT_BUTTON_SOUND; diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index 0e3a595..8b294c4 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -53,7 +53,11 @@ typedef struct { } clock_state_t; static bool clock_is_in_24h_mode(movement_settings_t *settings) { +#ifdef CLOCK_FACE_24H_ONLY + return true; +#else return settings->bit.clock_mode_24h; +#endif } static void clock_indicate(WatchIndicatorSegment indicator, bool on) { diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index fbc2c4b..ac4ec2e 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -51,7 +51,11 @@ void simple_clock_face_activate(movement_settings_t *settings, void *context) { if (watch_tick_animation_is_running()) watch_stop_tick_animation(); +#ifdef CLOCK_FACE_24H_ONLY + watch_set_indicator(WATCH_INDICATOR_24H); +#else if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); +#endif // handle chime indicator if (state->signal_enabled) watch_set_indicator(WATCH_INDICATOR_BELL); @@ -106,6 +110,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting sprintf(buf, "%02d%02d", date_time.unit.minute, date_time.unit.second); } else { // other stuff changed; let's do it all. +#ifndef CLOCK_FACE_24H_ONLY if (!settings->bit.clock_mode_24h) { // if we are in 12 hour mode, do some cleanup. if (date_time.unit.hour < 12) { @@ -116,6 +121,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting date_time.unit.hour %= 12; if (date_time.unit.hour == 0) date_time.unit.hour = 12; } +#endif pos = 0; if (event.event_type == EVENT_LOW_ENERGY_UPDATE) { if (!watch_tick_animation_is_running()) watch_start_tick_animation(500); From 5f1a6517320dabb13c65c8311e9a88281b15764e Mon Sep 17 00:00:00 2001 From: James Haggerty Date: Wed, 17 Apr 2024 20:54:46 +1000 Subject: [PATCH 028/220] Keep light on if interacting This makes it possible to do a bunch of things without having to keep touching the light button. I don't really see any downside with this. If you want the light to go off, just stop touching buttons. --- movement/movement.c | 11 +++++++++++ watch-library/simulator/main.c | 1 + 2 files changed, 12 insertions(+) diff --git a/movement/movement.c b/movement/movement.c index cb3dcf7..8f9a45d 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -543,6 +543,17 @@ bool app_loop(void) { event.subsecond = movement_state.subsecond; // the first trip through the loop overrides the can_sleep state can_sleep = wf->loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]); + + // Keep light on if user is still interacting with the watch. + if (movement_state.light_ticks > 0) { + switch (event.event_type) { + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_MODE_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + movement_illuminate_led(); + } + } + event.event_type = EVENT_NONE; } diff --git a/watch-library/simulator/main.c b/watch-library/simulator/main.c index 6898fd0..5e5070f 100644 --- a/watch-library/simulator/main.c +++ b/watch-library/simulator/main.c @@ -89,6 +89,7 @@ void main_loop_sleep(uint32_t ms) { main_loop_set_sleeping(true); emscripten_sleep(ms); main_loop_set_sleeping(false); + animation_frame_id = ANIMATION_FRAME_ID_INVALID; } bool main_loop_is_sleeping(void) { From c8702d346e4c2e30f1f789844db127f6bb369d2f Mon Sep 17 00:00:00 2001 From: voloved <36523934+voloved@users.noreply.github.com> Date: Tue, 23 Jul 2024 05:35:38 -0400 Subject: [PATCH 029/220] Added subscreens to periodic table face; added title and faster scrolling (#1) * Added subscreens to periodic table face; added title and faster scrolling * Resized buf for element display * Fixed scrolling to work on actual hardware * Added delay before _loop_text at title and bugfix on elements shorter than 6 char * Title screen displays when le_mode starts * Added documentation on usage and removed unneeded variable --- .../watch_faces/complication/periodic_face.c | 599 +++++++++++------- .../watch_faces/complication/periodic_face.h | 29 +- 2 files changed, 388 insertions(+), 240 deletions(-) diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c index 6746c26..cee0033 100644 --- a/movement/watch_faces/complication/periodic_face.c +++ b/movement/watch_faces/complication/periodic_face.c @@ -26,6 +26,15 @@ #include #include "periodic_face.h" +#define FREQ_FAST 8 +#define FREQ 2 + +static bool _quick_ticks_running; +static uint8_t _ts_ticks = 0; +static int16_t _text_pos; +static const char* _text_looping; +static const char title_text[] = "Periodic Table"; + void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { (void)settings; @@ -42,304 +51,418 @@ void periodic_face_activate(movement_settings_t *settings, void *context) (void)settings; periodic_state_t *state = (periodic_state_t *)context; - state->atomic_num = 1; + state->atomic_num = 0; state->mode = 0; state->selection_index = 0; - - movement_request_tick_frequency(2); + _quick_ticks_running = false; + movement_request_tick_frequency(FREQ); } typedef struct { - char name[3]; - uint16_t atomic_mass; + char symbol[3]; + char name[14]; // Longest is Rutherfordium + int16_t year_discovered; // Negative is BC + uint16_t atomic_mass; // In units of 0.01 AMU + uint16_t electronegativity; // In units of 0.01 char group[3]; } element; +typedef enum { + SCREEN_TITLE = 0, + SCREEN_ELEMENT, + SCREEN_ATOMIC_MASS, + SCREEN_DISCOVER_YEAR, + SCREEN_ELECTRONEGATIVITY, + SCREEN_FULL_NAME, + SCREENS_COUNT +} PeriodicScreens; + +const char screen_name[SCREENS_COUNT][3] = { + [SCREEN_ATOMIC_MASS] = "am", + [SCREEN_DISCOVER_YEAR] = " y", + [SCREEN_ELECTRONEGATIVITY] = "EL", + [SCREEN_FULL_NAME] = " n", +}; + // Comments on the table denote symbols that cannot be displayed #define MAX_ELEMENT 118 const element table[MAX_ELEMENT] = { - {"H ", 1, " "}, - {"He", 4, " 0"}, - {"Li", 7, " 1"}, - {"Be", 9, " 2"}, - {"B ", 11, " 3"}, - {"C ", 12, " 4"}, - {"N ", 14, " 5"}, - {"O ", 16, " 6"}, - {"F ", 19, " 7"}, - {"Ne", 20, " 0"}, - {"Na", 23, " 1"}, - {"Mg", 24, " 2"}, // - {"Al", 27, " 3"}, - {"Si", 28, " 4"}, - {"P ", 31, " 5"}, - {"S ", 32, " 6"}, - {"Cl", 355, " 7"}, - {"Ar", 40, " 0"}, - {"K ", 39, " 1"}, - {"Ca", 40, " 2"}, - {"Sc", 45, " T"}, - {"Ti", 48, " T"}, - {" W", 51, " T"}, // "V" - {"Cr", 52, " T"}, - {"Mn", 55, " T"}, - {"Fe", 56, " T"}, - {"Co", 59, " T"}, - {"Ni", 59, " T"}, - {"Cu", 635, " T"}, - {"Zn", 65, " T"}, - {"Ga", 70, " 3"}, - {"Ge", 73, " 4"}, - {"As", 75, " 5"}, // - {"Se", 79, " 6"}, - {"Br", 80, " 7"}, - {"Kr", 84, " 0"}, - {"Rb", 85, " 1"}, - {"Sr", 88, " 2"}, - {"Y ", 89, " T"}, - {"Zr", 91, " T"}, - {"Nb", 93, " T"}, - {"Mo", 96, " T"}, - {"Tc", 97, " T"}, - {"Ru", 101, " T"}, - {"Rh", 103, " T"}, - {"Pd", 106, " T"}, - {"Ag", 108, " T"}, // - {"Cd", 112, " T"}, - {"In", 115, " 3"}, - {"Sn", 119, " 4"}, - {"Sb", 122, " 5"}, - {"Te", 128, " 6"}, - {"I ", 127, " 7"}, - {"Xe", 131, " 0"}, - {"CS", 133, " 1"}, - {"Ba", 137, " 2"}, - {"La", 139, "1a"}, // La - {"Ce", 140, "1a"}, - {"Pr", 141, "1a"}, - {"Nd", 144, "1a"}, - {"Pm", 145, "1a"}, - {"Sm", 150, "1a"}, - {"Eu", 152, "1a"}, - {"Gd", 157, "1a"}, - {"Tb", 159, "1a"}, - {"Dy", 163, "1a"}, // .5 Rounded up due to space constraints - {"Ho", 165, "1a"}, - {"Er", 167, "1a"}, - {"Tm", 169, "1a"}, - {"Yb", 173, "1a"}, - {"Lu", 175, "1a"}, - {"Hf", 179, " T"}, - {"Ta", 181, " T"}, - {"W ", 184, " T"}, - {"Re", 186, " T"}, - {"OS", 190, " T"}, - {"Ir", 192, " T"}, - {"Pt", 195, " T"}, - {"Au", 197, " T"}, - {"Hg", 201, " T"}, // - {"Tl", 204, " 3"}, - {"Pb", 207, " 4"}, - {"Bi", 209, " 5"}, - {"Po", 209, " 6"}, - {"At", 210, " 7"}, - {"Rn", 222, " 0"}, - {"Fr", 223, " 1"}, - {"Ra", 226, " 2"}, - {"Ac", 227, "Ac"}, - {"Th", 232, "Ac"}, - {"Pa", 231, "Ac"}, - {"U ", 238, "Ac"}, - {"Np", 237, "Ac"}, // - {"Pu", 244, "Ac"}, - {"Am", 243, "Ac"}, - {"Cm", 247, "Ac"}, - {"Bk", 247, "Ac"}, // - {"Cf", 251, "Ac"}, - {"Es", 252, "Ac"}, // - {"Fm", 257, "Ac"}, - {"Md", 258, "Ac"}, - {"No", 259, "Ac"}, - {"Lr", 262, "Ac"}, - {"Rf", 267, " T"}, - {"Db", 262, " T"}, - {"Sg", 269, " T"}, // - {"Bh", 264, " T"}, - {"Hs", 269, " T"}, - {"Mt", 278, " T"}, - {"Ds", 281, " T"}, // - {"Rg", 282, " T"}, // - {"Cn", 285, " T"}, - {"Nh", 286, " 3"}, - {"Fl", 289, " 4"}, - {"Mc", 289, " 5"}, - {"LW", 293, " 6"}, // Lv - {"Ts", 294, " 7"}, // - {"Og", 294, " 0"}, // + { .symbol = "H", .name = "Hydrogen", .year_discovered = 1671, .atomic_mass = 101, .electronegativity = 220, .group = " " }, + { .symbol = "HE", .name = "Helium", .year_discovered = 1868, .atomic_mass = 400, .electronegativity = 0, .group = "0" }, + { .symbol = "LI", .name = "Lithium", .year_discovered = 1817, .atomic_mass = 694, .electronegativity = 98, .group = "1" }, + { .symbol = "BE", .name = "Beryllium", .year_discovered = 1798, .atomic_mass = 901, .electronegativity = 157, .group = "2" }, + { .symbol = "B", .name = "Boron", .year_discovered = 1787, .atomic_mass = 1081, .electronegativity = 204, .group = "3" }, + { .symbol = "C", .name = "Carbon", .year_discovered = -26000, .atomic_mass = 1201, .electronegativity = 255, .group = "4" }, + { .symbol = "N", .name = "Nitrogen", .year_discovered = 1772, .atomic_mass = 1401, .electronegativity = 304, .group = "5" }, + { .symbol = "O", .name = "Oxygen", .year_discovered = 1771, .atomic_mass = 1600, .electronegativity = 344, .group = "6" }, + { .symbol = "F", .name = "Fluorine", .year_discovered = 1771, .atomic_mass = 1900, .electronegativity = 398, .group = "7" }, + { .symbol = "NE", .name = "Neon", .year_discovered = 1898, .atomic_mass = 2018, .electronegativity = 0, .group = "0" }, + { .symbol = "NA", .name = "Sodium", .year_discovered = 1702, .atomic_mass = 2299, .electronegativity = 93, .group = "1" }, + { .symbol = "MG", .name = "Magnesium", .year_discovered = 1755, .atomic_mass = 2431, .electronegativity = 131, .group = "2" }, + { .symbol = "AL", .name = "Aluminium", .year_discovered = 1746, .atomic_mass = 2698, .electronegativity = 161, .group = "3" }, + { .symbol = "SI", .name = "Silicon", .year_discovered = 1739, .atomic_mass = 2809, .electronegativity = 190, .group = "4" }, + { .symbol = "P", .name = "Phosphorus", .year_discovered = 1669, .atomic_mass = 3097, .electronegativity = 219, .group = "5" }, + { .symbol = "S", .name = "Sulfur", .year_discovered = -2000, .atomic_mass = 3206, .electronegativity = 258, .group = "6" }, + { .symbol = "CL", .name = "Chlorine", .year_discovered = 1774, .atomic_mass = 3545., .electronegativity = 316, .group = "7" }, + { .symbol = "AR", .name = "Argon", .year_discovered = 1894, .atomic_mass = 3995., .electronegativity = 0, .group = "0" }, + { .symbol = "K", .name = "Potassium", .year_discovered = 1702, .atomic_mass = 3910, .electronegativity = 82, .group = "1" }, + { .symbol = "CA", .name = "Calcium", .year_discovered = 1739, .atomic_mass = 4008, .electronegativity = 100, .group = "2" }, + { .symbol = "SC", .name = "Scandium", .year_discovered = 1879, .atomic_mass = 4496, .electronegativity = 136, .group = " T" }, + { .symbol = "TI", .name = "Titanium", .year_discovered = 1791, .atomic_mass = 4787, .electronegativity = 154, .group = " T" }, + { .symbol = "W", .name = "Vanadium", .year_discovered = 1801, .atomic_mass = 5094, .electronegativity = 163, .group = " T" }, + { .symbol = "CR", .name = "Chromium", .year_discovered = 1797, .atomic_mass = 5200, .electronegativity = 166, .group = " T" }, + { .symbol = "MN", .name = "Manganese", .year_discovered = 1774, .atomic_mass = 5494, .electronegativity = 155, .group = " T" }, + { .symbol = "FE", .name = "Iron", .year_discovered = -5000, .atomic_mass = 5585, .electronegativity = 183, .group = " T" }, + { .symbol = "CO", .name = "Cobalt", .year_discovered = 1735, .atomic_mass = 5893, .electronegativity = 188, .group = " T" }, + { .symbol = "NI", .name = "Nickel", .year_discovered = 1751, .atomic_mass = 5869, .electronegativity = 191, .group = " T" }, + { .symbol = "CU", .name = "Copper", .year_discovered = -9000, .atomic_mass = 6355, .electronegativity = 190, .group = " T" }, + { .symbol = "ZN", .name = "Zinc", .year_discovered = -1000, .atomic_mass = 6538, .electronegativity = 165, .group = " T" }, + { .symbol = "GA", .name = "Gallium", .year_discovered = 1875, .atomic_mass = 6972, .electronegativity = 181, .group = "3" }, + { .symbol = "GE", .name = "Germanium", .year_discovered = 1886, .atomic_mass = 7263, .electronegativity = 201, .group = "4" }, + { .symbol = "AS", .name = "Arsenic", .year_discovered = 300, .atomic_mass = 7492, .electronegativity = 218, .group = "5" }, + { .symbol = "SE", .name = "Selenium", .year_discovered = 1817, .atomic_mass = 7897, .electronegativity = 255, .group = "6" }, + { .symbol = "BR", .name = "Bromine", .year_discovered = 1825, .atomic_mass = 7990., .electronegativity = 296, .group = "7" }, + { .symbol = "KR", .name = "Krypton", .year_discovered = 1898, .atomic_mass = 8380, .electronegativity = 300, .group = "0" }, + { .symbol = "RB", .name = "Rubidium", .year_discovered = 1861, .atomic_mass = 8547, .electronegativity = 82, .group = "1" }, + { .symbol = "SR", .name = "Strontium", .year_discovered = 1787, .atomic_mass = 8762, .electronegativity = 95, .group = "2" }, + { .symbol = "Y", .name = "Yttrium", .year_discovered = 1794, .atomic_mass = 8891, .electronegativity = 122, .group = " T" }, + { .symbol = "ZR", .name = "Zirconium", .year_discovered = 1789, .atomic_mass = 9122, .electronegativity = 133, .group = " T" }, + { .symbol = "NB", .name = "Niobium", .year_discovered = 1801, .atomic_mass = 9291, .electronegativity = 160, .group = " T" }, + { .symbol = "MO", .name = "Molybdenum", .year_discovered = 1778, .atomic_mass = 9595, .electronegativity = 216, .group = " T" }, + { .symbol = "TC", .name = "Technetium", .year_discovered = 1937, .atomic_mass = 9700, .electronegativity = 190, .group = " T" }, + { .symbol = "RU", .name = "Ruthenium", .year_discovered = 1844, .atomic_mass = 10107, .electronegativity = 220, .group = " T" }, + { .symbol = "RH", .name = "Rhodium", .year_discovered = 1804, .atomic_mass = 10291, .electronegativity = 228, .group = " T" }, + { .symbol = "PD", .name = "Palladium", .year_discovered = 1802, .atomic_mass = 10642, .electronegativity = 220, .group = " T" }, + { .symbol = "AG", .name = "Silver", .year_discovered = -5000, .atomic_mass = 10787, .electronegativity = 193, .group = " T" }, + { .symbol = "CD", .name = "Cadmium", .year_discovered = 1817, .atomic_mass = 11241, .electronegativity = 169, .group = " T" }, + { .symbol = "IN", .name = "Indium", .year_discovered = 1863, .atomic_mass = 11482, .electronegativity = 178, .group = "3" }, + { .symbol = "SN", .name = "Tin", .year_discovered = -3500, .atomic_mass = 11871, .electronegativity = 196, .group = "4" }, + { .symbol = "SB", .name = "Antimony", .year_discovered = -3000, .atomic_mass = 12176, .electronegativity = 205, .group = "5" }, + { .symbol = "TE", .name = "Tellurium", .year_discovered = 1782, .atomic_mass = 12760, .electronegativity = 210, .group = "6" }, + { .symbol = "I", .name = "Iodine", .year_discovered = 1811, .atomic_mass = 12690, .electronegativity = 266, .group = "7" }, + { .symbol = "XE", .name = "Xenon", .year_discovered = 1898, .atomic_mass = 13129, .electronegativity = 260, .group = "0" }, + { .symbol = "CS", .name = "Caesium", .year_discovered = 1860, .atomic_mass = 13291, .electronegativity = 79, .group = "1" }, + { .symbol = "BA", .name = "Barium", .year_discovered = 1772, .atomic_mass = 13733., .electronegativity = 89, .group = "2" }, + { .symbol = "LA", .name = "Lanthanum", .year_discovered = 1838, .atomic_mass = 13891, .electronegativity = 110, .group = "1a" }, + { .symbol = "CE", .name = "Cerium", .year_discovered = 1803, .atomic_mass = 14012, .electronegativity = 112, .group = "1a" }, + { .symbol = "PR", .name = "Praseodymium", .year_discovered = 1885, .atomic_mass = 14091, .electronegativity = 113, .group = "1a" }, + { .symbol = "ND", .name = "Neodymium", .year_discovered = 1841, .atomic_mass = 14424, .electronegativity = 114, .group = "1a" }, + { .symbol = "PM", .name = "Promethium", .year_discovered = 1945, .atomic_mass = 14500, .electronegativity = 113, .group = "1a" }, + { .symbol = "SM", .name = "Samarium", .year_discovered = 1879, .atomic_mass = 15036., .electronegativity = 117, .group = "1a" }, + { .symbol = "EU", .name = "Europium", .year_discovered = 1896, .atomic_mass = 15196, .electronegativity = 120, .group = "1a" }, + { .symbol = "GD", .name = "Gadolinium", .year_discovered = 1880, .atomic_mass = 15725, .electronegativity = 120, .group = "1a" }, + { .symbol = "TB", .name = "Terbium", .year_discovered = 1843, .atomic_mass = 15893, .electronegativity = 120, .group = "1a" }, + { .symbol = "DY", .name = "Dysprosium", .year_discovered = 1886, .atomic_mass = 16250, .electronegativity = 122, .group = "1a" }, + { .symbol = "HO", .name = "Holmium", .year_discovered = 1878, .atomic_mass = 16493, .electronegativity = 123, .group = "1a" }, + { .symbol = "ER", .name = "Erbium", .year_discovered = 1843, .atomic_mass = 16726, .electronegativity = 124, .group = "1a" }, + { .symbol = "TM", .name = "Thulium", .year_discovered = 1879, .atomic_mass = 16893, .electronegativity = 125, .group = "1a" }, + { .symbol = "YB", .name = "Ytterbium", .year_discovered = 1878, .atomic_mass = 17305, .electronegativity = 110, .group = "1a" }, + { .symbol = "LU", .name = "Lutetium", .year_discovered = 1906, .atomic_mass = 17497, .electronegativity = 127, .group = "1a" }, + { .symbol = "HF", .name = "Hafnium", .year_discovered = 1922, .atomic_mass = 17849, .electronegativity = 130, .group = " T" }, + { .symbol = "TA", .name = "Tantalum", .year_discovered = 1802, .atomic_mass = 18095, .electronegativity = 150, .group = " T" }, + { .symbol = "W", .name = "Tungsten", .year_discovered = 1781, .atomic_mass = 18384, .electronegativity = 236, .group = " T" }, + { .symbol = "RE", .name = "Rhenium", .year_discovered = 1908, .atomic_mass = 18621, .electronegativity = 190, .group = " T" }, + { .symbol = "OS", .name = "Osmium", .year_discovered = 1803, .atomic_mass = 19023, .electronegativity = 220, .group = " T" }, + { .symbol = "IR", .name = "Iridium", .year_discovered = 1803, .atomic_mass = 19222, .electronegativity = 220, .group = " T" }, + { .symbol = "PT", .name = "Platinum", .year_discovered = -600, .atomic_mass = 19508, .electronegativity = 228, .group = " T" }, + { .symbol = "AU", .name = "Gold", .year_discovered = -6000, .atomic_mass = 19697, .electronegativity = 254, .group = " T" }, + { .symbol = "HG", .name = "Mercury", .year_discovered = -1500, .atomic_mass = 20059, .electronegativity = 200, .group = " T" }, + { .symbol = "TL", .name = "Thallium", .year_discovered = 1861, .atomic_mass = 20438, .electronegativity = 162, .group = "3" }, + { .symbol = "PB", .name = "Lead", .year_discovered = -7000, .atomic_mass = 20720, .electronegativity = 187, .group = "4" }, + { .symbol = "BI", .name = "Bismuth", .year_discovered = 1500, .atomic_mass = 20898, .electronegativity = 202, .group = "5" }, + { .symbol = "PO", .name = "Polonium", .year_discovered = 1898, .atomic_mass = 20900, .electronegativity = 200, .group = "6" }, + { .symbol = "AT", .name = "Astatine", .year_discovered = 1940, .atomic_mass = 21000, .electronegativity = 220, .group = "7" }, + { .symbol = "RN", .name = "Radon", .year_discovered = 1899, .atomic_mass = 22200, .electronegativity = 220, .group = "0" }, + { .symbol = "FR", .name = "Francium", .year_discovered = 1939, .atomic_mass = 22300, .electronegativity = 79, .group = "1" }, + { .symbol = "RA", .name = "Radium", .year_discovered = 1898, .atomic_mass = 22600, .electronegativity = 90, .group = "2" }, + { .symbol = "AC", .name = "Actinium", .year_discovered = 1902, .atomic_mass = 22700, .electronegativity = 110, .group = "Ac" }, + { .symbol = "TH", .name = "Thorium", .year_discovered = 1829, .atomic_mass = 23204, .electronegativity = 130, .group = "Ac" }, + { .symbol = "PA", .name = "Protactinium", .year_discovered = 1913, .atomic_mass = 23104, .electronegativity = 150, .group = "Ac" }, + { .symbol = "U", .name = "Uranium", .year_discovered = 1789, .atomic_mass = 23803, .electronegativity = 138, .group = "Ac" }, + { .symbol = "NP", .name = "Neptunium", .year_discovered = 1940, .atomic_mass = 23700, .electronegativity = 136, .group = "Ac" }, + { .symbol = "PU", .name = "Plutonium", .year_discovered = 1941, .atomic_mass = 24400, .electronegativity = 128, .group = "Ac" }, + { .symbol = "AM", .name = "Americium", .year_discovered = 1944, .atomic_mass = 24300, .electronegativity = 113, .group = "Ac" }, + { .symbol = "CM", .name = "Curium", .year_discovered = 1944, .atomic_mass = 24700, .electronegativity = 128, .group = "Ac" }, + { .symbol = "BK", .name = "Berkelium", .year_discovered = 1949, .atomic_mass = 24700, .electronegativity = 130, .group = "Ac" }, + { .symbol = "CF", .name = "Californium", .year_discovered = 1950, .atomic_mass = 25100, .electronegativity = 130, .group = "Ac" }, + { .symbol = "ES", .name = "Einsteinium", .year_discovered = 1952, .atomic_mass = 25200, .electronegativity = 130, .group = "Ac" }, + { .symbol = "FM", .name = "Fermium", .year_discovered = 1953, .atomic_mass = 25700, .electronegativity = 130, .group = "Ac" }, + { .symbol = "MD", .name = "Mendelevium", .year_discovered = 1955, .atomic_mass = 25800, .electronegativity = 130, .group = "Ac" }, + { .symbol = "NO", .name = "Nobelium", .year_discovered = 1965, .atomic_mass = 25900, .electronegativity = 130, .group = "Ac" }, + { .symbol = "LR", .name = "Lawrencium", .year_discovered = 1961, .atomic_mass = 26600, .electronegativity = 130, .group = "Ac" }, + { .symbol = "RF", .name = "Rutherfordium", .year_discovered = 1969, .atomic_mass = 26700, .electronegativity = 0, .group = " T" }, + { .symbol = "DB", .name = "Dubnium", .year_discovered = 1970, .atomic_mass = 26800, .electronegativity = 0, .group = " T" }, + { .symbol = "SG", .name = "Seaborgium", .year_discovered = 1974, .atomic_mass = 26700, .electronegativity = 0, .group = " T" }, + { .symbol = "BH", .name = "Bohrium", .year_discovered = 1981, .atomic_mass = 27000, .electronegativity = 0, .group = " T" }, + { .symbol = "HS", .name = "Hassium", .year_discovered = 1984, .atomic_mass = 27100, .electronegativity = 0, .group = " T" }, + { .symbol = "MT", .name = "Meitnerium", .year_discovered = 1982, .atomic_mass = 27800, .electronegativity = 0, .group = " T" }, + { .symbol = "DS", .name = "Darmstadtium", .year_discovered = 1994, .atomic_mass = 28100, .electronegativity = 0, .group = " T" }, + { .symbol = "RG", .name = "Roentgenium", .year_discovered = 1994, .atomic_mass = 28200, .electronegativity = 0, .group = " T" }, + { .symbol = "CN", .name = "Copernicium", .year_discovered = 1996, .atomic_mass = 28500, .electronegativity = 0, .group = " T" }, + { .symbol = "NH", .name = "Nihonium", .year_discovered = 2004, .atomic_mass = 28600, .electronegativity = 0, .group = "3" }, + { .symbol = "FL", .name = "Flerovium", .year_discovered = 1999, .atomic_mass = 28900, .electronegativity = 0, .group = "4" }, + { .symbol = "MC", .name = "Moscovium", .year_discovered = 2003, .atomic_mass = 29000, .electronegativity = 0, .group = "5" }, + { .symbol = "LW", .name = "Livermorium", .year_discovered = 2000, .atomic_mass = 29300, .electronegativity = 0, .group = "6" }, + { .symbol = "TS", .name = "Tennessine", .year_discovered = 2009, .atomic_mass = 29400, .electronegativity = 0, .group = "7" }, + { .symbol = "OG", .name = "Oganesson", .year_discovered = 2002, .atomic_mass = 29400, .electronegativity = 0, .group = "0" }, }; -// Warning light for symbols that can't be displayed -static void _warning(periodic_state_t *state) -{ - char second_char = table[state->atomic_num - 1].name[1]; - if (second_char == 'p' || second_char == 'g' || second_char == 'y' || second_char == 's') - { - watch_set_indicator(WATCH_INDICATOR_BELL); - } - else - { - watch_clear_indicator(WATCH_INDICATOR_BELL); +static void _make_upper(char *string) { + size_t i = 0; + while(string[i] != 0) { + if (string[i] >= 'a' && string[i] <= 'z') + string[i]-=32; // 32 = 'a'-'A' + i++; } } -// Regular mode display -static void _periodic_face_update_lcd(periodic_state_t *state) +static void _display_element(periodic_state_t *state) { - // Colon as a decimal for Cl & Cu - if (state->atomic_num == 17 || state->atomic_num == 29) - { - watch_set_colon(); - } - else - { - watch_clear_colon(); - } - - _warning(state); + char buf[9]; + char ele[3]; + uint8_t atomic_num = state->atomic_num; + strcpy(ele, table[atomic_num - 1].symbol); + _make_upper(ele); + sprintf(buf, "%2s%3d %-2s", table[atomic_num - 1].group, atomic_num, ele); + watch_display_string(buf, 2); +} +static void _display_atomic_mass(periodic_state_t *state) +{ char buf[11]; - sprintf(buf, "%s%s%-3d%3d", table[state->atomic_num - 1].name, table[state->atomic_num - 1].group, table[state->atomic_num - 1].atomic_mass, state->atomic_num); - watch_display_string(buf, 0); + uint16_t mass = table[state->atomic_num - 1].atomic_mass; + uint16_t integer = mass / 100; + uint16_t decimal = mass % 100; + if (decimal == 0) + sprintf(buf, "%-2s%2s%4d", table[state->atomic_num - 1].symbol, screen_name[state->mode], integer); + else + sprintf(buf, "%-2s%2s%3d_%.2d", table[state->atomic_num - 1].symbol, screen_name[state->mode], integer, decimal); + watch_display_string(buf, 0); } -// Selection mode logic -static void _periodic_face_selection_increment(periodic_state_t *state) +static void _display_year_discovered(periodic_state_t *state) { - uint8_t digit0 = (state->atomic_num / 100) % 10; - uint8_t digit1 = (state->atomic_num / 10) % 10; - uint8_t digit2 = (state->atomic_num) % 10; - - // Increment the selected digit by 1 - switch (state->selection_index) - { - case 0: - digit0 ^= 1; - break; - case 1: - if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10) - digit1 = 0; - else - digit1 = (digit1 + 1) % 10; - break; - case 2: - if (digit0 == MAX_ELEMENT / 100 && digit1 == (MAX_ELEMENT / 10) % 10 && digit2 == MAX_ELEMENT % 10) - digit2 = 0; - else - digit2 = (digit2 + 1) % 10; - break; + char buf[11]; + char year_buf[7]; + int16_t year = table[state->atomic_num - 1].year_discovered; + if (abs(year) > 9999) + sprintf(year_buf, "---- "); + else + sprintf(year_buf, "%4d ", abs(year)); + if (year < 0) { + year_buf[4] = 'b'; + year_buf[5] = 'c'; } - - // Prevent 000 - if (digit0 == 0 && digit1 == 0 && digit2 == 0) { - digit2 = 1; - } - - // Prevent Overflow - if (digit0 == (MAX_ELEMENT / 100) % 10 && digit1 > (MAX_ELEMENT / 10) % 10) - { - digit2 = MAX_ELEMENT % 10; - digit1 = (MAX_ELEMENT / 10) % 10; - } - - state->atomic_num = digit0 * 100 + digit1 * 10 + digit2; + sprintf(buf, "%-2s%-2s%s", table[state->atomic_num - 1].symbol, screen_name[state->mode], year_buf); + watch_display_string(buf, 0); } -// Selection mode display -static void _periodic_face_selection(periodic_state_t *state, uint8_t subsec) +static void _display_name(periodic_state_t *state) { - uint8_t digit0 = (state->atomic_num / 100) % 10; - uint8_t digit1 = (state->atomic_num / 10) % 10; - uint8_t digit2 = (state->atomic_num) % 10; + char buf[11]; + _text_looping = table[state->atomic_num - 1].name; + _text_pos = 0; + sprintf(buf, "%-2s%-2s%s", table[state->atomic_num - 1].symbol, screen_name[state->mode], table[state->atomic_num - 1].name); + watch_display_string(buf, 0); +} - watch_display_string(" ", 0); +static void _display_electronegativity(periodic_state_t *state) +{ + char buf[11]; + uint16_t electronegativity = table[state->atomic_num - 1].electronegativity; + uint16_t integer = electronegativity / 100; + uint16_t decimal = electronegativity % 100; + if (decimal == 0) + sprintf(buf, "%-2s%2s%4d", table[state->atomic_num - 1].symbol, screen_name[state->mode], integer); + else + sprintf(buf, "%-2s%2s%3d_%.2d", table[state->atomic_num - 1].symbol, screen_name[state->mode], integer, decimal); + watch_display_string(buf, 0); +} - char buf[2] = {'\0'}; +static void start_quick_cyc(void){ + _quick_ticks_running = true; + movement_request_tick_frequency(FREQ_FAST); +} - buf[0] = (state->selection_index == 0 && subsec == 0) ? ' ' : digit0 + '0'; - watch_display_string(buf, 5); +static void stop_quick_cyc(void){ + _quick_ticks_running = false; + movement_request_tick_frequency(FREQ); +} - buf[0] = (state->selection_index == 1 && subsec == 0) ? ' ' : digit1 + '0'; - watch_display_string(buf, 6); +static int16_t _loop_text(const char* text, int8_t curr_loc, uint8_t char_len){ + // if curr_loc, then use that many ticks as a delay before looping + char buf[15]; + uint8_t next_pos; + uint8_t text_len = strlen(text); + uint8_t pos = 10 - char_len; + if (curr_loc == -1) curr_loc = 0; // To avoid double-showing the 0 + if (char_len >= text_len || curr_loc < 0) { + sprintf(buf, "%s", text); + watch_display_string(buf, pos); + if (curr_loc < 0) return ++curr_loc; + return 0; + } + else if (curr_loc == (text_len + 1)) + curr_loc = 0; + next_pos = curr_loc + 1; + sprintf(buf, "%.6s %.6s", text + curr_loc, text); + watch_display_string(buf, pos); + return next_pos; +} - buf[0] = (state->selection_index == 2 && subsec == 0) ? ' ' : digit2 + '0'; - watch_display_string(buf, 7); +static void _display_title(periodic_state_t *state){ + state->atomic_num = 0; + watch_clear_colon(); + watch_clear_all_indicators(); + _text_looping = title_text; + _text_pos = FREQ * -1; + _text_pos = _loop_text(_text_looping, _text_pos, 5); +} - char buf2[3]; - sprintf(buf2, "%s", table[state->atomic_num - 1].name); - watch_display_string(buf2, 0); +static void _display_screen(periodic_state_t *state, bool should_sound){ + watch_clear_display(); + watch_clear_all_indicators(); + switch (state->mode) + { + case SCREEN_TITLE: + _display_title(state); + break; + case SCREEN_ELEMENT: + case SCREENS_COUNT: + _display_element(state); + break; + case SCREEN_ATOMIC_MASS: + _display_atomic_mass(state); + break; + case SCREEN_DISCOVER_YEAR: + _display_year_discovered(state); + break; + case SCREEN_ELECTRONEGATIVITY: + _display_electronegativity(state); + break; + case SCREEN_FULL_NAME: + _display_name(state); + break; + } + if (should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); +} - _warning(state); +static void _handle_forward(periodic_state_t *state, bool should_sound){ + state->atomic_num = (state->atomic_num % MAX_ELEMENT) + 1; // Wraps back to 1 + state->mode = SCREEN_ELEMENT; + _display_screen(state, false); + if (should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); +} + +static void _handle_backward(periodic_state_t *state, bool should_sound){ + if (state->atomic_num <= 1) state->atomic_num = MAX_ELEMENT; + else state->atomic_num = state->atomic_num - 1; + state->mode = SCREEN_ELEMENT; + _display_screen(state, false); + if (should_sound) watch_buzzer_play_note(BUZZER_NOTE_A6, 50); +} + +static void _handle_mode_still_pressed(periodic_state_t *state, bool should_sound) { + if (_ts_ticks != 0){ + if (!watch_get_pin_level(BTN_MODE)) { + _ts_ticks = 0; + return; + } + else if (--_ts_ticks == 0){ + switch (state->mode) + { + case SCREEN_TITLE: + movement_move_to_face(0); + return; + case SCREEN_ELEMENT: + state->mode = SCREEN_TITLE; + _display_screen(state, should_sound); + break; + default: + state->mode = SCREEN_ELEMENT; + _display_screen(state, should_sound); + break; + } + _ts_ticks = 2; + } + } } bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { periodic_state_t *state = (periodic_state_t *)context; - switch (event.event_type) { case EVENT_ACTIVATE: - _periodic_face_update_lcd(state); + state->mode = SCREEN_TITLE; + _display_screen(state, false); break; case EVENT_TICK: - if (state->mode != 0) - { - _periodic_face_selection(state, event.subsecond % 2); + if (state->mode == SCREEN_TITLE) _text_pos = _loop_text(_text_looping, _text_pos, 5); + else if (state->mode == SCREEN_FULL_NAME) _text_pos = _loop_text(_text_looping, _text_pos, 6); + if (_quick_ticks_running) { + if (watch_get_pin_level(BTN_LIGHT)) _handle_backward(state, false); + else if (watch_get_pin_level(BTN_ALARM)) _handle_forward(state, false); + else stop_quick_cyc(); } + + _handle_mode_still_pressed(state, settings->bit.button_should_sound); break; case EVENT_LIGHT_BUTTON_UP: - // Only light LED when in regular mode - if (state->mode != MODE_VIEW) - { - state->selection_index = (state->selection_index + 1) % 3; - _periodic_face_selection(state, event.subsecond % 2); - } + _handle_backward(state, settings->bit.button_should_sound); break; case EVENT_LIGHT_BUTTON_DOWN: - if (state->mode != MODE_SELECT) - movement_illuminate_led(); break; case EVENT_ALARM_BUTTON_UP: - if (state->mode == MODE_VIEW) - { - state->atomic_num = (state->atomic_num % MAX_ELEMENT) + 1; // Wraps back to 1 - _periodic_face_update_lcd(state); - if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C7, 50); - } - else - { - _periodic_face_selection_increment(state); - _periodic_face_selection(state, event.subsecond % 2); - } + _handle_forward(state, settings->bit.button_should_sound); break; case EVENT_ALARM_LONG_PRESS: - // Toggle between selection mode and regular - if (state->mode == MODE_VIEW) - { - state->mode = MODE_SELECT; - _periodic_face_selection(state, event.subsecond % 2); + start_quick_cyc(); + _handle_forward(state, settings->bit.button_should_sound); + break; + case EVENT_LIGHT_LONG_PRESS: + start_quick_cyc(); + _handle_backward(state, settings->bit.button_should_sound); + break; + case EVENT_MODE_BUTTON_UP: + if (state->mode == SCREEN_TITLE) movement_move_to_next_face(); + else { + state->mode = (state->mode + 1) % SCREENS_COUNT; + if (state->mode == SCREEN_TITLE) + state->mode = (state->mode + 1) % SCREENS_COUNT; + if (state->mode == SCREEN_ELEMENT){ + _display_screen(state, false); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_A6, 50); + } + else + _display_screen(state, settings->bit.button_should_sound); } - else + break; + case EVENT_MODE_LONG_PRESS: + switch (state->mode) { - state->mode = MODE_VIEW; - _periodic_face_update_lcd(state); + case SCREEN_TITLE: + movement_move_to_face(0); + return true; + case SCREEN_ELEMENT: + state->mode = SCREEN_TITLE; + _display_screen(state, settings->bit.button_should_sound); + break; + default: + state->mode = SCREEN_ELEMENT; + _display_screen(state, settings->bit.button_should_sound); + break; } - if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_C8, 50); - + _ts_ticks = 2; break; case EVENT_TIMEOUT: break; case EVENT_LOW_ENERGY_UPDATE: + // Display title in LE mode. + if (state->mode == SCREEN_TITLE) break; + state->mode = SCREEN_TITLE; + _display_screen(state, false); break; default: return movement_default_loop_handler(event, settings); diff --git a/movement/watch_faces/complication/periodic_face.h b/movement/watch_faces/complication/periodic_face.h index 56476ca..a459e69 100644 --- a/movement/watch_faces/complication/periodic_face.h +++ b/movement/watch_faces/complication/periodic_face.h @@ -29,9 +29,34 @@ /* * Periodic Table Face + * Allows for viewing data of the Periodic Table on your wrist. + * When looking at an element, it'll show you the atomic number on the center of the screen, + * symbol on the right, and it's group on the top-right. + * Pressing the mode button will cycle through the pages. + * Page 1: Atomic Mass + * Page 2: Year Discovered + * Page 3: Electronegativity + * Page 4: Full Name of the Element + * + * Controls: + * Mode Press + * On Title: Next Screen + * Else: Cycle through info of an element + * Mode Hold + * On Title: First Screen + * On Element Symbol Screen: Go to Title Screen + * Else: Go to Symbol Screen of current element + * If you are in a subscreen and just keep holding MODE, you will go through all of these menus without needing to depress. + * + * Light Press + * Previous Element + * Light Hold + * Fast Cycle through Previous Elements * - * Elements can be viewed sequentially with a short press of the alarm button, - * or the atomic number can be input directly after holding down the alarm button. + * Alarm Press + * Next Element + * Alarm Hold + * Fast Cycle through Next Elements * */ From cb90a1980f98d1f5353721382f59a04337dc096b Mon Sep 17 00:00:00 2001 From: PrimmR Date: Wed, 24 Jul 2024 11:18:24 +0100 Subject: [PATCH 030/220] Added LED button combo --- .../watch_faces/complication/periodic_face.c | 38 +++++++++++++++---- .../watch_faces/complication/periodic_face.h | 4 ++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c index cee0033..162d3e4 100644 --- a/movement/watch_faces/complication/periodic_face.c +++ b/movement/watch_faces/complication/periodic_face.c @@ -2,6 +2,7 @@ * MIT License * * Copyright (c) 2023 PrimmR + * Copyright (c) 2024 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 @@ -34,6 +35,7 @@ static uint8_t _ts_ticks = 0; static int16_t _text_pos; static const char* _text_looping; static const char title_text[] = "Periodic Table"; +static bool _led_on = false; void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { @@ -410,20 +412,38 @@ bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, v _handle_mode_still_pressed(state, settings->bit.button_should_sound); break; case EVENT_LIGHT_BUTTON_UP: - _handle_backward(state, settings->bit.button_should_sound); - break; - case EVENT_LIGHT_BUTTON_DOWN: + if (!_led_on) _handle_backward(state, settings->bit.button_should_sound); + if (!watch_get_pin_level(BTN_ALARM)) _led_on = false; break; case EVENT_ALARM_BUTTON_UP: - _handle_forward(state, settings->bit.button_should_sound); + if (!_led_on) _handle_forward(state, settings->bit.button_should_sound); + if (!watch_get_pin_level(BTN_LIGHT)) _led_on = false; break; case EVENT_ALARM_LONG_PRESS: - start_quick_cyc(); - _handle_forward(state, settings->bit.button_should_sound); + if (!_led_on) { + start_quick_cyc(); + _handle_forward(state, settings->bit.button_should_sound); + } break; case EVENT_LIGHT_LONG_PRESS: - start_quick_cyc(); - _handle_backward(state, settings->bit.button_should_sound); + if (!_led_on) { + start_quick_cyc(); + _handle_backward(state, settings->bit.button_should_sound); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + if (watch_get_pin_level(BTN_ALARM)) _led_on = true; + stop_quick_cyc(); + break; + case EVENT_ALARM_BUTTON_DOWN: + if (watch_get_pin_level(BTN_LIGHT)) _led_on = true; + stop_quick_cyc(); + break; + case EVENT_LIGHT_LONG_UP: + _led_on = false; + break; + case EVENT_ALARM_LONG_UP: + _led_on = false; break; case EVENT_MODE_BUTTON_UP: if (state->mode == SCREEN_TITLE) movement_move_to_next_face(); @@ -468,6 +488,8 @@ bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, v return movement_default_loop_handler(event, settings); } + if (_led_on) movement_illuminate_led(); + return true; } diff --git a/movement/watch_faces/complication/periodic_face.h b/movement/watch_faces/complication/periodic_face.h index a459e69..c310082 100644 --- a/movement/watch_faces/complication/periodic_face.h +++ b/movement/watch_faces/complication/periodic_face.h @@ -2,6 +2,7 @@ * MIT License * * Copyright (c) 2023 PrimmR + * Copyright (c) 2024 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 @@ -58,6 +59,9 @@ * Alarm Hold * Fast Cycle through Next Elements * + * Light & Alarm Hold + * Activate LED backlight + * */ #define MODE_VIEW 0 From 6e26c01de042480f5b843b4fce1cefb70fcfe307 Mon Sep 17 00:00:00 2001 From: voloved <36523934+voloved@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:18:56 -0400 Subject: [PATCH 031/220] Holding light button on a non-element screen will turn on the light (#2) * Holding light button on a non-element screen will turn on the light. * The alarm and led button press moves back to the currently-selected element symbol page rather than the next and previous one * Usage update --- .../watch_faces/complication/periodic_face.c | 44 +++++++++---------- .../watch_faces/complication/periodic_face.h | 15 +++---- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c index 162d3e4..85753ff 100644 --- a/movement/watch_faces/complication/periodic_face.c +++ b/movement/watch_faces/complication/periodic_face.c @@ -35,7 +35,6 @@ static uint8_t _ts_ticks = 0; static int16_t _text_pos; static const char* _text_looping; static const char title_text[] = "Periodic Table"; -static bool _led_on = false; void periodic_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr) { @@ -412,38 +411,39 @@ bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, v _handle_mode_still_pressed(state, settings->bit.button_should_sound); break; case EVENT_LIGHT_BUTTON_UP: - if (!_led_on) _handle_backward(state, settings->bit.button_should_sound); - if (!watch_get_pin_level(BTN_ALARM)) _led_on = false; + if (state->mode <= SCREEN_ELEMENT) { + _handle_backward(state, settings->bit.button_should_sound); + } + else { + state->mode = SCREEN_ELEMENT; + _display_screen(state, settings->bit.button_should_sound); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: break; case EVENT_ALARM_BUTTON_UP: - if (!_led_on) _handle_forward(state, settings->bit.button_should_sound); - if (!watch_get_pin_level(BTN_LIGHT)) _led_on = false; + if (state->mode <= SCREEN_ELEMENT) { + _handle_forward(state, settings->bit.button_should_sound); + } + else { + state->mode = SCREEN_ELEMENT; + _display_screen(state, settings->bit.button_should_sound); + } break; case EVENT_ALARM_LONG_PRESS: - if (!_led_on) { + if (state->mode <= SCREEN_ELEMENT) { start_quick_cyc(); _handle_forward(state, settings->bit.button_should_sound); } break; case EVENT_LIGHT_LONG_PRESS: - if (!_led_on) { + if (state->mode <= SCREEN_ELEMENT) { start_quick_cyc(); _handle_backward(state, settings->bit.button_should_sound); } - break; - case EVENT_LIGHT_BUTTON_DOWN: - if (watch_get_pin_level(BTN_ALARM)) _led_on = true; - stop_quick_cyc(); - break; - case EVENT_ALARM_BUTTON_DOWN: - if (watch_get_pin_level(BTN_LIGHT)) _led_on = true; - stop_quick_cyc(); - break; - case EVENT_LIGHT_LONG_UP: - _led_on = false; - break; - case EVENT_ALARM_LONG_UP: - _led_on = false; + else { + movement_illuminate_led(); + } break; case EVENT_MODE_BUTTON_UP: if (state->mode == SCREEN_TITLE) movement_move_to_next_face(); @@ -488,8 +488,6 @@ bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, v return movement_default_loop_handler(event, settings); } - if (_led_on) movement_illuminate_led(); - return true; } diff --git a/movement/watch_faces/complication/periodic_face.h b/movement/watch_faces/complication/periodic_face.h index c310082..730b0fe 100644 --- a/movement/watch_faces/complication/periodic_face.h +++ b/movement/watch_faces/complication/periodic_face.h @@ -50,18 +50,17 @@ * If you are in a subscreen and just keep holding MODE, you will go through all of these menus without needing to depress. * * Light Press - * Previous Element + * On Title or Element Symbol Screen: Previous Element + * Else: Display currenlt-selected element symbol page * Light Hold - * Fast Cycle through Previous Elements + * On Title Screen or Element Symbol: Fast Cycle through Previous Elements + * Else: Activate LED backlight * * Alarm Press - * Next Element + * On Title or Element Symbol Screen: Next Element + * Else: Display currenlt-selected element symbol page * Alarm Hold - * Fast Cycle through Next Elements - * - * Light & Alarm Hold - * Activate LED backlight - * + * On Title Screen or Element Symbol: Fast Cycle through Next Elements */ #define MODE_VIEW 0 From a9e6b82f0022847ee5257da2158c57b8575e70c8 Mon Sep 17 00:00:00 2001 From: PrimmR Date: Thu, 25 Jul 2024 15:31:25 +0100 Subject: [PATCH 032/220] Update timeout & low energy behaviour --- movement/watch_faces/complication/periodic_face.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/periodic_face.c b/movement/watch_faces/complication/periodic_face.c index 85753ff..69ae4ad 100644 --- a/movement/watch_faces/complication/periodic_face.c +++ b/movement/watch_faces/complication/periodic_face.c @@ -477,13 +477,16 @@ bool periodic_face_loop(movement_event_t event, movement_settings_t *settings, v _ts_ticks = 2; break; case EVENT_TIMEOUT: - break; - case EVENT_LOW_ENERGY_UPDATE: - // Display title in LE mode. + // Display title after timeout if (state->mode == SCREEN_TITLE) break; state->mode = SCREEN_TITLE; _display_screen(state, false); break; + case EVENT_LOW_ENERGY_UPDATE: + // Display static title and tick animation during LE + watch_display_string("Pd Table", 0); + watch_start_tick_animation(500); + break; default: return movement_default_loop_handler(event, settings); } From 4375ca37e0ccfd60602cade0a571c363c80c58c1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 07:39:51 -0400 Subject: [PATCH 033/220] Added debouncing --- movement/movement.c | 60 +++++++++++++++++++++++++++++++++------------ movement/movement.h | 1 + 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index cb3dcf7..cfe914a 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -183,6 +183,17 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } +#define DEBOUNCE_FREQ 64 // 64 HZ is 15.625ms +static void cb_debounce(void) { + movement_state.debounce_occurring = false; + watch_rtc_disable_periodic_callback(DEBOUNCE_FREQ); +} + +static inline void _movement_enable_debounce_tick(void) { + movement_state.debounce_occurring = true; + watch_rtc_register_periodic_callback(cb_debounce, DEBOUNCE_FREQ); +} + static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -222,16 +233,28 @@ static void _movement_handle_scheduled_tasks(void) { } } +static uint8_t swap_endian(uint8_t num) { + uint8_t result = 0; + int i; + for (i = 0; i < 8; i++) { + result <<= 1; + result |= (num & 1); + num >>= 1; + } + return result; +} + void movement_request_tick_frequency(uint8_t freq) { // Movement uses the 128 Hz tick internally - if (freq == 128) return; + if (freq == 128 || freq == DEBOUNCE_FREQ ) return; // Movement requires at least a 1 Hz tick. // If we are asked for an invalid frequency, default back to 1 Hz. if (freq == 0 || __builtin_popcount(freq) != 1) freq = 1; // disable all callbacks except the 128 Hz one - watch_rtc_disable_matching_periodic_callbacks(0xFE); + int disable_mask = 0xFE ^ swap_endian(DEBOUNCE_FREQ); + watch_rtc_disable_matching_periodic_callbacks(disable_mask); movement_state.subsecond = 0; movement_state.tick_frequency = freq; @@ -614,6 +637,8 @@ bool app_loop(void) { static movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint16_t *down_timestamp) { // force alarm off if the user pressed a button. if (movement_state.alarm_ticks) movement_state.alarm_ticks = 0; + if ( movement_state.debounce_occurring) + return EVENT_NONE; if (pin_level) { // handle rising edge @@ -628,6 +653,7 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; _movement_disable_fast_tick_if_possible(); + _movement_enable_debounce_tick(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; @@ -663,20 +689,22 @@ void cb_alarm_fired(void) { void cb_fast_tick(void) { movement_state.fast_ticks++; - if (movement_state.light_ticks > 0) movement_state.light_ticks--; - if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; - // check timestamps and auto-fire the long-press events - // Notice: is it possible that two or more buttons have an identical timestamp? In this case - // only one of these buttons would receive the long press event. Don't bother for now... - if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_LIGHT_LONG_PRESS; - if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_MODE_LONG_PRESS; - if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_ALARM_LONG_PRESS; + if (!movement_state.debounce_occurring) { + if (movement_state.light_ticks > 0) movement_state.light_ticks--; + if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; + // check timestamps and auto-fire the long-press events + // Notice: is it possible that two or more buttons have an identical timestamp? In this case + // only one of these buttons would receive the long press event. Don't bother for now... + if (movement_state.light_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_LIGHT_LONG_PRESS; + if (movement_state.mode_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_MODE_LONG_PRESS; + if (movement_state.alarm_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_ALARM_LONG_PRESS; + } // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. if (movement_state.fast_ticks >= 128 * 20) { diff --git a/movement/movement.h b/movement/movement.h index 1dabfbc..56dfc7d 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -267,6 +267,7 @@ typedef struct { bool needs_background_tasks_handled; bool has_scheduled_background_task; bool needs_wake; + bool debounce_occurring; // low energy mode countdown int32_t le_mode_ticks; From 7d5aaf60caa943a248a7f635111ddfd50c230389 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 07:44:31 -0400 Subject: [PATCH 034/220] Leaving sleep with alarm button up doesn't trigger alarm button --- movement/movement.c | 9 ++++++++- movement/movement.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/movement/movement.c b/movement/movement.c index cfe914a..87110da 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -490,6 +490,7 @@ static void _sleep_mode_app_loop(void) { // we also have to handle background tasks here in the mini-runloop if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks(); + movement_state.ignore_alarm_after_sleep = true; event.event_type = EVENT_LOW_ENERGY_UPDATE; watch_faces[movement_state.current_face_idx].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]); @@ -675,9 +676,15 @@ void cb_mode_btn_interrupt(void) { void cb_alarm_btn_interrupt(void) { bool pin_level = watch_get_pin_level(BTN_ALARM); _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + if (movement_state.ignore_alarm_after_sleep){ + if (event_type == EVENT_ALARM_BUTTON_UP) movement_state.ignore_alarm_after_sleep = false; + return; + } + event.event_type = event_type; } + void cb_alarm_btn_extwake(void) { // wake up! _movement_reset_inactivity_countdown(); diff --git a/movement/movement.h b/movement/movement.h index 56dfc7d..1af8975 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -271,6 +271,7 @@ typedef struct { // low energy mode countdown int32_t le_mode_ticks; + bool ignore_alarm_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) int16_t timeout_ticks; From df2dac5a073537fc45657f5310a68f33884804aa Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 18:12:46 -0400 Subject: [PATCH 035/220] debouince now uses cb_fast_tick and holding the button now also gets debounced --- movement/movement.c | 106 ++++++++++++++++++++++++++++---------------- movement/movement.h | 9 +++- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 87110da..9f6f295 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -183,17 +183,6 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } -#define DEBOUNCE_FREQ 64 // 64 HZ is 15.625ms -static void cb_debounce(void) { - movement_state.debounce_occurring = false; - watch_rtc_disable_periodic_callback(DEBOUNCE_FREQ); -} - -static inline void _movement_enable_debounce_tick(void) { - movement_state.debounce_occurring = true; - watch_rtc_register_periodic_callback(cb_debounce, DEBOUNCE_FREQ); -} - static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -409,6 +398,9 @@ void app_init(void) { movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; + movement_state.debounce_ticks_light = -1; + movement_state.debounce_ticks_alarm = -1; + movement_state.debounce_ticks_mode = -1; movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); @@ -485,12 +477,12 @@ void app_wake_from_standby(void) { static void _sleep_mode_app_loop(void) { movement_state.needs_wake = false; + movement_state.ignore_alarm_btn_after_sleep = true; // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep. while (movement_state.le_mode_ticks == -1) { // we also have to handle background tasks here in the mini-runloop if (movement_state.needs_background_tasks_handled) _movement_handle_background_tasks(); - movement_state.ignore_alarm_after_sleep = true; event.event_type = EVENT_LOW_ENERGY_UPDATE; watch_faces[movement_state.current_face_idx].loop(event, &movement_state.settings, watch_face_contexts[movement_state.current_face_idx]); @@ -638,8 +630,6 @@ bool app_loop(void) { static movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint16_t *down_timestamp) { // force alarm off if the user pressed a button. if (movement_state.alarm_ticks) movement_state.alarm_ticks = 0; - if ( movement_state.debounce_occurring) - return EVENT_NONE; if (pin_level) { // handle rising edge @@ -654,36 +644,49 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; _movement_disable_fast_tick_if_possible(); - _movement_enable_debounce_tick(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -void cb_light_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_LIGHT); +static void light_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -void cb_mode_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_MODE); +static void mode_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -void cb_alarm_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_ALARM); +static void alarm_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); - if (movement_state.ignore_alarm_after_sleep){ - if (event_type == EVENT_ALARM_BUTTON_UP) movement_state.ignore_alarm_after_sleep = false; + if (movement_state.ignore_alarm_btn_after_sleep){ + if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; } event.event_type = event_type; } +void cb_light_btn_interrupt(void) { + movement_state.debounce_prev_pin_light = watch_get_pin_level(BTN_LIGHT); + movement_state.debounce_ticks_light = 1; + _movement_enable_fast_tick_if_needed(); +} + +void cb_mode_btn_interrupt(void) { + movement_state.debounce_prev_pin_mode = watch_get_pin_level(BTN_MODE); + movement_state.debounce_ticks_mode = 1; + _movement_enable_fast_tick_if_needed(); +} + +void cb_alarm_btn_interrupt(void) { + movement_state.debounce_prev_pin_alarm = watch_get_pin_level(BTN_ALARM); + movement_state.debounce_ticks_alarm = 1; + _movement_enable_fast_tick_if_needed(); +} void cb_alarm_btn_extwake(void) { // wake up! @@ -695,23 +698,48 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - movement_state.fast_ticks++; - if (!movement_state.debounce_occurring) { - if (movement_state.light_ticks > 0) movement_state.light_ticks--; - if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; - // check timestamps and auto-fire the long-press events - // Notice: is it possible that two or more buttons have an identical timestamp? In this case - // only one of these buttons would receive the long press event. Don't bother for now... - if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_LIGHT_LONG_PRESS; - if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_MODE_LONG_PRESS; - if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_ALARM_LONG_PRESS; + if (movement_state.debounce_ticks_light == 0) { + if (movement_state.debounce_prev_pin_light == watch_get_pin_level(BTN_LIGHT)) + light_btn_action(movement_state.debounce_prev_pin_light); + movement_state.debounce_ticks_light = -1; } + else if (movement_state.debounce_ticks_light > 0) { + movement_state.debounce_ticks_light--; + return; + } + if (movement_state.debounce_ticks_alarm == 0) { + if (movement_state.debounce_prev_pin_alarm == watch_get_pin_level(BTN_ALARM)) + alarm_btn_action(movement_state.debounce_prev_pin_alarm); + movement_state.debounce_ticks_alarm = -1; + } + else if (movement_state.debounce_ticks_alarm > 0) { + movement_state.debounce_ticks_alarm--; + return; + } + if (movement_state.debounce_ticks_mode == 0) { + if (movement_state.debounce_prev_pin_mode == watch_get_pin_level(BTN_MODE)) + mode_btn_action(movement_state.debounce_prev_pin_mode); + movement_state.debounce_ticks_mode = -1; + } + else if (movement_state.debounce_ticks_mode > 0) { + movement_state.debounce_ticks_mode--; + return; + } + movement_state.fast_ticks++; + if (movement_state.light_ticks > 0) movement_state.light_ticks--; + if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; + // check timestamps and auto-fire the long-press events + // Notice: is it possible that two or more buttons have an identical timestamp? In this case + // only one of these buttons would receive the long press event. Don't bother for now... + if (movement_state.light_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_LIGHT_LONG_PRESS; + if (movement_state.mode_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_MODE_LONG_PRESS; + if (movement_state.alarm_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_ALARM_LONG_PRESS; // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. if (movement_state.fast_ticks >= 128 * 20) { diff --git a/movement/movement.h b/movement/movement.h index 1af8975..3601d48 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -267,11 +267,16 @@ typedef struct { bool needs_background_tasks_handled; bool has_scheduled_background_task; bool needs_wake; - bool debounce_occurring; // low energy mode countdown int32_t le_mode_ticks; - bool ignore_alarm_after_sleep; + int8_t debounce_ticks_light; + int8_t debounce_ticks_alarm; + int8_t debounce_ticks_mode; + bool debounce_prev_pin_light; + bool debounce_prev_pin_alarm; + bool debounce_prev_pin_mode; + bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) int16_t timeout_ticks; From 4a4fce428e3230099977d1dc674865f1d08613d5 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 18:24:58 -0400 Subject: [PATCH 036/220] Removed some dead code --- movement/movement.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 9f6f295..6bcbf63 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -222,28 +222,16 @@ static void _movement_handle_scheduled_tasks(void) { } } -static uint8_t swap_endian(uint8_t num) { - uint8_t result = 0; - int i; - for (i = 0; i < 8; i++) { - result <<= 1; - result |= (num & 1); - num >>= 1; - } - return result; -} - void movement_request_tick_frequency(uint8_t freq) { // Movement uses the 128 Hz tick internally - if (freq == 128 || freq == DEBOUNCE_FREQ ) return; + if (freq == 128) return; // Movement requires at least a 1 Hz tick. // If we are asked for an invalid frequency, default back to 1 Hz. if (freq == 0 || __builtin_popcount(freq) != 1) freq = 1; // disable all callbacks except the 128 Hz one - int disable_mask = 0xFE ^ swap_endian(DEBOUNCE_FREQ); - watch_rtc_disable_matching_periodic_callbacks(disable_mask); + watch_rtc_disable_matching_periodic_callbacks(0xFE); movement_state.subsecond = 0; movement_state.tick_frequency = freq; From 947e299494fb34f44daf956d95e9c79769af683b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 20:13:54 -0400 Subject: [PATCH 037/220] Made the debounce register rising edges rather than falling edges --- movement/movement.c | 87 ++++++++++++++++++++++++++------------------- movement/movement.h | 12 +++---- 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 6bcbf63..f665792 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -99,6 +99,8 @@ #include #endif +#define DEBOUNCE_TICKS 2 // In terms of *7.8125ms + movement_state_t movement_state; void * watch_face_contexts[MOVEMENT_NUM_FACES]; watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES]; @@ -169,6 +171,9 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; + movement_state.debounce_ticks_light = 0; + movement_state.debounce_ticks_alarm = 0; + movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -386,9 +391,6 @@ void app_init(void) { movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; - movement_state.debounce_ticks_light = -1; - movement_state.debounce_ticks_alarm = -1; - movement_state.debounce_ticks_mode = -1; movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); @@ -638,17 +640,20 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e } } -static void light_btn_action(bool pin_level) { +static void light_btn_action(void) { + bool pin_level = watch_get_pin_level(BTN_LIGHT); _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -static void mode_btn_action(bool pin_level) { +static void mode_btn_action(void) { + bool pin_level = watch_get_pin_level(BTN_MODE); _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -static void alarm_btn_action(bool pin_level) { +static void alarm_btn_action(void) { + bool pin_level = watch_get_pin_level(BTN_ALARM); _movement_reset_inactivity_countdown(); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ @@ -659,20 +664,17 @@ static void alarm_btn_action(bool pin_level) { } void cb_light_btn_interrupt(void) { - movement_state.debounce_prev_pin_light = watch_get_pin_level(BTN_LIGHT); - movement_state.debounce_ticks_light = 1; + movement_state.debounce_btn_trig_light = true; _movement_enable_fast_tick_if_needed(); } void cb_mode_btn_interrupt(void) { - movement_state.debounce_prev_pin_mode = watch_get_pin_level(BTN_MODE); - movement_state.debounce_ticks_mode = 1; + movement_state.debounce_btn_trig_mode = true; _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_interrupt(void) { - movement_state.debounce_prev_pin_alarm = watch_get_pin_level(BTN_ALARM); - movement_state.debounce_ticks_alarm = 1; + movement_state.debounce_btn_trig_alarm = true; _movement_enable_fast_tick_if_needed(); } @@ -686,34 +688,45 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light == 0) { - if (movement_state.debounce_prev_pin_light == watch_get_pin_level(BTN_LIGHT)) - light_btn_action(movement_state.debounce_prev_pin_light); - movement_state.debounce_ticks_light = -1; + // printf("%d \r\n", movement_state.fast_ticks); + if (movement_state.debounce_ticks_light > 0) movement_state.debounce_ticks_light--; + if (movement_state.debounce_ticks_alarm > 0) movement_state.debounce_ticks_alarm--; + if (movement_state.debounce_ticks_mode > 0) movement_state.debounce_ticks_mode--; + if (movement_state.debounce_btn_trig_light) { + movement_state.debounce_btn_trig_light = false; + if (movement_state.debounce_ticks_light == 0) { + light_btn_action(); + movement_state.debounce_ticks_light = DEBOUNCE_TICKS; + } + else { + movement_state.light_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } } - else if (movement_state.debounce_ticks_light > 0) { - movement_state.debounce_ticks_light--; - return; + if (movement_state.debounce_btn_trig_alarm) { + movement_state.debounce_btn_trig_alarm = false; + if (movement_state.debounce_ticks_alarm == 0) { + alarm_btn_action(); + movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; + } + else { + movement_state.alarm_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } } - if (movement_state.debounce_ticks_alarm == 0) { - if (movement_state.debounce_prev_pin_alarm == watch_get_pin_level(BTN_ALARM)) - alarm_btn_action(movement_state.debounce_prev_pin_alarm); - movement_state.debounce_ticks_alarm = -1; + if (movement_state.debounce_btn_trig_mode) { + movement_state.debounce_btn_trig_mode = false; + if (movement_state.debounce_ticks_mode == 0) { + mode_btn_action(); + movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; + } + else { + movement_state.mode_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } } - else if (movement_state.debounce_ticks_alarm > 0) { - movement_state.debounce_ticks_alarm--; - return; - } - if (movement_state.debounce_ticks_mode == 0) { - if (movement_state.debounce_prev_pin_mode == watch_get_pin_level(BTN_MODE)) - mode_btn_action(movement_state.debounce_prev_pin_mode); - movement_state.debounce_ticks_mode = -1; - } - else if (movement_state.debounce_ticks_mode > 0) { - movement_state.debounce_ticks_mode--; - return; - } - movement_state.fast_ticks++; + if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) + movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; // check timestamps and auto-fire the long-press events diff --git a/movement/movement.h b/movement/movement.h index 3601d48..81e55ab 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -270,12 +270,12 @@ typedef struct { // low energy mode countdown int32_t le_mode_ticks; - int8_t debounce_ticks_light; - int8_t debounce_ticks_alarm; - int8_t debounce_ticks_mode; - bool debounce_prev_pin_light; - bool debounce_prev_pin_alarm; - bool debounce_prev_pin_mode; + uint8_t debounce_ticks_light; + uint8_t debounce_ticks_alarm; + uint8_t debounce_ticks_mode; + bool debounce_btn_trig_light; + bool debounce_btn_trig_alarm; + bool debounce_btn_trig_mode; bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) From 9727dac3c34b918b8ba3a5de2ce34474ffee61a6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 20:28:10 -0400 Subject: [PATCH 038/220] Revert "Leaving sleep with alarm button up doesn't trigger alarm button" This reverts commit 7d5aaf60caa943a248a7f635111ddfd50c230389. --- movement/movement.c | 114 ++++++++++++++------------------------------ movement/movement.h | 7 +-- 2 files changed, 37 insertions(+), 84 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index f665792..a9ff2fa 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -99,8 +99,6 @@ #include #endif -#define DEBOUNCE_TICKS 2 // In terms of *7.8125ms - movement_state_t movement_state; void * watch_face_contexts[MOVEMENT_NUM_FACES]; watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES]; @@ -171,9 +169,6 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; - movement_state.debounce_ticks_light = 0; - movement_state.debounce_ticks_alarm = 0; - movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -188,6 +183,16 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } +static void cb_debounce(void) { + movement_state.debounce_occurring = false; + watch_rtc_disable_periodic_callback(64); // 64 HZ is 15.625ms +} + +static inline void _movement_enable_debounce_tick(void) { + movement_state.debounce_occurring = true; + watch_rtc_register_periodic_callback(cb_debounce, 64); +} + static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -228,15 +233,15 @@ static void _movement_handle_scheduled_tasks(void) { } void movement_request_tick_frequency(uint8_t freq) { - // Movement uses the 128 Hz tick internally - if (freq == 128) return; + // Movement uses the 128 Hz tick internally; 64 is th edebounce frequency + if (freq == 128 || freq == 64 ) return; // Movement requires at least a 1 Hz tick. // If we are asked for an invalid frequency, default back to 1 Hz. if (freq == 0 || __builtin_popcount(freq) != 1) freq = 1; // disable all callbacks except the 128 Hz one - watch_rtc_disable_matching_periodic_callbacks(0xFE); + watch_rtc_disable_matching_periodic_callbacks(0xFC); movement_state.subsecond = 0; movement_state.tick_frequency = freq; @@ -620,6 +625,8 @@ bool app_loop(void) { static movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint16_t *down_timestamp) { // force alarm off if the user pressed a button. if (movement_state.alarm_ticks) movement_state.alarm_ticks = 0; + if ( movement_state.debounce_occurring) + return EVENT_NONE; if (pin_level) { // handle rising edge @@ -634,49 +641,36 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; _movement_disable_fast_tick_if_possible(); + _movement_enable_debounce_tick(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -static void light_btn_action(void) { +void cb_light_btn_interrupt(void) { bool pin_level = watch_get_pin_level(BTN_LIGHT); _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -static void mode_btn_action(void) { +void cb_mode_btn_interrupt(void) { bool pin_level = watch_get_pin_level(BTN_MODE); _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -static void alarm_btn_action(void) { +void cb_alarm_btn_interrupt(void) { bool pin_level = watch_get_pin_level(BTN_ALARM); _movement_reset_inactivity_countdown(); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ - if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; + if (event_type == EVENT_ALARM_BUTTON_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; } event.event_type = event_type; } -void cb_light_btn_interrupt(void) { - movement_state.debounce_btn_trig_light = true; - _movement_enable_fast_tick_if_needed(); -} - -void cb_mode_btn_interrupt(void) { - movement_state.debounce_btn_trig_mode = true; - _movement_enable_fast_tick_if_needed(); -} - -void cb_alarm_btn_interrupt(void) { - movement_state.debounce_btn_trig_alarm = true; - _movement_enable_fast_tick_if_needed(); -} void cb_alarm_btn_extwake(void) { // wake up! @@ -688,59 +682,23 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - // printf("%d \r\n", movement_state.fast_ticks); - if (movement_state.debounce_ticks_light > 0) movement_state.debounce_ticks_light--; - if (movement_state.debounce_ticks_alarm > 0) movement_state.debounce_ticks_alarm--; - if (movement_state.debounce_ticks_mode > 0) movement_state.debounce_ticks_mode--; - if (movement_state.debounce_btn_trig_light) { - movement_state.debounce_btn_trig_light = false; - if (movement_state.debounce_ticks_light == 0) { - light_btn_action(); - movement_state.debounce_ticks_light = DEBOUNCE_TICKS; - } - else { - movement_state.light_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } + movement_state.fast_ticks++; + if (!movement_state.debounce_occurring) { + if (movement_state.light_ticks > 0) movement_state.light_ticks--; + if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; + // check timestamps and auto-fire the long-press events + // Notice: is it possible that two or more buttons have an identical timestamp? In this case + // only one of these buttons would receive the long press event. Don't bother for now... + if (movement_state.light_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_LIGHT_LONG_PRESS; + if (movement_state.mode_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_MODE_LONG_PRESS; + if (movement_state.alarm_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_ALARM_LONG_PRESS; } - if (movement_state.debounce_btn_trig_alarm) { - movement_state.debounce_btn_trig_alarm = false; - if (movement_state.debounce_ticks_alarm == 0) { - alarm_btn_action(); - movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; - } - else { - movement_state.alarm_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } - } - if (movement_state.debounce_btn_trig_mode) { - movement_state.debounce_btn_trig_mode = false; - if (movement_state.debounce_ticks_mode == 0) { - mode_btn_action(); - movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; - } - else { - movement_state.mode_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } - } - if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) - movement_state.fast_ticks++; - if (movement_state.light_ticks > 0) movement_state.light_ticks--; - if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; - // check timestamps and auto-fire the long-press events - // Notice: is it possible that two or more buttons have an identical timestamp? In this case - // only one of these buttons would receive the long press event. Don't bother for now... - if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_LIGHT_LONG_PRESS; - if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_MODE_LONG_PRESS; - if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_ALARM_LONG_PRESS; // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. if (movement_state.fast_ticks >= 128 * 20) { diff --git a/movement/movement.h b/movement/movement.h index 81e55ab..5829ba8 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -267,15 +267,10 @@ typedef struct { bool needs_background_tasks_handled; bool has_scheduled_background_task; bool needs_wake; + bool debounce_occurring; // low energy mode countdown int32_t le_mode_ticks; - uint8_t debounce_ticks_light; - uint8_t debounce_ticks_alarm; - uint8_t debounce_ticks_mode; - bool debounce_btn_trig_light; - bool debounce_btn_trig_alarm; - bool debounce_btn_trig_mode; bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) From 36117ca20770c7c8815507b1c2e72677f4d49011 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 20:48:17 -0400 Subject: [PATCH 039/220] using cb_fast_tick again --- movement/movement.c | 128 ++++++++++++++++++++++++++++++-------------- movement/movement.h | 7 ++- 2 files changed, 95 insertions(+), 40 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index a9ff2fa..1c043fc 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -99,6 +99,8 @@ #include #endif +#define DEBOUNCE_TICKS 20 // In terms of *7.8125ms + movement_state_t movement_state; void * watch_face_contexts[MOVEMENT_NUM_FACES]; watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES]; @@ -169,6 +171,9 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; + movement_state.debounce_ticks_light = 0; + movement_state.debounce_ticks_alarm = 0; + movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -183,16 +188,6 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } -static void cb_debounce(void) { - movement_state.debounce_occurring = false; - watch_rtc_disable_periodic_callback(64); // 64 HZ is 15.625ms -} - -static inline void _movement_enable_debounce_tick(void) { - movement_state.debounce_occurring = true; - watch_rtc_register_periodic_callback(cb_debounce, 64); -} - static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -233,15 +228,15 @@ static void _movement_handle_scheduled_tasks(void) { } void movement_request_tick_frequency(uint8_t freq) { - // Movement uses the 128 Hz tick internally; 64 is th edebounce frequency - if (freq == 128 || freq == 64 ) return; + // Movement uses the 128 Hz tick internally + if (freq == 128) return; // Movement requires at least a 1 Hz tick. // If we are asked for an invalid frequency, default back to 1 Hz. if (freq == 0 || __builtin_popcount(freq) != 1) freq = 1; // disable all callbacks except the 128 Hz one - watch_rtc_disable_matching_periodic_callbacks(0xFC); + watch_rtc_disable_matching_periodic_callbacks(0xFE); movement_state.subsecond = 0; movement_state.tick_frequency = freq; @@ -625,8 +620,6 @@ bool app_loop(void) { static movement_event_type_t _figure_out_button_event(bool pin_level, movement_event_type_t button_down_event_type, uint16_t *down_timestamp) { // force alarm off if the user pressed a button. if (movement_state.alarm_ticks) movement_state.alarm_ticks = 0; - if ( movement_state.debounce_occurring) - return EVENT_NONE; if (pin_level) { // handle rising edge @@ -641,36 +634,46 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; _movement_disable_fast_tick_if_possible(); - _movement_enable_debounce_tick(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -void cb_light_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_LIGHT); +static void light_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -void cb_mode_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_MODE); +static void mode_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -void cb_alarm_btn_interrupt(void) { - bool pin_level = watch_get_pin_level(BTN_ALARM); +static void alarm_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ - if (event_type == EVENT_ALARM_BUTTON_UP) movement_state.ignore_alarm_btn_after_sleep = false; + if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; } event.event_type = event_type; } +void cb_light_btn_interrupt(void) { + movement_state.debounce_btn_trig_light = true; + _movement_enable_fast_tick_if_needed(); +} + +void cb_mode_btn_interrupt(void) { + movement_state.debounce_btn_trig_mode = true; + _movement_enable_fast_tick_if_needed(); +} + +void cb_alarm_btn_interrupt(void) { + movement_state.debounce_btn_trig_alarm = true; + _movement_enable_fast_tick_if_needed(); +} void cb_alarm_btn_extwake(void) { // wake up! @@ -682,23 +685,70 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - movement_state.fast_ticks++; - if (!movement_state.debounce_occurring) { - if (movement_state.light_ticks > 0) movement_state.light_ticks--; - if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; - // check timestamps and auto-fire the long-press events - // Notice: is it possible that two or more buttons have an identical timestamp? In this case - // only one of these buttons would receive the long press event. Don't bother for now... - if (movement_state.light_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_LIGHT_LONG_PRESS; - if (movement_state.mode_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_MODE_LONG_PRESS; - if (movement_state.alarm_down_timestamp > 0) - if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) - event.event_type = EVENT_ALARM_LONG_PRESS; + if (movement_state.debounce_ticks_light > 0) movement_state.debounce_ticks_light--; + if (movement_state.debounce_ticks_alarm > 0) movement_state.debounce_ticks_alarm--; + if (movement_state.debounce_ticks_mode > 0) movement_state.debounce_ticks_mode--; + if (movement_state.debounce_btn_trig_light) { + bool pin_level = watch_get_pin_level(BTN_LIGHT); + movement_state.debounce_btn_trig_light = false; + if (pin_level) { + light_btn_action(pin_level); + } + else if (movement_state.debounce_ticks_light == 0) { + light_btn_action(pin_level); + movement_state.debounce_ticks_light = DEBOUNCE_TICKS; + } + else { + movement_state.light_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } } + if (movement_state.debounce_btn_trig_alarm) { + bool pin_level = watch_get_pin_level(BTN_ALARM); + movement_state.debounce_btn_trig_alarm = false; + if (pin_level) { + alarm_btn_action(pin_level); + } + else if (movement_state.debounce_ticks_alarm == 0) { + alarm_btn_action(pin_level); + movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; + } + else { + movement_state.alarm_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } + } + if (movement_state.debounce_btn_trig_mode) { + bool pin_level = watch_get_pin_level(BTN_MODE); + movement_state.debounce_btn_trig_mode = false; + if (pin_level) { + mode_btn_action(pin_level); + } + else if (movement_state.debounce_ticks_mode == 0) { + mode_btn_action(pin_level); + movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; + } + else { + movement_state.mode_down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } + } + if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) + movement_state.fast_ticks++; + if (movement_state.light_ticks > 0) movement_state.light_ticks--; + if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; + // check timestamps and auto-fire the long-press events + // Notice: is it possible that two or more buttons have an identical timestamp? In this case + // only one of these buttons would receive the long press event. Don't bother for now... + if (movement_state.light_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.light_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_LIGHT_LONG_PRESS; + if (movement_state.mode_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.mode_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_MODE_LONG_PRESS; + if (movement_state.alarm_down_timestamp > 0) + if (movement_state.fast_ticks - movement_state.alarm_down_timestamp == MOVEMENT_LONG_PRESS_TICKS + 1) + event.event_type = EVENT_ALARM_LONG_PRESS; // this is just a fail-safe; fast tick should be disabled as soon as the button is up, the LED times out, and/or the alarm finishes. // but if for whatever reason it isn't, this forces the fast tick off after 20 seconds. if (movement_state.fast_ticks >= 128 * 20) { diff --git a/movement/movement.h b/movement/movement.h index 5829ba8..81e55ab 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -267,10 +267,15 @@ typedef struct { bool needs_background_tasks_handled; bool has_scheduled_background_task; bool needs_wake; - bool debounce_occurring; // low energy mode countdown int32_t le_mode_ticks; + uint8_t debounce_ticks_light; + uint8_t debounce_ticks_alarm; + uint8_t debounce_ticks_mode; + bool debounce_btn_trig_light; + bool debounce_btn_trig_alarm; + bool debounce_btn_trig_mode; bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) From 73c3ba3ae7489098a28462a6fb7e7e2c89968665 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 21:05:19 -0400 Subject: [PATCH 040/220] Cleaned up code --- movement/movement.c | 70 ++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 1c043fc..7b93892 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -684,55 +684,29 @@ void cb_alarm_fired(void) { movement_state.needs_background_tasks_handled = true; } +static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, bool *debounce_btn_trig, uint16_t *down_timestamp, void (*function)(bool)) { + if (*debounce_ticks > 0) (*debounce_ticks)--; + if (*debounce_btn_trig) { + bool pin_level = watch_get_pin_level(pin); + *debounce_btn_trig = false; + if (pin_level) { + function(pin_level); + } + else if (*debounce_ticks == 0) { + function(pin_level); + *debounce_ticks = DEBOUNCE_TICKS; + } + else { + *down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); + } + } +} + void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light > 0) movement_state.debounce_ticks_light--; - if (movement_state.debounce_ticks_alarm > 0) movement_state.debounce_ticks_alarm--; - if (movement_state.debounce_ticks_mode > 0) movement_state.debounce_ticks_mode--; - if (movement_state.debounce_btn_trig_light) { - bool pin_level = watch_get_pin_level(BTN_LIGHT); - movement_state.debounce_btn_trig_light = false; - if (pin_level) { - light_btn_action(pin_level); - } - else if (movement_state.debounce_ticks_light == 0) { - light_btn_action(pin_level); - movement_state.debounce_ticks_light = DEBOUNCE_TICKS; - } - else { - movement_state.light_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } - } - if (movement_state.debounce_btn_trig_alarm) { - bool pin_level = watch_get_pin_level(BTN_ALARM); - movement_state.debounce_btn_trig_alarm = false; - if (pin_level) { - alarm_btn_action(pin_level); - } - else if (movement_state.debounce_ticks_alarm == 0) { - alarm_btn_action(pin_level); - movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; - } - else { - movement_state.alarm_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } - } - if (movement_state.debounce_btn_trig_mode) { - bool pin_level = watch_get_pin_level(BTN_MODE); - movement_state.debounce_btn_trig_mode = false; - if (pin_level) { - mode_btn_action(pin_level); - } - else if (movement_state.debounce_ticks_mode == 0) { - mode_btn_action(pin_level); - movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; - } - else { - movement_state.mode_down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } - } + debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.debounce_btn_trig_light, &movement_state.light_down_timestamp, light_btn_action); + debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.debounce_btn_trig_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); + debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.debounce_btn_trig_mode, &movement_state.mode_down_timestamp, mode_btn_action); if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; From 7f2ac61375f8e746bf49b43d4ae792df54a76509 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 21:43:32 -0400 Subject: [PATCH 041/220] Fixed stuck fast_tick --- movement/movement.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 7b93892..2ce67a8 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -99,7 +99,7 @@ #include #endif -#define DEBOUNCE_TICKS 20 // In terms of *7.8125ms +#define DEBOUNCE_TICKS 2 // In terms of *7.8125ms movement_state_t movement_state; void * watch_face_contexts[MOVEMENT_NUM_FACES]; @@ -633,7 +633,6 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; @@ -685,21 +684,20 @@ void cb_alarm_fired(void) { } static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, bool *debounce_btn_trig, uint16_t *down_timestamp, void (*function)(bool)) { - if (*debounce_ticks > 0) (*debounce_ticks)--; + if (*debounce_ticks > 0) + { + if (--(*debounce_ticks) == 0) + _movement_disable_fast_tick_if_possible(); + } if (*debounce_btn_trig) { bool pin_level = watch_get_pin_level(pin); *debounce_btn_trig = false; - if (pin_level) { - function(pin_level); - } - else if (*debounce_ticks == 0) { + if (*debounce_ticks == 0) { function(pin_level); *debounce_ticks = DEBOUNCE_TICKS; } - else { + else *down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); - } } } From f7d1b8f9f33637467b95ed90c505ed66a6e27e0d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 29 Jul 2024 23:53:25 -0400 Subject: [PATCH 042/220] Delay for starting the debounce no loonger happens --- movement/movement.c | 41 +++++++++++++++++------------------------ movement/movement.h | 3 --- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 2ce67a8..0104767 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -182,6 +182,7 @@ static inline void _movement_enable_fast_tick_if_needed(void) { static inline void _movement_disable_fast_tick_if_possible(void) { if ((movement_state.light_ticks == -1) && (movement_state.alarm_ticks == -1) && + ((movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm) == 0) && ((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) { movement_state.fast_tick_enabled = false; watch_rtc_disable_periodic_callback(128); @@ -659,18 +660,28 @@ static void alarm_btn_action(bool pin_level) { event.event_type = event_type; } +static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { + bool pin_level = watch_get_pin_level(pin); + if (*debounce_ticks <= 1) { + function(pin_level); + *debounce_ticks = DEBOUNCE_TICKS; + } + else + *down_timestamp = 0; +} + void cb_light_btn_interrupt(void) { - movement_state.debounce_btn_trig_light = true; + debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); _movement_enable_fast_tick_if_needed(); } void cb_mode_btn_interrupt(void) { - movement_state.debounce_btn_trig_mode = true; + debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_interrupt(void) { - movement_state.debounce_btn_trig_alarm = true; + debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); _movement_enable_fast_tick_if_needed(); } @@ -683,28 +694,10 @@ void cb_alarm_fired(void) { movement_state.needs_background_tasks_handled = true; } -static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, bool *debounce_btn_trig, uint16_t *down_timestamp, void (*function)(bool)) { - if (*debounce_ticks > 0) - { - if (--(*debounce_ticks) == 0) - _movement_disable_fast_tick_if_possible(); - } - if (*debounce_btn_trig) { - bool pin_level = watch_get_pin_level(pin); - *debounce_btn_trig = false; - if (*debounce_ticks == 0) { - function(pin_level); - *debounce_ticks = DEBOUNCE_TICKS; - } - else - *down_timestamp = 0; - } -} - void cb_fast_tick(void) { - debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.debounce_btn_trig_light, &movement_state.light_down_timestamp, light_btn_action); - debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.debounce_btn_trig_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); - debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.debounce_btn_trig_mode, &movement_state.mode_down_timestamp, mode_btn_action); + if (movement_state.debounce_ticks_light > 0){{if (--movement_state.debounce_ticks_light == 0) {_movement_disable_fast_tick_if_possible();}}} + if (movement_state.debounce_ticks_alarm > 0){{if (--movement_state.debounce_ticks_alarm == 0) {_movement_disable_fast_tick_if_possible();}}} + if (movement_state.debounce_ticks_mode > 0){{if (--movement_state.debounce_ticks_mode == 0) {_movement_disable_fast_tick_if_possible();}}} if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; diff --git a/movement/movement.h b/movement/movement.h index 81e55ab..67ff2fe 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -273,9 +273,6 @@ typedef struct { uint8_t debounce_ticks_light; uint8_t debounce_ticks_alarm; uint8_t debounce_ticks_mode; - bool debounce_btn_trig_light; - bool debounce_btn_trig_alarm; - bool debounce_btn_trig_mode; bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) From 607946ed2e123a0a12421f0253555e826f49f116 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 30 Jul 2024 07:27:47 -0400 Subject: [PATCH 043/220] A little bit of clean-up --- movement/movement.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 0104767..c0835da 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -668,21 +668,19 @@ static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *d } else *down_timestamp = 0; + _movement_enable_fast_tick_if_needed(); } void cb_light_btn_interrupt(void) { debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); - _movement_enable_fast_tick_if_needed(); } void cb_mode_btn_interrupt(void) { debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); - _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_interrupt(void) { debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); - _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_extwake(void) { @@ -695,9 +693,9 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light > 0){{if (--movement_state.debounce_ticks_light == 0) {_movement_disable_fast_tick_if_possible();}}} - if (movement_state.debounce_ticks_alarm > 0){{if (--movement_state.debounce_ticks_alarm == 0) {_movement_disable_fast_tick_if_possible();}}} - if (movement_state.debounce_ticks_mode > 0){{if (--movement_state.debounce_ticks_mode == 0) {_movement_disable_fast_tick_if_possible();}}} + if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; From e297b3013e25c4195a8da1ea7e29457523458ee4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 31 Jul 2024 07:22:33 -0400 Subject: [PATCH 044/220] Using debounce that triggers when there's no change for Xms rather than just ignoring new presses after Xms --- movement/movement.c | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index c0835da..a4ca19a 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -171,9 +171,6 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; - movement_state.debounce_ticks_light = 0; - movement_state.debounce_ticks_alarm = 0; - movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -392,6 +389,9 @@ void app_init(void) { movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; + movement_state.debounce_ticks_light = 0; + movement_state.debounce_ticks_alarm = 0; + movement_state.debounce_ticks_mode = 0; movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); @@ -634,24 +634,28 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -static void light_btn_action(bool pin_level) { +static void light_btn_action(void) { _movement_reset_inactivity_countdown(); + bool pin_level = watch_get_pin_level(BTN_LIGHT); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -static void mode_btn_action(bool pin_level) { +static void mode_btn_action(void) { _movement_reset_inactivity_countdown(); + bool pin_level = watch_get_pin_level(BTN_MODE); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -static void alarm_btn_action(bool pin_level) { +static void alarm_btn_action(void) { _movement_reset_inactivity_countdown(); + bool pin_level = watch_get_pin_level(BTN_ALARM); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; @@ -660,27 +664,19 @@ static void alarm_btn_action(bool pin_level) { event.event_type = event_type; } -static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { - bool pin_level = watch_get_pin_level(pin); - if (*debounce_ticks <= 1) { - function(pin_level); - *debounce_ticks = DEBOUNCE_TICKS; - } - else - *down_timestamp = 0; +void cb_light_btn_interrupt(void) { + movement_state.debounce_ticks_light = DEBOUNCE_TICKS; _movement_enable_fast_tick_if_needed(); } -void cb_light_btn_interrupt(void) { - debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); -} - void cb_mode_btn_interrupt(void) { - debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); + movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; + _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_interrupt(void) { - debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); + movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; + _movement_enable_fast_tick_if_needed(); } void cb_alarm_btn_extwake(void) { @@ -693,9 +689,12 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) + light_btn_action(); + if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) + alarm_btn_action(); + if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) + mode_btn_action(); if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; From 027e42dc581fe6bd49b30c5b347fe86699df53b7 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 31 Jul 2024 07:25:09 -0400 Subject: [PATCH 045/220] Moved a few lines around to match main --- movement/movement.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index a4ca19a..33e7415 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -642,21 +642,21 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e } static void light_btn_action(void) { - _movement_reset_inactivity_countdown(); bool pin_level = watch_get_pin_level(BTN_LIGHT); + _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } static void mode_btn_action(void) { - _movement_reset_inactivity_countdown(); bool pin_level = watch_get_pin_level(BTN_MODE); + _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } static void alarm_btn_action(void) { - _movement_reset_inactivity_countdown(); bool pin_level = watch_get_pin_level(BTN_ALARM); uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + _movement_reset_inactivity_countdown(); if (movement_state.ignore_alarm_btn_after_sleep){ if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; From 7bbac4cd80a74f59443698228c21a6a4469be83d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 31 Jul 2024 07:33:25 -0400 Subject: [PATCH 046/220] Brought debounce time to 8ms rather than 15 --- movement/movement.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 33e7415..d364de5 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,6 +23,7 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 +#define DEBOUNCE_TICKS 1 // In terms of *7.8125ms #include #include @@ -99,8 +100,6 @@ #include #endif -#define DEBOUNCE_TICKS 2 // In terms of *7.8125ms - movement_state_t movement_state; void * watch_face_contexts[MOVEMENT_NUM_FACES]; watch_date_time scheduled_tasks[MOVEMENT_NUM_FACES]; From 149911e4addf203d90ed84f8a6884508637ef6d1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 2 Aug 2024 01:23:21 -0400 Subject: [PATCH 047/220] Time now auto-updates with DST --- movement/movement.c | 16 +++++++++ movement/movement.h | 1 + movement/watch_faces/clock/clock_face.c | 24 +++++++++++--- .../watch_faces/clock/simple_clock_face.c | 24 +++++++++++--- movement/watch_faces/settings/set_time_face.c | 10 ++---- watch-library/shared/watch/watch_utility.c | 33 +++++++++++++++++++ watch-library/shared/watch/watch_utility.h | 13 ++++++++ 7 files changed, 105 insertions(+), 16 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 8b45535..5c804fe 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -34,6 +34,7 @@ #include "filesystem.h" #include "movement.h" #include "shell.h" +#include "watch_utility.h" #ifndef MOVEMENT_FIRMWARE #include "movement_config.h" @@ -467,6 +468,21 @@ uint8_t movement_claim_backup_register(void) { return movement_state.next_available_backup_register++; } +int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { + if (!movement_state.settings.bit.dst_active) return movement_timezone_offsets[timezone_idx]; + uint8_t dst_result = is_dst(date_time); + switch (dst_result) + { + case DST_STARTED: + case DST_OCCURRING: + return movement_timezone_offsets[movement_dst_jump_table[timezone_idx]]; + case DST_ENDING: + case DST_ENDED: + default: + return movement_timezone_offsets[timezone_idx]; + } +} + void app_init(void) { #if defined(NO_FREQCORR) watch_rtc_freqcorr_write(0, 0); diff --git a/movement/movement.h b/movement/movement.h index d19ab3f..a89e377 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -314,5 +314,6 @@ void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); uint8_t movement_claim_backup_register(void); +int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time); #endif // MOVEMENT_H_ diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index eab5cd8..68019d8 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -280,12 +280,28 @@ void clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -bool clock_face_wants_background_task(movement_settings_t *settings, void *context) { - (void) settings; - clock_state_t *state = (clock_state_t *) context; - if (!state->time_signal_enabled) return false; +static void check_and_act_on_daylight_savings(bool dst_active, watch_date_time date_time) { + if (!dst_active) return; + uint8_t dst_result = is_dst(date_time); + switch (dst_result) + { + case DST_STARTED: + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + break; + case DST_ENDING: + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + break; + default: + return; + } + watch_rtc_set_date_time(date_time); +} +bool clock_face_wants_background_task(movement_settings_t *settings, void *context) { + clock_state_t *state = (clock_state_t *) context; watch_date_time date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(settings->bit.dst_active, date_time); + if (!state->time_signal_enabled) return false; return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index fbc2c4b..c0f8372 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -150,12 +150,28 @@ void simple_clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { - (void) settings; - simple_clock_state_t *state = (simple_clock_state_t *)context; - if (!state->signal_enabled) return false; +static void check_and_act_on_daylight_savings(bool dst_active, watch_date_time date_time) { + if (!dst_active) return; + uint8_t dst_result = is_dst(date_time); + switch (dst_result) + { + case DST_STARTED: + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + break; + case DST_ENDING: + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + break; + default: + return; + } + watch_rtc_set_date_time(date_time); +} +bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { + simple_clock_state_t *state = (simple_clock_state_t *)context; watch_date_time date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(settings->bit.dst_active, date_time); + if (!state->signal_enabled) return false; return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index fb0c806..90edac2 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -67,13 +67,6 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; case 7: // daylight savings time - if (settings->bit.dst_active) { // deactivate DST - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - settings->bit.time_zone = movement_dst_inverse_jump_table[settings->bit.time_zone]; - } else { // activate DST - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - settings->bit.time_zone = movement_dst_jump_table[settings->bit.time_zone]; - } settings->bit.dst_active = !settings->bit.dst_active; break; } @@ -161,8 +154,9 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_clear_colon(); sprintf(buf, "%s ", set_time_face_titles[current_page]); } else { + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); watch_set_colon(); - sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (movement_timezone_offsets[settings->bit.time_zone] / 60), (int8_t) (movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); + sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (tz / 60), (int8_t) (tz % 60) * (tz < 0 ? -1 : 1)); } } else { // daylight savings watch_clear_colon(); diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 64b3bb7..1410e41 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -83,6 +83,39 @@ uint8_t is_leap(uint16_t y) return !(y%4) && ((y%100) || !(y%400)); } +uint8_t is_dst(watch_date_time date_time) { + uint8_t weekday_first_of_month; + watch_date_time dst_start_time; + watch_date_time dst_end_time; + uint32_t unix_dst_start_time; + uint32_t unix_dst_end_time; + uint32_t unix_curr_time; + + dst_start_time.unit.month = 3; + dst_start_time.unit.hour = 2; + dst_start_time.unit.minute = 0; + dst_start_time.unit.second = 0; + weekday_first_of_month = watch_utility_get_iso8601_weekday_number(date_time.unit.year, dst_start_time.unit.month, 1); + dst_start_time.unit.day = 15 - weekday_first_of_month; + unix_dst_start_time = watch_utility_date_time_to_unix_time(dst_start_time, 0); + + dst_end_time.unit.month = 11; + dst_end_time.unit.hour = 2; + dst_end_time.unit.minute = 0; + dst_end_time.unit.second = 0; + weekday_first_of_month = watch_utility_get_iso8601_weekday_number(date_time.unit.year, dst_end_time.unit.month, 1); + dst_end_time.unit.day = 15 - weekday_first_of_month; + unix_dst_end_time = watch_utility_date_time_to_unix_time(dst_end_time, 0); + + date_time.unit.second = 0; + unix_curr_time = watch_utility_date_time_to_unix_time(date_time, 0); + + if (unix_curr_time == unix_dst_start_time) return DST_STARTED; + if (unix_curr_time == unix_dst_end_time) return DST_ENDING; + if (unix_curr_time > unix_dst_end_time || unix_curr_time < unix_dst_start_time) return DST_ENDED; + return DST_OCCURRING; +} + uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t day) { uint16_t DAYS_SO_FAR[] = { 0, // Jan diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h index e2326d1..d3f3981 100644 --- a/watch-library/shared/watch/watch_utility.h +++ b/watch-library/shared/watch/watch_utility.h @@ -45,6 +45,13 @@ typedef struct { uint32_t days; // 0-4294967295 } watch_duration_t; +typedef enum { + DST_STARTED, + DST_OCCURRING, + DST_ENDING, + DST_ENDED +} dst_t; + /** @brief Returns a two-letter weekday for the given timestamp, suitable for display * in positions 0-1 of the watch face * @param date_time The watch_date_time whose weekday you want. @@ -78,6 +85,12 @@ uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t */ uint8_t is_leap(uint16_t year); +/** @brief Returns off of dst_t based off if DST is occurring, srted, ended, or none of those. + * @param date_time The watch_date_time that you wish to convert. + * @return DST_OCCURRING, DST_HAPPENING, DST_ENDING, DST_ENDED + */ +uint8_t is_dst(watch_date_time date_time); + /** @brief Returns the UNIX time (seconds since 1970) for a given date/time in UTC. * @param date_time The watch_date_time that you wish to convert. * @param year The year of the date you wish to convert. From 4c546b14dc9ef8aa65dcee6560417665396c3bf0 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 2 Aug 2024 01:25:01 -0400 Subject: [PATCH 048/220] The timezone now auto-corrects with DST (but still displays the same offset in the settings screen to the user) --- movement/movement.c | 44 ------------------- movement/movement.h | 1 - movement/watch_faces/clock/beats_face.c | 4 +- .../clock/day_night_percentage_face.c | 5 ++- movement/watch_faces/clock/mars_time_face.c | 2 +- .../watch_faces/clock/world_clock2_face.c | 8 ++-- movement/watch_faces/clock/world_clock_face.c | 14 ++++-- .../watch_faces/complication/astronomy_face.c | 2 +- .../watch_faces/complication/countdown_face.c | 11 ++--- .../complication/moon_phase_face.c | 5 ++- .../watch_faces/complication/orrery_face.c | 2 +- .../complication/planetary_hours_face.c | 7 +-- .../complication/planetary_time_face.c | 8 ++-- .../watch_faces/complication/sailing_face.c | 15 ++++--- .../watch_faces/complication/solstice_face.c | 3 +- .../complication/sunrise_sunset_face.c | 5 ++- .../watch_faces/complication/timer_face.c | 11 ++--- .../watch_faces/complication/tomato_face.c | 11 ++--- movement/watch_faces/complication/totp_face.c | 5 ++- .../watch_faces/complication/totp_face_lfs.c | 3 +- .../accelerometer_data_acquisition_face.c | 2 +- .../settings/set_time_hackwatch_face.c | 5 ++- 22 files changed, 75 insertions(+), 98 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 5c804fe..52a9ac0 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -208,50 +208,6 @@ const uint8_t movement_dst_jump_table[] = { 0 // 40 AST + 1 = UTC }; -const uint8_t movement_dst_inverse_jump_table[] = { - 40, // 0 - 0, // 1 - 1, // 2 - 2, // 3 - 4, // 4 - 3, // 5 - 4, // 6 - 5, // 7 - 6, // 8 - 9, // 9 - 7, // 10 - 8, // 11 - 10, // 12 - 12, // 13 - 14, // 14 - 13, // 15 - 16, // 16 - 15, // 17 - 16, // 18 - 17, // 19 - 19, // 20 - 21, // 21 - 20, // 22 - 21, // 23 - 24, // 24 - 25, // 25 - 25, // 26 - 26, // 27 - 28, // 28 - 27, // 29 - 29, // 30 - 30, // 31 - 31, // 32 - 32, // 33 - 34, // 34 - 33, // 35 - 34, // 36 - 35, // 37 - 36, // 38 - 37, // 39 - 39 // 40 -}; - const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789"; const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378"; diff --git a/movement/movement.h b/movement/movement.h index a89e377..9aa1dbc 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -130,7 +130,6 @@ typedef struct { extern const int16_t movement_timezone_offsets[]; extern const uint8_t movement_dst_jump_table[]; -extern const uint8_t movement_dst_inverse_jump_table[]; extern const char movement_valid_position_0_chars[]; extern const char movement_valid_position_1_chars[]; diff --git a/movement/watch_faces/clock/beats_face.c b/movement/watch_faces/clock/beats_face.c index 85bcbe0..6954396 100644 --- a/movement/watch_faces/clock/beats_face.c +++ b/movement/watch_faces/clock/beats_face.c @@ -61,7 +61,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void case EVENT_ACTIVATE: case EVENT_TICK: date_time = watch_rtc_get_date_time(); - centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]); + centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time)); if (centibeats == state->last_centibeat_displayed) { // we missed this update, try again next subsecond state->next_subsecond_update = (event.subsecond + 1) % BEAT_REFRESH_FREQUENCY; @@ -76,7 +76,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void case EVENT_LOW_ENERGY_UPDATE: if (!watch_tick_animation_is_running()) watch_start_tick_animation(432); date_time = watch_rtc_get_date_time(); - centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]); + centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time)); sprintf(buf, "bt %4lu ", centibeats / 100); watch_display_string(buf, 0); diff --git a/movement/watch_faces/clock/day_night_percentage_face.c b/movement/watch_faces/clock/day_night_percentage_face.c index 86f07f9..4041c05 100644 --- a/movement/watch_faces/clock/day_night_percentage_face.c +++ b/movement/watch_faces/clock/day_night_percentage_face.c @@ -62,7 +62,8 @@ void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watc if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(day_night_percentage_state_t)); day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr; - watch_date_time utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0); + watch_date_time date_time = watch_rtc_get_date_time(); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); recalculate(utc_now, state); } } @@ -77,7 +78,7 @@ bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t char buf[12]; watch_date_time date_time = watch_rtc_get_date_time(); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); switch (event.event_type) { case EVENT_ACTIVATE: diff --git a/movement/watch_faces/clock/mars_time_face.c b/movement/watch_faces/clock/mars_time_face.c index 30f6a34..304c615 100644 --- a/movement/watch_faces/clock/mars_time_face.c +++ b/movement/watch_faces/clock/mars_time_face.c @@ -70,7 +70,7 @@ static void _h_to_hms(mars_clock_hms_t *date_time, double h) { static void _update(movement_settings_t *settings, mars_time_state_t *state) { char buf[11]; watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + uint32_t now = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); // TODO: I'm skipping over some steps here. // https://www.giss.nasa.gov/tools/mars24/help/algorithm.html double jdut = 2440587.5 + ((double)now / 86400.0); diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 2e1e969..6ff806b 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -165,6 +165,7 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, uint32_t timestamp; uint32_t previous_date_time; watch_date_time date_time; + int16_t tz; switch (event.event_type) { case EVENT_ACTIVATE: @@ -183,8 +184,9 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, /* Determine current time at time zone and store date/time */ date_time = watch_rtc_get_date_time(); - timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->current_zone] * 60); + tz = get_timezone_offset(settings->bit.time_zone, date_time); + timestamp = watch_utility_date_time_to_unix_time(date_time, tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, tz * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -284,7 +286,7 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, watch_clear_indicator(WATCH_INDICATOR_PM); refresh_face = false; } - result = div(movement_timezone_offsets[state->current_zone], 60); + result = div(get_timezone_offset(state->current_zone, watch_rtc_get_date_time()), 60); hours = result.quot; minutes = result.rem; diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index b12d9cd..c34db92 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -54,6 +54,7 @@ void world_clock_face_activate(movement_settings_t *settings, void *context) { static bool world_clock_face_do_display_mode(movement_event_t event, movement_settings_t *settings, world_clock_state_t *state) { char buf[11]; uint8_t pos; + int16_t tz; uint32_t timestamp; uint32_t previous_date_time; @@ -67,8 +68,9 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se case EVENT_TICK: case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); - timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->settings.bit.timezone_index] * 60); + tz = get_timezone_offset(settings->bit.time_zone, date_time); + timestamp = watch_utility_date_time_to_unix_time(date_time, tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, tz * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -125,6 +127,8 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se } static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_settings_t *settings, world_clock_state_t *state) { + watch_date_time date_time; + int16_t tz; switch (event.event_type) { case EVENT_MODE_BUTTON_UP: if (state->backup_register) watch_store_backup_data(state->settings.reg, state->backup_register); @@ -168,11 +172,13 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ } char buf[13]; + date_time = watch_rtc_get_date_time(); + tz = get_timezone_offset(settings->bit.time_zone, date_time); sprintf(buf, "%c%c %3d%02d ", movement_valid_position_0_chars[state->settings.bit.char_0], movement_valid_position_1_chars[state->settings.bit.char_1], - (int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] / 60), - (int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] % 60) * (movement_timezone_offsets[state->settings.bit.timezone_index] < 0 ? -1 : 1)); + (int8_t) (tz / 60), + (int8_t) (tz % 60) * (tz < 0 ? -1 : 1)); watch_set_colon(); watch_clear_indicator(WATCH_INDICATOR_PM); diff --git a/movement/watch_faces/complication/astronomy_face.c b/movement/watch_faces/complication/astronomy_face.c index 204a0a3..8db8760 100644 --- a/movement/watch_faces/complication/astronomy_face.c +++ b/movement/watch_faces/complication/astronomy_face.c @@ -80,7 +80,7 @@ static void _astronomy_face_recalculate(movement_settings_t *settings, astronomy #endif watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); date_time = watch_utility_date_time_from_unix_time(timestamp, 0); double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index be04040..04dc592 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -44,8 +44,8 @@ static void abort_quick_ticks(countdown_state_t *state) { } } -static inline int32_t get_tz_offset(movement_settings_t *settings) { - return movement_timezone_offsets[settings->bit.time_zone] * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { + return get_timezone_offset(settings->bit.time_zone, date_time) * 60; } static inline void store_countdown(countdown_state_t *state) { @@ -70,11 +70,12 @@ static inline void button_beep(movement_settings_t *settings) { static void start(countdown_state_t *state, movement_settings_t *settings) { watch_date_time now = watch_rtc_get_date_time(); + int16_t tz = get_tz_offset(settings, now); state->mode = cd_running; - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, tz); state->target_ts = watch_utility_offset_timestamp(state->now_ts, state->hours, state->minutes, state->seconds); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); movement_schedule_background_task(target_dt); watch_set_indicator(WATCH_INDICATOR_BELL); } @@ -176,7 +177,7 @@ void countdown_face_activate(movement_settings_t *settings, void *context) { countdown_state_t *state = (countdown_state_t *)context; if(state->mode == cd_running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); watch_set_indicator(WATCH_INDICATOR_BELL); } watch_set_colon(); diff --git a/movement/watch_faces/complication/moon_phase_face.c b/movement/watch_faces/complication/moon_phase_face.c index f74de64..47ed6e6 100644 --- a/movement/watch_faces/complication/moon_phase_face.c +++ b/movement/watch_faces/complication/moon_phase_face.c @@ -59,8 +59,9 @@ static void _update(movement_settings_t *settings, moon_phase_state_t *state, ui (void)state; char buf[11]; watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60) + offset; - date_time = watch_utility_date_time_from_unix_time(now, movement_timezone_offsets[settings->bit.time_zone] * 60); + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); + uint32_t now = watch_utility_date_time_to_unix_time(date_time, tz * 60) + offset; + date_time = watch_utility_date_time_from_unix_time(now, tz * 60); double currentfrac = fmod(now - FIRST_MOON, LUNAR_SECONDS) / LUNAR_SECONDS; double currentday = currentfrac * LUNAR_DAYS; uint8_t phase_index = 0; diff --git a/movement/watch_faces/complication/orrery_face.c b/movement/watch_faces/complication/orrery_face.c index 42fdf81..0570dd4 100644 --- a/movement/watch_faces/complication/orrery_face.c +++ b/movement/watch_faces/complication/orrery_face.c @@ -48,7 +48,7 @@ static const char orrery_celestial_body_names[NUM_AVAILABLE_BODIES][3] = { static void _orrery_face_recalculate(movement_settings_t *settings, orrery_state_t *state) { watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); date_time = watch_utility_date_time_from_unix_time(timestamp, 0); double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); double et = astro_convert_jd_to_julian_millenia_since_j2000(jd); diff --git a/movement/watch_faces/complication/planetary_hours_face.c b/movement/watch_faces/complication/planetary_hours_face.c index acded91..d9e32c5 100644 --- a/movement/watch_faces/complication/planetary_hours_face.c +++ b/movement/watch_faces/complication/planetary_hours_face.c @@ -134,7 +134,8 @@ static void _planetary_solar_phases(movement_settings_t *settings, planetary_hou state->no_location = false; watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times watch_date_time midnight; scratch_time.reg = midnight.reg = utc_now.reg; @@ -147,7 +148,7 @@ static void _planetary_solar_phases(movement_settings_t *settings, planetary_hou double lon = (double)lon_centi / 100.0; // save UTC offset - state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; + state->utc_offset = ((double)tz) / 60.0; // calculate sunrise and sunset of current day in decimal hours after midnight sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset); @@ -237,7 +238,7 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat // get current time watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); // the current date / time in UTC current_hour_epoch = watch_utility_date_time_to_unix_time(utc_now, 0); // set the current planetary hour as default screen diff --git a/movement/watch_faces/complication/planetary_time_face.c b/movement/watch_faces/complication/planetary_time_face.c index 56a18cf..836ada6 100644 --- a/movement/watch_faces/complication/planetary_time_face.c +++ b/movement/watch_faces/complication/planetary_time_face.c @@ -129,7 +129,8 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time state->no_location = false; watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times watch_date_time midnight; scratch_time.reg = midnight.reg = utc_now.reg; @@ -142,7 +143,7 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time double lon = (double)lon_centi / 100.0; // save UTC offset - state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; + state->utc_offset = ((double)tz) / 60.0; // get UNIX epoch time now_epoch = watch_utility_date_time_to_unix_time(utc_now, 0); @@ -206,11 +207,12 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting double night_hour_count = 0.0; uint8_t weekday, planet, planetary_hour; double hour_duration, current_hour, current_minute, current_second; + watch_date_time date_time = watch_rtc_get_date_time(); watch_set_colon(); // get current time and convert to UTC - state->scratch = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0); + state->scratch = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); // when current phase ends calculate the next phase if ( watch_utility_date_time_to_unix_time(state->scratch, 0) >= state->phase_end ) { diff --git a/movement/watch_faces/complication/sailing_face.c b/movement/watch_faces/complication/sailing_face.c index a6c13fe..16d60c9 100644 --- a/movement/watch_faces/complication/sailing_face.c +++ b/movement/watch_faces/complication/sailing_face.c @@ -33,8 +33,8 @@ #define sl_SELECTIONS 6 #define DEFAULT_MINUTES { 5,4,1,0,0,0 } -static inline int32_t get_tz_offset(movement_settings_t *settings) { - return movement_timezone_offsets[settings->bit.time_zone] * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { + return get_timezone_offset(settings->bit.time_zone, date_time) * 60; } static int lap = 0; @@ -165,7 +165,8 @@ static void ring(sailing_state_t *state, movement_settings_t *settings) { return; } state->nextbeep_ts = state->target_ts - beepseconds[beepflag+1]; - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->nextbeep_ts, get_tz_offset(settings)); + watch_date_time now = watch_rtc_get_date_time(); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->nextbeep_ts, get_tz_offset(settings, now)); movement_schedule_background_task_for_face(state->watch_face_index, target_dt); //background task is set, now we have time to play the tune. If this is cancelled accidentally, the next alarm will still ring. Sound is implemented non-blocking, so that neither buttons nor display output are compromised. for (int i = 0; i < 5; i++) { @@ -194,7 +195,7 @@ static void start(sailing_state_t *state, movement_settings_t *settings) {//gets } if (state->index > 5 || state->minutes[state->index] == 0) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); state->target_ts = state->now_ts; if (alarmflag != 0){ watch_buzzer_play_sequence(long_beep, NULL); @@ -205,7 +206,7 @@ static void start(sailing_state_t *state, movement_settings_t *settings) {//gets movement_request_tick_frequency(1); //synchronises tick with the moment the button was pressed. Solves 1s offset between sound and display, solves up to +-0.5s offset between button action and display. state->mode = sl_running; watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, state->minutes[state->index], 0); ring(state, settings); } @@ -253,11 +254,11 @@ void sailing_face_activate(movement_settings_t *settings, void *context) { sailing_state_t *state = (sailing_state_t *)context; if(state->mode == sl_running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); } if(state->mode == sl_counting) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); watch_set_indicator(WATCH_INDICATOR_LAP); } switch (alarmflag) { diff --git a/movement/watch_faces/complication/solstice_face.c b/movement/watch_faces/complication/solstice_face.c index e74f878..76d0d26 100644 --- a/movement/watch_faces/complication/solstice_face.c +++ b/movement/watch_faces/complication/solstice_face.c @@ -123,9 +123,10 @@ static watch_date_time jde_to_date_time(double JDE) { } static void calculate_datetimes(solstice_state_t *state, movement_settings_t *settings) { + watch_date_time date_time = watch_rtc_get_date_time(); for (int i = 0; i < 4; i++) { // TODO: handle DST changes - state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (movement_timezone_offsets[settings->bit.time_zone] / (60.0*24.0))); + state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (get_timezone_offset(settings->bit.time_zone, date_time) / (60.0*24.0))); } } diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 7330c42..a6ec764 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -54,7 +54,8 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s } watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -69,7 +70,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s // sunriset returns the rise/set times as signed decimal hours in UTC. // this can mean hours below 0 or above 31, which won't fit into a watch_date_time struct. // to deal with this, we set aside the offset in hours, and add it back before converting it to a watch_date_time. - double hours_from_utc = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; + double hours_from_utc = ((double)tz) / 60.0; // we loop twice because if it's after sunset today, we need to recalculate to display values for tomorrow. for(int i = 0; i < 2; i++) { diff --git a/movement/watch_faces/complication/timer_face.c b/movement/watch_faces/complication/timer_face.c index 29392d6..5ee2967 100644 --- a/movement/watch_faces/complication/timer_face.c +++ b/movement/watch_faces/complication/timer_face.c @@ -36,8 +36,8 @@ static const int8_t _sound_seq_start[] = {BUZZER_NOTE_C8, 2, 0}; static uint8_t _beeps_to_play; // temporary counter for ring signals playing -static inline int32_t _get_tz_offset(movement_settings_t *settings) { - return movement_timezone_offsets[settings->bit.time_zone] * 60; +static inline int32_t _get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { + return get_timezone_offset(settings->bit.time_zone, date_time) * 60; } static void _signal_callback() { @@ -50,7 +50,8 @@ static void _signal_callback() { static void _start(timer_state_t *state, movement_settings_t *settings, bool with_beep) { if (state->timers[state->current_timer].value == 0) return; watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + int32_t tz = _get_tz_offset(settings, now); + state->now_ts = watch_utility_date_time_to_unix_time(now, tz); if (state->mode == pausing) state->target_ts = state->now_ts + state->paused_left; else @@ -58,7 +59,7 @@ static void _start(timer_state_t *state, movement_settings_t *settings, bool wit state->timers[state->current_timer].unit.hours, state->timers[state->current_timer].unit.minutes, state->timers[state->current_timer].unit.seconds); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, _get_tz_offset(settings)); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); state->mode = running; movement_schedule_background_task_for_face(state->watch_face_index, target_dt); watch_set_indicator(WATCH_INDICATOR_BELL); @@ -210,7 +211,7 @@ void timer_face_activate(movement_settings_t *settings, void *context) { watch_set_colon(); if(state->mode == running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings, now)); watch_set_indicator(WATCH_INDICATOR_BELL); } else { state->pausing_seconds = 1; diff --git a/movement/watch_faces/complication/tomato_face.c b/movement/watch_faces/complication/tomato_face.c index 3d46ba9..15b67c6 100644 --- a/movement/watch_faces/complication/tomato_face.c +++ b/movement/watch_faces/complication/tomato_face.c @@ -30,8 +30,8 @@ static uint8_t focus_min = 25; static uint8_t break_min = 5; -static inline int32_t get_tz_offset(movement_settings_t *settings) { - return movement_timezone_offsets[settings->bit.time_zone] * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { + return get_timezone_offset(settings->bit.time_zone, date_time) * 60; } static uint8_t get_length(tomato_state_t *state) { @@ -47,12 +47,13 @@ static uint8_t get_length(tomato_state_t *state) { static void tomato_start(tomato_state_t *state, movement_settings_t *settings) { watch_date_time now = watch_rtc_get_date_time(); + int32_t tz = get_tz_offset(settings, now); int8_t length = (int8_t) get_length(state); state->mode = tomato_run; - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, tz); state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, length, 0); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); movement_schedule_background_task(target_dt); watch_set_indicator(WATCH_INDICATOR_BELL); } @@ -126,7 +127,7 @@ void tomato_face_activate(movement_settings_t *settings, void *context) { tomato_state_t *state = (tomato_state_t *)context; if (state->mode == tomato_run) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); watch_set_indicator(WATCH_INDICATOR_BELL); } watch_set_colon(); diff --git a/movement/watch_faces/complication/totp_face.c b/movement/watch_faces/complication/totp_face.c index a593e9c..10a5bfc 100644 --- a/movement/watch_faces/complication/totp_face.c +++ b/movement/watch_faces/complication/totp_face.c @@ -157,8 +157,9 @@ static void totp_generate_and_display(totp_state_t *totp_state) { totp_display(totp_state); } -static inline uint32_t totp_compute_base_timestamp(movement_settings_t *settings) { - return watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60); +static uint32_t totp_compute_base_timestamp(movement_settings_t *settings) { + watch_date_time date_time = watch_rtc_get_date_time(); + return watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); } void totp_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { diff --git a/movement/watch_faces/complication/totp_face_lfs.c b/movement/watch_faces/complication/totp_face_lfs.c index 820ad52..f560627 100644 --- a/movement/watch_faces/complication/totp_face_lfs.c +++ b/movement/watch_faces/complication/totp_face_lfs.c @@ -210,7 +210,8 @@ void totp_face_lfs_activate(movement_settings_t *settings, void *context) { } #endif - totp_state->timestamp = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60); + watch_date_time date_time = watch_rtc_get_date_time(); + totp_state->timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); totp_face_set_record(totp_state, 0); } diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c index 6d174d5..9cadf67 100644 --- a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c @@ -442,7 +442,7 @@ static void start_reading(accelerometer_data_acquisition_state_t *state, movemen accelerometer_data_acquisition_record_t record; watch_date_time date_time = watch_rtc_get_date_time(); - state->starting_timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + state->starting_timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); record.header.info.record_type = ACCELEROMETER_DATA_ACQUISITION_HEADER; record.header.info.range = ACCELEROMETER_RANGE; record.header.info.temperature = lis2dw_get_temperature(); diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index fbe8cbb..bdd1b0a 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -232,12 +232,13 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s watch_clear_colon(); sprintf(buf, "%s ", set_time_hackwatch_face_titles[current_page]); } else { + int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time_settings); watch_set_colon(); sprintf(buf, "%s %3d%02d ", set_time_hackwatch_face_titles[current_page], - (int8_t)(movement_timezone_offsets[settings->bit.time_zone] / 60), - (int8_t)(movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); + (int8_t)(tz / 60), + (int8_t)(tz % 60) * (tz < 0 ? -1 : 1)); } } From bae8c6582559e2617ceae22ed34f7d0be8e10a9f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 2 Aug 2024 07:37:30 -0400 Subject: [PATCH 049/220] Made the faces I care about not need to continuously re-find the timezone --- movement/watch_faces/clock/world_clock2_face.c | 9 ++++----- movement/watch_faces/clock/world_clock2_face.h | 1 + movement/watch_faces/clock/world_clock_face.c | 15 +++++---------- movement/watch_faces/clock/world_clock_face.h | 1 + .../watch_faces/complication/moon_phase_face.c | 1 + .../complication/sunrise_sunset_face.c | 6 +++--- .../complication/sunrise_sunset_face.h | 1 + 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 6ff806b..cf37a40 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -154,6 +154,7 @@ void world_clock2_face_activate(movement_settings_t *settings, void *context) movement_request_tick_frequency(4); break; } + state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); refresh_face = true; } @@ -165,7 +166,6 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, uint32_t timestamp; uint32_t previous_date_time; watch_date_time date_time; - int16_t tz; switch (event.event_type) { case EVENT_ACTIVATE: @@ -184,9 +184,8 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, /* Determine current time at time zone and store date/time */ date_time = watch_rtc_get_date_time(); - tz = get_timezone_offset(settings->bit.time_zone, date_time); - timestamp = watch_utility_date_time_to_unix_time(date_time, tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, tz * 60); + timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -286,7 +285,7 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, watch_clear_indicator(WATCH_INDICATOR_PM); refresh_face = false; } - result = div(get_timezone_offset(state->current_zone, watch_rtc_get_date_time()), 60); + result = div(state->tz, 60); hours = result.quot; minutes = result.rem; diff --git a/movement/watch_faces/clock/world_clock2_face.h b/movement/watch_faces/clock/world_clock2_face.h index 0baac21..efc107e 100644 --- a/movement/watch_faces/clock/world_clock2_face.h +++ b/movement/watch_faces/clock/world_clock2_face.h @@ -104,6 +104,7 @@ typedef struct { world_clock2_mode_t current_mode; uint8_t current_zone; uint32_t previous_date_time; + int16_t tz; } world_clock2_state_t; void world_clock2_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index c34db92..e7f393e 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -54,13 +54,13 @@ void world_clock_face_activate(movement_settings_t *settings, void *context) { static bool world_clock_face_do_display_mode(movement_event_t event, movement_settings_t *settings, world_clock_state_t *state) { char buf[11]; uint8_t pos; - int16_t tz; uint32_t timestamp; uint32_t previous_date_time; watch_date_time date_time; switch (event.event_type) { case EVENT_ACTIVATE: + state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); watch_set_colon(); state->previous_date_time = 0xFFFFFFFF; @@ -68,9 +68,8 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se case EVENT_TICK: case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); - tz = get_timezone_offset(settings->bit.time_zone, date_time); - timestamp = watch_utility_date_time_to_unix_time(date_time, tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, tz * 60); + timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -127,8 +126,6 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se } static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_settings_t *settings, world_clock_state_t *state) { - watch_date_time date_time; - int16_t tz; switch (event.event_type) { case EVENT_MODE_BUTTON_UP: if (state->backup_register) watch_store_backup_data(state->settings.reg, state->backup_register); @@ -172,13 +169,11 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ } char buf[13]; - date_time = watch_rtc_get_date_time(); - tz = get_timezone_offset(settings->bit.time_zone, date_time); sprintf(buf, "%c%c %3d%02d ", movement_valid_position_0_chars[state->settings.bit.char_0], movement_valid_position_1_chars[state->settings.bit.char_1], - (int8_t) (tz / 60), - (int8_t) (tz % 60) * (tz < 0 ? -1 : 1)); + (int8_t) (state->tz / 60), + (int8_t) (state->tz % 60) * (state->tz < 0 ? -1 : 1)); watch_set_colon(); watch_clear_indicator(WATCH_INDICATOR_PM); diff --git a/movement/watch_faces/clock/world_clock_face.h b/movement/watch_faces/clock/world_clock_face.h index 92e91a6..e76d85e 100644 --- a/movement/watch_faces/clock/world_clock_face.h +++ b/movement/watch_faces/clock/world_clock_face.h @@ -62,6 +62,7 @@ typedef struct { uint8_t backup_register; uint8_t current_screen; uint32_t previous_date_time; + int16_t tz; } world_clock_state_t; void world_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/complication/moon_phase_face.c b/movement/watch_faces/complication/moon_phase_face.c index 47ed6e6..c571843 100644 --- a/movement/watch_faces/complication/moon_phase_face.c +++ b/movement/watch_faces/complication/moon_phase_face.c @@ -139,6 +139,7 @@ bool moon_phase_face_loop(movement_event_t event, movement_settings_t *settings, switch (event.event_type) { case EVENT_ACTIVATE: + _update(settings, state, state->offset); break; case EVENT_TICK: diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index a6ec764..ad03d88 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -54,8 +54,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s } watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, state->tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -70,7 +69,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s // sunriset returns the rise/set times as signed decimal hours in UTC. // this can mean hours below 0 or above 31, which won't fit into a watch_date_time struct. // to deal with this, we set aside the offset in hours, and add it back before converting it to a watch_date_time. - double hours_from_utc = ((double)tz) / 60.0; + double hours_from_utc = ((double)state->tz) / 60.0; // we loop twice because if it's after sunset today, we need to recalculate to display values for tomorrow. for(int i = 0; i < 2; i++) { @@ -317,6 +316,7 @@ void sunrise_sunset_face_activate(movement_settings_t *settings, void *context) movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1); state->working_latitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.latitude); state->working_longitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.longitude); + state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); } bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index 16e65b7..71b8462 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -52,6 +52,7 @@ typedef struct { uint8_t rise_index; uint8_t active_digit; bool location_changed; + int16_t tz; watch_date_time rise_set_expires; sunrise_sunset_lat_lon_settings_t working_latitude; sunrise_sunset_lat_lon_settings_t working_longitude; From ccf99a9727014dc504b6e62d12990ab96d3360b2 Mon Sep 17 00:00:00 2001 From: Robert Masen Date: Fri, 2 Aug 2024 18:20:44 -0500 Subject: [PATCH 050/220] add temp input to simulator --- .../shared/driver/thermistor_driver.c | 11 ++++++++- watch-library/simulator/shell.html | 23 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/watch-library/shared/driver/thermistor_driver.c b/watch-library/shared/driver/thermistor_driver.c index 73bdef4..8ce5934 100644 --- a/watch-library/shared/driver/thermistor_driver.c +++ b/watch-library/shared/driver/thermistor_driver.c @@ -45,7 +45,15 @@ void thermistor_driver_disable(void) { // Disable the enable pin's output circuitry. watch_disable_digital_output(THERMISTOR_ENABLE_PIN); } - +#if __EMSCRIPTEN__ +#include +float thermistor_driver_get_temperature(void) +{ + return EM_ASM_DOUBLE({ + return temp_c || 25.0; + }); +} +#else float thermistor_driver_get_temperature(void) { // set the enable pin to the level that powers the thermistor circuit. watch_set_pin_level(THERMISTOR_ENABLE_PIN, THERMISTOR_ENABLE_VALUE); @@ -56,3 +64,4 @@ float thermistor_driver_get_temperature(void) { return watch_utility_thermistor_temperature(value, THERMISTOR_HIGH_SIDE, THERMISTOR_B_COEFFICIENT, THERMISTOR_NOMINAL_TEMPERATURE, THERMISTOR_NOMINAL_RESISTANCE, THERMISTOR_SERIES_RESISTANCE); } +#endif diff --git a/watch-library/simulator/shell.html b/watch-library/simulator/shell.html index 29fbed0..9cdb290 100644 --- a/watch-library/simulator/shell.html +++ b/watch-library/simulator/shell.html @@ -905,6 +905,11 @@
+

Temp.

+
+ C + +
@@ -962,6 +967,7 @@ lat = 0; lon = 0; tx = ""; + temp_c = 25.0; function updateLocation(location) { lat = Math.round(location.coords.latitude * 100); lon = Math.round(location.coords.longitude * 100); @@ -1038,10 +1044,25 @@ document.getElementById(skin).checked = true; setSkin(skin); } + + function setTemp() { + let tempInput = document.getElementById("temp-c"); + if (!tempInput) { + return console.warn("no input found"); + } + if (tempInput.value == "") { + return console.warn("no value in input"); + } + + try { + temp_c = Number.parseFloat(tempInput.value); + } catch (e) { + return console.warn("input value is not a valid float:", tempInput.value, e); + } + } loadPrefs(); {{{ SCRIPT }}} - From db165bec30ab114a5da838278d095094b18d5291 Mon Sep 17 00:00:00 2001 From: Christian Buschau Date: Sat, 3 Aug 2024 12:22:15 +0200 Subject: [PATCH 051/220] Fix all days in a month --- movement/watch_faces/complication/day_one_face.c | 2 +- movement/watch_faces/complication/time_left_face.c | 2 +- movement/watch_faces/settings/set_time_face.c | 2 +- movement/watch_faces/settings/set_time_hackwatch_face.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/day_one_face.c b/movement/watch_faces/complication/day_one_face.c index 27601ed..d9bf0f7 100644 --- a/movement/watch_faces/complication/day_one_face.c +++ b/movement/watch_faces/complication/day_one_face.c @@ -27,7 +27,7 @@ #include "day_one_face.h" #include "watch.h" -static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; +static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) { // from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation diff --git a/movement/watch_faces/complication/time_left_face.c b/movement/watch_faces/complication/time_left_face.c index cc1077a..9992bbf 100644 --- a/movement/watch_faces/complication/time_left_face.c +++ b/movement/watch_faces/complication/time_left_face.c @@ -158,7 +158,7 @@ static void _draw(time_left_state_t *state, uint8_t subsecond) { /// @brief handle short or long pressing the alarm button static void _handle_alarm_button(time_left_state_t *state) { - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; uint32_t tmp_day; switch (state->current_page) { case TIME_LEFT_FACE_SETTINGS_STATE: // birth year diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index a8c88e4..4b4be64 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -33,7 +33,7 @@ static bool _quick_ticks_running; static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time, uint8_t current_page) { // handles short or long pressing of the alarm button - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (current_page) { case 0: // hour diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index fbe8cbb..269612f 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -47,7 +47,7 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { uint8_t current_page = *((uint8_t *)context); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz date_time_settings = watch_rtc_get_date_time(); From fa2907e098ceccc249e864aad864140208bb4215 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 2 Aug 2024 17:57:29 -0400 Subject: [PATCH 052/220] Some more cleanup --- movement/movement.c | 111 +++++++++--------- movement/movement.h | 2 +- movement/movement_config.h | 7 ++ movement/watch_faces/settings/set_time_face.c | 9 +- watch-library/shared/watch/watch_utility.h | 9 +- 5 files changed, 76 insertions(+), 62 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 52a9ac0..4afe273 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -96,6 +96,11 @@ #define MOVEMENT_DEFAULT_LED_DURATION 1 #endif +// Default to having DST get set +#ifndef MOVEMENT_DEFAULT_DST_ACTIVE +#define MOVEMENT_DEFAULT_DST_ACTIVE true +#endif + #if __EMSCRIPTEN__ #include #endif @@ -107,7 +112,8 @@ const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 2 const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800}; movement_event_t event; -const int16_t movement_timezone_offsets[] = { +#define NUM_TIME_ZONES 41 +const int16_t movement_timezone_offsets[NUM_TIME_ZONES] = { 0, // 0 : 0:00:00 (UTC) 60, // 1 : 1:00:00 (Central European Time) 120, // 2 : 2:00:00 (South African Standard Time) @@ -164,47 +170,47 @@ const int16_t movement_timezone_offsets[] = { * having to separately change the hour and timezone info * in the time set face. */ -const uint8_t movement_dst_jump_table[] = { - 1, // 0 UTC + 1 = CET - 2, // 1 CET + 1 = SAST - 3, // 2 SAST + 1 = AST - 5, // 3 AST + 1 = GST - 6, // 4 IST + 1 = AT - 7, // 5 GST + 1 = PST - 8, // 6 AT + 1 = IST - 10, // 7 PST + 1 = KT - 11, // 8 IST + 1 = MT - 9, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway - 12, // 10 KT + 1 = TST - 11, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway - 13, // 12 TST + 1 = CST - 15, // 13 CST + 1 = JST - 14, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway - 17, // 15 JST + 1 = AEST - 18, // 16 ACST + 1 = LHST - 19, // 17 AEST + 1 = SIT - 18, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway - 20, // 19 SIT + 1 = NZST - 22, // 20 NZST + 1 = TT - 23, // 21 CST + 1 = CDT - 24, // 22 TT + 1 = LIT - 23, // 23 CDT is already a daylight timezone - 24, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway - 26, // 25 BIT + 1 = NT - 27, // 26 NT + 1 = HAST - 29, // 27 HAST + 1 = AST - 28, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway - 30, // 29 AST + 1 = PST - 31, // 30 PST + 1 = MST - 32, // 31 MST + 1 = CST - 33, // 32 CST + 1 = EST - 35, // 33 EST + 1 = AST - 36, // 34 VST + 1 = NST - 37, // 35 AST + 1 = BT - 38, // 36 NST + 1 = NDT - 39, // 37 BT + 1 = 39 - 38, // 38 NDT is already a daylight timezone - 40, // 39 FNT + 1 = AST +const int16_t movement_timezone_dst_offsets[NUM_TIME_ZONES] = { + 60, // 0 UTC + 1 = CET + 120, // 1 CET + 1 = SAST + 189, // 2 SAST + 1 = AST + 240, // 3 AST + 1 = GST + 270, // 4 IST + 1 = AT + 300, // 5 GST + 1 = PST + 330, // 6 AT + 1 = IST + 360, // 7 PST + 1 = KT + 390, // 8 IST + 1 = MT + 345, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway + 420, // 10 KT + 1 = TST + 390, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway + 480, // 12 TST + 1 = CST + 540, // 13 CST + 1 = JST + 525, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway + 600, // 15 JST + 1 = AEST + 630, // 16 ACST + 1 = LHST + 660, // 17 AEST + 1 = SIT + 630, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway + 720, // 19 SIT + 1 = NZST + 780, // 20 NZST + 1 = TT + 825, // 21 CST + 1 = CDT + 840, // 22 TT + 1 = LIT + 825, // 23 CDT is already a daylight timezone + 840, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway + -660, // 25 BIT + 1 = NT + -600, // 26 NT + 1 = HAST + -540, // 27 HAST + 1 = AST + -570, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway + -480, // 29 AST + 1 = PST + -420, // 30 PST + 1 = MST + -360, // 31 MST + 1 = CST + -300, // 32 CST + 1 = EST + -240, // 33 EST + 1 = AST + -210, // 34 VST + 1 = NST + -180, // 35 AST + 1 = BT + -150, // 36 NST + 1 = NDT + -120, // 37 BT + 1 = 39 + -150, // 38 NDT is already a daylight timezone + -60, // 39 FNT + 1 = AST 0 // 40 AST + 1 = UTC }; @@ -426,20 +432,13 @@ uint8_t movement_claim_backup_register(void) { int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { if (!movement_state.settings.bit.dst_active) return movement_timezone_offsets[timezone_idx]; - uint8_t dst_result = is_dst(date_time); - switch (dst_result) - { - case DST_STARTED: - case DST_OCCURRING: - return movement_timezone_offsets[movement_dst_jump_table[timezone_idx]]; - case DST_ENDING: - case DST_ENDED: - default: - return movement_timezone_offsets[timezone_idx]; - } + if (dst_occurring(date_time)) + return movement_timezone_dst_offsets[timezone_idx]; + return movement_timezone_offsets[timezone_idx]; } void app_init(void) { + const int16_t* timezone_offsets; #if defined(NO_FREQCORR) watch_rtc_freqcorr_write(0, 0); #elif defined(WATCH_IS_BLUE_BOARD) @@ -457,6 +456,7 @@ void app_init(void) { movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL; movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL; movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; + movement_state.settings.bit.dst_active = MOVEMENT_DEFAULT_DST_ACTIVE; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; movement_state.next_available_backup_register = 4; @@ -468,8 +468,9 @@ void app_init(void) { int32_t time_zone_offset = EM_ASM_INT({ return -new Date().getTimezoneOffset(); }); - for (int i = 0, count = sizeof(movement_timezone_offsets) / sizeof(movement_timezone_offsets[0]); i < count; i++) { - if (movement_timezone_offsets[i] == time_zone_offset) { + timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets; + for (int i = 0; i < NUM_TIME_ZONES; i++) { + if (timezone_offsets[i] == time_zone_offset) { movement_state.settings.bit.time_zone = i; break; } diff --git a/movement/movement.h b/movement/movement.h index 9aa1dbc..d0595fb 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -129,7 +129,7 @@ typedef struct { } movement_event_t; extern const int16_t movement_timezone_offsets[]; -extern const uint8_t movement_dst_jump_table[]; +extern const int16_t movement_timezone_dst_offsets[]; extern const char movement_valid_position_0_chars[]; extern const char movement_valid_position_1_chars[]; diff --git a/movement/movement_config.h b/movement/movement_config.h index 10a30af..46e1d2e 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -95,4 +95,11 @@ const watch_face_t watch_faces[] = { */ #define MOVEMENT_DEFAULT_LED_DURATION 1 +/* Set if using DST + * Valid values are: + * false: Don't allow the watch to use DST + * true: Allow the watch to use DST + */ +#define MOVEMENT_DEFAULT_DST_ACTIVE true + #endif // MOVEMENT_CONFIG_H_ diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 90edac2..b16cb6d 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -25,6 +25,7 @@ #include #include "set_time_face.h" #include "watch.h" +#include "watch_utility.h" #define SET_TIME_FACE_NUM_SETTINGS (8) const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO", "DS"}; @@ -150,18 +151,18 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_clear_indicator(WATCH_INDICATOR_PM); sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); } else if (current_page < 7) { // zone + char dst_char = (settings->bit.dst_active && dst_occurring(watch_rtc_get_date_time())) ? 'd' : ' '; if (event.subsecond % 2) { watch_clear_colon(); - sprintf(buf, "%s ", set_time_face_titles[current_page]); + sprintf(buf, "%s %c", set_time_face_titles[current_page], dst_char); } else { int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); watch_set_colon(); - sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (tz / 60), (int8_t) (tz % 60) * (tz < 0 ? -1 : 1)); + sprintf(buf, "%s %3d%02d %c", set_time_face_titles[current_page], (int8_t) (tz / 60), (int8_t) (tz % 60) * (tz < 0 ? -1 : 1), dst_char); } } else { // daylight savings watch_clear_colon(); - if (settings->bit.dst_active) sprintf(buf, "%s dsT y", set_time_face_titles[current_page]); - else sprintf(buf, "%s dsT n", set_time_face_titles[current_page]); + sprintf(buf, "%s dsT %c", set_time_face_titles[current_page], settings->bit.dst_active ? 'y' : 'n'); } // blink up the parameter we're setting diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h index d3f3981..bdc2a88 100644 --- a/watch-library/shared/watch/watch_utility.h +++ b/watch-library/shared/watch/watch_utility.h @@ -46,7 +46,7 @@ typedef struct { } watch_duration_t; typedef enum { - DST_STARTED, + DST_STARTING, DST_OCCURRING, DST_ENDING, DST_ENDED @@ -89,7 +89,12 @@ uint8_t is_leap(uint16_t year); * @param date_time The watch_date_time that you wish to convert. * @return DST_OCCURRING, DST_HAPPENING, DST_ENDING, DST_ENDED */ -uint8_t is_dst(watch_date_time date_time); +uint8_t get_dst_status(watch_date_time date_time); + +/** @brief Returns true if it's DST and false otherwise. + * @param date_time The watch_date_time that you wish to convert. + */ +bool dst_occurring(watch_date_time date_time); /** @brief Returns the UNIX time (seconds since 1970) for a given date/time in UTC. * @param date_time The watch_date_time that you wish to convert. From 74421c7e65f9a735bcc394eed027b59baef1cb97 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 08:46:34 -0400 Subject: [PATCH 053/220] Day roll back repeat fix --- movement/watch_faces/clock/clock_face.c | 32 +++++++++-------- .../watch_faces/clock/simple_clock_face.c | 34 +++++++++++-------- movement/watch_faces/settings/set_time_face.c | 1 + .../settings/set_time_hackwatch_face.c | 9 +++-- watch-library/shared/watch/watch_utility.c | 21 +++++++----- 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index 68019d8..f9ec58a 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -280,27 +280,31 @@ void clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -static void check_and_act_on_daylight_savings(bool dst_active, watch_date_time date_time) { - if (!dst_active) return; - uint8_t dst_result = is_dst(date_time); - switch (dst_result) - { - case DST_STARTED: - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - break; - case DST_ENDING: - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - break; - default: +static void check_and_act_on_daylight_savings(movement_settings_t *settings, watch_date_time date_time) { + if (!settings ->bit.dst_active) return; + uint8_t dst_result = get_dst_status(date_time); + + if (settings ->bit.dst_skip_rolling_back && dst_result == DST_ENDED) { + settings ->bit.dst_skip_rolling_back = false; return; } - watch_rtc_set_date_time(date_time); + else if (dst_result == DST_ENDING && !settings ->bit.dst_skip_rolling_back) { + settings ->bit.dst_skip_rolling_back = true; + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + watch_rtc_set_date_time(date_time); + return; + } + else if (dst_result == DST_STARTING) { + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + watch_rtc_set_date_time(date_time); + return; + } } bool clock_face_wants_background_task(movement_settings_t *settings, void *context) { clock_state_t *state = (clock_state_t *) context; watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(settings->bit.dst_active, date_time); + check_and_act_on_daylight_savings(settings, date_time); if (!state->time_signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index c0f8372..a7e08a9 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -150,27 +150,31 @@ void simple_clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -static void check_and_act_on_daylight_savings(bool dst_active, watch_date_time date_time) { - if (!dst_active) return; - uint8_t dst_result = is_dst(date_time); - switch (dst_result) - { - case DST_STARTED: - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - break; - case DST_ENDING: - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - break; - default: +static void check_and_act_on_daylight_savings(movement_settings_t *settings, watch_date_time date_time) { + if (!settings ->bit.dst_active) return; + uint8_t dst_result = get_dst_status(date_time); + + if (settings ->bit.dst_skip_rolling_back && (dst_result == DST_ENDED)) { + settings ->bit.dst_skip_rolling_back = false; return; } - watch_rtc_set_date_time(date_time); + else if (dst_result == DST_ENDING && !settings ->bit.dst_skip_rolling_back) { + settings ->bit.dst_skip_rolling_back = true; + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + watch_rtc_set_date_time(date_time); + return; + } + else if (dst_result == DST_STARTING) { + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + watch_rtc_set_date_time(date_time); + return; + } } bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { simple_clock_state_t *state = (simple_clock_state_t *)context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(settings->bit.dst_active, date_time); + date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(settings, date_time); if (!state->signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index b16cb6d..3f7d0c7 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -72,6 +72,7 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time break; } watch_rtc_set_date_time(date_time); + settings->bit.dst_skip_rolling_back = false; } static void _abort_quick_ticks() { diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index bdd1b0a..be293ab 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -94,6 +94,7 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } date_time_settings.unit.second = 0; watch_rtc_set_date_time(date_time_settings); + settings->bit.dst_skip_rolling_back = false; } break; case EVENT_ALARM_BUTTON_DOWN: @@ -134,8 +135,10 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } break; } - if (current_page != 2) // Do not set time when we are at seconds, it was already set previously + if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); + settings->bit.dst_skip_rolling_back = false; + } break; case EVENT_ALARM_LONG_UP://Setting seconds on long release @@ -178,8 +181,10 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } - if (current_page != 2) // Do not set time when we are at seconds, it was already set previously + if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); + settings->bit.dst_skip_rolling_back = false; + } //TODO: Do not update whole RTC, just what we are changing break; case EVENT_TIMEOUT: diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 1410e41..5938b60 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -83,38 +83,43 @@ uint8_t is_leap(uint16_t y) return !(y%4) && ((y%100) || !(y%400)); } -uint8_t is_dst(watch_date_time date_time) { - uint8_t weekday_first_of_month; +uint8_t get_dst_status(watch_date_time date_time) { watch_date_time dst_start_time; watch_date_time dst_end_time; uint32_t unix_dst_start_time; uint32_t unix_dst_end_time; uint32_t unix_curr_time; + dst_start_time.unit.year = date_time.unit.year; dst_start_time.unit.month = 3; dst_start_time.unit.hour = 2; dst_start_time.unit.minute = 0; dst_start_time.unit.second = 0; - weekday_first_of_month = watch_utility_get_iso8601_weekday_number(date_time.unit.year, dst_start_time.unit.month, 1); - dst_start_time.unit.day = 15 - weekday_first_of_month; + dst_start_time.unit.day = 15 - watch_utility_get_iso8601_weekday_number(dst_start_time.unit.year + WATCH_RTC_REFERENCE_YEAR, dst_start_time.unit.month, 1); unix_dst_start_time = watch_utility_date_time_to_unix_time(dst_start_time, 0); + dst_end_time.unit.year = date_time.unit.year; dst_end_time.unit.month = 11; dst_end_time.unit.hour = 2; dst_end_time.unit.minute = 0; dst_end_time.unit.second = 0; - weekday_first_of_month = watch_utility_get_iso8601_weekday_number(date_time.unit.year, dst_end_time.unit.month, 1); - dst_end_time.unit.day = 15 - weekday_first_of_month; + dst_end_time.unit.day = 15 - watch_utility_get_iso8601_weekday_number(dst_end_time.unit.year + WATCH_RTC_REFERENCE_YEAR, dst_end_time.unit.month, 1); unix_dst_end_time = watch_utility_date_time_to_unix_time(dst_end_time, 0); + if (date_time.unit.second > 45) // In emu, it's been seen that we may trigger at 59sec rather than exactly 0 each time + date_time.unit.minute = (date_time.unit.minute + 1) % 60; date_time.unit.second = 0; unix_curr_time = watch_utility_date_time_to_unix_time(date_time, 0); - if (unix_curr_time == unix_dst_start_time) return DST_STARTED; + if (unix_curr_time == unix_dst_start_time) return DST_STARTING; if (unix_curr_time == unix_dst_end_time) return DST_ENDING; if (unix_curr_time > unix_dst_end_time || unix_curr_time < unix_dst_start_time) return DST_ENDED; return DST_OCCURRING; -} +} + +bool dst_occurring(watch_date_time date_time) { + return get_dst_status(date_time) <= DST_OCCURRING; +} uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t day) { uint16_t DAYS_SO_FAR[] = { From e50390b673fda13a31463509a0d26165f1a4d1ea Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 09:33:49 -0400 Subject: [PATCH 054/220] DST roll is now a global variable in watch_rtc --- movement/movement.c | 20 +++++++++++++ movement/movement.h | 1 + movement/watch_faces/clock/clock_face.c | 29 +++++------------- .../clock/minute_repeater_decimal_face.c | 4 +-- .../clock/repetition_minute_face.c | 4 +-- .../clock/simple_clock_bin_led_face.c | 4 +-- .../watch_faces/clock/simple_clock_face.c | 30 +++++-------------- movement/watch_faces/settings/set_time_face.c | 1 - .../settings/set_time_hackwatch_face.c | 3 -- watch-library/hardware/watch/watch_rtc.c | 14 +++++++++ watch-library/shared/watch/watch_rtc.h | 6 ++++ watch-library/simulator/watch/watch_rtc.c | 14 +++++++++ 12 files changed, 75 insertions(+), 55 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 4afe273..eabc29c 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -430,6 +430,26 @@ uint8_t movement_claim_backup_register(void) { return movement_state.next_available_backup_register++; } +uint8_t check_and_act_on_daylight_savings(watch_date_time date_time) { + if (movement_state.settings.bit.dst_active) return date_time.unit.hour; + uint8_t dst_result = get_dst_status(date_time); + bool dst_skip_rolling_back = get_dst_skip_rolling_back(); + + if (dst_skip_rolling_back && (dst_result == DST_ENDED)) { + clear_dst_skip_rolling_back(); + } + else if (dst_result == DST_ENDING && !dst_skip_rolling_back) { + set_dst_skip_rolling_back(); + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + watch_rtc_set_date_time(date_time); + } + else if (dst_result == DST_STARTING) { + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + watch_rtc_set_date_time(date_time); + } + return date_time.unit.hour; +} + int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { if (!movement_state.settings.bit.dst_active) return movement_timezone_offsets[timezone_idx]; if (dst_occurring(date_time)) diff --git a/movement/movement.h b/movement/movement.h index d0595fb..6dd38ba 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -313,6 +313,7 @@ void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); uint8_t movement_claim_backup_register(void); +uint8_t check_and_act_on_daylight_savings(watch_date_time date_time); // Returns the currently set hour int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time); #endif // MOVEMENT_H_ diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index f9ec58a..af601ce 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -280,31 +280,16 @@ void clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -static void check_and_act_on_daylight_savings(movement_settings_t *settings, watch_date_time date_time) { - if (!settings ->bit.dst_active) return; - uint8_t dst_result = get_dst_status(date_time); - - if (settings ->bit.dst_skip_rolling_back && dst_result == DST_ENDED) { - settings ->bit.dst_skip_rolling_back = false; - return; - } - else if (dst_result == DST_ENDING && !settings ->bit.dst_skip_rolling_back) { - settings ->bit.dst_skip_rolling_back = true; - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - watch_rtc_set_date_time(date_time); - return; - } - else if (dst_result == DST_STARTING) { - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - watch_rtc_set_date_time(date_time); - return; - } -} - bool clock_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; clock_state_t *state = (clock_state_t *) context; watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(settings, date_time); + uint8_t hour_dst = check_and_act_on_daylight_savings(date_time); + if(hour_dst != date_time.unit.hour) { + char buf[3 + 1]; + sprintf(buf, "%2d", hour_dst); + watch_display_string(buf, 4); + } if (!state->time_signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/movement/watch_faces/clock/minute_repeater_decimal_face.c b/movement/watch_faces/clock/minute_repeater_decimal_face.c index 2cedc30..ab0e6de 100644 --- a/movement/watch_faces/clock/minute_repeater_decimal_face.c +++ b/movement/watch_faces/clock/minute_repeater_decimal_face.c @@ -230,9 +230,9 @@ void minute_repeater_decimal_face_resign(movement_settings_t *settings, void *co bool minute_repeater_decimal_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context; - if (!state->signal_enabled) return false; - watch_date_time date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(date_time); + if (!state->signal_enabled) return false; return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c index e9e5e31..1afc4ce 100644 --- a/movement/watch_faces/clock/repetition_minute_face.c +++ b/movement/watch_faces/clock/repetition_minute_face.c @@ -213,9 +213,9 @@ void repetition_minute_face_resign(movement_settings_t *settings, void *context) bool repetition_minute_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; repetition_minute_state_t *state = (repetition_minute_state_t *)context; - if (!state->signal_enabled) return false; - watch_date_time date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(date_time); + if (!state->signal_enabled) return false; return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.c b/movement/watch_faces/clock/simple_clock_bin_led_face.c index cf39c18..1762cfa 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.c +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.c @@ -214,9 +214,9 @@ void simple_clock_bin_led_face_resign(movement_settings_t *settings, void *conte bool simple_clock_bin_led_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context; - if (!state->signal_enabled) return false; - watch_date_time date_time = watch_rtc_get_date_time(); + check_and_act_on_daylight_savings(date_time); + if (!state->signal_enabled) return false; return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index a7e08a9..3866e2c 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -150,31 +150,15 @@ void simple_clock_face_resign(movement_settings_t *settings, void *context) { (void) context; } -static void check_and_act_on_daylight_savings(movement_settings_t *settings, watch_date_time date_time) { - if (!settings ->bit.dst_active) return; - uint8_t dst_result = get_dst_status(date_time); - - if (settings ->bit.dst_skip_rolling_back && (dst_result == DST_ENDED)) { - settings ->bit.dst_skip_rolling_back = false; - return; - } - else if (dst_result == DST_ENDING && !settings ->bit.dst_skip_rolling_back) { - settings ->bit.dst_skip_rolling_back = true; - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - watch_rtc_set_date_time(date_time); - return; - } - else if (dst_result == DST_STARTING) { - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - watch_rtc_set_date_time(date_time); - return; - } -} - bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { simple_clock_state_t *state = (simple_clock_state_t *)context; - date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(settings, date_time); + watch_date_time date_time = watch_rtc_get_date_time(); + uint8_t hour_dst = check_and_act_on_daylight_savings(date_time); + if(hour_dst != date_time.unit.hour) { + char buf[3 + 1]; + sprintf(buf, "%2d", hour_dst); + watch_display_string(buf, 4); + } if (!state->signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 3f7d0c7..b16cb6d 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -72,7 +72,6 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time break; } watch_rtc_set_date_time(date_time); - settings->bit.dst_skip_rolling_back = false; } static void _abort_quick_ticks() { diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index be293ab..44ae04e 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -94,7 +94,6 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } date_time_settings.unit.second = 0; watch_rtc_set_date_time(date_time_settings); - settings->bit.dst_skip_rolling_back = false; } break; case EVENT_ALARM_BUTTON_DOWN: @@ -137,7 +136,6 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); - settings->bit.dst_skip_rolling_back = false; } break; @@ -183,7 +181,6 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); - settings->bit.dst_skip_rolling_back = false; } //TODO: Do not update whole RTC, just what we are changing break; diff --git a/watch-library/hardware/watch/watch_rtc.c b/watch-library/hardware/watch/watch_rtc.c index 93cb9f1..67ecb53 100644 --- a/watch-library/hardware/watch/watch_rtc.c +++ b/watch-library/hardware/watch/watch_rtc.c @@ -30,6 +30,19 @@ ext_irq_cb_t btn_alarm_callback; ext_irq_cb_t a2_callback; ext_irq_cb_t a4_callback; +static bool dst_skip_rolling_back; +bool get_dst_skip_rolling_back(void) { + return dst_skip_rolling_back; +} + +void set_dst_skip_rolling_back(void) { + dst_skip_rolling_back = true; +} + +void clear_dst_skip_rolling_back(void) { + dst_skip_rolling_back = false; +} + bool _watch_rtc_is_enabled(void) { return RTC->MODE2.CTRLA.bit.ENABLE; } @@ -60,6 +73,7 @@ void watch_rtc_set_date_time(watch_date_time date_time) { _sync_rtc(); // Double sync as without it at high Hz faces setting time is unrealiable (specifically, set_time_hackwatch) RTC->MODE2.CLOCK.reg = date_time.reg; _sync_rtc(); + clear_dst_skip_rolling_back(); } watch_date_time watch_rtc_get_date_time(void) { diff --git a/watch-library/shared/watch/watch_rtc.h b/watch-library/shared/watch/watch_rtc.h index 3e63bb5..a9134eb 100644 --- a/watch-library/shared/watch/watch_rtc.h +++ b/watch-library/shared/watch/watch_rtc.h @@ -157,5 +157,11 @@ void watch_rtc_enable(bool en); */ void watch_rtc_freqcorr_write(int16_t value, int16_t sign); +/** @brief Returns if we're currently at a point where the we rolled back for DST and need to ignore the next DST segment + */ +bool get_dst_skip_rolling_back(void); +void set_dst_skip_rolling_back(void); +void clear_dst_skip_rolling_back(void); + /// @} #endif diff --git a/watch-library/simulator/watch/watch_rtc.c b/watch-library/simulator/watch/watch_rtc.c index 2bb6074..9fe9e29 100644 --- a/watch-library/simulator/watch/watch_rtc.c +++ b/watch-library/simulator/watch/watch_rtc.c @@ -39,6 +39,19 @@ ext_irq_cb_t btn_alarm_callback; ext_irq_cb_t a2_callback; ext_irq_cb_t a4_callback; +static bool dst_skip_rolling_back; +bool get_dst_skip_rolling_back(void) { + return dst_skip_rolling_back; +} + +void set_dst_skip_rolling_back(void) { + dst_skip_rolling_back = true; +} + +void clear_dst_skip_rolling_back(void) { + dst_skip_rolling_back = false; +} + bool _watch_rtc_is_enabled(void) { return true; } @@ -57,6 +70,7 @@ void watch_rtc_set_date_time(watch_date_time date_time) { const date = new Date(year, month - 1, day, hour, minute, second); return date - Date.now(); }, date_time.reg); + clear_dst_skip_rolling_back(); } watch_date_time watch_rtc_get_date_time(void) { From aebea960c01772bcb54210b30109eb1b11b4e34d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 10:08:24 -0400 Subject: [PATCH 055/220] Cleanup to the default branch --- movement/movement.c | 2 +- movement/watch_faces/clock/simple_clock_face.c | 1 + movement/watch_faces/settings/set_time_face.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index eabc29c..256aa8a 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -458,7 +458,6 @@ int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { } void app_init(void) { - const int16_t* timezone_offsets; #if defined(NO_FREQCORR) watch_rtc_freqcorr_write(0, 0); #elif defined(WATCH_IS_BLUE_BOARD) @@ -485,6 +484,7 @@ void app_init(void) { filesystem_init(); #if __EMSCRIPTEN__ + const int16_t* timezone_offsets; int32_t time_zone_offset = EM_ASM_INT({ return -new Date().getTimezoneOffset(); }); diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 3866e2c..20dfb54 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -151,6 +151,7 @@ void simple_clock_face_resign(movement_settings_t *settings, void *context) { } bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; simple_clock_state_t *state = (simple_clock_state_t *)context; watch_date_time date_time = watch_rtc_get_date_time(); uint8_t hour_dst = check_and_act_on_daylight_savings(date_time); diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index b16cb6d..1b19df8 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -134,7 +134,7 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v return movement_default_loop_handler(event, settings); } - char buf[11]; + char buf[13]; if (current_page < 3) { watch_set_colon(); if (settings->bit.clock_mode_24h) { From 6ae5dfef708ed3f9ec1348d20f0b48f5f1915fcf Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 11:20:25 -0400 Subject: [PATCH 056/220] Leap Years Now Handled Dynamically --- apps/beats-time/app.c | 8 +++--- .../watch_faces/complication/day_one_face.c | 8 +++--- .../watch_faces/complication/time_left_face.c | 26 ++++++------------- movement/watch_faces/settings/set_time_face.c | 14 ++++------ .../settings/set_time_hackwatch_face.c | 14 ++++------ 5 files changed, 25 insertions(+), 45 deletions(-) diff --git a/apps/beats-time/app.c b/apps/beats-time/app.c index ef27ffe..f979247 100644 --- a/apps/beats-time/app.c +++ b/apps/beats-time/app.c @@ -2,6 +2,7 @@ #include #include #include "watch.h" +#include "watch_utility.h" const int8_t UTC_OFFSET = 4; // set to your current UTC offset to see correct beats time const uint8_t BEAT_REFRESH_FREQUENCY = 8; @@ -224,13 +225,10 @@ void set_time_mode_handle_secondary_button(void) { break; case 5: // day date_time.unit.day = date_time.unit.day + 1; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. - if (date_time.unit.day > days_in_month[date_time.unit.month - 1]) { - date_time.unit.day = 1; - } break; } + if (date_time.unit.day > days_in_month[date_time.unit.month - 1] + (is_leap(date_time.unit.year) && date_time.unit.month == 2)) + date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/watch_faces/complication/day_one_face.c b/movement/watch_faces/complication/day_one_face.c index d9bf0f7..4429611 100644 --- a/movement/watch_faces/complication/day_one_face.c +++ b/movement/watch_faces/complication/day_one_face.c @@ -26,8 +26,9 @@ #include #include "day_one_face.h" #include "watch.h" +#include "watch_utility.h" -static const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) { // from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation @@ -66,13 +67,12 @@ static void _day_one_face_increment(day_one_state_t *state) { break; case PAGE_DAY: state->birth_day = state->birth_day + 1; - if (state->birth_day == 0 || state->birth_day > days_in_month[state->birth_month - 1]) { - state->birth_day = 1; - } break; default: break; } + if (state->birth_day == 0 || state->birth_day > (days_in_month[state->birth_month - 1] + (is_leap(state->birth_year) && state->birth_month == 2))) + state->birth_day = 1; } void day_one_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { diff --git a/movement/watch_faces/complication/time_left_face.c b/movement/watch_faces/complication/time_left_face.c index 9992bbf..74ed35b 100644 --- a/movement/watch_faces/complication/time_left_face.c +++ b/movement/watch_faces/complication/time_left_face.c @@ -27,6 +27,7 @@ #include "time_left_face.h" #include "watch.h" #include "watch_private_display.h" +#include "watch_utility.h" const char _state_titles[][3] = {{'D', 'L', ' '}, {'D', 'L', ' '}, {'D', 'A', ' '}, {'D', 'A', ' '}, {'Y', 'R', 'b'}, {'M', 'O', 'b'}, {'D', 'A', 'b'}, {'Y', 'R', 'd'}, {'M', 'O', 'd'}, {'D', 'A', 'd'}}; @@ -158,8 +159,7 @@ static void _draw(time_left_state_t *state, uint8_t subsecond) { /// @brief handle short or long pressing the alarm button static void _handle_alarm_button(time_left_state_t *state) { - const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint32_t tmp_day; + const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (state->current_page) { case TIME_LEFT_FACE_SETTINGS_STATE: // birth year state->birth_date.bit.year++; @@ -169,14 +169,7 @@ static void _handle_alarm_button(time_left_state_t *state) { state->birth_date.bit.month = (state->birth_date.bit.month % 12) + 1; break; case TIME_LEFT_FACE_SETTINGS_STATE + 2: // birth day - tmp_day = state->birth_date.bit.day; // use a temporary variable to avoid messing up the months - tmp_day++; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[state->birth_date.bit.month - 1]) && (state->birth_date.bit.month != 2 || (state->birth_date.bit.year % 4) != 0)) - || (state->birth_date.bit.month == 2 && (state->birth_date.bit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - state->birth_date.bit.day = tmp_day; + state->birth_date.bit.day++; break; case TIME_LEFT_FACE_SETTINGS_STATE + 3: // target year state->target_date.bit.year++; @@ -186,16 +179,13 @@ static void _handle_alarm_button(time_left_state_t *state) { state->target_date.bit.month = (state->target_date.bit.month % 12) + 1; break; case TIME_LEFT_FACE_SETTINGS_STATE + 5: // target day - tmp_day = state->target_date.bit.day; - tmp_day++; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[state->target_date.bit.month - 1]) && (state->target_date.bit.month != 2 || (state->target_date.bit.year % 4) != 0)) - || (state->target_date.bit.month == 2 && (state->target_date.bit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - state->target_date.bit.day = tmp_day; + state->target_date.bit.day++; break; } + if (state->birth_date.bit.day > (days_in_month[state->birth_date.bit.month - 1] + (is_leap(state->birth_date.bit.year) && state->birth_date.bit.month == 2))) + state->birth_date.bit.day = 1; + if (state->target_date.bit.day > (days_in_month[state->target_date.bit.month - 1] + (is_leap(state->target_date.bit.year) && state->target_date.bit.month == 2))) + state->target_date.bit.day = 1; } static void _initiate_setting(time_left_state_t *state) { diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 4b4be64..02cbb17 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -25,6 +25,7 @@ #include #include "set_time_face.h" #include "watch.h" +#include "watch_utility.h" #define SET_TIME_FACE_NUM_SETTINGS (7) const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; @@ -33,7 +34,7 @@ static bool _quick_ticks_running; static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time, uint8_t current_page) { // handles short or long pressing of the alarm button - const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (current_page) { case 0: // hour @@ -52,14 +53,7 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time.unit.month = (date_time.unit.month % 12) + 1; break; case 5: { // day - uint32_t tmp_day = date_time.unit.day; // use a temporary variable to avoid messing up the months - tmp_day = tmp_day + 1; - // handle February 29th on a leap year - if (((tmp_day > days_in_month[date_time.unit.month - 1]) && (date_time.unit.month != 2 || (date_time.unit.year % 4) != 0)) - || (date_time.unit.month == 2 && (date_time.unit.year % 4) == 0 && tmp_day > 29)) { - tmp_day = 1; - } - date_time.unit.day = tmp_day; + date_time.unit.day = date_time.unit.day + 1; break; } case 6: // time zone @@ -67,6 +61,8 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } + if (date_time.unit.day > (days_in_month[date_time.unit.month - 1] + (is_leap(date_time.unit.year) &&date_time.unit.month == 2))) + date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index 269612f..c760fd9 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -26,6 +26,7 @@ #include #include "set_time_hackwatch_face.h" #include "watch.h" +#include "watch_utility.h" char set_time_hackwatch_face_titles[][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; #define set_time_hackwatch_face_NUM_SETTINGS (sizeof(set_time_hackwatch_face_titles) / sizeof(*set_time_hackwatch_face_titles)) @@ -47,7 +48,7 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { uint8_t current_page = *((uint8_t *)context); - const uint8_t days_in_month[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz date_time_settings = watch_rtc_get_date_time(); @@ -119,10 +120,8 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s break; case 5: // day date_time_settings.unit.day = date_time_settings.unit.day - 2; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. if (date_time_settings.unit.day == 0) { - date_time_settings.unit.day = days_in_month[date_time_settings.unit.month - 1]; + date_time_settings.unit.day = days_in_month[date_time_settings.unit.month - 1] + (is_leap(date_time_settings.unit.year) && date_time_settings.unit.month == 2); } else date_time_settings.unit.day++; break; @@ -167,17 +166,14 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s break; case 5: // day date_time_settings.unit.day = date_time_settings.unit.day + 1; - // can't set to the 29th on a leap year. if it's february 29, set to 11:59 on the 28th. - // and it should roll over. - if (date_time_settings.unit.day > days_in_month[date_time_settings.unit.month - 1]) { - date_time_settings.unit.day = 1; - } break; case 6: // time zone settings->bit.time_zone++; if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } + if (date_time_settings.unit.day > (days_in_month[date_time_settings.unit.month - 1] + (is_leap(date_time_settings.unit.year) && date_time_settings.unit.month == 2))) + date_time_settings.unit.day = 1; if (current_page != 2) // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); //TODO: Do not update whole RTC, just what we are changing From 84f0db06549418c3d1a3bf1cf6ada00bcb0a2747 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 11:38:45 -0400 Subject: [PATCH 057/220] Fix to remove compiler complaint --- movement/watch_faces/complication/sunrise_sunset_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index ad2f533..98c7103 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -49,7 +49,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s double rise, set, minutes, seconds; bool show_next_match = false; movement_location_t movement_location; - if (state->longLatToUse == 0) + if (state->longLatToUse == 0 || _location_count <= 1) movement_location = (movement_location_t) watch_get_backup_data(1); else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; From 51176344dc5ea5393386dd609b92fddbddb895eb Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 3 Aug 2024 11:42:42 -0400 Subject: [PATCH 058/220] Made it so the code works with a completely empty preset list --- movement/watch_faces/complication/sunrise_sunset_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 98c7103..fbf60cf 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -359,7 +359,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti _sunrise_sunset_face_update_location_register(state); } _sunrise_sunset_face_update_settings_display(event, context); - } else if (_location_count == 1) { + } else if (_location_count <= 1) { movement_illuminate_led(); } if (state->page == 0) { @@ -368,7 +368,7 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti } break; case EVENT_LIGHT_LONG_PRESS: - if (_location_count == 1) break; + if (_location_count <= 1) break; else if (!state->page) movement_illuminate_led(); break; case EVENT_LIGHT_BUTTON_UP: From 7a15ed35910adb89e1ee197fd05f3928d108ec8f Mon Sep 17 00:00:00 2001 From: Christian Buschau Date: Wed, 26 Jun 2024 10:11:50 +0200 Subject: [PATCH 059/220] faces/alarm_thermometer: new watch face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../sensor/alarm_thermometer_face.c | 154 ++++++++++++++++++ .../sensor/alarm_thermometer_face.h | 74 +++++++++ 4 files changed, 230 insertions(+) create mode 100644 movement/watch_faces/sensor/alarm_thermometer_face.c create mode 100644 movement/watch_faces/sensor/alarm_thermometer_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..da32a45 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/sensor/alarm_thermometer_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..fda91ac 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "alarm_thermometer_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/sensor/alarm_thermometer_face.c b/movement/watch_faces/sensor/alarm_thermometer_face.c new file mode 100644 index 0000000..3b7821e --- /dev/null +++ b/movement/watch_faces/sensor/alarm_thermometer_face.c @@ -0,0 +1,154 @@ +/* + * MIT License + * + * Copyright (c) 2024 Christian Buschau + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "alarm_thermometer_face.h" +#include "thermistor_driver.h" + +static float _alarm_thermometer_face_update(bool in_fahrenheit) { + thermistor_driver_enable(); + float temperature_c = thermistor_driver_get_temperature(); + char buf[14]; + if (in_fahrenheit) { + sprintf(buf, "%4.1f#F", temperature_c * 1.8 + 32.0); + } else { + sprintf(buf, "%4.1f#C", temperature_c); + } + watch_display_string(buf, 4); + thermistor_driver_disable(); + return temperature_c; +} + +static void _alarm_thermometer_face_clear(int last[]) { + for (size_t i = 0; i < LAST_SIZE; i++) { + last[i] = INT_MIN; + } +} + +void alarm_thermometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(alarm_thermometer_state_t)); + memset(*context_ptr, 0, sizeof(alarm_thermometer_state_t)); + } +} + +void alarm_thermometer_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + alarm_thermometer_state_t *state = (alarm_thermometer_state_t *)context; + state->mode = MODE_NORMAL; + _alarm_thermometer_face_clear(state->last); + watch_display_string("AT", 0); +} + +bool alarm_thermometer_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + alarm_thermometer_state_t *state = (alarm_thermometer_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + _alarm_thermometer_face_update(settings->bit.use_imperial_units); + break; + case EVENT_TICK: + if (watch_rtc_get_date_time().unit.second % 5 == 0) { + switch (state->mode) { + case MODE_NORMAL: + _alarm_thermometer_face_update(settings->bit.use_imperial_units); + break; + case MODE_ALARM: + for (size_t i = LAST_SIZE - 1; i > 0; i--) { + state->last[i] = state->last[i - 1]; + } + state->last[0] = roundf(_alarm_thermometer_face_update(settings->bit.use_imperial_units) * 10.0f); + bool constant = true; + for (size_t i = 1; i < LAST_SIZE; i++) { + if (state->last[i - 1] != state->last[i]) { + constant = false; + break; + } + } + if (constant) { + state->mode = MODE_FREEZE; + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + movement_play_alarm(); + } + break; + case MODE_FREEZE: + break; + } + } + break; + case EVENT_ALARM_BUTTON_UP: + switch (state->mode) { + case MODE_NORMAL: + state->mode = MODE_ALARM; + watch_set_indicator(WATCH_INDICATOR_BELL); + _alarm_thermometer_face_clear(state->last); + break; + case MODE_FREEZE: + state->mode = MODE_NORMAL; + watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + break; + case MODE_ALARM: + state->mode = MODE_NORMAL; + watch_clear_indicator(WATCH_INDICATOR_BELL); + _alarm_thermometer_face_update(settings->bit.use_imperial_units); + break; + } + if (settings->bit.button_should_sound) { + watch_buzzer_play_note(BUZZER_NOTE_C7, 50); + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state->mode != MODE_FREEZE) { + settings->bit.use_imperial_units = !settings->bit.use_imperial_units; + _alarm_thermometer_face_update(settings->bit.use_imperial_units); + } + break; + case EVENT_LOW_ENERGY_UPDATE: + if (!watch_tick_animation_is_running()) { + state->mode = MODE_NORMAL; + watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + watch_start_tick_animation(1000); + } + if (watch_rtc_get_date_time().unit.minute % 5 == 0) { + _alarm_thermometer_face_update(settings->bit.use_imperial_units); + watch_display_string(" ", 8); + } + break; + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void alarm_thermometer_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} diff --git a/movement/watch_faces/sensor/alarm_thermometer_face.h b/movement/watch_faces/sensor/alarm_thermometer_face.h new file mode 100644 index 0000000..1b3aabf --- /dev/null +++ b/movement/watch_faces/sensor/alarm_thermometer_face.h @@ -0,0 +1,74 @@ +/* + * MIT License + * + * Copyright (c) 2024 Christian Buschau + * + * 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 ALARM_THERMOMETER_FACE_H_ +#define ALARM_THERMOMETER_FACE_H_ + +#include +#include "movement.h" + +/* + * ALARM THERMOMETER + * + * This watch face shows the current temperature in degrees Celsius. Press and + * hold the alarm button to toggle between Celsius and Fahrenheit. Press and + * release the alarm button to start a "timer". The watch will sound an alarm + * when the temperature remains constant for at least 30 seconds and the + * temperature will stop updating until you press the alarm button. You can + * cancel the alarm by pressing the button again. If the temperature doesn't + * remain constant until the low energy timeout is reached, the alarm will stop. + * This is useful to measure e.g. the room temperature. If you lay off your + * watch from your wrist, it will take some time until it cools down, and will + * notify you when the measurement is constant enough. + * THIS WATCH FACE IS NOT INTENDED TO DIAGNOSE, TREAT, CURE OR PREVENT ANY + * DISEASE. + */ + +#define LAST_SIZE 6 + +typedef enum { + MODE_NORMAL, + MODE_ALARM, + MODE_FREEZE +} alarm_thermometer_mode_t; + +typedef struct { + int last[LAST_SIZE]; + alarm_thermometer_mode_t mode; +} alarm_thermometer_state_t; + +void alarm_thermometer_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void alarm_thermometer_face_activate(movement_settings_t *settings, void *context); +bool alarm_thermometer_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void alarm_thermometer_face_resign(movement_settings_t *settings, void *context); + +#define alarm_thermometer_face ((const watch_face_t){ \ + alarm_thermometer_face_setup, \ + alarm_thermometer_face_activate, \ + alarm_thermometer_face_loop, \ + alarm_thermometer_face_resign, \ + NULL, \ +}) + +#endif // ALARM_THERMOMETER_FACE_H_ From 2824a62908b9f7b6e24b25c5ba46e0f3287ae80f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 4 Aug 2024 09:43:56 -0400 Subject: [PATCH 060/220] Bugfix on not registering the top of an hour --- movement/movement.c | 22 +++++++++++++++---- movement/movement.h | 2 +- movement/watch_faces/clock/clock_face.c | 7 +----- .../watch_faces/clock/simple_clock_face.c | 7 +----- watch-library/shared/watch/watch_utility.c | 6 ++--- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 256aa8a..aead621 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -430,8 +430,8 @@ uint8_t movement_claim_backup_register(void) { return movement_state.next_available_backup_register++; } -uint8_t check_and_act_on_daylight_savings(watch_date_time date_time) { - if (movement_state.settings.bit.dst_active) return date_time.unit.hour; +bool check_and_act_on_daylight_savings(watch_date_time date_time) { + if (!movement_state.settings.bit.dst_active) return false; uint8_t dst_result = get_dst_status(date_time); bool dst_skip_rolling_back = get_dst_skip_rolling_back(); @@ -439,15 +439,17 @@ uint8_t check_and_act_on_daylight_savings(watch_date_time date_time) { clear_dst_skip_rolling_back(); } else if (dst_result == DST_ENDING && !dst_skip_rolling_back) { - set_dst_skip_rolling_back(); date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; watch_rtc_set_date_time(date_time); + set_dst_skip_rolling_back(); + return true; } else if (dst_result == DST_STARTING) { date_time.unit.hour = (date_time.unit.hour + 1) % 24; watch_rtc_set_date_time(date_time); + return true; } - return date_time.unit.hour; + return false; } int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { @@ -476,6 +478,18 @@ void app_init(void) { movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL; movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; movement_state.settings.bit.dst_active = MOVEMENT_DEFAULT_DST_ACTIVE; + +#ifdef MAKEFILE_TIMEZONE + timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets; + for (int i = 0; i < NUM_TIME_ZONES; i++) { + if (timezone_offsets[i] == MAKEFILE_TIMEZONE) { + movement_state.settings.bit.time_zone = i; + break; + } + } +#else + movement_state.settings.bit.time_zone = 35; // Atlantic Time as default +#endif movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; movement_state.next_available_backup_register = 4; diff --git a/movement/movement.h b/movement/movement.h index 6dd38ba..e75f4e8 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -313,7 +313,7 @@ void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); uint8_t movement_claim_backup_register(void); -uint8_t check_and_act_on_daylight_savings(watch_date_time date_time); // Returns the currently set hour +bool check_and_act_on_daylight_savings(watch_date_time date_time); // Returns if the time was changed due to DST int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time); #endif // MOVEMENT_H_ diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index af601ce..e29d587 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -284,12 +284,7 @@ bool clock_face_wants_background_task(movement_settings_t *settings, void *conte (void) settings; clock_state_t *state = (clock_state_t *) context; watch_date_time date_time = watch_rtc_get_date_time(); - uint8_t hour_dst = check_and_act_on_daylight_savings(date_time); - if(hour_dst != date_time.unit.hour) { - char buf[3 + 1]; - sprintf(buf, "%2d", hour_dst); - watch_display_string(buf, 4); - } + check_and_act_on_daylight_savings(date_time); if (!state->time_signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 20dfb54..118dc32 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -154,12 +154,7 @@ bool simple_clock_face_wants_background_task(movement_settings_t *settings, void (void) settings; simple_clock_state_t *state = (simple_clock_state_t *)context; watch_date_time date_time = watch_rtc_get_date_time(); - uint8_t hour_dst = check_and_act_on_daylight_savings(date_time); - if(hour_dst != date_time.unit.hour) { - char buf[3 + 1]; - sprintf(buf, "%2d", hour_dst); - watch_display_string(buf, 4); - } + check_and_act_on_daylight_savings(date_time); if (!state->signal_enabled) return false; return date_time.unit.minute == 0; diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 5938b60..3dfb37b 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -106,10 +106,10 @@ uint8_t get_dst_status(watch_date_time date_time) { dst_end_time.unit.day = 15 - watch_utility_get_iso8601_weekday_number(dst_end_time.unit.year + WATCH_RTC_REFERENCE_YEAR, dst_end_time.unit.month, 1); unix_dst_end_time = watch_utility_date_time_to_unix_time(dst_end_time, 0); - if (date_time.unit.second > 45) // In emu, it's been seen that we may trigger at 59sec rather than exactly 0 each time - date_time.unit.minute = (date_time.unit.minute + 1) % 60; - date_time.unit.second = 0; unix_curr_time = watch_utility_date_time_to_unix_time(date_time, 0); + unix_curr_time -= date_time.unit.second; + if (date_time.unit.second > 45) // In emu, it's been seen that we may trigger at 59sec rather than exactly 0 each time + unix_curr_time += 60; if (unix_curr_time == unix_dst_start_time) return DST_STARTING; if (unix_curr_time == unix_dst_end_time) return DST_ENDING; From 598e87618654c53a5a41e4a77b981b07c738283f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 4 Aug 2024 10:11:33 -0400 Subject: [PATCH 061/220] check_and_act_on_daylight_savings now only occurs in one spot --- movement/movement.c | 48 ++++++++++--------- movement/movement.h | 1 - movement/watch_faces/clock/clock_face.c | 4 +- .../clock/minute_repeater_decimal_face.c | 4 +- .../clock/repetition_minute_face.c | 4 +- .../clock/simple_clock_bin_led_face.c | 4 +- .../watch_faces/clock/simple_clock_face.c | 4 +- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index aead621..ebd831f 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -247,6 +247,31 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } +static bool _check_and_act_on_daylight_savings(void) { + if (!movement_state.settings.bit.dst_active) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + // No need for all of the unix time calculations for times not at the beginning or end of the hour + if (date_time.unit.minute > 1 && date_time.unit.minute < 59) return false; + uint8_t dst_result = get_dst_status(date_time); + bool dst_skip_rolling_back = get_dst_skip_rolling_back(); + + if (dst_skip_rolling_back && (dst_result == DST_ENDED)) { + clear_dst_skip_rolling_back(); + } + else if (dst_result == DST_ENDING && !dst_skip_rolling_back) { + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + watch_rtc_set_date_time(date_time); + set_dst_skip_rolling_back(); + return true; + } + else if (dst_result == DST_STARTING) { + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + watch_rtc_set_date_time(date_time); + return true; + } + return false; +} + static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -256,6 +281,7 @@ static void _movement_handle_background_tasks(void) { watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]); } } + _check_and_act_on_daylight_savings(); movement_state.needs_background_tasks_handled = false; } @@ -430,28 +456,6 @@ uint8_t movement_claim_backup_register(void) { return movement_state.next_available_backup_register++; } -bool check_and_act_on_daylight_savings(watch_date_time date_time) { - if (!movement_state.settings.bit.dst_active) return false; - uint8_t dst_result = get_dst_status(date_time); - bool dst_skip_rolling_back = get_dst_skip_rolling_back(); - - if (dst_skip_rolling_back && (dst_result == DST_ENDED)) { - clear_dst_skip_rolling_back(); - } - else if (dst_result == DST_ENDING && !dst_skip_rolling_back) { - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - watch_rtc_set_date_time(date_time); - set_dst_skip_rolling_back(); - return true; - } - else if (dst_result == DST_STARTING) { - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - watch_rtc_set_date_time(date_time); - return true; - } - return false; -} - int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { if (!movement_state.settings.bit.dst_active) return movement_timezone_offsets[timezone_idx]; if (dst_occurring(date_time)) diff --git a/movement/movement.h b/movement/movement.h index e75f4e8..d0595fb 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -313,7 +313,6 @@ void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); uint8_t movement_claim_backup_register(void); -bool check_and_act_on_daylight_savings(watch_date_time date_time); // Returns if the time was changed due to DST int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time); #endif // MOVEMENT_H_ diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index e29d587..eab5cd8 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -283,9 +283,9 @@ void clock_face_resign(movement_settings_t *settings, void *context) { bool clock_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; clock_state_t *state = (clock_state_t *) context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(date_time); if (!state->time_signal_enabled) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/minute_repeater_decimal_face.c b/movement/watch_faces/clock/minute_repeater_decimal_face.c index ab0e6de..2cedc30 100644 --- a/movement/watch_faces/clock/minute_repeater_decimal_face.c +++ b/movement/watch_faces/clock/minute_repeater_decimal_face.c @@ -230,9 +230,9 @@ void minute_repeater_decimal_face_resign(movement_settings_t *settings, void *co bool minute_repeater_decimal_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; minute_repeater_decimal_state_t *state = (minute_repeater_decimal_state_t *)context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(date_time); if (!state->signal_enabled) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/repetition_minute_face.c b/movement/watch_faces/clock/repetition_minute_face.c index 1afc4ce..e9e5e31 100644 --- a/movement/watch_faces/clock/repetition_minute_face.c +++ b/movement/watch_faces/clock/repetition_minute_face.c @@ -213,9 +213,9 @@ void repetition_minute_face_resign(movement_settings_t *settings, void *context) bool repetition_minute_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; repetition_minute_state_t *state = (repetition_minute_state_t *)context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(date_time); if (!state->signal_enabled) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/simple_clock_bin_led_face.c b/movement/watch_faces/clock/simple_clock_bin_led_face.c index 1762cfa..cf39c18 100644 --- a/movement/watch_faces/clock/simple_clock_bin_led_face.c +++ b/movement/watch_faces/clock/simple_clock_bin_led_face.c @@ -214,9 +214,9 @@ void simple_clock_bin_led_face_resign(movement_settings_t *settings, void *conte bool simple_clock_bin_led_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; simple_clock_bin_led_state_t *state = (simple_clock_bin_led_state_t *)context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(date_time); if (!state->signal_enabled) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + return date_time.unit.minute == 0; } diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index 118dc32..fbc2c4b 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -153,9 +153,9 @@ void simple_clock_face_resign(movement_settings_t *settings, void *context) { bool simple_clock_face_wants_background_task(movement_settings_t *settings, void *context) { (void) settings; simple_clock_state_t *state = (simple_clock_state_t *)context; - watch_date_time date_time = watch_rtc_get_date_time(); - check_and_act_on_daylight_savings(date_time); if (!state->signal_enabled) return false; + watch_date_time date_time = watch_rtc_get_date_time(); + return date_time.unit.minute == 0; } From 7000d08ba577569076a6b0c6799b7cb71f5cb772 Mon Sep 17 00:00:00 2001 From: Alex Maestas Date: Mon, 5 Aug 2024 23:06:18 +0000 Subject: [PATCH 062/220] add a format command --- movement/filesystem.c | 26 +++++++++++++++++++++++++- movement/filesystem.h | 1 + movement/shell_cmd_list.c | 10 ++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/movement/filesystem.c b/movement/filesystem.c index 9df0a8d..b5e3fca 100644 --- a/movement/filesystem.c +++ b/movement/filesystem.c @@ -127,6 +127,22 @@ bool filesystem_init(void) { return err == LFS_ERR_OK; } +int _filesystem_format(void); +int _filesystem_format(void) { + int err = lfs_unmount(&lfs); + if (err < 0) { + printf("Couldn't unmount - continuing to format, but you should reboot afterwards!\r\n"); + } + + err = lfs_format(&lfs, &cfg); + if (err < 0) return err; + + err = lfs_mount(&lfs, &cfg); + if (err < 0) return err; + printf("Filesystem re-mounted with %ld bytes free.\r\n", filesystem_get_free_space()); + return 0; +} + bool filesystem_file_exists(char *filename) { info.type = 0; lfs_stat(&lfs, filename, &info); @@ -251,6 +267,15 @@ int filesystem_cmd_rm(int argc, char *argv[]) { return 0; } +int filesystem_cmd_format(int argc, char *argv[]) { + (void) argc; + if(strcmp(argv[1], "YES") == 0) { + return _filesystem_format(); + } + return 1; +} + + int filesystem_cmd_echo(int argc, char *argv[]) { (void) argc; @@ -279,4 +304,3 @@ int filesystem_cmd_echo(int argc, char *argv[]) { return 0; } - diff --git a/movement/filesystem.h b/movement/filesystem.h index fa3d9d1..472f281 100644 --- a/movement/filesystem.h +++ b/movement/filesystem.h @@ -100,6 +100,7 @@ int filesystem_cmd_ls(int argc, char *argv[]); int filesystem_cmd_cat(int argc, char *argv[]); int filesystem_cmd_df(int argc, char *argv[]); int filesystem_cmd_rm(int argc, char *argv[]); +int filesystem_cmd_format(int argc, char *argv[]); int filesystem_cmd_echo(int argc, char *argv[]); #endif // FILESYSTEM_H_ diff --git a/movement/shell_cmd_list.c b/movement/shell_cmd_list.c index 0ea08a5..e2d700b 100644 --- a/movement/shell_cmd_list.c +++ b/movement/shell_cmd_list.c @@ -85,6 +85,13 @@ shell_command_t g_shell_commands[] = { .max_args = 1, .cb = filesystem_cmd_rm, }, + { + .name = "format", + .help = "usage: format YES", + .min_args = 1, + .max_args = 1, + .cb = filesystem_cmd_format, + }, { .name = "echo", .help = "usage: echo TEXT {>,>>} FILE", @@ -109,7 +116,7 @@ static int help_cmd(int argc, char *argv[]) { printf("Command List:\r\n"); for (size_t i = 0; i < g_num_shell_commands; i++) { - printf(" %s\t%s\r\n", + printf(" %s\t%s\r\n", g_shell_commands[i].name, (g_shell_commands[i].help) ? g_shell_commands[i].help : "" ); @@ -156,4 +163,3 @@ static int stress_cmd(int argc, char *argv[]) { return 0; } - From ca1da33b8241b91446c966babac7617851af53d2 Mon Sep 17 00:00:00 2001 From: Alex Maestas Date: Mon, 5 Aug 2024 23:09:24 +0000 Subject: [PATCH 063/220] in filesystem_init, handle the error code correctly --- movement/filesystem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/filesystem.c b/movement/filesystem.c index b5e3fca..a1953bc 100644 --- a/movement/filesystem.c +++ b/movement/filesystem.c @@ -120,7 +120,7 @@ bool filesystem_init(void) { printf("Ignore that error! Formatting filesystem...\r\n"); err = lfs_format(&lfs, &cfg); if (err < 0) return false; - err = lfs_mount(&lfs, &cfg) == LFS_ERR_OK; + err = lfs_mount(&lfs, &cfg); printf("Filesystem mounted with %ld bytes free.\r\n", filesystem_get_free_space()); } From 44bdc8503eeb53caa4165b432ac04f45fac50431 Mon Sep 17 00:00:00 2001 From: Alex Maestas Date: Mon, 5 Aug 2024 23:11:54 +0000 Subject: [PATCH 064/220] explain usage in format command when arg isn't YES --- movement/filesystem.c | 1 + 1 file changed, 1 insertion(+) diff --git a/movement/filesystem.c b/movement/filesystem.c index a1953bc..326db74 100644 --- a/movement/filesystem.c +++ b/movement/filesystem.c @@ -272,6 +272,7 @@ int filesystem_cmd_format(int argc, char *argv[]) { if(strcmp(argv[1], "YES") == 0) { return _filesystem_format(); } + printf("usage: format YES\r\n"); return 1; } From 1da9d0aefe1010f9f4ac6825017410b56d9249bc Mon Sep 17 00:00:00 2001 From: Struan Date: Wed, 7 Aug 2024 22:24:57 -0600 Subject: [PATCH 065/220] fix: july has 31 days --- movement/watch_faces/settings/set_time_hackwatch_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index fbe8cbb..166b26e 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -47,7 +47,7 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { uint8_t current_page = *((uint8_t *)context); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; + const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz date_time_settings = watch_rtc_get_date_time(); From f85a7f2c780144223ef07a058437a6392aa51d25 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 10 Aug 2024 06:38:46 -0400 Subject: [PATCH 066/220] Swapped the bell and alarm icon on the clock face to match Casio's doc --- movement/watch_faces/clock/clock_face.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index eab5cd8..66b40c4 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -70,11 +70,11 @@ static void clock_indicate(WatchIndicatorSegment indicator, bool on) { } static void clock_indicate_alarm(movement_settings_t *settings) { - clock_indicate(WATCH_INDICATOR_BELL, settings->bit.alarm_enabled); + clock_indicate(WATCH_INDICATOR_SIGNAL, settings->bit.alarm_enabled); } static void clock_indicate_time_signal(clock_state_t *clock) { - clock_indicate(WATCH_INDICATOR_SIGNAL, clock->time_signal_enabled); + clock_indicate(WATCH_INDICATOR_BELL, clock->time_signal_enabled); } static void clock_indicate_24h(movement_settings_t *settings) { From 09576807eb1095ae5d0789ca8dd82e5afe1e7db3 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 10 Aug 2024 07:40:52 -0400 Subject: [PATCH 067/220] Made the days_in_month its own function --- apps/beats-time/app.c | 3 +-- movement/watch_faces/complication/day_one_face.c | 4 +--- movement/watch_faces/complication/time_left_face.c | 5 ++--- movement/watch_faces/settings/set_time_face.c | 3 +-- movement/watch_faces/settings/set_time_hackwatch_face.c | 5 ++--- watch-library/shared/watch/watch_utility.c | 8 ++++++++ watch-library/shared/watch/watch_utility.h | 6 ++++++ 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/beats-time/app.c b/apps/beats-time/app.c index f979247..8d6c1db 100644 --- a/apps/beats-time/app.c +++ b/apps/beats-time/app.c @@ -204,7 +204,6 @@ void set_time_mode_handle_primary_button(void) { void set_time_mode_handle_secondary_button(void) { watch_date_time date_time = watch_rtc_get_date_time(); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31}; switch (application_state.page) { case 0: // hour @@ -227,7 +226,7 @@ void set_time_mode_handle_secondary_button(void) { date_time.unit.day = date_time.unit.day + 1; break; } - if (date_time.unit.day > days_in_month[date_time.unit.month - 1] + (is_leap(date_time.unit.year) && date_time.unit.month == 2)) + if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/watch_faces/complication/day_one_face.c b/movement/watch_faces/complication/day_one_face.c index 4429611..aa65321 100644 --- a/movement/watch_faces/complication/day_one_face.c +++ b/movement/watch_faces/complication/day_one_face.c @@ -28,8 +28,6 @@ #include "watch.h" #include "watch_utility.h" -static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - static uint32_t _day_one_face_juliandaynum(uint16_t year, uint16_t month, uint16_t day) { // from here: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation return (1461 * (year + 4800 + (month - 14) / 12)) / 4 + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 - (3 * ((year + 4900 + (month - 14) / 12) / 100))/4 + day - 32075; @@ -71,7 +69,7 @@ static void _day_one_face_increment(day_one_state_t *state) { default: break; } - if (state->birth_day == 0 || state->birth_day > (days_in_month[state->birth_month - 1] + (is_leap(state->birth_year) && state->birth_month == 2))) + if (state->birth_day == 0 || state->birth_day > days_in_month(state->birth_month, state->birth_year)) state->birth_day = 1; } diff --git a/movement/watch_faces/complication/time_left_face.c b/movement/watch_faces/complication/time_left_face.c index 74ed35b..99b0f87 100644 --- a/movement/watch_faces/complication/time_left_face.c +++ b/movement/watch_faces/complication/time_left_face.c @@ -159,7 +159,6 @@ static void _draw(time_left_state_t *state, uint8_t subsecond) { /// @brief handle short or long pressing the alarm button static void _handle_alarm_button(time_left_state_t *state) { - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (state->current_page) { case TIME_LEFT_FACE_SETTINGS_STATE: // birth year state->birth_date.bit.year++; @@ -182,9 +181,9 @@ static void _handle_alarm_button(time_left_state_t *state) { state->target_date.bit.day++; break; } - if (state->birth_date.bit.day > (days_in_month[state->birth_date.bit.month - 1] + (is_leap(state->birth_date.bit.year) && state->birth_date.bit.month == 2))) + if (state->birth_date.bit.day > days_in_month(state->birth_date.bit.month, state->birth_date.bit.year)) state->birth_date.bit.day = 1; - if (state->target_date.bit.day > (days_in_month[state->target_date.bit.month - 1] + (is_leap(state->target_date.bit.year) && state->target_date.bit.month == 2))) + if (state->target_date.bit.day > days_in_month(state->target_date.bit.month, state->birth_date.bit.year)) state->target_date.bit.day = 1; } diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 02cbb17..503dffc 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -34,7 +34,6 @@ static bool _quick_ticks_running; static void _handle_alarm_button(movement_settings_t *settings, watch_date_time date_time, uint8_t current_page) { // handles short or long pressing of the alarm button - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; switch (current_page) { case 0: // hour @@ -61,7 +60,7 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } - if (date_time.unit.day > (days_in_month[date_time.unit.month - 1] + (is_leap(date_time.unit.year) &&date_time.unit.month == 2))) + if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) date_time.unit.day = 1; watch_rtc_set_date_time(date_time); } diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index c760fd9..8ba56cb 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -48,7 +48,6 @@ void set_time_hackwatch_face_activate(movement_settings_t *settings, void *conte bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { uint8_t current_page = *((uint8_t *)context); - const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (event.subsecond == 15) // Delay displayed time update by ~0.5 seconds, to align phase exactly to main clock at 1Hz date_time_settings = watch_rtc_get_date_time(); @@ -121,7 +120,7 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s case 5: // day date_time_settings.unit.day = date_time_settings.unit.day - 2; if (date_time_settings.unit.day == 0) { - date_time_settings.unit.day = days_in_month[date_time_settings.unit.month - 1] + (is_leap(date_time_settings.unit.year) && date_time_settings.unit.month == 2); + date_time_settings.unit.day = days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR); } else date_time_settings.unit.day++; break; @@ -172,7 +171,7 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } - if (date_time_settings.unit.day > (days_in_month[date_time_settings.unit.month - 1] + (is_leap(date_time_settings.unit.year) && date_time_settings.unit.month == 2))) + if (date_time_settings.unit.day > days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR)) date_time_settings.unit.day = 1; if (current_page != 2) // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index 64b3bb7..c00791e 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -315,3 +315,11 @@ uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minut new += seconds; return new; } + +uint8_t days_in_month(uint8_t month, uint16_t year) { + static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint8_t days = days_in_month[month - 1]; + if (month == 2 && is_leap(year)) + days += 1; + return days; +} diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h index e2326d1..5533e19 100644 --- a/watch-library/shared/watch/watch_utility.h +++ b/watch-library/shared/watch/watch_utility.h @@ -164,4 +164,10 @@ float watch_utility_thermistor_temperature(uint16_t value, bool highside, float */ uint32_t watch_utility_offset_timestamp(uint32_t now, int8_t hours, int8_t minutes, int8_t seconds); +/** @brief Returns the number of days in a month. It also handles Leap Years for February. + * @param month The month of the date (1-12) + * @param year The year of the date (ex. 2022) + */ +uint8_t days_in_month(uint8_t month, uint16_t year); + #endif From 9861da84c389c43639d85b39833c51e434152660 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 11 Aug 2024 11:00:08 -0400 Subject: [PATCH 068/220] Changed debounce to ignore button presses after a press down or up rathe rthan dact after a set amount of time due to an issue of delay_ms allowing a shoft-bricking --- movement/movement.c | 50 +++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index d364de5..2a45f8a 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,7 +23,8 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 -#define DEBOUNCE_TICKS 1 // In terms of *7.8125ms +#define DEBOUNCE_TICKS_DOWN 2 // In terms of *7.8125ms +#define DEBOUNCE_TICKS_UP 2 // In terms of *7.8125ms #include #include @@ -170,6 +171,9 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; + movement_state.debounce_ticks_light = 0; + movement_state.debounce_ticks_alarm = 0; + movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -388,9 +392,6 @@ void app_init(void) { movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; - movement_state.debounce_ticks_light = 0; - movement_state.debounce_ticks_alarm = 0; - movement_state.debounce_ticks_mode = 0; movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); @@ -633,29 +634,25 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; - _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -static void light_btn_action(void) { - bool pin_level = watch_get_pin_level(BTN_LIGHT); +static void light_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } -static void mode_btn_action(void) { - bool pin_level = watch_get_pin_level(BTN_MODE); +static void mode_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } -static void alarm_btn_action(void) { - bool pin_level = watch_get_pin_level(BTN_ALARM); - uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); +static void alarm_btn_action(bool pin_level) { _movement_reset_inactivity_countdown(); + uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; @@ -663,19 +660,27 @@ static void alarm_btn_action(void) { event.event_type = event_type; } -void cb_light_btn_interrupt(void) { - movement_state.debounce_ticks_light = DEBOUNCE_TICKS; +static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { + if (*debounce_ticks <= 1) { + bool pin_level = watch_get_pin_level(pin); + function(pin_level); + *debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP; + } + else + *down_timestamp = 0; _movement_enable_fast_tick_if_needed(); } +void cb_light_btn_interrupt(void) { + debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); +} + void cb_mode_btn_interrupt(void) { - movement_state.debounce_ticks_mode = DEBOUNCE_TICKS; - _movement_enable_fast_tick_if_needed(); + debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); } void cb_alarm_btn_interrupt(void) { - movement_state.debounce_ticks_alarm = DEBOUNCE_TICKS; - _movement_enable_fast_tick_if_needed(); + debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); } void cb_alarm_btn_extwake(void) { @@ -688,12 +693,9 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) - light_btn_action(); - if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) - alarm_btn_action(); - if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) - mode_btn_action(); + if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; From 2cdfa2d3b3bd59f17c20540b3326dd869dc90c10 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 02:45:22 -0400 Subject: [PATCH 069/220] Set the debounce tick variables to 0 to make the face work the same as stock. --- movement/movement.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 2a45f8a..9ebf8ec 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,8 +23,8 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 -#define DEBOUNCE_TICKS_DOWN 2 // In terms of *7.8125ms -#define DEBOUNCE_TICKS_UP 2 // In terms of *7.8125ms +#define DEBOUNCE_TICKS_DOWN 0 // In terms of *7.8125ms +#define DEBOUNCE_TICKS_UP 0 // In terms of *7.8125ms #include #include From e9837ff0cbf08183e7f6b2736458078e6c9665f8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 02:52:30 -0400 Subject: [PATCH 070/220] Stops Running cb_fast_tick when the watch debounce timer is defined as 0 --- movement/movement.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 9ebf8ec..9d97ede 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -661,14 +661,14 @@ static void alarm_btn_action(bool pin_level) { } static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { - if (*debounce_ticks <= 1) { + if (*debounce_ticks == 0) { bool pin_level = watch_get_pin_level(pin); function(pin_level); *debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP; + if (*debounce_ticks != 0) _movement_enable_fast_tick_if_needed(); } else *down_timestamp = 0; - _movement_enable_fast_tick_if_needed(); } void cb_light_btn_interrupt(void) { From 20b4a32835d6b8aedda40cb49025cf3fb78beebe Mon Sep 17 00:00:00 2001 From: Joseph Bryant Date: Sun, 18 Aug 2024 20:09:06 +0100 Subject: [PATCH 071/220] make sure we don't miss our scheduled tasks --- movement/movement.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/movement.c b/movement/movement.c index cb3dcf7..67d5360 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -201,7 +201,7 @@ static void _movement_handle_scheduled_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { if (scheduled_tasks[i].reg) { - if (scheduled_tasks[i].reg == date_time.reg) { + if (scheduled_tasks[i].reg <= date_time.reg) { scheduled_tasks[i].reg = 0; movement_event_t background_event = { EVENT_BACKGROUND_TASK, 0 }; watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]); From dcd4d12c0ab55aea50fc93f4de390a815d4ff6f5 Mon Sep 17 00:00:00 2001 From: Joseph Bryant Date: Sun, 18 Aug 2024 21:50:41 +0100 Subject: [PATCH 072/220] avoid delta overflow in countdown draw --- movement/watch_faces/complication/countdown_face.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index be04040..6ae3d93 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -87,7 +87,10 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { switch (state->mode) { case cd_running: - delta = state->target_ts - state->now_ts; + if (state->target_ts <= state->now_ts) + delta = 0; + else + delta = state->target_ts - state->now_ts; result = div(delta, 60); state->seconds = result.rem; result = div(result.quot, 60); From e2c5babb2c7cd0141a5a78e9f94458804bbd9764 Mon Sep 17 00:00:00 2001 From: Joseph Bryant Date: Sun, 18 Aug 2024 22:03:05 +0100 Subject: [PATCH 073/220] don't change the bell indicator from within background task --- movement/watch_faces/complication/countdown_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index 6ae3d93..b48ef59 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -100,6 +100,7 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { break; case cd_reset: case cd_paused: + watch_clear_indicator(WATCH_INDICATOR_BELL); sprintf(buf, "CD %2d%02d%02d", state->hours, state->minutes, state->seconds); break; case cd_setting: @@ -133,7 +134,6 @@ static void pause(countdown_state_t *state) { static void reset(countdown_state_t *state) { state->mode = cd_reset; movement_cancel_background_task(); - watch_clear_indicator(WATCH_INDICATOR_BELL); load_countdown(state); } From a4fc048f94c8b35618b74c86336cbc51d5c09da1 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Thu, 22 Aug 2024 14:53:17 +0200 Subject: [PATCH 074/220] Update TOTP Face Documentation The TOTP face header file documentation contained outdated instructions for configuring the complication with TOTP credentials. This PR updates the documentation to match what is expected in `totp_face.c`. --- movement/watch_faces/complication/totp_face.h | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/movement/watch_faces/complication/totp_face.h b/movement/watch_faces/complication/totp_face.h index 19a2cd4..acdcc8b 100644 --- a/movement/watch_faces/complication/totp_face.h +++ b/movement/watch_faces/complication/totp_face.h @@ -48,15 +48,19 @@ * o SHA512 * * Instructions: - * o Find your secret key(s) and convert them to the required format. - * o Use https://cryptii.com/pipes/base32-to-hex to convert base32 to hex - * o Use https://github.com/susam/mintotp to generate test codes for verification - * o Edit global variables in "totp_face.c" to configure your stored keys: - * o "keys", "key_sizes", "timesteps", and "algorithms" set the - * cryptographic parameters for each secret key. - * o "labels" sets the two-letter label for each key - * (This replaces the day-of-week indicator) - * o Once finished, remove the two provided examples. + * o Find your secret key(s). + * o Use https://github.com/susam/mintotp to generate test codes for + * verification + * o Edit global `credentials` variable in "totp_face.c" to configure your + * TOTP credentials. The file includes two examples that you can use as a + * reference. Credentials are added with the `CREDENTIAL` macro in the form + * `CREDENTIAL(label, key, algorithm, timestep)` where: + * o `label` is a 2 character label that is displayed in the weekday digits + * to identify the TOTP credential. + * o `key` is a string with the base32 encoded secret. + * o `algorithm` is one of the supported hashing algorithms listed above. + * o `timestep` is how often the TOTP refreshes in seconds. This is usually + * 30 seconds. * * If you have more than one secret key, press ALARM to cycle through them. * Press LIGHT to cycle in the other direction or keep it pressed longer to From f02e73c1c46f7896d16de98704a43aeaf19912a0 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Thu, 22 Aug 2024 14:54:41 +0200 Subject: [PATCH 075/220] Update Devcontainer Dockerfile This PR updates the .devcontainer Dockerfile which was no longer working. Attempting to build an image from the Dockerfile would result in an error: ``` E: The repository 'http://archive.ubuntu.com/ubuntu kinetic Release' does not have a Release file. ``` AFAIU, this is because the 22.04 release is no longer supported. I changed the base image to the current LTS release, which should remain supported until 2029. In addition, the Dockerfile is also modified to install `emscripten` APT package. While this is not the recommented installation method from the Emscripten docs, it is much more convenient and works enough to build the simulator using the resulting Docker image. --- .devcontainer/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 55565a0..4f40656 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,4 @@ -FROM ubuntu:22.10 - -# TODO: install emscripten (https://emscripten.org/docs/getting_started/downloads.html) +FROM ubuntu:24.04 # TODO: Clean this up once buildkit is supported gracefully in devcontainers # https://github.com/microsoft/vscode-remote-release/issues/1409 @@ -27,7 +25,9 @@ RUN apt-get update \ # ca certs need to be available for fetching git submodules ca-certificates \ # python is used to convert binaries to uf2 files - python3 python-is-python3 + python3 python-is-python3 \ + # emscripten for building simulator + emscripten # Download and verify both x86-64 and aarch64 toolchains. This is unfortunate and # slows down the build, but it's a clean-ish option until buildkit can be used. @@ -47,4 +47,4 @@ RUN /bin/sh -c 'set -ex && \ fi' RUN rm $X86_64_TOOLCHAIN_FILENAME -RUN rm $AARCH64_TOOLCHAIN_FILENAME \ No newline at end of file +RUN rm $AARCH64_TOOLCHAIN_FILENAME From 9640f452cd815f9133a6c1de8f474b8dc769f66d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 22 Aug 2024 20:46:47 -0400 Subject: [PATCH 076/220] Made the T and Y characters look more unique on the 4 and 6 position --- watch-library/shared/watch/watch_private_display.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c index c12957d..7ea4141 100644 --- a/watch-library/shared/watch/watch_private_display.c +++ b/watch-library/shared/watch/watch_private_display.c @@ -43,6 +43,8 @@ void watch_display_character(uint8_t character, uint8_t position) { else if (character == 'M' || character == 'm' || character == 'N') character = 'n'; // M and uppercase N need to be lowercase n else if (character == 'c') character = 'C'; // C needs to be uppercase else if (character == 'J') character = 'j'; // same + else if (character == 't' || character == 'T') character = '+'; // t in those locations looks like E + else if (character == 'y' || character == 'Y') character = '4'; // t in those locations looks like g else if (character == 'v' || character == 'V' || character == 'U' || character == 'W' || character == 'w') character = 'u'; // bottom segment duplicated, so show in top half } else { if (character == 'u') character = 'v'; // we can use the bottom segment; move to lower half From c6f2bff75e456af85fce4728db310320095ca9b5 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 23 Aug 2024 17:35:16 -0400 Subject: [PATCH 077/220] Code review edits --- movement/movement.c | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 9d97ede..b49ae7b 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,8 +23,16 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 -#define DEBOUNCE_TICKS_DOWN 0 // In terms of *7.8125ms -#define DEBOUNCE_TICKS_UP 0 // In terms of *7.8125ms +#define DEBOUNCE_TICKS_DOWN 0 +#define DEBOUNCE_TICKS_UP 0 +/* +DEBOUNCE_TICKS_DOWN and DEBOUNCE_TICKS_UP are in terms of fast_cb ticks after a button is pressed. +The logic is that pressed of a button are ignored until the cb_fast_tick function runs this variable amount of times. +Without modifying the code, the cb_fast_tick frequency is 128Hz, or 7.8125ms. +It is not suggested to set this value to one for debouncing, as the callback occurs asynchronously of the button's press, +meaning that if a button was pressed and 7ms passed since th elast time cb_fast_tick was called, then there will be only 812.5us +of debounce time. +*/ #include #include @@ -640,19 +648,21 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e } } -static void light_btn_action(bool pin_level) { - _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); +static movement_event_type_t btn_action(bool pin_level, int code, uint16_t *timestamp) { + _movement_reset_inactivity_countdown(); + return _figure_out_button_event(pin_level, code, timestamp); } -static void mode_btn_action(bool pin_level) { - _movement_reset_inactivity_countdown(); - event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); +static void light_btn_action(bool pin_level) { + event.event_type = btn_action(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); +} + +static void mode_btn_action(bool pin_level) { + event.event_type = btn_action(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } static void alarm_btn_action(bool pin_level) { - _movement_reset_inactivity_countdown(); - uint8_t event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); + uint8_t event_type = btn_action(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); if (movement_state.ignore_alarm_btn_after_sleep){ if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; return; @@ -671,6 +681,12 @@ static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *d *down_timestamp = 0; } +static void movement_disable_if_debounce_complete(void) { + if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); + if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); +} + void cb_light_btn_interrupt(void) { debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); } @@ -693,9 +709,7 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); + movement_disable_if_debounce_complete(); if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; From d5a8c57c8256433c11ea1b5b88f0d4900f20d697 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 23 Aug 2024 22:25:02 -0400 Subject: [PATCH 078/220] Additional code review change --- movement/movement.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index b49ae7b..c81e0a7 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -681,10 +681,15 @@ static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *d *down_timestamp = 0; } +static void disable_if_needed(uint8_t *ticks) { + if (*ticks > 0 && --*ticks == 0) + _movement_disable_fast_tick_if_possible(); +} + static void movement_disable_if_debounce_complete(void) { - if (movement_state.debounce_ticks_light > 0 && --movement_state.debounce_ticks_light == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_alarm > 0 && --movement_state.debounce_ticks_alarm == 0) _movement_disable_fast_tick_if_possible(); - if (movement_state.debounce_ticks_mode > 0 && --movement_state.debounce_ticks_mode == 0) _movement_disable_fast_tick_if_possible(); + disable_if_needed(&movement_state.debounce_ticks_light); + disable_if_needed(&movement_state.debounce_ticks_alarm); + disable_if_needed(&movement_state.debounce_ticks_mode); } void cb_light_btn_interrupt(void) { From fe259ee526088109bb018e29e0c0f89c406b8aea Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 26 Aug 2024 21:40:56 -0400 Subject: [PATCH 079/220] Comment change --- watch-library/shared/watch/watch_private_display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c index 7ea4141..3f15c52 100644 --- a/watch-library/shared/watch/watch_private_display.c +++ b/watch-library/shared/watch/watch_private_display.c @@ -43,8 +43,8 @@ void watch_display_character(uint8_t character, uint8_t position) { else if (character == 'M' || character == 'm' || character == 'N') character = 'n'; // M and uppercase N need to be lowercase n else if (character == 'c') character = 'C'; // C needs to be uppercase else if (character == 'J') character = 'j'; // same - else if (character == 't' || character == 'T') character = '+'; // t in those locations looks like E - else if (character == 'y' || character == 'Y') character = '4'; // t in those locations looks like g + else if (character == 't' || character == 'T') character = '+'; // t in those locations looks like E otherwise + else if (character == 'y' || character == 'Y') character = '4'; // y in those locations looks like g otherwise else if (character == 'v' || character == 'V' || character == 'U' || character == 'W' || character == 'w') character = 'u'; // bottom segment duplicated, so show in top half } else { if (character == 'u') character = 'v'; // we can use the bottom segment; move to lower half From 77fb6202c9637d83aef7ebdececd0ffb34157bca Mon Sep 17 00:00:00 2001 From: metehan-arslan <44465987+metehan-arslan@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:56:36 +0300 Subject: [PATCH 080/220] Update SIGNAL_TUNE_KIM_POSSIBLE notes --- movement/movement_custom_signal_tunes.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index cc21a5a..e0f21af 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -70,16 +70,15 @@ int8_t signal_tune[] = { #ifdef SIGNAL_TUNE_KIM_POSSIBLE int8_t signal_tune[] = { BUZZER_NOTE_G7, 6, - BUZZER_NOTE_REST, 1, - BUZZER_NOTE_G4, 3, + BUZZER_NOTE_G4, 2, BUZZER_NOTE_REST, 5, BUZZER_NOTE_G7, 6, - BUZZER_NOTE_REST, 1, - BUZZER_NOTE_G4, 3, + BUZZER_NOTE_G4, 2, BUZZER_NOTE_REST, 5, BUZZER_NOTE_A7SHARP_B7FLAT, 6, BUZZER_NOTE_REST, 2, - BUZZER_NOTE_G7, 6, + BUZZER_NOTE_G7, 6, + BUZZER_NOTE_G4, 2, 0 }; #endif // SIGNAL_TUNE_KIM_POSSIBLE From 2a194dfa699487c4814ada4da31f1711afba7ea4 Mon Sep 17 00:00:00 2001 From: metehan-arslan <44465987+metehan-arslan@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:40:32 +0000 Subject: [PATCH 081/220] style: remove extra whitespace --- movement/movement_custom_signal_tunes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index e0f21af..27c1d39 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -77,7 +77,7 @@ int8_t signal_tune[] = { BUZZER_NOTE_REST, 5, BUZZER_NOTE_A7SHARP_B7FLAT, 6, BUZZER_NOTE_REST, 2, - BUZZER_NOTE_G7, 6, + BUZZER_NOTE_G7, 6, BUZZER_NOTE_G4, 2, 0 }; From cae5d8a33f5e7aa248b07e7a1b59fbc60b8c5a3b Mon Sep 17 00:00:00 2001 From: Joseph Bryant Date: Fri, 30 Aug 2024 07:23:28 +0100 Subject: [PATCH 082/220] wait for RTC SYNCBUSY in watch_register_extwake_callback --- watch-library/hardware/watch/watch_deepsleep.c | 1 + 1 file changed, 1 insertion(+) diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c index efdad6d..a25b667 100644 --- a/watch-library/hardware/watch/watch_deepsleep.c +++ b/watch-library/hardware/watch/watch_deepsleep.c @@ -77,6 +77,7 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le RTC->MODE2.TAMPCTRL.reg = config; // re-enable the RTC RTC->MODE2.CTRLA.bit.ENABLE = 1; + while (RTC->MODE2.SYNCBUSY.bit.ENABLE); // wait for RTC to be enabled NVIC_ClearPendingIRQ(RTC_IRQn); NVIC_EnableIRQ(RTC_IRQn); From 18154deef4d3877d3c9c0364a9da631e4e9f7da5 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 08:31:26 -0400 Subject: [PATCH 083/220] Adds a simple calculator face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/simple_calculator_face.c | 415 ++++++++++++++++++ .../complication/simple_calculator_face.h | 145 ++++++ 4 files changed, 562 insertions(+) create mode 100644 movement/watch_faces/complication/simple_calculator_face.c create mode 100644 movement/watch_faces/complication/simple_calculator_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..40b5be3 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/simple_calculator_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..55d4124 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "simple_calculator_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c new file mode 100644 index 0000000..95804ce --- /dev/null +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -0,0 +1,415 @@ +/* + * MIT License + * + * Copyright (c) 2024 Patrick McGuire + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "simple_calculator_face.h" + +void simple_calculator_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(simple_calculator_state_t)); + memset(*context_ptr, 0, sizeof(simple_calculator_state_t)); + } +} + +void simple_calculator_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + simple_calculator_state_t *state = (simple_calculator_state_t *)context; + state->placeholder = PLACEHOLDER_ONES; + state->mode = MODE_ENTERING_FIRST_NUM; + movement_request_tick_frequency(4); +} + +static void increment_placeholder(calculator_number_t *number, calculator_placeholder_t placeholder) { + uint8_t *digits[] = { + &number->hundredths, + &number->tenths, + &number->ones, + &number->tens, + &number->hundreds, + &number->thousands + }; + *digits[placeholder] = (*digits[placeholder] + 1) % 10; +} + +static float convert_to_float(calculator_number_t number) { + float result = 0.0; + + // Add the whole number portion + result += number.thousands * 1000.0f; + result += number.hundreds * 100.0f; + result += number.tens * 10.0f; + result += number.ones * 1.0f; + + // Add the fractional portion + result += number.tenths * 0.1f; + result += number.hundredths * 0.01f; + + // Round to nearest hundredth + result = roundf(result * 100) / 100; + return result; +} + +static char* update_display_number(calculator_number_t *number, char *display_string, uint8_t which_num) { + char sign = ' '; + if (number->negative) sign = '-'; + sprintf(display_string, "CA%d%c%d%d%d%d%d%d", + which_num, + sign, + number->thousands, + number->hundreds, + number->tens, + number->ones, + number->tenths, + number->hundredths); + + return display_string; +} + +static void set_operation(simple_calculator_state_t *state) { + switch (state->operation) { + case 0: + watch_display_string(" Add", 0); + break; + case 1: + watch_display_string(" sub", 0); + break; + case 2: + watch_display_string(" n&ul", 0); + break; + case 3: + watch_display_string(" div", 0); + break; + case 4: + watch_display_string(" root", 0); + break; + case 5: + watch_display_string(" pow", 0); + break; + } +} + +static void cycle_operation(simple_calculator_state_t *state) { + state->operation = (state->operation + 1) % OPERATIONS_COUNT; // Assuming there are 6 operations + printf("Current operation: %d\n", state->operation); // For debugging +} + + +static calculator_number_t convert_to_string(float number) { + calculator_number_t result; + + // Handle negative numbers + bool is_negative = (number < 0); + if (is_negative) { + number = -number; + result.negative = true; + } + + int int_part = (int)number; + float decimal_part_float = ((number - int_part) * 100); // two decimal places + printf("decimal_part_float = %f\n", decimal_part_float); //For debugging + int decimal_part = round(decimal_part_float); + printf("decimal_part = %d\n", decimal_part); //For debugging + + result.thousands = int_part / 1000 % 10; + result.hundreds = int_part / 100 % 10; + result.tens = int_part / 10 % 10; + result.ones = int_part % 10; + + result.tenths = decimal_part / 10 % 10; + result.hundredths = decimal_part % 10; + + return result; +} + +static void reset_to_zero(calculator_number_t *number) { + number->negative = false; + number->hundredths = 0; + number->tenths = 0; + number->ones = 0; + number->tens = 0; + number->hundreds = 0; + number->thousands = 0; +} + +static void set_number(calculator_number_t *number, calculator_placeholder_t placeholder, char *display_string, char *temp_display_string, movement_event_t event, uint8_t which_num) { + uint8_t display_index; + // Update display string with current number + update_display_number(number, display_string, which_num); + + // Copy the updated display string to a temporary buffer + strcpy(temp_display_string, display_string); + + // Determine the display index based on the placeholder + display_index = 9 - placeholder; + + // Blink selected placeholder + // Check if `event.subsecond` is even + if (event.subsecond % 2 == 0) { + // Replace the character at the index corresponding to the current placeholder with a space + temp_display_string[display_index] = ' '; + } + + // Display the (possibly modified) string + watch_display_string(temp_display_string, 0); +} + +static void view_results(simple_calculator_state_t *state, char *display_string) { + float first_num_float, second_num_float, result_float = 0.0f; // For arithmetic operations + // Convert the numbers to float + first_num_float = convert_to_float(state->first_num); + if (state->first_num.negative) first_num_float = first_num_float * -1; + printf("first_num_float = %f\n", first_num_float); // For debugging // For debugging + second_num_float = convert_to_float(state->second_num); + if (state->second_num.negative) second_num_float = second_num_float * -1; + printf("second_num_float = %f\n", second_num_float); // For debugging + + // Perform the calculation based on the selected operation + switch (state->operation) { + case OP_ADD: + result_float = first_num_float + second_num_float; + break; + case OP_SUB: + result_float = first_num_float - second_num_float; + break; + case OP_MULT: + result_float = first_num_float * second_num_float; + break; + case OP_DIV: + if (second_num_float != 0) { + result_float = first_num_float / second_num_float; + } else { + state->mode = MODE_ERROR; + return; + } + break; + case OP_ROOT: + if (first_num_float >= 0) { + result_float = sqrtf(first_num_float); + } else { + state->mode = MODE_ERROR; + return; + } + break; + case OP_POWER: + result_float = powf(first_num_float, second_num_float); // Power operation + break; + default: + result_float = 0.0f; + break; + } + + if (result_float > 9999.99 || result_float < -9999.99) { + state->mode = MODE_ERROR; + return; + } + + result_float = roundf(result_float * 100.0f) / 100.0f; // Might not be needed + printf("result as float = %f\n", result_float); // For debugging + + // Convert the float result to a string + state->result = convert_to_string(result_float); + + // Update the display with the result + update_display_number(&state->result, display_string, 3); + watch_display_string(display_string, 0); +} + +static void reset_from_error(simple_calculator_state_t *state) { + reset_to_zero(&state->first_num); + reset_to_zero(&state->second_num); + state->mode = MODE_ENTERING_FIRST_NUM; +} +bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + simple_calculator_state_t *state = (simple_calculator_state_t *)context; + char display_string[10]; + char temp_display_string[10]; // Temporary buffer for blinking effect + + switch (event.event_type) { + case EVENT_ACTIVATE: + break; + + case EVENT_TICK: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + set_number(&state->first_num, + state->placeholder, + display_string, + temp_display_string, + event, + 1); + break; + + case MODE_CHOOSING: + set_operation(state); + break; + + case MODE_ENTERING_SECOND_NUM: + // If doing a square root calculation, skip to results + if (state->operation == 4) { + state->mode = MODE_VIEW_RESULTS; + // otherwise, set the second number + } else { + set_number(&state->second_num, + state->placeholder, + display_string, + temp_display_string, + event, + 2); + } + break; + + case MODE_VIEW_RESULTS: + view_results(state, display_string); + break; + case MODE_ERROR: + watch_display_string("CA Error ", 0); + break; + } + break; + + case EVENT_LIGHT_BUTTON_DOWN: + break; + + case EVENT_LIGHT_BUTTON_UP: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + case MODE_ENTERING_SECOND_NUM: + // Move to the next placeholder when the light button is pressed + state->placeholder = (state->placeholder + 1) % MAX_PLACEHOLDERS; // Loop back to the start after PLACEHOLDER_THOUSANDS + break; + case MODE_CHOOSING: + cycle_operation(state); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_LIGHT_LONG_PRESS: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + // toggle negative on state->first_num + state->first_num.negative = !state->first_num.negative; + break; + case MODE_ENTERING_SECOND_NUM: + // toggle negative on state->second_num + state->first_num.negative = !state->first_num.negative; + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_CHOOSING: + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_ALARM_BUTTON_UP: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + // Increment the digit in the current placeholder + increment_placeholder(&state->first_num, state->placeholder); + update_display_number(&state->first_num, display_string, 1); + break; + case MODE_CHOOSING: + // Confirm and select the current operation + printf("Selected operation: %d\n", state->operation); // For debugging + state->mode = MODE_ENTERING_SECOND_NUM; + break; + case MODE_ENTERING_SECOND_NUM: + // Increment the digit in the current placeholder + increment_placeholder(&state->second_num, state->placeholder); + update_display_number(&state->second_num, display_string, 2); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_ALARM_LONG_PRESS: + switch (state->mode) { + case MODE_ENTERING_FIRST_NUM: + reset_to_zero(&state->first_num); + break; + case MODE_ENTERING_SECOND_NUM: + reset_to_zero(&state->second_num); + break; + case MODE_ERROR: + reset_from_error(state); + break; + case MODE_CHOOSING: + case MODE_VIEW_RESULTS: + break; + } + break; + + case EVENT_MODE_BUTTON_DOWN: + break; + + case EVENT_MODE_BUTTON_UP: + if (state->mode == MODE_ERROR) { + reset_from_error(state); + } else { + state->placeholder = PLACEHOLDER_ONES; + state->mode = (state->mode + 1) % 4; + if (state->mode == MODE_ENTERING_FIRST_NUM) { + state->first_num = state->result; + reset_to_zero(&state->second_num); + } + printf("Current mode: %d\n", state->mode); // For debugging + } + break; + + case EVENT_MODE_LONG_PRESS: + movement_move_to_next_face(); + break; + + case EVENT_TIMEOUT: + movement_request_tick_frequency(1); + movement_move_to_face(0); + break; + + default: + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void simple_calculator_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + movement_request_tick_frequency(1); +} + diff --git a/movement/watch_faces/complication/simple_calculator_face.h b/movement/watch_faces/complication/simple_calculator_face.h new file mode 100644 index 0000000..1f77dc0 --- /dev/null +++ b/movement/watch_faces/complication/simple_calculator_face.h @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2024 Patrick McGuire + * + * 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 SIMPLE_CALCULATOR_FACE_H_ +#define SIMPLE_CALCULATOR_FACE_H_ + +#include "movement.h" + +/* + * Simple Calculator + * + * How to use: + * + * Flow: + * Enter first number -> Select operator -> Enter second number -> View Results + * + * How to read the display: + * - "CA" is displayed at the top to tell you that you're in the CAlculator + * - The top-right digit (1, 2, or 3) lets you know whether you're entering the + * first number (1), entering the second number (2), or viewing the results (3). + * - To the right of the top-right digit will show the number's sign. If the + * number is negative, a "-" will be displayed, otherwise it is empty. + * - The 4 large digits to the left are whole numbers and the 2 smaller digits + * on the right are the tenths and hundredths decimal places. + * + * Entering the first number: + * - Press ALARM to increment the selected (blinking) digit + * - Press LIGHT to move to the next placeholder + * - LONG PRESS the LIGHT button to toggle the number's sign to make it + * negative + * - LONG PRESS the ALARM button to reset the number to 0 + * - Press MODE to proceed to selecting the operator + * + * Selecting the operator: + * - Press the LIGHT button to cycle through available operators. They are: + * + Add + * - Subtract + * * Multiply + * / Divide + * sqrtf() Square root + * powf() Power (exponent calculation) + * - Press MODE or ALARM to proceed to entering the second number + * + * Entering the second number: + * - Everything is the same as setting the first number except that pressing + * MODE here will proceed to viewing the results + * + * Viewing the results: + * - Pressing MODE will start a new calculation with the result as the first + * number. (LONG PRESS ALARM to reset the value to 0) + * + * Errors: + * - An error will be triggered if the result is not able to be displayed, that + * is, if the value is greater than 9,999.99 or less than -9,999.99. + * - An error will also be triggered if an impossible operation is selected, + * for instance trying to divide by 0 or get the square root of a negative + * number. + * - Exit error mode and start over with any button press. + * + */ + +#define OPERATIONS_COUNT 6 +#define MAX_PLACEHOLDERS 6 + +typedef struct { + bool negative; + uint8_t hundredths; + uint8_t tenths; + uint8_t ones; + uint8_t tens; + uint8_t hundreds; + uint8_t thousands; +} calculator_number_t; + +typedef enum { + PLACEHOLDER_HUNDREDTHS, + PLACEHOLDER_TENTHS, + PLACEHOLDER_ONES, + PLACEHOLDER_TENS, + PLACEHOLDER_HUNDREDS, + PLACEHOLDER_THOUSANDS +} calculator_placeholder_t; + +typedef enum { + OP_ADD, + OP_SUB, + OP_MULT, + OP_DIV, + OP_ROOT, + OP_POWER, +} calculator_operation_t; + +typedef enum { + MODE_ENTERING_FIRST_NUM, + MODE_CHOOSING, + MODE_ENTERING_SECOND_NUM, + MODE_VIEW_RESULTS, + MODE_ERROR +} calculator_mode_t; + +typedef struct { + calculator_number_t first_num; + calculator_number_t second_num; + calculator_number_t result; + calculator_operation_t operation; + calculator_mode_t mode; + calculator_placeholder_t placeholder; +} simple_calculator_state_t; + +void simple_calculator_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void simple_calculator_face_activate(movement_settings_t *settings, void *context); +bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void simple_calculator_face_resign(movement_settings_t *settings, void *context); + +#define simple_calculator_face ((const watch_face_t){ \ + simple_calculator_face_setup, \ + simple_calculator_face_activate, \ + simple_calculator_face_loop, \ + simple_calculator_face_resign, \ + NULL, \ +}) + +#endif // SIMPLE_CALCULATOR_FACE_H_ + From 28db77f90c0ff21fa9657039ab69f427e91fc99d Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 08:36:02 -0400 Subject: [PATCH 084/220] forgot to mention long press MODE for next face --- movement/watch_faces/complication/simple_calculator_face.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/movement/watch_faces/complication/simple_calculator_face.h b/movement/watch_faces/complication/simple_calculator_face.h index 1f77dc0..4f1e8d5 100644 --- a/movement/watch_faces/complication/simple_calculator_face.h +++ b/movement/watch_faces/complication/simple_calculator_face.h @@ -32,6 +32,8 @@ * * How to use: * + * Important note: LONG PRESS MODE to move to next watch face + * * Flow: * Enter first number -> Select operator -> Enter second number -> View Results * From e13d42b5b5d1234293e67d162affb6499956b180 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 12:41:57 -0400 Subject: [PATCH 085/220] mode=movement_move_to_next_face & longmode = face0 --- .../watch_faces/complication/simple_calculator_face.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index 95804ce..ada289d 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -380,6 +380,14 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se case EVENT_MODE_BUTTON_UP: if (state->mode == MODE_ERROR) { reset_from_error(state); + } else if (state->mode == MODE_ENTERING_FIRST_NUM && + state->first_num.hundredths == 0 && + state->first_num.tenths == 0 && + state->first_num.ones== 0 && + state->first_num.tens == 0 && + state->first_num.hundreds == 0 && + state->first_num.thousands == 0) { + movement_move_to_next_face(); } else { state->placeholder = PLACEHOLDER_ONES; state->mode = (state->mode + 1) % 4; @@ -392,7 +400,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se break; case EVENT_MODE_LONG_PRESS: - movement_move_to_next_face(); + movement_move_to_face(0); break; case EVENT_TIMEOUT: From b607b6f7a9177f3fc7e16d842f0b7f6f18a86d9e Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 12:44:29 -0400 Subject: [PATCH 086/220] removed note about mode long press --- movement/watch_faces/complication/simple_calculator_face.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.h b/movement/watch_faces/complication/simple_calculator_face.h index 4f1e8d5..1f77dc0 100644 --- a/movement/watch_faces/complication/simple_calculator_face.h +++ b/movement/watch_faces/complication/simple_calculator_face.h @@ -32,8 +32,6 @@ * * How to use: * - * Important note: LONG PRESS MODE to move to next watch face - * * Flow: * Enter first number -> Select operator -> Enter second number -> View Results * From c75a21196f088064d0763d07065df076311e940c Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 14:46:23 -0400 Subject: [PATCH 087/220] commented out debugging printf() statements --- .../complication/simple_calculator_face.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index ada289d..3221981 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -115,7 +115,7 @@ static void set_operation(simple_calculator_state_t *state) { static void cycle_operation(simple_calculator_state_t *state) { state->operation = (state->operation + 1) % OPERATIONS_COUNT; // Assuming there are 6 operations - printf("Current operation: %d\n", state->operation); // For debugging + //printf("Current operation: %d\n", state->operation); // For debugging } @@ -131,9 +131,9 @@ static calculator_number_t convert_to_string(float number) { int int_part = (int)number; float decimal_part_float = ((number - int_part) * 100); // two decimal places - printf("decimal_part_float = %f\n", decimal_part_float); //For debugging + //printf("decimal_part_float = %f\n", decimal_part_float); //For debugging int decimal_part = round(decimal_part_float); - printf("decimal_part = %d\n", decimal_part); //For debugging + //printf("decimal_part = %d\n", decimal_part); //For debugging result.thousands = int_part / 1000 % 10; result.hundreds = int_part / 100 % 10; @@ -183,10 +183,10 @@ static void view_results(simple_calculator_state_t *state, char *display_string) // Convert the numbers to float first_num_float = convert_to_float(state->first_num); if (state->first_num.negative) first_num_float = first_num_float * -1; - printf("first_num_float = %f\n", first_num_float); // For debugging // For debugging + //printf("first_num_float = %f\n", first_num_float); // For debugging // For debugging second_num_float = convert_to_float(state->second_num); if (state->second_num.negative) second_num_float = second_num_float * -1; - printf("second_num_float = %f\n", second_num_float); // For debugging + //printf("second_num_float = %f\n", second_num_float); // For debugging // Perform the calculation based on the selected operation switch (state->operation) { @@ -229,7 +229,7 @@ static void view_results(simple_calculator_state_t *state, char *display_string) } result_float = roundf(result_float * 100.0f) / 100.0f; // Might not be needed - printf("result as float = %f\n", result_float); // For debugging + //printf("result as float = %f\n", result_float); // For debugging // Convert the float result to a string state->result = convert_to_string(result_float); @@ -341,7 +341,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se break; case MODE_CHOOSING: // Confirm and select the current operation - printf("Selected operation: %d\n", state->operation); // For debugging + //printf("Selected operation: %d\n", state->operation); // For debugging state->mode = MODE_ENTERING_SECOND_NUM; break; case MODE_ENTERING_SECOND_NUM: @@ -395,7 +395,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se state->first_num = state->result; reset_to_zero(&state->second_num); } - printf("Current mode: %d\n", state->mode); // For debugging + //printf("Current mode: %d\n", state->mode); // For debugging } break; From 70426751f85e9986b063b73aa8aa792b9773329b Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sat, 31 Aug 2024 17:47:15 -0400 Subject: [PATCH 088/220] negative toggle was only for first_num -- fixed --- movement/watch_faces/complication/simple_calculator_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index 95804ce..3542590 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -321,7 +321,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se break; case MODE_ENTERING_SECOND_NUM: // toggle negative on state->second_num - state->first_num.negative = !state->first_num.negative; + state->second_num.negative = !state->second_num.negative; break; case MODE_ERROR: reset_from_error(state); From 6f7693e880878ee348b951e58dd5387a4e7e735b Mon Sep 17 00:00:00 2001 From: Metehan <99metehanarslan@gmail.com> Date: Sun, 1 Sep 2024 02:33:05 +0300 Subject: [PATCH 089/220] add layla tune --- movement/movement_custom_signal_tunes.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index cc21a5a..d069a3f 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -84,4 +84,21 @@ int8_t signal_tune[] = { }; #endif // SIGNAL_TUNE_KIM_POSSIBLE +#ifdef SIGNAL_TUNE_LAYLA +int8_t signal_tune[] = { + BUZZER_NOTE_A6, 4, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_C7, 5, + BUZZER_NOTE_REST, 2, + BUZZER_NOTE_D7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D7, 5, + BUZZER_NOTE_REST, 2, + BUZZER_NOTE_D7, 15, + 0 +}; +#endif // SIGNAL_TUNE_LAYLA + #endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_ From 12b1432aae00a698665320289a00d2035dee845c Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sun, 1 Sep 2024 10:10:59 -0400 Subject: [PATCH 090/220] mode long press = reset_all() --- .../complication/simple_calculator_face.c | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index 0623525..b3b2d0e 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -92,22 +92,22 @@ static char* update_display_number(calculator_number_t *number, char *display_st static void set_operation(simple_calculator_state_t *state) { switch (state->operation) { - case 0: + case OP_ADD: watch_display_string(" Add", 0); break; - case 1: + case OP_SUB: watch_display_string(" sub", 0); break; - case 2: + case OP_MULT: watch_display_string(" n&ul", 0); break; - case 3: + case OP_DIV: watch_display_string(" div", 0); break; - case 4: + case OP_ROOT: watch_display_string(" root", 0); break; - case 5: + case OP_POWER: watch_display_string(" pow", 0); break; } @@ -239,10 +239,12 @@ static void view_results(simple_calculator_state_t *state, char *display_string) watch_display_string(display_string, 0); } -static void reset_from_error(simple_calculator_state_t *state) { +static void reset_all(simple_calculator_state_t *state) { reset_to_zero(&state->first_num); reset_to_zero(&state->second_num); state->mode = MODE_ENTERING_FIRST_NUM; + state->operation = OP_ADD; + state->placeholder = PLACEHOLDER_ONES; } bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { simple_calculator_state_t *state = (simple_calculator_state_t *)context; @@ -270,7 +272,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se case MODE_ENTERING_SECOND_NUM: // If doing a square root calculation, skip to results - if (state->operation == 4) { + if (state->operation == OP_ROOT) { state->mode = MODE_VIEW_RESULTS; // otherwise, set the second number } else { @@ -306,7 +308,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se cycle_operation(state); break; case MODE_ERROR: - reset_from_error(state); + reset_all(state); break; case MODE_VIEW_RESULTS: break; @@ -324,7 +326,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se state->second_num.negative = !state->second_num.negative; break; case MODE_ERROR: - reset_from_error(state); + reset_all(state); break; case MODE_CHOOSING: case MODE_VIEW_RESULTS: @@ -350,7 +352,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se update_display_number(&state->second_num, display_string, 2); break; case MODE_ERROR: - reset_from_error(state); + reset_all(state); break; case MODE_VIEW_RESULTS: break; @@ -366,7 +368,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se reset_to_zero(&state->second_num); break; case MODE_ERROR: - reset_from_error(state); + reset_all(state); break; case MODE_CHOOSING: case MODE_VIEW_RESULTS: @@ -379,7 +381,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se case EVENT_MODE_BUTTON_UP: if (state->mode == MODE_ERROR) { - reset_from_error(state); + reset_all(state); } else if (state->mode == MODE_ENTERING_FIRST_NUM && state->first_num.hundredths == 0 && state->first_num.tenths == 0 && @@ -400,7 +402,22 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se break; case EVENT_MODE_LONG_PRESS: - movement_move_to_face(0); + if (state->first_num.hundredths == 0 && + state->first_num.tenths == 0 && + state->first_num.ones== 0 && + state->first_num.tens == 0 && + state->first_num.hundreds == 0 && + state->first_num.thousands == 0 && + state->second_num.hundredths == 0 && + state->second_num.tenths == 0 && + state->second_num.ones== 0 && + state->second_num.tens == 0 && + state->second_num.hundreds == 0 && + state->second_num.thousands == 0) { + movement_move_to_face(0); + } else { + reset_all(state); + } break; case EVENT_TIMEOUT: From 6268ce4381e68febae1f903fef1f5b637ddb395b Mon Sep 17 00:00:00 2001 From: Metehan <99metehanarslan@gmail.com> Date: Mon, 2 Sep 2024 00:03:21 +0300 Subject: [PATCH 091/220] update layla tune --- movement/movement_custom_signal_tunes.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index d069a3f..20c7235 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -86,17 +86,19 @@ int8_t signal_tune[] = { #ifdef SIGNAL_TUNE_LAYLA int8_t signal_tune[] = { - BUZZER_NOTE_A6, 4, + BUZZER_NOTE_A6, 5, BUZZER_NOTE_REST, 1, BUZZER_NOTE_C7, 5, - BUZZER_NOTE_REST, 2, + BUZZER_NOTE_REST, 1, BUZZER_NOTE_D7, 5, BUZZER_NOTE_REST, 1, BUZZER_NOTE_F7, 5, BUZZER_NOTE_REST, 1, BUZZER_NOTE_D7, 5, - BUZZER_NOTE_REST, 2, - BUZZER_NOTE_D7, 15, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_C7, 5, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D7, 20, 0 }; #endif // SIGNAL_TUNE_LAYLA From 696f7f12ec1ca58aff02d6908f85bb47dd8fd4fd Mon Sep 17 00:00:00 2001 From: Metehan <99metehanarslan@gmail.com> Date: Mon, 2 Sep 2024 02:11:27 +0300 Subject: [PATCH 092/220] add power rangers tune --- movement/movement_custom_signal_tunes.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index cc21a5a..39ec3f1 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -84,4 +84,21 @@ int8_t signal_tune[] = { }; #endif // SIGNAL_TUNE_KIM_POSSIBLE +#ifdef SIGNAL_TUNE_POWER_RANGERS +int8_t signal_tune[] = { + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_C8, 6, + BUZZER_NOTE_REST, 2, + BUZZER_NOTE_D8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_F8, 6, + BUZZER_NOTE_REST, 8, + BUZZER_NOTE_D8, 6, + 0 +}; +#endif // SIGNAL_TUNE_POWER_RANGERS + #endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_ From b774900ae62c6ed7442eec71d0327a81a7da3ed9 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Mon, 2 Sep 2024 13:10:26 -0400 Subject: [PATCH 093/220] finally squashed the bug --- .../complication/simple_calculator_face.c | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index b3b2d0e..6bfd9e8 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -36,11 +36,23 @@ void simple_calculator_face_setup(movement_settings_t *settings, uint8_t watch_f } } +static void reset_to_zero(calculator_number_t *number) { + number->negative = false; + number->hundredths = 0; + number->tenths = 0; + number->ones = 0; + number->tens = 0; + number->hundreds = 0; + number->thousands = 0; +} + void simple_calculator_face_activate(movement_settings_t *settings, void *context) { (void) settings; simple_calculator_state_t *state = (simple_calculator_state_t *)context; state->placeholder = PLACEHOLDER_ONES; state->mode = MODE_ENTERING_FIRST_NUM; + reset_to_zero(&state->second_num); + reset_to_zero(&state->result); movement_request_tick_frequency(4); } @@ -71,12 +83,18 @@ static float convert_to_float(calculator_number_t number) { // Round to nearest hundredth result = roundf(result * 100) / 100; + + // Handle negative numbers + if (number.negative) result = -result; + //printf("convert_to_float results = %f\n", result); // For debugging + return result; } static char* update_display_number(calculator_number_t *number, char *display_string, uint8_t which_num) { char sign = ' '; if (number->negative) sign = '-'; + sprintf(display_string, "CA%d%c%d%d%d%d%d%d", which_num, sign, @@ -115,7 +133,6 @@ static void set_operation(simple_calculator_state_t *state) { static void cycle_operation(simple_calculator_state_t *state) { state->operation = (state->operation + 1) % OPERATIONS_COUNT; // Assuming there are 6 operations - //printf("Current operation: %d\n", state->operation); // For debugging } @@ -123,15 +140,17 @@ static calculator_number_t convert_to_string(float number) { calculator_number_t result; // Handle negative numbers - bool is_negative = (number < 0); - if (is_negative) { + if (number < 0) { number = -number; result.negative = true; - } + } else result.negative = false; + // Get each digit from each placeholder int int_part = (int)number; + float decimal_part_float = ((number - int_part) * 100); // two decimal places //printf("decimal_part_float = %f\n", decimal_part_float); //For debugging + int decimal_part = round(decimal_part_float); //printf("decimal_part = %d\n", decimal_part); //For debugging @@ -146,22 +165,15 @@ static calculator_number_t convert_to_string(float number) { return result; } -static void reset_to_zero(calculator_number_t *number) { - number->negative = false; - number->hundredths = 0; - number->tenths = 0; - number->ones = 0; - number->tens = 0; - number->hundreds = 0; - number->thousands = 0; -} - +// This is the main function for setting the first_num and second_num +// WISH: there must be a way to pass less to this function? static void set_number(calculator_number_t *number, calculator_placeholder_t placeholder, char *display_string, char *temp_display_string, movement_event_t event, uint8_t which_num) { + + // Create the display index uint8_t display_index; - // Update display string with current number + + // Update display string with current number and copy into temp string update_display_number(number, display_string, which_num); - - // Copy the updated display string to a temporary buffer strcpy(temp_display_string, display_string); // Determine the display index based on the placeholder @@ -179,14 +191,13 @@ static void set_number(calculator_number_t *number, calculator_placeholder_t pla } static void view_results(simple_calculator_state_t *state, char *display_string) { - float first_num_float, second_num_float, result_float = 0.0f; // For arithmetic operations - // Convert the numbers to float + + // Initialize float variables to do the math + float first_num_float, second_num_float, result_float = 0.0f; + + // Convert the passed numbers to floats first_num_float = convert_to_float(state->first_num); - if (state->first_num.negative) first_num_float = first_num_float * -1; - //printf("first_num_float = %f\n", first_num_float); // For debugging // For debugging second_num_float = convert_to_float(state->second_num); - if (state->second_num.negative) second_num_float = second_num_float * -1; - //printf("second_num_float = %f\n", second_num_float); // For debugging // Perform the calculation based on the selected operation switch (state->operation) { @@ -216,29 +227,37 @@ static void view_results(simple_calculator_state_t *state, char *display_string) } break; case OP_POWER: - result_float = powf(first_num_float, second_num_float); // Power operation + result_float = powf(first_num_float, second_num_float); break; default: result_float = 0.0f; break; } + // Be sure the result can fit on the watch display, else error if (result_float > 9999.99 || result_float < -9999.99) { state->mode = MODE_ERROR; return; } result_float = roundf(result_float * 100.0f) / 100.0f; // Might not be needed + //printf("result as float = %f\n", result_float); // For debugging // Convert the float result to a string + // This isn't strictly necessary, but allows easily reusing the result as + // the next calculation's first_num state->result = convert_to_string(result_float); // Update the display with the result update_display_number(&state->result, display_string, 3); + + //printf("display_string = %s\n", display_string); // For debugging + watch_display_string(display_string, 0); } +// Used both when returning from errors and when long pressing MODE static void reset_all(simple_calculator_state_t *state) { reset_to_zero(&state->first_num); reset_to_zero(&state->second_num); @@ -246,6 +265,7 @@ static void reset_all(simple_calculator_state_t *state) { state->operation = OP_ADD; state->placeholder = PLACEHOLDER_ONES; } + bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { simple_calculator_state_t *state = (simple_calculator_state_t *)context; char display_string[10]; @@ -258,6 +278,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se case EVENT_TICK: switch (state->mode) { case MODE_ENTERING_FIRST_NUM: + // See the WISH for this function above set_number(&state->first_num, state->placeholder, display_string, @@ -274,8 +295,8 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se // If doing a square root calculation, skip to results if (state->operation == OP_ROOT) { state->mode = MODE_VIEW_RESULTS; - // otherwise, set the second number } else { + // See the WISH for this function above set_number(&state->second_num, state->placeholder, display_string, @@ -288,6 +309,7 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se case MODE_VIEW_RESULTS: view_results(state, display_string); break; + case MODE_ERROR: watch_display_string("CA Error ", 0); break; @@ -340,16 +362,21 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se // Increment the digit in the current placeholder increment_placeholder(&state->first_num, state->placeholder); update_display_number(&state->first_num, display_string, 1); + + //printf("display_string = %s\n", display_string); // For debugging + break; case MODE_CHOOSING: // Confirm and select the current operation - //printf("Selected operation: %d\n", state->operation); // For debugging state->mode = MODE_ENTERING_SECOND_NUM; break; case MODE_ENTERING_SECOND_NUM: // Increment the digit in the current placeholder increment_placeholder(&state->second_num, state->placeholder); update_display_number(&state->second_num, display_string, 2); + + //printf("display_string = %s\n", display_string); // For debugging + break; case MODE_ERROR: reset_all(state); @@ -391,30 +418,30 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se state->first_num.thousands == 0) { movement_move_to_next_face(); } else { + // Reset the placeholder and proceed to the next MODE state->placeholder = PLACEHOLDER_ONES; state->mode = (state->mode + 1) % 4; + // When looping back to MODE_ENTERING_FIRST_NUM, reuse the + // previous calculation's results as the next calculation's + // first_num; also reset other numbers if (state->mode == MODE_ENTERING_FIRST_NUM) { state->first_num = state->result; reset_to_zero(&state->second_num); + reset_to_zero(&state->result); } - //printf("Current mode: %d\n", state->mode); // For debugging } break; case EVENT_MODE_LONG_PRESS: + // Move to next face if first number is 0 if (state->first_num.hundredths == 0 && state->first_num.tenths == 0 && state->first_num.ones== 0 && state->first_num.tens == 0 && state->first_num.hundreds == 0 && - state->first_num.thousands == 0 && - state->second_num.hundredths == 0 && - state->second_num.tenths == 0 && - state->second_num.ones== 0 && - state->second_num.tens == 0 && - state->second_num.hundreds == 0 && - state->second_num.thousands == 0) { + state->first_num.thousands == 0) { movement_move_to_face(0); + // otherwise, start over } else { reset_all(state); } From 2e878e146c10474bc1ac8c3f718f3aea76b1330a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 14 Aug 2024 22:13:23 -0400 Subject: [PATCH 094/220] Start of Wordle --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/wordle_face.c | 753 ++++++++++++++++++ .../watch_faces/complication/wordle_face.h | 86 ++ 4 files changed, 841 insertions(+) create mode 100644 movement/watch_faces/complication/wordle_face.c create mode 100644 movement/watch_faces/complication/wordle_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..f2b20eb 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/wordle_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..f697e72 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "wordle_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c new file mode 100644 index 0000000..8cd993e --- /dev/null +++ b/movement/watch_faces/complication/wordle_face.c @@ -0,0 +1,753 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "wordle_face.h" + +/* +TODO: +* Add quick iteration (8x freq to get to the letter we want) +* Fix the word matching (if answer is AAAAA and we put in AACAA, the C blinks) +* Verify pressing back always work when the board is G_G_G +* Add daily streak and wait for next day +*/ + + +/* +Letter | Usage in Text +E | 12.7% +T | 9.1% But looks bad across all positions +A | 8.2% +O | 7.5% +I | 7.0% +N | 6.7% Few uses in 5 letter words than C +S | 6.3% +H | 6.1% +R | 6.0% +D | 4.3% +L | 4.0% +C | 2.8% +*/ +static const char _valid_letters[] = {'A', 'C', 'd', 'E', 'H', 'I', 'L', 'O', 'R', 'S'}; + +// From: https://github.com/charlesreid1/five-letter-words/blob/master/sgb-words.txt +static const char _legal_words[][WORDLE_LENGTH + 1] = { +"AAAAA", "SHALL", "HEARd", "ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", +"HEARd", "ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", +"ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", +"CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", +"CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", +"HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", +"AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", +"COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", +"IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", +"CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", +"CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", +"CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", +"SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", +"AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", +"SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", +"CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", +"AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", +"REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", +"RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", +"LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", +"SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", +"CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", +"CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", +"OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", +"SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", +"CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", +"SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", +"SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", +"HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", +"dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", +"SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", +"SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", +"HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", +"RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", +"ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", +"CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", +"HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", +"HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", +"CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", +"dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", +"LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", +"ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", +"SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", +"dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", +"SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", +"CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", +"CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", +"SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", +"RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", +"SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", +"ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", +"SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", +"ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", +"RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", +"dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", +"HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", +"LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", +"COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", +"REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", +"HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", +"AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", +"RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", +"HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", +"RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", +"ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", +"CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", +"SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", +"ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", +"IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", +"CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", +"ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", +"CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", +"SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", +"CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", +"HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", +"dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", +"RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", +"ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", +"HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", +"SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", +"LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", +"HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", +"LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", +"CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", +"dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", +"RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", +"HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", +"dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", +"CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", +"SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", +"COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", +"HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", +"SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", +"REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", +"SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", +"CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", +"CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", +"RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", +"dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", +"ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", +"COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", +"SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", +"CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", +"SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", +"LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", +"HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", +"COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", +"ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", +"COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", +"ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", +"ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", +"dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", +"CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", +"SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", +"CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", +"IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", +"OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", +"EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", +"CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", +"AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", +"CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", +"LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", +"SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", +"SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", +"COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", +"ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", +"CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", +"dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", +"OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", +"dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", +"CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", +"EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", +"SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", +"RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", +"SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", +"CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", +"CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", +"OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", +"ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", +"SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", +"CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", +"dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", +"ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", +"CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", +"SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", +"AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", +"HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", +"ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", +"SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", +"LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", +"LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", +"CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", +"LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", +"dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", +"COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", +"CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", +"CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", +"EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", +"LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", +"HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", +"LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", +"HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", +"CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", +"LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", +"HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", +"ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", +"dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", +"SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", +"SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", +"HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", +"dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", +"ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", +"LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", +"LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", +"RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", +"SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", +"RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", +"AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", +"ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", +"COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", +"ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", +"CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", +"AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", +"RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", +"SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", +"SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", +"CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", +"REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", +"LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", +"SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", +"SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", +"HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", +"SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", +"ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", +"ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", +"HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", +"LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", +"EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", +"HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", +"COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", +"SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", +"SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", +"CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", +"EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", +"HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", +"ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", +"EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", +"AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", +"dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", +"SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", +"OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", +"SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", +"dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", +"HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", +"AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", +"COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", +"SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", +"dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", +"RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", +"EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", +"LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", +"IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", +"SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", +"AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", +"dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", +"HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", +"CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", +"LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", +"dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", +"dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", +"dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", +"COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", +"LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", +"ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", +"dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", +"LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", +"IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", +"RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", +"AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", +"HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", +"IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", +"dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", +"HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", +"SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", +"CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", +"COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", +"LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", +"REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", +"COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", +"SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", +"SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", +"LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", +"SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", +"LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", +"ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", +"COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", +"dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", +"dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", +"dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", +"CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", +"HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", +"CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", +"ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", +"LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", +"HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", +"SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", +"dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", +"CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", +"SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", +"SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", +"dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", +"OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", +"dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", +"ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", +"RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", +"CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", +"LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", +"dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", +"SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", +"dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", +"CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", +"ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", +"dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", +"COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", +"SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", +"OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", +"SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", +"CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", +"ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", +"RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", +"CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", +"HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", +"SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", +"HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", +"SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", +"REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", +"AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", +"CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", +"dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", +"ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", +"dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", +"ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", +"HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", +"SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", +"AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", +"SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", +"RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", +"HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", +"CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", +"RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", +"COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", +"HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", +"CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", +"SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", +"ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", +"EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", +"ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", +"SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", +"AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", +"SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", +"dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", +"SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", +"CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", +"COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", +"dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", +"OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", +"COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", +"ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", +"HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", +"REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", +"dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", +"CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", +"OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", +"HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", +"dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", +"SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", +"SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", +"AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", +"dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", +"ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", +"HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", +"ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", +"dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", +"ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", +"CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", +"LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", +"HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", +"CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", +"OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", +"SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", +"SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", +"CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", +"AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", +"dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", +"COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", +"CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", +"OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", +"dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", +"SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", +"LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", +"SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", +"CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", +"HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", +"HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", +"dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", +"ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", +"ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", +"HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", +"AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", +"CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", +"CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", +"OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", +"RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", +"RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", +"LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", +"AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", +"ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", +"LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", +"CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", +"COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", +"OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", +"ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", +"LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", +"SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", +"dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", +"OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", +"SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", +"HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", +"IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", +"LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", +"AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", +"HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", +"SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", +"LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", +"RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", +"SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", +"RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", +"HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", +"RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", +"ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", +"CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", +"CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", +"LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", +"OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", +"dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", +"SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", +"dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", +"RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", +"ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", +"SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", "ASSEd", +"LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", "ASSEd", "OSIER", +}; + +static const uint32_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); +static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); + +static uint32_t get_random(uint32_t max) { + #if __EMSCRIPTEN__ + return rand() % max; + #else + return arc4random_uniform(max); + #endif +} + +static void display_letter(wordle_state_t *state) { + char buf[1 + 1]; + if (state->word_elements[state->position] >= _num_valid_letters) { + watch_display_string("-", state->position + 5); + return; + } + sprintf(buf, "%c", _valid_letters[state->word_elements[state->position]]); + watch_display_string(buf, state->position + 5); +} + +static void display_all_letters(wordle_state_t *state) { + uint8_t prev_pos = state->position; + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + state->position = i; + display_letter(state); + } + state->position = prev_pos; +} + +static bool check_word(wordle_state_t *state) { + + // Exact + bool is_exact_match = true; + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) + state->word_elements_result[i] = WORDLE_LETTER_CORRECT; + else { + state->word_elements_result[i] = WORDLE_LETTER_WRONG; + is_exact_match = false; + } + } + + // Wrong Location + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_WRONG) continue; + for (size_t j = 0; j < WORDLE_LENGTH; j++) { + if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { + printf("me: %c them: %c\r\n", _valid_letters[state->word_elements[i]], _legal_words[state->curr_answer][j]); + state->word_elements_result[j] = WORDLE_LETTER_WRONG_LOC; + break; + } + } + } + + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + printf("%d : %d\r\n", i, state->word_elements_result[i]); + } + + + + return is_exact_match; +} + +static void display_attempt(uint8_t attempt) { + char buf[2]; + sprintf(buf, "%d", attempt); + watch_display_string(buf, 3); +} + +static void reset_board(wordle_state_t *state) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + state->word_elements[i] = _num_valid_letters; + state->word_elements_result[i] = WORDLE_LETTER_WRONG; + } + state->curr_answer = get_random(_num_words); + state->position = 0; + state->attempt = 1; + state->curr_screen = SCREEN_PLAYING; + watch_clear_colon(); + watch_display_string(" ", 4); + display_attempt(state->attempt); + display_all_letters(state); + printf("rand: %s\r\n", _legal_words[state->curr_answer]); +} + +static void display_title(wordle_state_t *state) { + char buf[11]; + state->curr_screen = SCREEN_TITLE; + printf("streak %d \r\n", state->streak); + sprintf(buf, "WO St%2ddy", state->streak); + watch_display_string(buf, 0); + watch_set_colon(); +} + +static void display_lose(wordle_state_t *state, uint8_t subsecond) { + char buf[WORDLE_LENGTH + 5]; + sprintf(buf," L %s", subsecond % 2 ? _legal_words[state->curr_answer] : " "); + watch_display_string(buf, 0); +} + +static void display_win(wordle_state_t *state, uint8_t subsecond) { + char buf[11]; + sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); + watch_display_string(buf, 0); +} + +static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (word_elements_result[i] != WORDLE_LETTER_CORRECT) + return i; + } + return 0; +} + +static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { + uint8_t pos = curr_pos; + do { + pos++; + if (pos > WORDLE_LENGTH) return WORDLE_LENGTH + 1; + } while (word_elements_result[pos] == WORDLE_LETTER_CORRECT); + return pos; +} + +static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { + int8_t pos = curr_pos; + do { + pos--; + if (pos < 0) return curr_pos; + } while (word_elements_result[pos] == WORDLE_LETTER_CORRECT); + return pos; +} + +static void display_result(wordle_state_t *state, uint8_t subsecond) { + char buf[WORDLE_LENGTH + 1]; + for (size_t i = 0; i < WORDLE_LENGTH; i++) + { + switch (state->word_elements_result[i]) + { + case WORDLE_LETTER_WRONG: + buf[i] = '-'; + break; + case WORDLE_LETTER_CORRECT: + buf[i] = _valid_letters[state->word_elements[i]]; + break; + case WORDLE_LETTER_WRONG_LOC: + if (subsecond % 2) + buf[i] = ' '; + else + buf[i] = _valid_letters[state->word_elements[i]]; + default: + break; + } + } + watch_display_string(buf, 5); +} + +static bool act_on_btn(wordle_state_t *state) { + if (state->curr_screen == SCREEN_RESULT) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) + state->word_elements[i] = _num_valid_letters; + } + display_attempt(state->attempt); + display_all_letters(state); + state->position = get_first_pos(state->word_elements_result); + state->curr_screen = SCREEN_PLAYING; + return true; + } + if (state->curr_screen == SCREEN_TITLE) { + reset_board(state); + return true; + } + if (state->curr_screen >= SCREEN_WIN) { + display_title(state); + return true; + } + return false; +} + +void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(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; + + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +void wordle_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + wordle_state_t *state = (wordle_state_t *)context; + movement_request_tick_frequency(2); + if (state->curr_screen == SCREEN_TITLE) + display_title(state); +} + +bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + wordle_state_t *state = (wordle_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + if (state->curr_screen == SCREEN_PLAYING) + display_all_letters(state); + break; + case EVENT_TICK: + switch (state->curr_screen) + { + case SCREEN_PLAYING: + if (event.subsecond % 2) { + display_letter(state); + } else { + watch_display_string(" ", state->position + 5); + } + break; + case SCREEN_RESULT: + display_result(state, event.subsecond); + break; + case SCREEN_LOSE: + display_lose(state, event.subsecond); + break; + case SCREEN_WIN: + display_win(state, event.subsecond); + break; + default: + break; + } + break; + case EVENT_LIGHT_BUTTON_UP: + if (act_on_btn(state)) break; + if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = 0; + else state->word_elements[state->position] = (state->word_elements[state->position] + 1) % _num_valid_letters; + display_letter(state); + break; + case EVENT_LIGHT_LONG_PRESS: + if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = _num_valid_letters - 1; + else state->word_elements[state->position] = (state->word_elements[state->position] + _num_valid_letters - 1) % _num_valid_letters; + display_letter(state); + break; + case EVENT_ALARM_BUTTON_UP: + if (act_on_btn(state)) break; + display_letter(state); + if (state->word_elements[state->position] == _num_valid_letters) break; + state->position = get_next_pos(state->position, state->word_elements_result); + if(WORDLE_LENGTH == (state->position)) { + bool exact_match = check_word(state); + if (exact_match) { + state->curr_screen = SCREEN_WIN; + state->streak++; + break; + } + if (state->attempt++ > WORDLE_MAX_ATTEMPTS) { + state->curr_screen = SCREEN_LOSE; + state->streak = 0; + break; + } + state->curr_screen = SCREEN_RESULT; + break; + } + break; + case EVENT_ALARM_LONG_PRESS: + display_letter(state); + state->position = get_prev_pos(state->position, state->word_elements_result); + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_TIMEOUT: + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void wordle_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h new file mode 100644 index 0000000..542a01b --- /dev/null +++ b/movement/watch_faces/complication/wordle_face.h @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WORDLE_FACE_H_ +#define WORDLE_FACE_H_ + +#include "movement.h" + +/* + * A DESCRIPTION OF YOUR WATCH FACE + * + * and a description of how use it + * + */ + +#define WORDLE_LENGTH 5 +#define WORDLE_MAX_ATTEMPTS 5 +#define USE_DAILY_STREAK true + +typedef enum { + WORDLE_LETTER_WRONG = 0, + WORDLE_LETTER_WRONG_LOC, + WORDLE_LETTER_CORRECT, + WORDLE_LETTER_COUNT +} WordleLetterResult; + +typedef enum { + SCREEN_PLAYING = 0, + SCREEN_RESULT, + SCREEN_TITLE, + SCREEN_WIN, + SCREEN_LOSE, + SCREEN_COUNT +} WordleScreen; + +typedef struct { + // Anything you need to keep track of, put it here! + uint8_t word_elements[WORDLE_LENGTH]; + WordleLetterResult word_elements_result[WORDLE_LENGTH]; + uint8_t attempt : 3; + uint8_t position : 3; + uint8_t unused : 2; + uint16_t curr_answer; + uint8_t streak; + WordleScreen curr_screen; +#if USE_DAILY_STREAK + // For the day info +#endif +} wordle_state_t; + +void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void wordle_face_activate(movement_settings_t *settings, void *context); +bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void wordle_face_resign(movement_settings_t *settings, void *context); + +#define wordle_face ((const watch_face_t){ \ + wordle_face_setup, \ + wordle_face_activate, \ + wordle_face_loop, \ + wordle_face_resign, \ + NULL, \ +}) + +#endif // WORDLE_FACE_H_ + From 6bf22edbdcc87e8ca9fc484c55bafe94ff16f414 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 14 Aug 2024 23:13:34 -0400 Subject: [PATCH 095/220] Continued Wordle dev --- .../watch_faces/complication/wordle_face.c | 552 +++----------- .../watch_faces/complication/wordle_face.h | 1 + utils/wordle_list.py | 697 ++++++++++++++++++ 3 files changed, 803 insertions(+), 447 deletions(-) create mode 100644 utils/wordle_list.py diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 8cd993e..f6f63fd 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -32,439 +32,81 @@ TODO: * Fix the word matching (if answer is AAAAA and we put in AACAA, the C blinks) * Verify pressing back always work when the board is G_G_G * Add daily streak and wait for next day +* Add a way tpo recount previous attempts */ -/* -Letter | Usage in Text -E | 12.7% -T | 9.1% But looks bad across all positions -A | 8.2% -O | 7.5% -I | 7.0% -N | 6.7% Few uses in 5 letter words than C -S | 6.3% -H | 6.1% -R | 6.0% -D | 4.3% -L | 4.0% -C | 2.8% -*/ -static const char _valid_letters[] = {'A', 'C', 'd', 'E', 'H', 'I', 'L', 'O', 'R', 'S'}; - // From: https://github.com/charlesreid1/five-letter-words/blob/master/sgb-words.txt + +/* +Letter | Usage in sgb-words.txt +S | 2674 +E | 2658 +A | 2181 +R | 1799 +O | 1683 +I | 1539 +T | 1462 But looks bad across all positions +L | 1434 +N | 1219 +D | 1100 lowercase d looks like a in certain positions +U | 1068 C has more words with the other letters here (457 total vs 390) +C | 920 +P | 895 +*/ +static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; + static const char _legal_words[][WORDLE_LENGTH + 1] = { -"AAAAA", "SHALL", "HEARd", "ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", -"HEARd", "ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", -"ORdER", "CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", -"CLOSE", "CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", -"CLASS", "HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", -"HORSE", "AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", -"AddEd", "COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", -"COLOR", "IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", -"IdEAS", "CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", -"CRIEd", "CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", -"CLEAR", "CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", -"CHILd", "SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", -"SIdES", "AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", -"AREAS", "SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", -"SCALE", "CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", -"CELLS", "AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", -"AHEAd", "REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", -"REACH", "RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", -"RAdIO", "LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", -"LOCAL", "SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", -"SEEdS", "CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", -"CROSS", "CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", -"CASES", "OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", -"OLdER", "SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", -"SHOES", "CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", -"CHAIR", "SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", -"SCORE", "SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", -"SHORE", "HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", -"HEAdS", "dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", -"dRESS", "SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", -"SHARE", "SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", -"SOLId", "HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", -"HILLS", "RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", -"RAISE", "ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", -"ROAdS", "CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", -"CHORd", "HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", -"HOLES", "HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", -"HOLdS", "CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", -"CALLS", "dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", -"dOORS", "LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", -"LOOSE", "ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", -"ASIdE", "SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", -"SHELL", "dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", -"dRIEd", "SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", -"SHAdE", "CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", -"CARdS", "CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", -"CHOSE", "SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", -"SOLAR", "RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", -"RISES", "SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", -"SALES", "ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", -"ACRES", "SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", -"SLIdE", "ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", -"ERROR", "RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", -"RACEd", "dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", -"dRILL", "HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", -"HELLO", "LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", -"LEAdS", "COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", -"COACH", "REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", -"REAdS", "HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", -"HERdS", "AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", -"AROSE", "RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", -"RACES", "HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", -"HEELS", "RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", -"RIdER", "ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", -"ROLLS", "CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", -"CRASH", "SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", -"SAILS", "ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", -"ARISE", "IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", -"IdEAL", "CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", -"CRIES", "ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", -"ASHES", "CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", -"CHASE", "SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", -"SLICE", "CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", -"CHEER", "HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", -"HIdES", "dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", -"dEEdS", "RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", -"RIdES", "ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", -"ROSES", "HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", -"HIREd", "SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", -"SALAd", "LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", -"LOAdS", "HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", -"HEARS", "LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", -"LOSES", "CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", -"CORAL", "dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", -"dAREd", "RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", -"RAdAR", "HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", -"HAIRS", "dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", -"dOLLS", "CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", -"CAREd", "SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", -"SELLS", "COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", -"COOLS", "HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", -"HARSH", "SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", -"SOILS", "REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", -"REEdS", "SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", -"SHEER", "CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", -"CHILL", "CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", -"CORdS", "RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", -"RAILS", "dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", -"dEALS", "ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", -"ACIdS", "COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", -"COCOA", "SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", -"SCARE", "CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", -"CEASE", "SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", -"SEALS", "LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", -"LORdS", "HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", -"HALLS", "COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", -"COALS", "ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", -"ROdEO", "COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", -"COdES", "ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", -"ELdER", "ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", -"ROLES", "dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", -"dREAd", "CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", -"CEdAR", "SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", -"SLASH", "CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", -"CARES", "IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", -"IdOLS", "OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", -"OdORS", "EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", -"EASEd", "CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", -"CHOIR", "AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", -"AIdEd", "CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", -"CHAOS", "LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", -"LEASE", "SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", -"SHEAR", "SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", -"SLEdS", "COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", -"COILS", "ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", -"ACHEd", "CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", -"CELLO", "dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", -"dRIES", "OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", -"OASIS", "dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", -"dRIER", "CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", -"CACAO", "EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", -"EERIE", "SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", -"SCARS", "RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", -"RAIdS", "SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", -"SOLES", "CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", -"CAROL", "CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", -"CHESS", "OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", -"OASES", "ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", -"ASSES", "SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", -"SHEdS", "CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", -"CLASH", "dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", -"dISCS", "ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", -"ERASE", "CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", -"CIdER", "SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", -"SHALE", "AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", -"AISLE", "HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", -"HEIRS", "ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", -"ROARS", "SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", -"SCOLd", "LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", -"LEASH", "LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", -"LASSO", "CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", -"CHORE", "LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", -"LACEd", "dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", -"dOSES", "COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", -"COLdS", "CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", -"CORES", "CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", -"CHILI", "EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", -"EASEL", "LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", -"LACES", "HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", -"HORdE", "LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", -"LASER", "HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", -"HARES", "CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", -"CREEd", "LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", -"LILAC", "HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", -"HOOdS", "ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", -"ROACH", "dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", -"dIALS", "SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", -"SOLOS", "SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", -"SISAL", "HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", -"HOSES", "dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", -"dARES", "ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", -"ACHES", "LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", -"LOSER", "LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", -"LAdLE", "RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", -"RAdII", "SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", -"SORES", "RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", -"RELIC", "AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", -"AIdES", "ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", -"ALdER", "COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", -"COdEd", "ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", -"ISLES", "CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", -"CLOdS", "AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", -"AdORE", "RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", -"RARER", "SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", -"SILLS", "SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", -"SHOAL", "CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", -"CACHE", "REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", -"REELS", "LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", -"LIARS", "SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", -"SOARS", "SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", -"SIdEd", "HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", -"HEALS", "SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", -"SOdAS", "ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", -"ERREd", "ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", -"ARdOR", "HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", -"HIRES", "LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", -"LEECH", "EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", -"EROdE", "HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", -"HOARd", "COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", -"COOEd", "SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", -"SHREd", "SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", -"SLOSH", "CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", -"CHIdE", "EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", -"EASES", "HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", -"HALOS", "ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", -"ACRId", "EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", -"EIdER", "AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", -"AddER", "dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", -"dEARS", "SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", -"SEERS", "OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", -"OddER", "SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", -"SIdLE", "dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", -"dOLEd", "HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", -"HAILS", "AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", -"AIREd", "COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", -"COCCI", "SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", -"SILOS", "dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", -"dOSEd", "RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", -"RILLS", "EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", -"EARLS", "LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", -"LAIRS", "IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", -"IdLER", "SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", -"SCALd", "AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", -"AdIOS", "dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", -"dALES", "HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", -"HEEdS", "CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", -"CEdEd", "LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", -"LARCH", "dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", -"dOLES", "dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", -"dROOL", "dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", -"dELLS", "COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", -"COCOS", "LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", -"LEERS", "ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", -"ALIAS", "dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", -"dICEd", "LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", -"LOdES", "IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", -"IdLEd", "RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", -"RACER", "AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", -"AILEd", "HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", -"HERES", "IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", -"IdLES", "dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", -"dOdOS", "HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", -"HELLS", "SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", -"SOLEd", "CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", -"CASEd", "COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", -"COEdS", "LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", -"LORES", "REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", -"REARS", "COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", -"COLIC", "SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", -"SIRES", "SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", -"SORER", "LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", -"LOLLS", "SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", -"SCAdS", "LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", -"LEACH", "ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", -"ARCEd", "COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", -"COREd", "dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", -"dEAdS", "dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", -"dROLL", "dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", -"dICES", "CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", -"CEdES", "HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", -"HOSEd", "CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", -"CRESS", "ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", -"ICIER", "LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", -"LARdS", "HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", -"HEROS", "SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", -"SHOOS", "dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", -"dECAL", "CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", -"CILIA", "SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", -"SEARS", "SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", -"SARIS", "dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", -"dILLS", "OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", -"OILEd", "dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", -"dOERS", "ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", -"ALOHA", "RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", -"RISER", "CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", -"CRIER", "LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", -"LOESS", "dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", -"dECOR", "SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", -"SHIEd", "dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", -"dROSS", "CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", -"CREdO", "ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", -"ALOES", "dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", -"dIOdE", "COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", -"COHOS", "SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", -"SIREE", "OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", -"OCHRE", "SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", -"SIREd", "CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", -"CAdRE", "ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", -"ECHOS", "RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", -"RILEd", "CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", -"CIRCA", "HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", -"HALLO", "SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", -"SHIES", "HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", -"HOLEd", "SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", -"SEdER", "REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", -"REALS", "AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", -"AERIE", "CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", -"CRASS", "dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", -"dACHA", "ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", -"ACHOO", "dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", -"dREAR", "ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", -"ARIAS", "HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", -"HOOCH", "SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", -"SHILL", "AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", -"AHHHH", "SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", -"SHISH", "RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", -"RILES", "HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", -"HEERd", "CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", -"CREEL", "RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", -"RHEAS", "COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", -"COdER", "HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", -"HALEd", "CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", -"CHARd", "SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", -"SHOEd", "ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", -"ESSES", "EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", -"EAREd", "ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", -"ELIdE", "SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", -"SALSA", "AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", -"AHOLd", "SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", -"SHAHS", "dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", -"dISCO", "SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", -"SHARd", "CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", -"CORER", "COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", -"COLAS", "dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", -"dROId", "OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", -"OLdIE", "COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", -"COdAS", "ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", -"ORCAS", "HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", -"HALES", "REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", -"REdId", "dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", -"dAdOS", "CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", -"CLAdS", "OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", -"OOdLE", "HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", -"HIdER", "dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", -"dELIS", "SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", -"SCROd", "SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", -"SHIRE", "AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", -"AddLE", "dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", -"dEICE", "ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", -"ORALS", "HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", -"HIRER", "ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", -"ROILS", "dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", -"dOLOR", "ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", -"ILIAC", "CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", -"CILLS", "LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", -"LOCOS", "HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", -"HILAR", "CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", -"CROCS", "OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", -"OHHHH", "SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", -"SECCO", "SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", -"SHAdS", "CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", -"CHOOS", "AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", -"AREAL", "dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", -"dIdOS", "COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", -"COOCH", "CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", -"CHILE", "OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", -"OCHER", "dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", -"dOLCE", "SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", -"SLOES", "LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", -"LAIRd", "SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", -"SHERd", "CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", -"CHARS", "HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", -"HAdES", "HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", -"HOARS", "dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", -"dARER", "ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", -"ROIdS", "ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", -"ARRAS", "HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", -"HALER", "AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", -"AIRER", "CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", -"CASAS", "CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", -"CARER", "OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", -"OAREd", "RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", -"RILLE", "RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", -"RICEd", "LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", -"LIRAS", "AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", -"AIdER", "ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", -"ICHOR", "LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", -"LOCHS", "CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", -"CALLA", "COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", -"COCAS", "OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", -"OILER", "ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", -"ASSAI", "LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", -"LAdES", "SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", -"SHIER", "dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", -"dICER", "OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", -"OLEOS", "SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", -"SHOER", "HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", -"HOdAd", "IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", -"IOdIC", "LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", -"LIERS", "AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", -"AIOLI", "HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", -"HOERS", "SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", -"SLIER", "LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", -"LASES", "RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", -"RASAE", "SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", -"SOCLE", "RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", -"RICER", "HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", -"HAddA", "RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", -"RICES", "ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", -"ROOdS", "CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", -"CEdER", "CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", -"CEILS", "LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", -"LISLE", "OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", -"OLIOS", "dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", -"dOSER", "dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", -"SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", -"dILdO", "SELAH", "RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", -"RIALS", "ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", -"ICERS", "SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", -"SHIRR", "LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", "ASSEd", -"LAdEd", "LASEd", "HOLER", "LACER", "ARSES", "CIRRI", "dIRER", "ASSEd", "OSIER", + "PLACE", "SINCE", "PAPER", "LINES", "LEARN", "SPACE", "CLOSE", "CLASS", "PIECE", + "COLOR", "ALONE", "PLANE", "SPELL", "CLEAR", "AREAS", "SENSE", "OCEAN", "SCALE", + "CELLS", "SLEEP", "LOCAL", "CLEAN", "PEACE", "CROSS", "CASES", "CROPS", "PLAIN", + "PAIRS", "SCORE", "NOISE", "PIANO", "PLANS", "PRICE", "RAISE", "SCENE", "PRESS", + "APPLE", "CALLS", "POLES", "LOOSE", "OPERA", "INNER", "SOLAR", "RISES", "SALES", + "ACRES", "ERROR", "NAILS", "COINS", "SLOPE", "CANOE", "CANAL", "LIONS", "AROSE", + "RACES", "SPARE", "PIPES", "RAINS", "ROLLS", "SAILS", "ARISE", "ROPES", "CRIES", + "OPENS", "APRON", "SPEAR", "SLICE", "SPOON", "ROSES", "LINEN", "POLAR", "PEARL", + "LOSES", "CORAL", "SPOIL", "PANEL", "SELLS", "COOLS", "SOILS", "POOLS", "RISEN", + "PILES", "SLAIN", "PANIC", "CRISP", "RAILS", "SCOPE", "CONES", "COCOA", "REINS", + "NOSES", "SCARE", "CEASE", "PRIOR", "POLIO", "SEALS", "COALS", "LOANS", "SPINS", + "PAINS", "ONION", "SCRAP", "ROLES", "SNAIL", "LOOPS", "CREEP", "CARES", "ALIEN", + "CRANE", "SLIPS", "SPINE", "LEAPS", "PROSE", "SNARE", "PINES", "SCALP", "LEASE", + "COILS", "ARENA", "PEARS", "CLIPS", "LANES", "RESIN", "SONAR", "CORPS", "NIECE", + "CELLO", "REPEL", "SPICE", "OASIS", "PACES", "COLON", "CACAO", "RINSE", "SPOOL", + "SPILL", "SNAPS", "EERIE", "EARNS", "PERIL", "LINER", "SCARS", "SOLES", "PAILS", + "CAROL", "CANON", "POLLS", "NASAL", "SCORN", "OASES", "ASSES", "PILLS", "SPORE", + "SPIES", "ERASE", "AISLE", "LOINS", "LEANS", "LANCE", "PANES", "PORES", "POISE", + "IRONS", "ROARS", "SCOOP", "LASSO", "PRONE", "NICER", "SNARL", "CORES", "RIPEN", + "PEERS", "EASEL", "LACES", "SOAPS", "SNIPS", "PIERS", "LASER", "LILAC", "SIREN", + "CLASP", "POSSE", "POSES", "SLOOP", "SLAPS", "SOLOS", "SISAL", "SEEPS", "SPANS", + "CLAPS", "ACORN", "LOSER", "LAPSE", "ASPEN", "SNORE", "PROPS", "PESOS", "SONIC", + "SPARS", "SORES", "RELIC", "NOOSE", "NEARS", "CAPES", "CANES", "SPIRE", "ISLES", + "SNEER", "RARER", "NINES", "PECAN", "PENCE", "SILLS", "COPRA", "POPES", "SPREE", + "SCANS", "REELS", "LIARS", "LEPER", "SOARS", "PLEAS", "PALER", "EPICS", "CAPER", + "CONIC", "OPALS", "EASES", "ARSON", "CLANS", "PLIES", "CROON", "PREEN", "SEERS", + "COCCI", "SILOS", "SNIPE", "SANER", "RILLS", "CORNS", "PRIES", "LOONS", "EARLS", + "PEONS", "PALES", "LAIRS", "PEELS", "PEALS", "CRONE", "ENROL", "COOPS", "LAPEL", + "RASPS", "ASPIC", "PENIS", "INANE", "SLOPS", "COCOS", "LEERS", "LOPES", "ALIAS", + "RACER", "COPSE", "PALLS", "COPES", "ICONS", "REAPS", "SNOOP", "LORES", "REARS", + "COLIC", "PLOPS", "SIRES", "CARPS", "LISPS", "PEEPS", "SORER", "LOLLS", "PARES", + "ACNES", "NEONS", "NOONS", "PAPAS", "RIPER", "ELOPE", "CRESS", "NAPES", "ICIER", + "CILIA", "SEARS", "SARIS", "PAPAL", "ROSIN", "CREPE", "RISER", "PACER", "SALON", + "CRIER", "LOESS", "PIPER", "COONS", "SEINE", "IONIC", "SCRIP", "PENAL", "ALOES", + "APACE", "SIREE", "ROPER", "ANION", "LONER", "CIRCA", "CARNE", "ANISE", "SPECS", + "ANNAS", "PICAS", "REALS", "AERIE", "ORLON", "CRASS", "SPIEL", "LAPIS", "ARIAS", + "PAEAN", "SINES", "SCION", "RAPES", "SCARP", "SEPIA", "POSER", "LIENS", "RILES", + "APSES", "NONCE", "ANOLE", "RECAP", "CARON", "PORNO", "CREEL", "CAPOS", "OPINE", + "NISEI", "RERAN", "NARCO", "CLOPS", "ESSES", "SCONE", "SALSA", "ROANS", "RAPER", + "CORER", "COLAS", "CAIRN", "CRAPS", "CLONE", "NOELS", "ORCAS", "PARSE", "EPEES", + "LANAI", "SEPAL", "CAPON", "PREPS", "NARCS", "REPRO", "ORALS", "ROILS", "ILIAC", + "CILLS", "LOCOS", "RENAL", "CROCS", "PARAS", "SECCO", "PIONS", "PARER", "PLEIN", + "AREAL", "SOLON", "PSOAS", "SCOPS", "SLOES", "NOIRE", "POLIS", "PASSE", "NONES", + "SARAN", "POLOS", "APERS", "ARRAS", "PRISE", "SPIER", "AIRER", "APIAN", "CASAS", + "CARER", "POOPS", "SPINA", "PENES", "RILLE", "CANER", "LIRAS", "PRIER", "LOPER", + "CALLA", "PONES", "COCAS", "OILER", "ASSAI", "LAPIN", "ASANA", "OLEOS", "LIERS", + "ANILE", "PLENA", "AIOLI", "SLIER", "CANNA", "PEASE", "LASES", "RASAE", "PAREN", + "SOCLE", "RICER", "RICES", "ELANS", "CEILS", "LISLE", "OLIOS", "APSOS", "RIALS", + "ICERS", "COPER", "PEENS", "POLER", "LACER", "ARSES", "SPOOR", "CIRRI", "APNEA", + "NARES", "OSIER", }; static const uint32_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); @@ -478,10 +120,13 @@ static uint32_t get_random(uint32_t max) { #endif } -static void display_letter(wordle_state_t *state) { +static void display_letter(wordle_state_t *state, bool display_dash) { char buf[1 + 1]; if (state->word_elements[state->position] >= _num_valid_letters) { - watch_display_string("-", state->position + 5); + if (display_dash) + watch_display_string("-", state->position + 5); + else + watch_display_string(" ", state->position + 5); return; } sprintf(buf, "%c", _valid_letters[state->word_elements[state->position]]); @@ -492,7 +137,7 @@ static void display_all_letters(wordle_state_t *state) { uint8_t prev_pos = state->position; for (size_t i = 0; i < WORDLE_LENGTH; i++) { state->position = i; - display_letter(state); + display_letter(state, false); } state->position = prev_pos; } @@ -512,8 +157,8 @@ static bool check_word(wordle_state_t *state) { // Wrong Location for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (state->word_elements_result[i] != WORDLE_LETTER_WRONG) continue; for (size_t j = 0; j < WORDLE_LENGTH; j++) { + if (state->word_elements_result[j] != WORDLE_LETTER_WRONG) continue; if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { printf("me: %c them: %c\r\n", _valid_letters[state->word_elements[i]], _legal_words[state->curr_answer][j]); state->word_elements_result[j] = WORDLE_LETTER_WRONG_LOC; @@ -550,12 +195,18 @@ static void reset_board(wordle_state_t *state) { watch_display_string(" ", 4); display_attempt(state->attempt); display_all_letters(state); + watch_display_string("-", 5); printf("rand: %s\r\n", _legal_words[state->curr_answer]); } static void display_title(wordle_state_t *state) { - char buf[11]; state->curr_screen = SCREEN_TITLE; + watch_display_string("WO WORDLE", 0); +} + +static void display_streak(wordle_state_t *state) { + char buf[12]; + state->curr_screen = SCREEN_STREAK; printf("streak %d \r\n", state->streak); sprintf(buf, "WO St%2ddy", state->streak); watch_display_string(buf, 0); @@ -563,13 +214,14 @@ static void display_title(wordle_state_t *state) { } static void display_lose(wordle_state_t *state, uint8_t subsecond) { - char buf[WORDLE_LENGTH + 5]; + char buf[WORDLE_LENGTH + 6]; sprintf(buf," L %s", subsecond % 2 ? _legal_words[state->curr_answer] : " "); watch_display_string(buf, 0); } static void display_win(wordle_state_t *state, uint8_t subsecond) { - char buf[11]; + (void) state; + char buf[13]; sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -625,7 +277,9 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { } static bool act_on_btn(wordle_state_t *state) { - if (state->curr_screen == SCREEN_RESULT) { + switch (state->curr_screen) + { + case SCREEN_RESULT: for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) state->word_elements[i] = _num_valid_letters; @@ -635,14 +289,18 @@ static bool act_on_btn(wordle_state_t *state) { state->position = get_first_pos(state->word_elements_result); state->curr_screen = SCREEN_PLAYING; return true; - } - if (state->curr_screen == SCREEN_TITLE) { + case SCREEN_TITLE: + display_streak(state); + return true; + case SCREEN_STREAK: reset_board(state); return true; - } - if (state->curr_screen >= SCREEN_WIN) { + case SCREEN_WIN: + case SCREEN_LOSE: display_title(state); - return true; + return true; + default: + return false; } return false; } @@ -681,7 +339,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi { case SCREEN_PLAYING: if (event.subsecond % 2) { - display_letter(state); + display_letter(state, true); } else { watch_display_string(" ", state->position + 5); } @@ -703,16 +361,16 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi if (act_on_btn(state)) break; if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = 0; else state->word_elements[state->position] = (state->word_elements[state->position] + 1) % _num_valid_letters; - display_letter(state); + display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = _num_valid_letters - 1; else state->word_elements[state->position] = (state->word_elements[state->position] + _num_valid_letters - 1) % _num_valid_letters; - display_letter(state); + display_letter(state, true); break; case EVENT_ALARM_BUTTON_UP: if (act_on_btn(state)) break; - display_letter(state); + display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; state->position = get_next_pos(state->position, state->word_elements_result); if(WORDLE_LENGTH == (state->position)) { @@ -722,7 +380,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi state->streak++; break; } - if (state->attempt++ > WORDLE_MAX_ATTEMPTS) { + if (state->attempt++ >= WORDLE_MAX_ATTEMPTS) { state->curr_screen = SCREEN_LOSE; state->streak = 0; break; @@ -732,7 +390,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi } break; case EVENT_ALARM_LONG_PRESS: - display_letter(state); + display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); break; case EVENT_LIGHT_BUTTON_DOWN: diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 542a01b..b8e5d0f 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -49,6 +49,7 @@ typedef enum { SCREEN_PLAYING = 0, SCREEN_RESULT, SCREEN_TITLE, + SCREEN_STREAK, SCREEN_WIN, SCREEN_LOSE, SCREEN_COUNT diff --git a/utils/wordle_list.py b/utils/wordle_list.py new file mode 100644 index 0000000..30da4bd --- /dev/null +++ b/utils/wordle_list.py @@ -0,0 +1,697 @@ +# From: https://github.com/charlesreid1/five-letter-words/blob/master/sgb-words.txt +words = [ + "WHICH", "THERE", "THEIR", "ABOUT", "WOULD", "THESE", "OTHER", "WORDS", "COULD", + "WRITE", "FIRST", "WATER", "AFTER", "WHERE", "RIGHT", "THINK", "THREE", "YEARS", + "PLACE", "SOUND", "GREAT", "AGAIN", "STILL", "EVERY", "SMALL", "FOUND", "THOSE", + "NEVER", "UNDER", "MIGHT", "WHILE", "HOUSE", "WORLD", "BELOW", "ASKED", "GOING", + "LARGE", "UNTIL", "ALONG", "SHALL", "BEING", "OFTEN", "EARTH", "BEGAN", "SINCE", + "STUDY", "NIGHT", "LIGHT", "ABOVE", "PAPER", "PARTS", "YOUNG", "STORY", "POINT", + "TIMES", "HEARD", "WHOLE", "WHITE", "GIVEN", "MEANS", "MUSIC", "MILES", "THING", + "TODAY", "LATER", "USING", "MONEY", "LINES", "ORDER", "GROUP", "AMONG", "LEARN", + "KNOWN", "SPACE", "TABLE", "EARLY", "TREES", "SHORT", "HANDS", "STATE", "BLACK", + "SHOWN", "STOOD", "FRONT", "VOICE", "KINDS", "MAKES", "COMES", "CLOSE", "POWER", + "LIVED", "VOWEL", "TAKEN", "BUILT", "HEART", "READY", "QUITE", "CLASS", "BRING", + "ROUND", "HORSE", "SHOWS", "PIECE", "GREEN", "STAND", "BIRDS", "START", "RIVER", + "TRIED", "LEAST", "FIELD", "WHOSE", "GIRLS", "LEAVE", "ADDED", "COLOR", "THIRD", + "HOURS", "MOVED", "PLANT", "DOING", "NAMES", "FORMS", "HEAVY", "IDEAS", "CRIED", + "CHECK", "FLOOR", "BEGIN", "WOMAN", "ALONE", "PLANE", "SPELL", "WATCH", "CARRY", + "WROTE", "CLEAR", "NAMED", "BOOKS", "CHILD", "GLASS", "HUMAN", "TAKES", "PARTY", + "BUILD", "SEEMS", "BLOOD", "SIDES", "SEVEN", "MOUTH", "SOLVE", "NORTH", "VALUE", + "DEATH", "MAYBE", "HAPPY", "TELLS", "GIVES", "LOOKS", "SHAPE", "LIVES", "STEPS", + "AREAS", "SENSE", "SPEAK", "FORCE", "OCEAN", "SPEED", "WOMEN", "METAL", "SOUTH", + "GRASS", "SCALE", "CELLS", "LOWER", "SLEEP", "WRONG", "PAGES", "SHIPS", "NEEDS", + "ROCKS", "EIGHT", "MAJOR", "LEVEL", "TOTAL", "AHEAD", "REACH", "STARS", "STORE", + "SIGHT", "TERMS", "CATCH", "WORKS", "BOARD", "COVER", "SONGS", "EQUAL", "STONE", + "WAVES", "GUESS", "DANCE", "SPOKE", "BREAK", "CAUSE", "RADIO", "WEEKS", "LANDS", + "BASIC", "LIKED", "TRADE", "FRESH", "FINAL", "FIGHT", "MEANT", "DRIVE", "SPENT", + "LOCAL", "WAXES", "KNOWS", "TRAIN", "BREAD", "HOMES", "TEETH", "COAST", "THICK", + "BROWN", "CLEAN", "QUIET", "SUGAR", "FACTS", "STEEL", "FORTH", "RULES", "NOTES", + "UNITS", "PEACE", "MONTH", "VERBS", "SEEDS", "HELPS", "SHARP", "VISIT", "WOODS", + "CHIEF", "WALLS", "CROSS", "WINGS", "GROWN", "CASES", "FOODS", "CROPS", "FRUIT", + "STICK", "WANTS", "STAGE", "SHEEP", "NOUNS", "PLAIN", "DRINK", "BONES", "APART", + "TURNS", "MOVES", "TOUCH", "ANGLE", "BASED", "RANGE", "MARKS", "TIRED", "OLDER", + "FARMS", "SPEND", "SHOES", "GOODS", "CHAIR", "TWICE", "CENTS", "EMPTY", "ALIKE", + "STYLE", "BROKE", "PAIRS", "COUNT", "ENJOY", "SCORE", "SHORE", "ROOTS", "PAINT", + "HEADS", "SHOOK", "SERVE", "ANGRY", "CROWD", "WHEEL", "QUICK", "DRESS", "SHARE", + "ALIVE", "NOISE", "SOLID", "CLOTH", "SIGNS", "HILLS", "TYPES", "DRAWN", "WORTH", + "TRUCK", "PIANO", "UPPER", "LOVED", "USUAL", "FACES", "DROVE", "CABIN", "BOATS", + "TOWNS", "PROUD", "COURT", "MODEL", "PRIME", "FIFTY", "PLANS", "YARDS", "PROVE", + "TOOLS", "PRICE", "SHEET", "SMELL", "BOXES", "RAISE", "MATCH", "TRUTH", "ROADS", + "THREW", "ENEMY", "LUNCH", "CHART", "SCENE", "GRAPH", "DOUBT", "GUIDE", "WINDS", + "BLOCK", "GRAIN", "SMOKE", "MIXED", "GAMES", "WAGON", "SWEET", "TOPIC", "EXTRA", + "PLATE", "TITLE", "KNIFE", "FENCE", "FALLS", "CLOUD", "WHEAT", "PLAYS", "ENTER", + "BROAD", "STEAM", "ATOMS", "PRESS", "LYING", "BASIS", "CLOCK", "TASTE", "GROWS", + "THANK", "STORM", "AGREE", "BRAIN", "TRACK", "SMILE", "FUNNY", "BEACH", "STOCK", + "HURRY", "SAVED", "SORRY", "GIANT", "TRAIL", "OFFER", "OUGHT", "ROUGH", "DAILY", + "AVOID", "KEEPS", "THROW", "ALLOW", "CREAM", "LAUGH", "EDGES", "TEACH", "FRAME", + "BELLS", "DREAM", "MAGIC", "OCCUR", "ENDED", "CHORD", "FALSE", "SKILL", "HOLES", + "DOZEN", "BRAVE", "APPLE", "CLIMB", "OUTER", "PITCH", "RULER", "HOLDS", "FIXED", + "COSTS", "CALLS", "BLANK", "STAFF", "LABOR", "EATEN", "YOUTH", "TONES", "HONOR", + "GLOBE", "GASES", "DOORS", "POLES", "LOOSE", "APPLY", "TEARS", "EXACT", "BRUSH", + "CHEST", "LAYER", "WHALE", "MINOR", "FAITH", "TESTS", "JUDGE", "ITEMS", "WORRY", + "WASTE", "HOPED", "STRIP", "BEGUN", "ASIDE", "LAKES", "BOUND", "DEPTH", "CANDY", + "EVENT", "WORSE", "AWARE", "SHELL", "ROOMS", "RANCH", "IMAGE", "SNAKE", "ALOUD", + "DRIED", "LIKES", "MOTOR", "POUND", "KNEES", "REFER", "FULLY", "CHAIN", "SHIRT", + "FLOUR", "DROPS", "SPITE", "ORBIT", "BANKS", "SHOOT", "CURVE", "TRIBE", "TIGHT", + "BLIND", "SLEPT", "SHADE", "CLAIM", "FLIES", "THEME", "QUEEN", "FIFTH", "UNION", + "HENCE", "STRAW", "ENTRY", "ISSUE", "BIRTH", "FEELS", "ANGER", "BRIEF", "RHYME", + "GLORY", "GUARD", "FLOWS", "FLESH", "OWNED", "TRICK", "YOURS", "SIZES", "NOTED", + "WIDTH", "BURST", "ROUTE", "LUNGS", "UNCLE", "BEARS", "ROYAL", "KINGS", "FORTY", + "TRIAL", "CARDS", "BRASS", "OPERA", "CHOSE", "OWNER", "VAPOR", "BEATS", "MOUSE", + "TOUGH", "WIRES", "METER", "TOWER", "FINDS", "INNER", "STUCK", "ARROW", "POEMS", + "LABEL", "SWING", "SOLAR", "TRULY", "TENSE", "BEANS", "SPLIT", "RISES", "WEIGH", + "HOTEL", "STEMS", "PRIDE", "SWUNG", "GRADE", "DIGIT", "BADLY", "BOOTS", "PILOT", + "SALES", "SWEPT", "LUCKY", "PRIZE", "STOVE", "TUBES", "ACRES", "WOUND", "STEEP", + "SLIDE", "TRUNK", "ERROR", "PORCH", "SLAVE", "EXIST", "FACED", "MINES", "MARRY", + "JUICE", "RACED", "WAVED", "GOOSE", "TRUST", "FEWER", "FAVOR", "MILLS", "VIEWS", + "JOINT", "EAGER", "SPOTS", "BLEND", "RINGS", "ADULT", "INDEX", "NAILS", "HORNS", + "BALLS", "FLAME", "RATES", "DRILL", "TRACE", "SKINS", "WAXED", "SEATS", "STUFF", + "RATIO", "MINDS", "DIRTY", "SILLY", "COINS", "HELLO", "TRIPS", "LEADS", "RIFLE", + "HOPES", "BASES", "SHINE", "BENCH", "MORAL", "FIRES", "MEALS", "SHAKE", "SHOPS", + "CYCLE", "MOVIE", "SLOPE", "CANOE", "TEAMS", "FOLKS", "FIRED", "BANDS", "THUMB", + "SHOUT", "CANAL", "HABIT", "REPLY", "RULED", "FEVER", "CRUST", "SHELF", "WALKS", + "MIDST", "CRACK", "PRINT", "TALES", "COACH", "STIFF", "FLOOD", "VERSE", "AWAKE", + "ROCKY", "MARCH", "FAULT", "SWIFT", "FAINT", "CIVIL", "GHOST", "FEAST", "BLADE", + "LIMIT", "GERMS", "READS", "DUCKS", "DAIRY", "WORST", "GIFTS", "LISTS", "STOPS", + "RAPID", "BRICK", "CLAWS", "BEADS", "BEAST", "SKIRT", "CAKES", "LIONS", "FROGS", + "TRIES", "NERVE", "GRAND", "ARMED", "TREAT", "HONEY", "MOIST", "LEGAL", "PENNY", + "CROWN", "SHOCK", "TAXES", "SIXTY", "ALTAR", "PULLS", "SPORT", "DRUMS", "TALKS", + "DYING", "DATES", "DRANK", "BLOWS", "LEVER", "WAGES", "PROOF", "DRUGS", "TANKS", + "SINGS", "TAILS", "PAUSE", "HERDS", "AROSE", "HATED", "CLUES", "NOVEL", "SHAME", + "BURNT", "RACES", "FLASH", "WEARY", "HEELS", "TOKEN", "COATS", "SPARE", "SHINY", + "ALARM", "DIMES", "SIXTH", "CLERK", "MERCY", "SUNNY", "GUEST", "FLOAT", "SHONE", + "PIPES", "WORMS", "BILLS", "SWEAT", "SUITS", "SMART", "UPSET", "RAINS", "SANDY", + "RAINY", "PARKS", "SADLY", "FANCY", "RIDER", "UNITY", "BUNCH", "ROLLS", "CRASH", + "CRAFT", "NEWLY", "GATES", "HATCH", "PATHS", "FUNDS", "WIDER", "GRACE", "GRAVE", + "TIDES", "ADMIT", "SHIFT", "SAILS", "PUPIL", "TIGER", "ANGEL", "CRUEL", "AGENT", + "DRAMA", "URGED", "PATCH", "NESTS", "VITAL", "SWORD", "BLAME", "WEEDS", "SCREW", + "VOCAL", "BACON", "CHALK", "CARGO", "CRAZY", "ACTED", "GOATS", "ARISE", "WITCH", + "LOVES", "QUEER", "DWELL", "BACKS", "ROPES", "SHOTS", "MERRY", "PHONE", "CHEEK", + "PEAKS", "IDEAL", "BEARD", "EAGLE", "CREEK", "CRIES", "ASHES", "STALL", "YIELD", + "MAYOR", "OPENS", "INPUT", "FLEET", "TOOTH", "CUBIC", "WIVES", "BURNS", "POETS", + "APRON", "SPEAR", "ORGAN", "CLIFF", "STAMP", "PASTE", "RURAL", "BAKED", "CHASE", + "SLICE", "SLANT", "KNOCK", "NOISY", "SORTS", "STAYS", "WIPED", "BLOWN", "PILED", + "CLUBS", "CHEER", "WIDOW", "TWIST", "TENTH", "HIDES", "COMMA", "SWEEP", "SPOON", + "STERN", "CREPT", "MAPLE", "DEEDS", "RIDES", "MUDDY", "CRIME", "JELLY", "RIDGE", + "DRIFT", "DUSTY", "DEVIL", "TEMPO", "HUMOR", "SENDS", "STEAL", "TENTS", "WAIST", + "ROSES", "REIGN", "NOBLE", "CHEAP", "DENSE", "LINEN", "GEESE", "WOVEN", "POSTS", + "HIRED", "WRATH", "SALAD", "BOWED", "TIRES", "SHARK", "BELTS", "GRASP", "BLAST", + "POLAR", "FUNGI", "TENDS", "PEARL", "LOADS", "JOKES", "VEINS", "FROST", "HEARS", + "LOSES", "HOSTS", "DIVER", "PHASE", "TOADS", "ALERT", "TASKS", "SEAMS", "CORAL", + "FOCUS", "NAKED", "PUPPY", "JUMPS", "SPOIL", "QUART", "MACRO", "FEARS", "FLUNG", + "SPARK", "VIVID", "BROOK", "STEER", "SPRAY", "DECAY", "PORTS", "SOCKS", "URBAN", + "GOALS", "GRANT", "MINUS", "FILMS", "TUNES", "SHAFT", "FIRMS", "SKIES", "BRIDE", + "WRECK", "FLOCK", "STARE", "HOBBY", "BONDS", "DARED", "FADED", "THIEF", "CRUDE", + "PANTS", "FLUTE", "VOTES", "TONAL", "RADAR", "WELLS", "SKULL", "HAIRS", "ARGUE", + "WEARS", "DOLLS", "VOTED", "CAVES", "CARED", "BROOM", "SCENT", "PANEL", "FAIRY", + "OLIVE", "BENDS", "PRISM", "LAMPS", "CABLE", "PEACH", "RUINS", "RALLY", "SCHWA", + "LAMBS", "SELLS", "COOLS", "DRAFT", "CHARM", "LIMBS", "BRAKE", "GAZED", "CUBES", + "DELAY", "BEAMS", "FETCH", "RANKS", "ARRAY", "HARSH", "CAMEL", "VINES", "PICKS", + "NAVAL", "PURSE", "RIGID", "CRAWL", "TOAST", "SOILS", "SAUCE", "BASIN", "PONDS", + "TWINS", "WRIST", "FLUID", "POOLS", "BRAND", "STALK", "ROBOT", "REEDS", "HOOFS", + "BUSES", "SHEER", "GRIEF", "BLOOM", "DWELT", "MELTS", "RISEN", "FLAGS", "KNELT", + "FIBER", "ROOFS", "FREED", "ARMOR", "PILES", "AIMED", "ALGAE", "TWIGS", "LEMON", + "DITCH", "DRUNK", "RESTS", "CHILL", "SLAIN", "PANIC", "CORDS", "TUNED", "CRISP", + "LEDGE", "DIVED", "SWAMP", "CLUNG", "STOLE", "MOLDS", "YARNS", "LIVER", "GAUGE", + "BREED", "STOOL", "GULLS", "AWOKE", "GROSS", "DIARY", "RAILS", "BELLY", "TREND", + "FLASK", "STAKE", "FRIED", "DRAWS", "ACTOR", "HANDY", "BOWLS", "HASTE", "SCOPE", + "DEALS", "KNOTS", "MOONS", "ESSAY", "THUMP", "HANGS", "BLISS", "DEALT", "GAINS", + "BOMBS", "CLOWN", "PALMS", "CONES", "ROAST", "TIDAL", "BORED", "CHANT", "ACIDS", + "DOUGH", "CAMPS", "SWORE", "LOVER", "HOOKS", "MALES", "COCOA", "PUNCH", "AWARD", + "REINS", "NINTH", "NOSES", "LINKS", "DRAIN", "FILLS", "NYLON", "LUNAR", "PULSE", + "FLOWN", "ELBOW", "FATAL", "SITES", "MOTHS", "MEATS", "FOXES", "MINED", "ATTIC", + "FIERY", "MOUNT", "USAGE", "SWEAR", "SNOWY", "RUSTY", "SCARE", "TRAPS", "RELAX", + "REACT", "VALID", "ROBIN", "CEASE", "GILLS", "PRIOR", "SAFER", "POLIO", "LOYAL", + "SWELL", "SALTY", "MARSH", "VAGUE", "WEAVE", "MOUND", "SEALS", "MULES", "VIRUS", + "SCOUT", "ACUTE", "WINDY", "STOUT", "FOLDS", "SEIZE", "HILLY", "JOINS", "PLUCK", + "STACK", "LORDS", "DUNES", "BURRO", "HAWKS", "TROUT", "FEEDS", "SCARF", "HALLS", + "COALS", "TOWEL", "SOULS", "ELECT", "BUGGY", "PUMPS", "LOANS", "SPINS", "FILES", + "OXIDE", "PAINS", "PHOTO", "RIVAL", "FLATS", "SYRUP", "RODEO", "SANDS", "MOOSE", + "PINTS", "CURLY", "COMIC", "CLOAK", "ONION", "CLAMS", "SCRAP", "DIDST", "COUCH", + "CODES", "FAILS", "OUNCE", "LODGE", "GREET", "GYPSY", "UTTER", "PAVED", "ZONES", + "FOURS", "ALLEY", "TILES", "BLESS", "CREST", "ELDER", "KILLS", "YEAST", "ERECT", + "BUGLE", "MEDAL", "ROLES", "HOUND", "SNAIL", "ALTER", "ANKLE", "RELAY", "LOOPS", + "ZEROS", "BITES", "MODES", "DEBTS", "REALM", "GLOVE", "RAYON", "SWIMS", "POKED", + "STRAY", "LIFTS", "MAKER", "LUMPS", "GRAZE", "DREAD", "BARNS", "DOCKS", "MASTS", + "POURS", "WHARF", "CURSE", "PLUMP", "ROBES", "SEEKS", "CEDAR", "CURLS", "JOLLY", + "MYTHS", "CAGES", "GLOOM", "LOCKS", "PEDAL", "BEETS", "CROWS", "ANODE", "SLASH", + "CREEP", "ROWED", "CHIPS", "FISTS", "WINES", "CARES", "VALVE", "NEWER", "MOTEL", + "IVORY", "NECKS", "CLAMP", "BARGE", "BLUES", "ALIEN", "FROWN", "STRAP", "CREWS", + "SHACK", "GONNA", "SAVES", "STUMP", "FERRY", "IDOLS", "COOKS", "JUICY", "GLARE", + "CARTS", "ALLOY", "BULBS", "LAWNS", "LASTS", "FUELS", "ODDLY", "CRANE", "FILED", + "WEIRD", "SHAWL", "SLIPS", "TROOP", "BOLTS", "SUITE", "SLEEK", "QUILT", "TRAMP", + "BLAZE", "ATLAS", "ODORS", "SCRUB", "CRABS", "PROBE", "LOGIC", "ADOBE", "EXILE", + "REBEL", "GRIND", "STING", "SPINE", "CLING", "DESKS", "GROVE", "LEAPS", "PROSE", + "LOFTY", "AGONY", "SNARE", "TUSKS", "BULLS", "MOODS", "HUMID", "FINER", "DIMLY", + "PLANK", "CHINA", "PINES", "GUILT", "SACKS", "BRACE", "QUOTE", "LATHE", "GAILY", + "FONTS", "SCALP", "ADOPT", "FOGGY", "FERNS", "GRAMS", "CLUMP", "PERCH", "TUMOR", + "TEENS", "CRANK", "FABLE", "HEDGE", "GENES", "SOBER", "BOAST", "TRACT", "CIGAR", + "UNITE", "OWING", "THIGH", "HAIKU", "SWISH", "DIKES", "WEDGE", "BOOTH", "EASED", + "FRAIL", "COUGH", "TOMBS", "DARTS", "FORTS", "CHOIR", "POUCH", "PINCH", "HAIRY", + "BUYER", "TORCH", "VIGOR", "WALTZ", "HEATS", "HERBS", "USERS", "FLINT", "CLICK", + "MADAM", "BLEAK", "BLUNT", "AIDED", "LACKS", "MASKS", "WADED", "RISKS", "NURSE", + "CHAOS", "SEWED", "CURED", "AMPLE", "LEASE", "STEAK", "SINKS", "MERIT", "BLUFF", + "BATHE", "GLEAM", "BONUS", "COLTS", "SHEAR", "GLAND", "SILKY", "SKATE", "BIRCH", + "ANVIL", "SLEDS", "GROAN", "MAIDS", "MEETS", "SPECK", "HYMNS", "HINTS", "DROWN", + "BOSOM", "SLICK", "QUEST", "COILS", "SPIED", "SNOWS", "STEAD", "SNACK", "PLOWS", + "BLOND", "TAMED", "THORN", "WAITS", "GLUED", "BANJO", "TEASE", "ARENA", "BULKY", + "CARVE", "STUNT", "WARMS", "SHADY", "RAZOR", "FOLLY", "LEAFY", "NOTCH", "FOOLS", + "OTTER", "PEARS", "FLUSH", "GENUS", "ACHED", "FIVES", "FLAPS", "SPOUT", "SMOTE", + "FUMES", "ADAPT", "CUFFS", "TASTY", "STOOP", "CLIPS", "DISKS", "SNIFF", "LANES", + "BRISK", "IMPLY", "DEMON", "SUPER", "FURRY", "RAGED", "GROWL", "TEXTS", "HARDY", + "STUNG", "TYPED", "HATES", "WISER", "TIMID", "SERUM", "BEAKS", "ROTOR", "CASTS", + "BATHS", "GLIDE", "PLOTS", "TRAIT", "RESIN", "SLUMS", "LYRIC", "PUFFS", "DECKS", + "BROOD", "MOURN", "ALOFT", "ABUSE", "WHIRL", "EDGED", "OVARY", "QUACK", "HEAPS", + "SLANG", "AWAIT", "CIVIC", "SAINT", "BEVEL", "SONAR", "AUNTS", "PACKS", "FROZE", + "TONIC", "CORPS", "SWARM", "FRANK", "REPAY", "GAUNT", "WIRED", "NIECE", "CELLO", + "NEEDY", "CHUCK", "STONY", "MEDIA", "SURGE", "HURTS", "REPEL", "HUSKY", "DATED", + "HUNTS", "MISTS", "EXERT", "DRIES", "MATES", "SWORN", "BAKER", "SPICE", "OASIS", + "BOILS", "SPURS", "DOVES", "SNEAK", "PACES", "COLON", "SIEGE", "STRUM", "DRIER", + "CACAO", "HUMUS", "BALES", "PIPED", "NASTY", "RINSE", "BOXER", "SHRUB", "AMUSE", + "TACKS", "CITED", "SLUNG", "DELTA", "LADEN", "LARVA", "RENTS", "YELLS", "SPOOL", + "SPILL", "CRUSH", "JEWEL", "SNAPS", "STAIN", "KICKS", "TYING", "SLITS", "RATED", + "EERIE", "SMASH", "PLUMS", "ZEBRA", "EARNS", "BUSHY", "SCARY", "SQUAD", "TUTOR", + "SILKS", "SLABS", "BUMPS", "EVILS", "FANGS", "SNOUT", "PERIL", "PIVOT", "YACHT", + "LOBBY", "JEANS", "GRINS", "VIOLA", "LINER", "COMET", "SCARS", "CHOPS", "RAIDS", + "EATER", "SLATE", "SKIPS", "SOLES", "MISTY", "URINE", "KNOBS", "SLEET", "HOLLY", + "PESTS", "FORKS", "GRILL", "TRAYS", "PAILS", "BORNE", "TENOR", "WARES", "CAROL", + "WOODY", "CANON", "WAKES", "KITTY", "MINER", "POLLS", "SHAKY", "NASAL", "SCORN", + "CHESS", "TAXIS", "CRATE", "SHYLY", "TULIP", "FORGE", "NYMPH", "BUDGE", "LOWLY", + "ABIDE", "DEPOT", "OASES", "ASSES", "SHEDS", "FUDGE", "PILLS", "RIVET", "THINE", + "GROOM", "LANKY", "BOOST", "BROTH", "HEAVE", "GRAVY", "BEECH", "TIMED", "QUAIL", + "INERT", "GEARS", "CHICK", "HINGE", "TRASH", "CLASH", "SIGHS", "RENEW", "BOUGH", + "DWARF", "SLOWS", "QUILL", "SHAVE", "SPORE", "SIXES", "CHUNK", "MADLY", "PACED", + "BRAID", "FUZZY", "MOTTO", "SPIES", "SLACK", "MUCUS", "MAGMA", "AWFUL", "DISCS", + "ERASE", "POSED", "ASSET", "CIDER", "TAPER", "THEFT", "CHURN", "SATIN", "SLOTS", + "TAXED", "BULLY", "SLOTH", "SHALE", "TREAD", "RAKED", "CURDS", "MANOR", "AISLE", + "BULGE", "LOINS", "STAIR", "TAPES", "LEANS", "BUNKS", "SQUAT", "TOWED", "LANCE", + "PANES", "SAKES", "HEIRS", "CASTE", "DUMMY", "PORES", "FAUNA", "CROOK", "POISE", + "EPOCH", "RISKY", "WARNS", "FLING", "BERRY", "GRAPE", "FLANK", "DRAGS", "SQUID", + "PELTS", "ICING", "IRONY", "IRONS", "BARKS", "WHOOP", "CHOKE", "DIETS", "WHIPS", + "TALLY", "DOZED", "TWINE", "KITES", "BIKES", "TICKS", "RIOTS", "ROARS", "VAULT", + "LOOMS", "SCOLD", "BLINK", "DANDY", "PUPAE", "SIEVE", "SPIKE", "DUCTS", "LENDS", + "PIZZA", "BRINK", "WIDEN", "PLUMB", "PAGAN", "FEATS", "BISON", "SOGGY", "SCOOP", + "ARGON", "NUDGE", "SKIFF", "AMBER", "SEXES", "ROUSE", "SALTS", "HITCH", "EXALT", + "LEASH", "DINED", "CHUTE", "SNORT", "GUSTS", "MELON", "CHEAT", "REEFS", "LLAMA", + "LASSO", "DEBUT", "QUOTA", "OATHS", "PRONE", "MIXES", "RAFTS", "DIVES", "STALE", + "INLET", "FLICK", "PINTO", "BROWS", "UNTIE", "BATCH", "GREED", "CHORE", "STIRS", + "BLUSH", "ONSET", "BARBS", "VOLTS", "BEIGE", "SWOOP", "PADDY", "LACED", "SHOVE", + "JERKY", "POPPY", "LEAKS", "FARES", "DODGE", "GODLY", "SQUAW", "AFFIX", "BRUTE", + "NICER", "UNDUE", "SNARL", "MERGE", "DOSES", "SHOWY", "DADDY", "ROOST", "VASES", + "SWIRL", "PETTY", "COLDS", "CURRY", "COBRA", "GENIE", "FLARE", "MESSY", "CORES", + "SOAKS", "RIPEN", "WHINE", "AMINO", "PLAID", "SPINY", "MOWED", "BATON", "PEERS", + "VOWED", "PIOUS", "SWANS", "EXITS", "AFOOT", "PLUGS", "IDIOM", "CHILI", "RITES", + "SERFS", "CLEFT", "BERTH", "GRUBS", "ANNEX", "DIZZY", "HASTY", "LATCH", "WASPS", + "MIRTH", "BARON", "PLEAD", "ALOOF", "AGING", "PIXEL", "BARED", "MUMMY", "HOTLY", + "AUGER", "BUDDY", "CHAPS", "BADGE", "STARK", "FAIRS", "GULLY", "MUMPS", "EMERY", + "FILLY", "OVENS", "DRONE", "GAUZE", "IDIOT", "FUSSY", "ANNOY", "SHANK", "GOUGE", + "BLEED", "ELVES", "ROPED", "UNFIT", "BAGGY", "MOWER", "SCANT", "GRABS", "FLEAS", + "LOUSY", "ALBUM", "SAWED", "COOKY", "MURKY", "INFER", "BURLY", "WAGED", "DINGY", + "BRINE", "KNEEL", "CREAK", "VANES", "SMOKY", "SPURT", "COMBS", "EASEL", "LACES", + "HUMPS", "RUMOR", "AROMA", "HORDE", "SWISS", "LEAPT", "OPIUM", "SLIME", "AFIRE", + "PANSY", "MARES", "SOAPS", "HUSKS", "SNIPS", "HAZEL", "LINED", "CAFES", "NAIVE", + "WRAPS", "SIZED", "PIERS", "BESET", "AGILE", "TONGS", "STEED", "FRAUD", "BOOTY", + "VALOR", "DOWNY", "WITTY", "MOSSY", "PSALM", "SCUBA", "TOURS", "POLKA", "MILKY", + "GAUDY", "SHRUG", "TUFTS", "WILDS", "LASER", "TRUSS", "HARES", "CREED", "LILAC", + "SIREN", "TARRY", "BRIBE", "SWINE", "MUTED", "FLIPS", "CURES", "SINEW", "BOXED", + "HOOPS", "GASPS", "HOODS", "NICHE", "YUCCA", "GLOWS", "SEWER", "WHACK", "FUSES", + "GOWNS", "DROOP", "BUCKS", "PANGS", "MAILS", "WHISK", "HAVEN", "CLASP", "SLING", + "STINT", "URGES", "CHAMP", "PIETY", "CHIRP", "PLEAT", "POSSE", "SUNUP", "MENUS", + "HOWLS", "QUAKE", "KNACK", "PLAZA", "FIEND", "CAKED", "BANGS", "ERUPT", "POKER", + "OLDEN", "CRAMP", "VOTER", "POSES", "MANLY", "SLUMP", "FINED", "GRIPS", "GAPED", + "PURGE", "HIKED", "MAIZE", "FLUFF", "STRUT", "SLOOP", "PROWL", "ROACH", "COCKS", + "BLAND", "DIALS", "PLUME", "SLAPS", "SOUPS", "DULLY", "WILLS", "FOAMS", "SOLOS", + "SKIER", "EAVES", "TOTEM", "FUSED", "LATEX", "VEILS", "MUSED", "MAINS", "MYRRH", + "RACKS", "GALLS", "GNATS", "BOUTS", "SISAL", "SHUTS", "HOSES", "DRYLY", "HOVER", + "GLOSS", "SEEPS", "DENIM", "PUTTY", "GUPPY", "LEAKY", "DUSKY", "FILTH", "OBOES", + "SPANS", "FOWLS", "ADORN", "GLAZE", "HAUNT", "DARES", "OBEYS", "BAKES", "ABYSS", + "SMELT", "GANGS", "ACHES", "TRAWL", "CLAPS", "UNDID", "SPICY", "HOIST", "FADES", + "VICAR", "ACORN", "PUSSY", "GRUFF", "MUSTY", "TARTS", "SNUFF", "HUNCH", "TRUCE", + "TWEED", "DRYER", "LOSER", "SHEAF", "MOLES", "LAPSE", "TAWNY", "VEXED", "AUTOS", + "WAGER", "DOMES", "SHEEN", "CLANG", "SPADE", "SOWED", "BROIL", "SLYLY", "STUDS", + "GRUNT", "DONOR", "SLUGS", "ASPEN", "HOMER", "CROAK", "TITHE", "HALTS", "AVERT", + "HAVOC", "HOGAN", "GLINT", "RUDDY", "JEEPS", "FLAKY", "LADLE", "TAUNT", "SNORE", + "FINES", "PROPS", "PRUNE", "PESOS", "RADII", "POKES", "TILED", "DAISY", "HERON", + "VILLA", "FARCE", "BINDS", "CITES", "FIXES", "JERKS", "LIVID", "WAKED", "INKED", + "BOOMS", "CHEWS", "LICKS", "HYENA", "SCOFF", "LUSTY", "SONIC", "SMITH", "USHER", + "TUCKS", "VIGIL", "MOLTS", "SECTS", "SPARS", "DUMPS", "SCALY", "WISPS", "SORES", + "MINCE", "PANDA", "FLIER", "AXLES", "PLIED", "BOOBY", "PATIO", "RABBI", "PETAL", + "POLYP", "TINTS", "GRATE", "TROLL", "TOLLS", "RELIC", "PHONY", "BLEAT", "FLAWS", + "FLAKE", "SNAGS", "APTLY", "DRAWL", "ULCER", "SOAPY", "BOSSY", "MONKS", "CRAGS", + "CAGED", "TWANG", "DINER", "TAPED", "CADET", "GRIDS", "SPAWN", "GUILE", "NOOSE", + "MORES", "GIRTH", "SLIMY", "AIDES", "SPASM", "BURRS", "ALIBI", "LYMPH", "SAUCY", + "MUGGY", "LITER", "JOKED", "GOOFY", "EXAMS", "ENACT", "STORK", "LURED", "TOXIC", + "OMENS", "NEARS", "COVET", "WRUNG", "FORUM", "VENOM", "MOODY", "ALDER", "SASSY", + "FLAIR", "GUILD", "PRAYS", "WRENS", "HAULS", "STAVE", "TILTS", "PECKS", "STOMP", + "GALES", "TEMPT", "CAPES", "MESAS", "OMITS", "TEPEE", "HARRY", "WRING", "EVOKE", + "LIMES", "CLUCK", "LUNGE", "HIGHS", "CANES", "GIDDY", "LITHE", "VERGE", "KHAKI", + "QUEUE", "LOATH", "FOYER", "OUTDO", "FARED", "DETER", "CRUMB", "ASTIR", "SPIRE", + "JUMPY", "EXTOL", "BUOYS", "STUBS", "LUCID", "THONG", "AFORE", "WHIFF", "MAXIM", + "HULLS", "CLOGS", "SLATS", "JIFFY", "ARBOR", "CINCH", "IGLOO", "GOODY", "GAZES", + "DOWEL", "CALMS", "BITCH", "SCOWL", "GULPS", "CODED", "WAVER", "MASON", "LOBES", + "EBONY", "FLAIL", "ISLES", "CLODS", "DAZED", "ADEPT", "OOZED", "SEDAN", "CLAYS", + "WARTS", "KETCH", "SKUNK", "MANES", "ADORE", "SNEER", "MANGO", "FIORD", "FLORA", + "ROOMY", "MINKS", "THAWS", "WATTS", "FREER", "EXULT", "PLUSH", "PALED", "TWAIN", + "CLINK", "SCAMP", "PAWED", "GROPE", "BRAVO", "GABLE", "STINK", "SEVER", "WANED", + "RARER", "REGAL", "WARDS", "FAWNS", "BABES", "UNIFY", "AMEND", "OAKEN", "GLADE", + "VISOR", "HEFTY", "NINES", "THROB", "PECAN", "BUTTS", "PENCE", "SILLS", "JAILS", + "FLYER", "SABER", "NOMAD", "MITER", "BEEPS", "DOMED", "GULFS", "CURBS", "HEATH", + "MOORS", "AORTA", "LARKS", "TANGY", "WRYLY", "CHEEP", "RAGES", "EVADE", "LURES", + "FREAK", "VOGUE", "TUNIC", "SLAMS", "KNITS", "DUMPY", "MANIA", "SPITS", "FIRTH", + "HIKES", "TROTS", "NOSED", "CLANK", "DOGMA", "BLOAT", "BALSA", "GRAFT", "MIDDY", + "STILE", "KEYED", "FINCH", "SPERM", "CHAFF", "WILES", "AMIGO", "COPRA", "AMISS", + "EYING", "TWIRL", "LURCH", "POPES", "CHINS", "SMOCK", "TINES", "GUISE", "GRITS", + "JUNKS", "SHOAL", "CACHE", "TAPIR", "ATOLL", "DEITY", "TOILS", "SPREE", "MOCKS", + "SCANS", "SHORN", "REVEL", "RAVEN", "HOARY", "REELS", "SCUFF", "MIMIC", "WEEDY", + "CORNY", "TRUER", "ROUGE", "EMBER", "FLOES", "TORSO", "WIPES", "EDICT", "SULKY", + "RECUR", "GROIN", "BASTE", "KINKS", "SURER", "PIGGY", "MOLDY", "FRANC", "LIARS", + "INEPT", "GUSTY", "FACET", "JETTY", "EQUIP", "LEPER", "SLINK", "SOARS", "CATER", + "DOWRY", "SIDED", "YEARN", "DECOY", "TABOO", "OVALS", "HEALS", "PLEAS", "BERET", + "SPILT", "GAYLY", "ROVER", "ENDOW", "PYGMY", "CARAT", "ABBEY", "VENTS", "WAKEN", + "CHIMP", "FUMED", "SODAS", "VINYL", "CLOUT", "WADES", "MITES", "SMIRK", "BORES", + "BUNNY", "SURLY", "FROCK", "FORAY", "PURER", "MILKS", "QUERY", "MIRED", "BLARE", + "FROTH", "GRUEL", "NAVEL", "PALER", "PUFFY", "CASKS", "GRIME", "DERBY", "MAMMA", + "GAVEL", "TEDDY", "VOMIT", "MOANS", "ALLOT", "DEFER", "WIELD", "VIPER", "LOUSE", + "ERRED", "HEWED", "ABHOR", "WREST", "WAXEN", "ADAGE", "ARDOR", "STABS", "PORED", + "RONDO", "LOPED", "FISHY", "BIBLE", "HIRES", "FOALS", "FEUDS", "JAMBS", "THUDS", + "JEERS", "KNEAD", "QUIRK", "RUGBY", "EXPEL", "GREYS", "RIGOR", "ESTER", "LYRES", + "ABACK", "GLUES", "LOTUS", "LURID", "RUNGS", "HUTCH", "THYME", "VALET", "TOMMY", + "YOKES", "EPICS", "TRILL", "PIKES", "OZONE", "CAPER", "CHIME", "FREES", "FAMED", + "LEECH", "SMITE", "NEIGH", "ERODE", "ROBED", "HOARD", "SALVE", "CONIC", "GAWKY", + "CRAZE", "JACKS", "GLOAT", "MUSHY", "RUMPS", "FETUS", "WINCE", "PINKS", "SHALT", + "TOOTS", "GLENS", "COOED", "RUSTS", "STEWS", "SHRED", "PARKA", "CHUGS", "WINKS", + "CLOTS", "SHREW", "BOOED", "FILMY", "JUROR", "DENTS", "GUMMY", "GRAYS", "HOOKY", + "BUTTE", "DOGIE", "POLED", "REAMS", "FIFES", "SPANK", "GAYER", "TEPID", "SPOOK", + "TAINT", "FLIRT", "ROGUE", "SPIKY", "OPALS", "MISER", "COCKY", "COYLY", "BALMY", + "SLOSH", "BRAWL", "APHID", "FAKED", "HYDRA", "BRAGS", "CHIDE", "YANKS", "ALLAY", + "VIDEO", "ALTOS", "EASES", "METED", "CHASM", "LONGS", "EXCEL", "TAFFY", "IMPEL", + "SAVOR", "KOALA", "QUAYS", "DAWNS", "PROXY", "CLOVE", "DUETS", "DREGS", "TARDY", + "BRIAR", "GRIMY", "ULTRA", "MEATY", "HALVE", "WAILS", "SUEDE", "MAUVE", "ENVOY", + "ARSON", "COVES", "GOOEY", "BREWS", "SOFAS", "CHUMS", "AMAZE", "ZOOMS", "ABBOT", + "HALOS", "SCOUR", "SUING", "CRIBS", "SAGAS", "ENEMA", "WORDY", "HARPS", "COUPE", + "MOLAR", "FLOPS", "WEEPS", "MINTS", "ASHEN", "FELTS", "ASKEW", "MUNCH", "MEWED", + "DIVAN", "VICES", "JUMBO", "BLOBS", "BLOTS", "SPUNK", "ACRID", "TOPAZ", "CUBED", + "CLANS", "FLEES", "SLURS", "GNAWS", "WELDS", "FORDS", "EMITS", "AGATE", "PUMAS", + "MENDS", "DARKS", "DUKES", "PLIES", "CANNY", "HOOTS", "OOZES", "LAMED", "FOULS", + "CLEFS", "NICKS", "MATED", "SKIMS", "BRUNT", "TUBER", "TINGE", "FATES", "DITTY", + "THINS", "FRETS", "EIDER", "BAYOU", "MULCH", "FASTS", "AMASS", "DAMPS", "MORNS", + "FRIAR", "PALSY", "VISTA", "CROON", "CONCH", "UDDER", "TACOS", "SKITS", "MIKES", + "QUITS", "PREEN", "ASTER", "ADDER", "ELEGY", "PULPY", "SCOWS", "BALED", "HOVEL", + "LAVAS", "CRAVE", "OPTIC", "WELTS", "BUSTS", "KNAVE", "RAZED", "SHINS", "TOTES", + "SCOOT", "DEARS", "CROCK", "MUTES", "TRIMS", "SKEIN", "DOTED", "SHUNS", "VEERS", + "FAKES", "YOKED", "WOOED", "HACKS", "SPRIG", "WANDS", "LULLS", "SEERS", "SNOBS", + "NOOKS", "PINED", "PERKY", "MOOED", "FRILL", "DINES", "BOOZE", "TRIPE", "PRONG", + "DRIPS", "ODDER", "LEVEE", "ANTIC", "SIDLE", "PITHY", "CORKS", "YELPS", "JOKER", + "FLECK", "BUFFS", "SCRAM", "TIERS", "BOGEY", "DOLED", "IRATE", "VALES", "COPED", + "HAILS", "ELUDE", "BULKS", "AIRED", "VYING", "STAGS", "STREW", "COCCI", "PACTS", + "SCABS", "SILOS", "DUSTS", "YODEL", "TERSE", "JADED", "BASER", "JIBES", "FOILS", + "SWAYS", "FORGO", "SLAYS", "PREYS", "TREKS", "QUELL", "PEEKS", "ASSAY", "LURKS", + "EJECT", "BOARS", "TRITE", "BELCH", "GNASH", "WANES", "LUTES", "WHIMS", "DOSED", + "CHEWY", "SNIPE", "UMBRA", "TEEMS", "DOZES", "KELPS", "UPPED", "BRAWN", "DOPED", + "SHUSH", "RINDS", "SLUSH", "MORON", "VOILE", "WOKEN", "FJORD", "SHEIK", "JESTS", + "KAYAK", "SLEWS", "TOTED", "SANER", "DRAPE", "PATTY", "RAVES", "SULFA", "GRIST", + "SKIED", "VIXEN", "CIVET", "VOUCH", "TIARA", "HOMEY", "MOPED", "RUNTS", "SERGE", + "KINKY", "RILLS", "CORNS", "BRATS", "PRIES", "AMBLE", "FRIES", "LOONS", "TSARS", + "DATUM", "MUSKY", "PIGMY", "GNOME", "RAVEL", "OVULE", "ICILY", "LIKEN", "LEMUR", + "FRAYS", "SILTS", "SIFTS", "PLODS", "RAMPS", "TRESS", "EARLS", "DUDES", "WAIVE", + "KARAT", "JOLTS", "PEONS", "BEERS", "HORNY", "PALES", "WREAK", "LAIRS", "LYNCH", + "STANK", "SWOON", "IDLER", "ABORT", "BLITZ", "ENSUE", "ATONE", "BINGO", "ROVES", + "KILTS", "SCALD", "ADIOS", "CYNIC", "DULLS", "MEMOS", "ELFIN", "DALES", "PEELS", + "PEALS", "BARES", "SINUS", "CRONE", "SABLE", "HINDS", "SHIRK", "ENROL", "WILTS", + "ROAMS", "DUPED", "CYSTS", "MITTS", "SAFES", "SPATS", "COOPS", "FILET", "KNELL", + "REFIT", "COVEY", "PUNKS", "KILNS", "FITLY", "ABATE", "TALCS", "HEEDS", "DUELS", + "WANLY", "RUFFS", "GAUSS", "LAPEL", "JAUNT", "WHELP", "CLEAT", "GAUZY", "DIRGE", + "EDITS", "WORMY", "MOATS", "SMEAR", "PRODS", "BOWEL", "FRISK", "VESTS", "BAYED", + "RASPS", "TAMES", "DELVE", "EMBED", "BEFIT", "WAFER", "CEDED", "NOVAS", "FEIGN", + "SPEWS", "LARCH", "HUFFS", "DOLES", "MAMAS", "HULKS", "PRIED", "BRIMS", "IRKED", + "ASPIC", "SWIPE", "MEALY", "SKIMP", "BLUER", "SLAKE", "DOWDY", "PENIS", "BRAYS", + "PUPAS", "EGRET", "FLUNK", "PHLOX", "GRIPE", "PEONY", "DOUSE", "BLURS", "DARNS", + "SLUNK", "LEFTS", "CHATS", "INANE", "VIALS", "STILT", "RINKS", "WOOFS", "WOWED", + "BONGS", "FROND", "INGOT", "EVICT", "SINGE", "SHYER", "FLIED", "SLOPS", "DOLTS", + "DROOL", "DELLS", "WHELK", "HIPPY", "FETED", "ETHER", "COCOS", "HIVES", "JIBED", + "MAZES", "TRIOS", "SIRUP", "SQUAB", "LATHS", "LEERS", "PASTA", "RIFTS", "LOPES", + "ALIAS", "WHIRS", "DICED", "SLAGS", "LODES", "FOXED", "IDLED", "PROWS", "PLAIT", + "MALTS", "CHAFE", "COWER", "TOYED", "CHEFS", "KEELS", "STIES", "RACER", "ETUDE", + "SUCKS", "SULKS", "MICAS", "CZARS", "COPSE", "AILED", "ABLER", "RABID", "GOLDS", + "CROUP", "SNAKY", "VISAS", "PALLS", "MOPES", "BONED", "WISPY", "RAVED", "SWAPS", + "JUNKY", "DOILY", "PAWNS", "TAMER", "POACH", "BAITS", "DAMNS", "GUMBO", "DAUNT", + "PRANK", "HUNKS", "BUXOM", "HERES", "HONKS", "STOWS", "UNBAR", "IDLES", "ROUTS", + "SAGES", "GOADS", "REMIT", "COPES", "DEIGN", "CULLS", "GIRDS", "HAVES", "LUCKS", + "STUNK", "DODOS", "SHAMS", "SNUBS", "ICONS", "USURP", "DOOMS", "HELLS", "SOLED", + "COMAS", "PAVES", "MATHS", "PERKS", "LIMPS", "WOMBS", "BLURB", "DAUBS", "COKES", + "SOURS", "STUNS", "CASED", "MUSTS", "COEDS", "COWED", "APING", "ZONED", "RUMMY", + "FETES", "SKULK", "QUAFF", "RAJAH", "DEANS", "REAPS", "GALAS", "TILLS", "ROVED", + "KUDOS", "TONED", "PARED", "SCULL", "VEXES", "PUNTS", "SNOOP", "BAILS", "DAMES", + "HAZES", "LORES", "MARTS", "VOIDS", "AMEBA", "RAKES", "ADZES", "HARMS", "REARS", + "SATYR", "SWILL", "HEXES", "COLIC", "LEEKS", "HURLS", "YOWLS", "IVIES", "PLOPS", + "MUSKS", "PAPAW", "JELLS", "BUSED", "CRUET", "BIDED", "FILCH", "ZESTS", "ROOKS", + "LAXLY", "RENDS", "LOAMS", "BASKS", "SIRES", "CARPS", "POKEY", "FLITS", "MUSES", + "BAWLS", "SHUCK", "VILER", "LISPS", "PEEPS", "SORER", "LOLLS", "PRUDE", "DIKED", + "FLOSS", "FLOGS", "SCUMS", "DOPES", "BOGIE", "PINKY", "LEAFS", "TUBAS", "SCADS", + "LOWED", "YESES", "BIKED", "QUALM", "EVENS", "CANED", "GAWKS", "WHITS", "WOOLY", + "GLUTS", "ROMPS", "BESTS", "DUNCE", "CRONY", "JOIST", "TUNAS", "BONER", "MALLS", + "PARCH", "AVERS", "CRAMS", "PARES", "DALLY", "BIGOT", "KALES", "FLAYS", "LEACH", + "GUSHY", "POOCH", "HUGER", "SLYER", "GOLFS", "MIRES", "FLUES", "LOAFS", "ARCED", + "ACNES", "NEONS", "FIEFS", "DINTS", "DAZES", "POUTS", "CORED", "YULES", "LILTS", + "BEEFS", "MUTTS", "FELLS", "COWLS", "SPUDS", "LAMES", "JAWED", "DUPES", "DEADS", + "BYLAW", "NOONS", "NIFTY", "CLUED", "VIREO", "GAPES", "METES", "CUTER", "MAIMS", + "DROLL", "CUPID", "MAULS", "SEDGE", "PAPAS", "WHEYS", "EKING", "LOOTS", "HILTS", + "MEOWS", "BEAUS", "DICES", "PEPPY", "RIPER", "FOGEY", "GISTS", "YOGAS", "GILTS", + "SKEWS", "CEDES", "ZEALS", "ALUMS", "OKAYS", "ELOPE", "GRUMP", "WAFTS", "SOOTS", + "BLIMP", "HEFTS", "MULLS", "HOSED", "CRESS", "DOFFS", "RUDER", "PIXIE", "WAIFS", + "OUSTS", "PUCKS", "BIERS", "GULCH", "SUETS", "HOBOS", "LINTS", "BRANS", "TEALS", + "GARBS", "PEWEE", "HELMS", "TURFS", "QUIPS", "WENDS", "BANES", "NAPES", "ICIER", + "SWATS", "BAGEL", "HEXED", "OGRES", "GONER", "GILDS", "PYRES", "LARDS", "BIDES", + "PAGED", "TALON", "FLOUT", "MEDIC", "VEALS", "PUTTS", "DIRKS", "DOTES", "TIPPY", + "BLURT", "PITHS", "ACING", "BARER", "WHETS", "GAITS", "WOOLS", "DUNKS", "HEROS", + "SWABS", "DIRTS", "JUTES", "HEMPS", "SURFS", "OKAPI", "CHOWS", "SHOOS", "DUSKS", + "PARRY", "DECAL", "FURLS", "CILIA", "SEARS", "NOVAE", "MURKS", "WARPS", "SLUES", + "LAMER", "SARIS", "WEANS", "PURRS", "DILLS", "TOGAS", "NEWTS", "MEANY", "BUNTS", + "RAZES", "GOONS", "WICKS", "RUSES", "VENDS", "GEODE", "DRAKE", "JUDOS", "LOFTS", + "PULPS", "LAUDS", "MUCKS", "VISES", "MOCHA", "OILED", "ROMAN", "ETHYL", "GOTTA", + "FUGUE", "SMACK", "GOURD", "BUMPY", "RADIX", "FATTY", "BORAX", "CUBIT", "CACTI", + "GAMMA", "FOCAL", "AVAIL", "PAPAL", "GOLLY", "ELITE", "VERSA", "BILLY", "ADIEU", + "ANNUM", "HOWDY", "RHINO", "NORMS", "BOBBY", "AXIOM", "SETUP", "YOLKS", "TERNS", + "MIXER", "GENRE", "KNOLL", "ABODE", "JUNTA", "GORGE", "COMBO", "ALPHA", "OVERT", + "KINDA", "SPELT", "PRICK", "NOBLY", "EPHOD", "AUDIO", "MODAL", "VELDT", "WARTY", + "FLUKE", "BONNY", "BREAM", "ROSIN", "BOLLS", "DOERS", "DOWNS", "BEADY", "MOTIF", + "HUMPH", "FELLA", "MOULD", "CREPE", "KERNS", "ALOHA", "GLYPH", "AZURE", "RISER", + "BLEST", "LOCUS", "LUMPY", "BERYL", "WANNA", "BRIER", "TUNER", "ROWDY", "MURAL", + "TIMER", "CANST", "KRILL", "QUOTH", "LEMME", "TRIAD", "TENON", "AMPLY", "DEEPS", + "PADRE", "LEANT", "PACER", "OCTAL", "DOLLY", "TRANS", "SUMAC", "FOAMY", "LOLLY", + "GIVER", "QUIPU", "CODEX", "MANNA", "UNWED", "VODKA", "FERNY", "SALON", "DUPLE", + "BORON", "REVUE", "CRIER", "ALACK", "INTER", "DILLY", "WHIST", "CULTS", "SPAKE", + "RESET", "LOESS", "DECOR", "MOVER", "VERVE", "ETHIC", "GAMUT", "LINGO", "DUNNO", + "ALIGN", "SISSY", "INCUR", "REEDY", "AVANT", "PIPER", "WAXER", "CALYX", "BASIL", + "COONS", "SEINE", "PINEY", "LEMMA", "TRAMS", "WINCH", "WHIRR", "SAITH", "IONIC", + "HEADY", "HAREM", "TUMMY", "SALLY", "SHIED", "DROSS", "FARAD", "SAVER", "TILDE", + "JINGO", "BOWER", "SERIF", "FACTO", "BELLE", "INSET", "BOGUS", "CAVED", "FORTE", + "SOOTY", "BONGO", "TOVES", "CREDO", "BASAL", "YELLA", "AGLOW", "GLEAN", "GUSTO", + "HYMEN", "ETHOS", "TERRA", "BRASH", "SCRIP", "SWASH", "ALEPH", "TINNY", "ITCHY", + "WANTA", "TRICE", "JOWLS", "GONGS", "GARDE", "BORIC", "TWILL", "SOWER", "HENRY", + "AWASH", "LIBEL", "SPURN", "SABRE", "REBUT", "PENAL", "OBESE", "SONNY", "QUIRT", + "MEBBE", "TACIT", "GREEK", "XENON", "HULLO", "PIQUE", "ROGER", "NEGRO", "HADST", + "GECKO", "BEGET", "UNCUT", "ALOES", "LOUIS", "QUINT", "CLUNK", "RAPED", "SALVO", + "DIODE", "MATEY", "HERTZ", "XYLEM", "KIOSK", "APACE", "CAWED", "PETER", "WENCH", + "COHOS", "SORTA", "GAMBA", "BYTES", "TANGO", "NUTTY", "AXIAL", "ALECK", "NATAL", + "CLOMP", "GORED", "SIREE", "BANDY", "GUNNY", "RUNIC", "WHIZZ", "RUPEE", "FATED", + "WIPER", "BARDS", "BRINY", "STAID", "HOCKS", "OCHRE", "YUMMY", "GENTS", "SOUPY", + "ROPER", "SWATH", "CAMEO", "EDGER", "SPATE", "GIMME", "EBBED", "BREVE", "THETA", + "DEEMS", "DYKES", "SERVO", "TELLY", "TABBY", "TARES", "BLOCS", "WELCH", "GHOUL", + "VITAE", "CUMIN", "DINKY", "BRONC", "TABOR", "TEENY", "COMER", "BORER", "SIRED", + "PRIVY", "MAMMY", "DEARY", "GYROS", "SPRIT", "CONGA", "QUIRE", "THUGS", "FUROR", + "BLOKE", "RUNES", "BAWDY", "CADRE", "TOXIN", "ANNUL", "EGGED", "ANION", "NODES", + "PICKY", "STEIN", "JELLO", "AUDIT", "ECHOS", "FAGOT", "LETUP", "EYRIE", "FOUNT", + "CAPED", "AXONS", "AMUCK", "BANAL", "RILED", "PETIT", "UMBER", "MILER", "FIBRE", + "AGAVE", "BATED", "BILGE", "VITRO", "FEINT", "PUDGY", "MATER", "MANIC", "UMPED", + "PESKY", "STREP", "SLURP", "PYLON", "PUREE", "CARET", "TEMPS", "NEWEL", "YAWNS", + "SEEDY", "TREED", "COUPS", "RANGY", "BRADS", "MANGY", "LONER", "CIRCA", "TIBIA", + "AFOUL", "MOMMY", "TITER", "CARNE", "KOOKY", "MOTES", "AMITY", "SUAVE", "HIPPO", + "CURVY", "SAMBA", "NEWSY", "ANISE", "IMAMS", "TULLE", "AWAYS", "LIVEN", "HALLO", + "WALES", "OPTED", "CANTO", "IDYLL", "BODES", "CURIO", "WRACK", "HIKER", "CHIVE", + "YOKEL", "DOTTY", "DEMUR", "CUSPS", "SPECS", "QUADS", "LAITY", "TONER", "DECRY", + "WRITS", "SAUTE", "CLACK", "AUGHT", "LOGOS", "TIPSY", "NATTY", "DUCAL", "BIDET", + "BULGY", "METRE", "LUSTS", "UNARY", "GOETH", "BALER", "SITED", "SHIES", "HASPS", + "BRUNG", "HOLED", "SWANK", "LOOKY", "MELEE", "HUFFY", "LOAMY", "PIMPS", "TITAN", + "BINGE", "SHUNT", "FEMUR", "LIBRA", "SEDER", "HONED", "ANNAS", "COYPU", "SHIMS", + "ZOWIE", "JIHAD", "SAVVY", "NADIR", "BASSO", "MONIC", "MANED", "MOUSY", "OMEGA", + "LAVER", "PRIMA", "PICAS", "FOLIO", "MECCA", "REALS", "TROTH", "TESTY", "BALKY", + "CRIMP", "CHINK", "ABETS", "SPLAT", "ABACI", "VAUNT", "CUTIE", "PASTY", "MORAY", + "LEVIS", "RATTY", "ISLET", "JOUST", "MOTET", "VIRAL", "NUKES", "GRADS", "COMFY", + "VOILA", "WOOZY", "BLUED", "WHOMP", "SWARD", "METRO", "SKEET", "CHINE", "AERIE", + "BOWIE", "TUBBY", "EMIRS", "COATI", "UNZIP", "SLOBS", "TRIKE", "FUNKY", "DUCAT", + "DEWEY", "SKOAL", "WADIS", "OOMPH", "TAKER", "MINIM", "GETUP", "STOIC", "SYNOD", + "RUNTY", "FLYBY", "BRAZE", "INLAY", "VENUE", "LOUTS", "PEATY", "ORLON", "HUMPY", + "RADON", "BEAUT", "RASPY", "UNFED", "CRICK", "NAPPY", "VIZOR", "YIPES", "REBUS", + "DIVOT", "KIWIS", "VETCH", "SQUIB", "SITAR", "KIDDO", "DYERS", "COTTA", "MATZO", + "LAGER", "ZEBUS", "CRASS", "DACHA", "KNEED", "DICTA", "FAKIR", "KNURL", "RUNNY", + "UNPIN", "JULEP", "GLOBS", "NUDES", "SUSHI", "TACKY", "STOKE", "KAPUT", "BUTCH", + "HULAS", "CROFT", "ACHOO", "GENII", "NODAL", "OUTGO", "SPIEL", "VIOLS", "FETID", + "CAGEY", "FUDGY", "EPOXY", "LEGGY", "HANKY", "LAPIS", "FELON", "BEEFY", "COOTS", + "MELBA", "CADDY", "SEGUE", "BETEL", "FRIZZ", "DREAR", "KOOKS", "TURBO", "HOAGY", + "MOULT", "HELIX", "ZONAL", "ARIAS", "NOSEY", "PAEAN", "LACEY", "BANNS", "SWAIN", + "FRYER", "RETCH", "TENET", "GIGAS", "WHINY", "OGLED", "RUMEN", "BEGOT", "CRUSE", + "ABUTS", "RIVEN", "BALKS", "SINES", "SIGMA", "ABASE", "ENNUI", "GORES", "UNSET", + "AUGUR", "SATED", "ODIUM", "LATIN", "DINGS", "MOIRE", "SCION", "HENNA", "KRAUT", + "DICKS", "LIFER", "PRIGS", "BEBOP", "GAGES", "GAZER", "FANNY", "GIBES", "AURAL", + "TEMPI", "HOOCH", "RAPES", "SNUCK", "HARTS", "TECHS", "EMEND", "NINNY", "GUAVA", + "SCARP", "LIEGE", "TUFTY", "SEPIA", "TOMES", "CAROB", "EMCEE", "PRAMS", "POSER", + "VERSO", "HUBBA", "JOULE", "BAIZE", "BLIPS", "SCRIM", "CUBBY", "CLAVE", "WINOS", + "REARM", "LIENS", "LUMEN", "CHUMP", "NANNY", "TRUMP", "FICHU", "CHOMP", "HOMOS", + "PURTY", "MASER", "WOOSH", "PATSY", "SHILL", "RUSKS", "AVAST", "SWAMI", "BODED", + "AHHHH", "LOBED", "NATCH", "SHISH", "TANSY", "SNOOT", "PAYER", "ALTHO", "SAPPY", + "LAXER", "HUBBY", "AEGIS", "RILES", "DITTO", "JAZZY", "DINGO", "QUASI", "SEPTA", + "PEAKY", "LORRY", "HEERD", "BITTY", "PAYEE", "SEAMY", "APSES", "IMBUE", "BELIE", + "CHARY", "SPOOF", "PHYLA", "CLIME", "BABEL", "WACKY", "SUMPS", "SKIDS", "KHANS", + "CRYPT", "INURE", "NONCE", "OUTEN", "FAIRE", "HOOEY", "ANOLE", "KAZOO", "CALVE", + "LIMBO", "ARGOT", "DUCKY", "FAKER", "VIBES", "GASSY", "UNLIT", "NERVY", "FEMME", + "BITER", "FICHE", "BOORS", "GAFFE", "SAXES", "RECAP", "SYNCH", "FACIE", "DICEY", + "OUIJA", "HEWER", "LEGIT", "GURUS", "EDIFY", "TWEAK", "CARON", "TYPOS", "RERUN", + "POLLY", "SURDS", "HAMZA", "NULLS", "HATER", "LEFTY", "MOGUL", "MAFIA", "DEBUG", + "PATES", "BLABS", "SPLAY", "TALUS", "PORNO", "MOOLA", "NIXED", "KILOS", "SNIDE", + "HORSY", "GESSO", "JAGGY", "TROVE", "NIXES", "CREEL", "PATER", "IOTAS", "CADGE", + "SKYED", "HOKUM", "FURZE", "ANKHS", "CURIE", "NUTSY", "HILUM", "REMIX", "ANGST", + "BURLS", "JIMMY", "VEINY", "TRYST", "CODON", "BEFOG", "GAMED", "FLUME", "AXMAN", + "DOOZY", "LUBES", "RHEAS", "BOZOS", "BUTYL", "KELLY", "MYNAH", "JOCKS", "DONUT", + "AVIAN", "WURST", "CHOCK", "QUASH", "QUALS", "HAYED", "BOMBE", "CUSHY", "SPACY", + "PUKED", "LEERY", "THEWS", "PRINK", "AMENS", "TESLA", "INTRO", "FIVER", "FRUMP", + "CAPOS", "OPINE", "CODER", "NAMER", "JOWLY", "PUKES", "HALED", "CHARD", "DUFFS", + "BRUIN", "REUSE", "WHANG", "TOONS", "FRATS", "SILTY", "TELEX", "CUTUP", "NISEI", + "NEATO", "DECAF", "SOFTY", "BIMBO", "ADLIB", "LOONY", "SHOED", "AGUES", "PEEVE", + "NOWAY", "GAMEY", "SARGE", "RERAN", "EPACT", "POTTY", "CONED", "UPEND", "NARCO", + "IKATS", "WHORL", "JINKS", "TIZZY", "WEEPY", "POSIT", "MARGE", "VEGAN", "CLOPS", + "NUMBS", "REEKS", "RUBES", "ROWER", "BIPED", "TIFFS", "HOCUS", "HAMMY", "BUNCO", + "FIXIT", "TYKES", "CHAWS", "YUCKY", "HOKEY", "RESEW", "MAVEN", "ADMAN", "SCUZZ", + "SLOGS", "SOUSE", "NACHO", "MIMED", "MELDS", "BOFFO", "DEBIT", "PINUP", "VAGUS", + "GULAG", "RANDY", "BOSUN", "EDUCE", "FAXES", "AURAS", "PESTO", "ANTSY", "BETAS", + "FIZZY", "DORKY", "SNITS", "MOXIE", "THANE", "MYLAR", "NOBBY", "GAMIN", "GOUTY", + "ESSES", "GOYIM", "PANED", "DRUID", "JADES", "REHAB", "GOFER", "TZARS", "OCTET", + "HOMED", "SOCKO", "DORKS", "EARED", "ANTED", "ELIDE", "FAZES", "OXBOW", "DOWSE", + "SITUS", "MACAW", "SCONE", "DRILY", "HYPER", "SALSA", "MOOCH", "GATED", "UNJAM", + "LIPID", "MITRE", "VENAL", "KNISH", "RITZY", "DIVAS", "TORUS", "MANGE", "DIMER", + "RECUT", "MESON", "WINED", "FENDS", "PHAGE", "FIATS", "CAULK", "CAVIL", "PANTY", + "ROANS", "BILKS", "HONES", "BOTCH", "ESTOP", "SULLY", "SOOTH", "GELDS", "AHOLD", + "RAPER", "PAGER", "FIXER", "INFIX", "HICKS", "TUXES", "PLEBE", "TWITS", "ABASH", + "TWIXT", "WACKO", "PRIMP", "NABLA", "GIRTS", "MIFFS", "EMOTE", "XEROX", "REBID", + "SHAHS", "RUTTY", "GROUT", "GRIFT", "DEIFY", "BIDDY", "KOPEK", "SEMIS", "BRIES", + "ACMES", "PITON", "HUSSY", "TORTS", "DISCO", "WHORE", "BOOZY", "GIBED", "VAMPS", + "AMOUR", "SOPPY", "GONZO", "DURST", "WADER", "TUTUS", "PERMS", "CATTY", "GLITZ", + "BRIGS", "NERDS", "BARMY", "GIZMO", "OWLET", "SAYER", "MOLLS", "SHARD", "WHOPS", + "COMPS", "CORER", "COLAS", "MATTE", "DROID", "PLOYS", "VAPID", "CAIRN", "DEISM", + "MIXUP", "YIKES", "PROSY", "RAKER", "FLUBS", "WHISH", "REIFY", "CRAPS", "SHAGS", + "CLONE", "HAZED", "MACHO", "RECTO", "REFIX", "DRAMS", "BIKER", "AQUAS", "PORKY", + "DOYEN", "EXUDE", "GOOFS", "DIVVY", "NOELS", "JIVED", "HULKY", "CAGER", "HARPY", + "OLDIE", "VIVAS", "ADMIX", "CODAS", "ZILCH", "DEIST", "ORCAS", "RETRO", "PILAF", + "PARSE", "RANTS", "ZINGY", "TODDY", "CHIFF", "MICRO", "VEEPS", "GIRLY", "NEXUS", + "DEMOS", "BIBBS", "ANTES", "LULUS", "GNARL", "ZIPPY", "IVIED", "EPEES", "WIMPS", + "TROMP", "GRAIL", "YOYOS", "POUFS", "HALES", "ROUST", "CABAL", "RAWER", "PAMPA", + "MOSEY", "KEFIR", "BURGS", "UNMET", "CUSPY", "BOOBS", "BOONS", "HYPES", "DYNES", + "NARDS", "LANAI", "YOGIS", "SEPAL", "QUARK", "TOKED", "PRATE", "AYINS", "HAWED", + "SWIGS", "VITAS", "TOKER", "DOPER", "BOSSA", "LINTY", "FOIST", "MONDO", "STASH", + "KAYOS", "TWERP", "ZESTY", "CAPON", "WIMPY", "REWED", "FUNGO", "TAROT", "FROSH", + "KABOB", "PINKO", "REDID", "MIMEO", "HEIST", "TARPS", "LAMAS", "SUTRA", "DINAR", + "WHAMS", "BUSTY", "SPAYS", "MAMBO", "NABOB", "PREPS", "ODOUR", "CABBY", "CONKS", + "SLUFF", "DADOS", "HOURI", "SWART", "BALMS", "GUTSY", "FAXED", "EGADS", "PUSHY", + "RETRY", "AGORA", "DRUBS", "DAFFY", "CHITS", "MUFTI", "KARMA", "LOTTO", "TOFFS", + "BURPS", "DEUCE", "ZINGS", "KAPPA", "CLADS", "DOGGY", "DUPER", "SCAMS", "OGLER", + "MIMES", "THROE", "ZETAS", "WALED", "PROMO", "BLATS", "MUFFS", "OINKS", "VIAND", + "COSET", "FINKS", "FADDY", "MINIS", "SNAFU", "SAUNA", "USURY", "MUXES", "CRAWS", + "STATS", "CONDO", "COXES", "LOOPY", "DORMS", "ASCOT", "DIPPY", "EXECS", "DOPEY", + "ENVOI", "UMPTY", "GISMO", "FAZED", "STROP", "JIVES", "SLIMS", "BATIK", "PINGS", + "SONLY", "LEGGO", "PEKOE", "PRAWN", "LUAUS", "CAMPY", "OODLE", "PREXY", "PROMS", + "TOUTS", "OGLES", "TWEET", "TOADY", "NAIAD", "HIDER", "NUKED", "FATSO", "SLUTS", + "OBITS", "NARCS", "TYROS", "DELIS", "WOOER", "HYPED", "POSET", "BYWAY", "TEXAS", + "SCROD", "AVOWS", "FUTON", "TORTE", "TUPLE", "CAROM", "KEBAB", "TAMPS", "JILTS", + "DUALS", "ARTSY", "REPRO", "MODEM", "TOPED", "PSYCH", "SICKO", "KLUTZ", "TARNS", + "COXED", "DRAYS", "CLOYS", "ANDED", "PIKER", "AIMER", "SURAS", "LIMOS", "FLACK", + "HAPAX", "DUTCH", "MUCKY", "SHIRE", "KLIEG", "STAPH", "LAYUP", "TOKES", "AXING", + "TOPER", "DUVET", "COWRY", "PROFS", "BLAHS", "ADDLE", "SUDSY", "BATTY", "COIFS", + "SUETY", "GABBY", "HAFTA", "PITAS", "GOUDA", "DEICE", "TAUPE", "TOPES", "DUCHY", + "NITRO", "CARNY", "LIMEY", "ORALS", "HIRER", "TAXER", "ROILS", "RUBLE", "ELATE", + "DOLOR", "WRYER", "SNOTS", "QUAIS", "COKED", "GIMEL", "GORSE", "MINAS", "GOEST", + "AGAPE", "MANTA", "JINGS", "ILIAC", "ADMEN", "OFFEN", "CILLS", "OFFAL", "LOTTA", + "BOLAS", "THWAP", "ALWAY", "BOGGY", "DONNA", "LOCOS", "BELAY", "GLUEY", "BITSY", + "MIMSY", "HILAR", "OUTTA", "VROOM", "FETAL", "RATHS", "RENAL", "DYADS", "CROCS", + "VIRES", "CULPA", "KIVAS", "FEIST", "TEATS", "THATS", "YAWLS", "WHENS", "ABACA", + "OHHHH", "APHIS", "FUSTY", "ECLAT", "PERDU", "MAYST", "EXEAT", "MOLLY", "SUPRA", + "WETLY", "PLASM", "BUFFA", "SEMEN", "PUKKA", "TAGUA", "PARAS", "STOAT", "SECCO", + "CARTE", "HAUTE", "MOLAL", "SHADS", "FORMA", "OVOID", "PIONS", "MODUS", "BUENO", + "RHEUM", "SCURF", "PARER", "EPHAH", "DOEST", "SPRUE", "FLAMS", "MOLTO", "DIETH", + "CHOOS", "MIKED", "BRONX", "GOOPY", "BALLY", "PLUMY", "MOONY", "MORTS", "YOURN", + "BIPOD", "SPUME", "ALGAL", "AMBIT", "MUCHO", "SPUED", "DOZER", "HARUM", "GROAT", + "SKINT", "LAUDE", "THRUM", "PAPPY", "ONCET", "RIMED", "GIGUE", "LIMED", "PLEIN", + "REDLY", "HUMPF", "LITES", "SEEST", "GREBE", "ABSIT", "THANX", "PSHAW", "YAWPS", + "PLATS", "PAYED", "AREAL", "TILTH", "YOUSE", "GWINE", "THEES", "WATSA", "LENTO", + "SPITZ", "YAWED", "GIPSY", "SPRAT", "CORNU", "AMAHS", "BLOWY", "WAHOO", "LUBRA", + "MECUM", "WHOOO", "COQUI", "SABRA", "EDEMA", "MRADS", "DICOT", "ASTRO", "KITED", + "OUZEL", "DIDOS", "GRATA", "BONNE", "AXMEN", "KLUNK", "SUMMA", "LAVES", "PURLS", + "YAWNY", "TEARY", "MASSE", "LARGO", "BAZAR", "PSSST", "SYLPH", "LULAB", "TOQUE", + "FUGIT", "PLUNK", "ORTHO", "LUCRE", "COOCH", "WHIPT", "FOLKY", "TYRES", "WHEEE", + "CORKY", "INJUN", "SOLON", "DIDOT", "KERFS", "RAYED", "WASSA", "CHILE", "BEGAT", + "NIPPY", "LITRE", "MAGNA", "REBOX", "HYDRO", "MILCH", "BRENT", "GYVES", "LAZED", + "FEUED", "MAVIS", "INAPT", "BAULK", "CASUS", "SCRUM", "WISED", "FOSSA", "DOWER", + "KYRIE", "BHOYS", "SCUSE", "FEUAR", "OHMIC", "JUSTE", "UKASE", "BEAUX", "TUSKY", + "ORATE", "MUSTA", "LARDY", "INTRA", "QUIFF", "EPSOM", "NEATH", "OCHER", "TARED", + "HOMME", "MEZZO", "CORMS", "PSOAS", "BEAKY", "TERRY", "INFRA", "SPIVS", "TUANS", + "BELLI", "BERGS", "ANIMA", "WEIRS", "MAHUA", "SCOPS", "MANSE", "TITRE", "CURIA", + "KEBOB", "CYCAD", "TALKY", "FUCKS", "TAPIS", "AMIDE", "DOLCE", "SLOES", "JAKES", + "RUSSE", "BLASH", "TUTTI", "PRUTA", "PANGA", "BLEBS", "TENCH", "SWARF", "HEREM", + "MISSY", "MERSE", "PAWKY", "LIMEN", "VIVRE", "CHERT", "UNSEE", "TIROS", "BRACK", + "FOOTS", "WELSH", "FOSSE", "KNOPS", "ILEUM", "NOIRE", "FIRMA", "PODGY", "LAIRD", + "THUNK", "SHUTE", "ROWAN", "SHOJI", "POESY", "UNCAP", "FAMES", "GLEES", "COSTA", + "TURPS", "FORES", "SOLUM", "IMAGO", "BYRES", "FONDU", "CONEY", "POLIS", "DICTU", + "KRAAL", "SHERD", "MUMBO", "WROTH", "CHARS", "UNBOX", "VACUO", "SLUED", "WEEST", + "HADES", "WILED", "SYNCS", "MUSER", "EXCON", "HOARS", "SIBYL", "PASSE", "JOEYS", + "LOTSA", "LEPTA", "SHAYS", "BOCKS", "ENDUE", "DARER", "NONES", "ILEUS", "PLASH", + "BUSBY", "WHEAL", "BUFFO", "YOBBO", "BILES", "POXES", "ROOTY", "LICIT", "TERCE", + "BROMO", "HAYEY", "DWEEB", "IMBED", "SARAN", "BRUIT", "PUNKY", "SOFTS", "BIFFS", + "LOPPY", "AGARS", "AQUAE", "LIVRE", "BIOME", "BUNDS", "SHEWS", "DIEMS", "GINNY", + "DEGUM", "POLOS", "DESEX", "UNMAN", "DUNGY", "VITAM", "WEDGY", "GLEBE", "APERS", + "RIDGY", "ROIDS", "WIFEY", "VAPES", "WHOAS", "BUNKO", "YOLKY", "ULNAS", "REEKY", + "BODGE", "BRANT", "DAVIT", "DEQUE", "LIKER", "JENNY", "TACTS", "FULLS", "TREAP", + "LIGNE", "ACKED", "REFRY", "VOWER", "AARGH", "CHURL", "MOMMA", "GAOLS", "WHUMP", + "ARRAS", "MARLS", "TILER", "GROGS", "MEMES", "MIDIS", "TIDED", "HALER", "DUCES", + "TWINY", "POSTE", "UNRIG", "PRISE", "DRABS", "QUIDS", "FACER", "SPIER", "BARIC", + "GEOID", "REMAP", "TRIER", "GUNKS", "STENO", "STOMA", "AIRER", "OVATE", "TORAH", + "APIAN", "SMUTS", "POCKS", "YURTS", "EXURB", "DEFOG", "NUDER", "BOSKY", "NIMBI", + "MOTHY", "JOYED", "LABIA", "PARDS", "JAMMY", "BIGLY", "FAXER", "HOPPY", "NURBS", + "COTES", "DISHY", "VISED", "CELEB", "PISMO", "CASAS", "WITHS", "DODGY", "SCUDI", + "MUNGS", "MUONS", "UREAS", "IOCTL", "UNHIP", "KRONE", "SAGER", "VERST", "EXPAT", + "GRONK", "UVULA", "SHAWM", "BILGY", "BRAES", "CENTO", "WEBBY", "LIPPY", "GAMIC", + "LORDY", "MAZED", "TINGS", "SHOAT", "FAERY", "WIRER", "DIAZO", "CARER", "RATER", + "GREPS", "RENTE", "ZLOTY", "VIERS", "UNAPT", "POOPS", "FECAL", "KEPIS", "TAXON", + "EYERS", "WONTS", "SPINA", "STOAE", "YENTA", "POOEY", "BURET", "JAPAN", "BEDEW", + "HAFTS", "SELFS", "OARED", "HERBY", "PRYER", "OAKUM", "DINKS", "TITTY", "SEPOY", + "PENES", "FUSEE", "WINEY", "GIMPS", "NIHIL", "RILLE", "GIBER", "OUSEL", "UMIAK", + "CUPPY", "HAMES", "SHITS", "AZINE", "GLADS", "TACET", "BUMPH", "COYER", "HONKY", + "GAMER", "GOOKY", "WASPY", "SEDGY", "BENTS", "VARIA", "DJINN", "JUNCO", "PUBIC", + "WILCO", "LAZES", "IDYLS", "LUPUS", "RIVES", "SNOOD", "SCHMO", "SPAZZ", "FINIS", + "NOTER", "PAVAN", "ORBED", "BATES", "PIPET", "BADDY", "GOERS", "SHAKO", "STETS", + "SEBUM", "SEETH", "LOBAR", "RAVER", "AJUGA", "RICED", "VELDS", "DRIBS", "VILLE", + "DHOWS", "UNSEW", "HALMA", "KRONA", "LIMBY", "JIFFS", "TREYS", "BAUDS", "PFFFT", + "MIMER", "PLEBS", "CANER", "JIBER", "CUPPA", "WASHY", "CHUFF", "UNARM", "YUKKY", + "STYES", "WAKER", "FLAKS", "MACES", "RIMES", "GIMPY", "GUANO", "LIRAS", "KAPOK", + "SCUDS", "BWANA", "ORING", "AIDER", "PRIER", "KLUGY", "MONTE", "GOLEM", "VELAR", + "FIRER", "PIETA", "UMBEL", "CAMPO", "UNPEG", "FOVEA", "ABEAM", "BOSON", "ASKER", + "GOTHS", "VOCAB", "VINED", "TROWS", "TIKIS", "LOPER", "INDIE", "BOFFS", "SPANG", + "GRAPY", "TATER", "ICHOR", "KILTY", "LOCHS", "SUPES", "DEGAS", "FLICS", "TORSI", + "BETHS", "WEBER", "RESAW", "LAWNY", "COVEN", "MUJIK", "RELET", "THERM", "HEIGH", + "SHNOR", "TRUED", "ZAYIN", "LIEST", "BARFS", "BASSI", "QOPHS", "ROILY", "FLABS", + "PUNNY", "OKRAS", "HANKS", "DIPSO", "NERFS", "FAUNS", "CALLA", "PSEUD", "LURER", + "MAGUS", "OBEAH", "ATRIA", "TWINK", "PALMY", "POCKY", "PENDS", "RECTA", "PLONK", + "SLAWS", "KEENS", "NICAD", "PONES", "INKER", "WHEWS", "GROKS", "MOSTS", "TREWS", + "ULNAR", "GYPPY", "COCAS", "EXPOS", "ERUCT", "OILER", "VACUA", "DRECK", "DATER", + "ARUMS", "TUBAL", "VOXEL", "DIXIT", "BEERY", "ASSAI", "LADES", "ACTIN", "GHOTI", + "BUZZY", "MEADS", "GRODY", "RIBBY", "CLEWS", "CREME", "EMAIL", "PYXIE", "KULAK", + "BOCCI", "RIVED", "DUDDY", "HOPER", "LAPIN", "WONKS", "PETRI", "PHIAL", "FUGAL", + "HOLON", "BOOMY", "DUOMO", "MUSOS", "SHIER", "HAYER", "PORGY", "HIVED", "LITHO", + "FISTY", "STAGY", "LUVYA", "MARIA", "SMOGS", "ASANA", "YOGIC", "SLOMO", "FAWNY", + "AMINE", "WEFTS", "GONAD", "TWIRP", "BRAVA", "PLYER", "FERMI", "LOGES", "NITER", + "REVET", "UNATE", "GYVED", "TOTTY", "ZAPPY", "HONER", "GIROS", "DICER", "CALKS", + "LUXES", "MONAD", "CRUFT", "QUOIN", "FUMER", "AMPED", "SHLEP", "VINCA", "YAHOO", + "VULVA", "ZOOEY", "DRYAD", "NIXIE", "MOPER", "IAMBS", "LUNES", "NUDIE", "LIMNS", + "WEALS", "NOHOW", "MIAOW", "GOUTS", "MYNAS", "MAZER", "KIKES", "OXEYE", "STOUP", + "JUJUS", "DEBAR", "PUBES", "TAELS", "DEFUN", "RANDS", "BLEAR", "PAVER", "GOOSY", + "SPROG", "OLEOS", "TOFFY", "PAWER", "MACED", "CRITS", "KLUGE", "TUBED", "SAHIB", + "GANEF", "SCATS", "SPUTA", "VANED", "ACNED", "TAXOL", "PLINK", "OWETH", "TRIBS", + "RESAY", "BOULE", "THOUS", "HAPLY", "GLANS", "MAXIS", "BEZEL", "ANTIS", "PORKS", + "QUOIT", "ALKYD", "GLARY", "BEAMY", "HEXAD", "BONKS", "TECUM", "KERBS", "FILAR", + "FRIER", "REDUX", "ABUZZ", "FADER", "SHOER", "COUTH", "TRUES", "GUYED", "GOONY", + "BOOKY", "FUZES", "HURLY", "GENET", "HODAD", "CALIX", "FILER", "PAWLS", "IODIC", + "UTERO", "HENGE", "UNSAY", "LIERS", "PIING", "WEALD", "SEXED", "FOLIC", "POXED", + "CUNTS", "ANILE", "KITHS", "BECKS", "TATTY", "PLENA", "REBAR", "ABLED", "TOYER", + "ATTAR", "TEAKS", "AIOLI", "AWING", "ANENT", "FECES", "REDIP", "WISTS", "PRATS", + "MESNE", "MUTER", "SMURF", "OWEST", "BAHTS", "LOSSY", "FTPED", "HUNKY", "HOERS", + "SLIER", "SICKS", "FATLY", "DELFT", "HIVER", "HIMBO", "PENGO", "BUSKS", "LOXES", + "ZONKS", "ILIUM", "APORT", "IKONS", "MULCT", "REEVE", "CIVVY", "CANNA", "BARFY", + "KAIAK", "SCUDO", "KNOUT", "GAPER", "BHANG", "PEASE", "UTERI", "LASES", "PATEN", + "RASAE", "AXELS", "STOAS", "OMBRE", "STYLI", "GUNKY", "HAZER", "KENAF", "AHOYS", + "AMMOS", "WEENY", "URGER", "KUDZU", "PAREN", "BOLOS", "FETOR", "NITTY", "TECHY", + "LIETH", "SOMAS", "DARKY", "VILLI", "GLUON", "JANES", "CANTS", "FARTS", "SOCLE", + "JINNS", "RUING", "SLILY", "RICER", "HADDA", "WOWEE", "RICES", "NERTS", "CAULS", + "SWIVE", "LILTY", "MICKS", "ARITY", "PASHA", "FINIF", "OINKY", "GUTTY", "TETRA", + "WISES", "WOLDS", "BALDS", "PICOT", "WHATS", "SHIKI", "BUNGS", "SNARF", "LEGOS", + "DUNGS", "STOGY", "BERMS", "TANGS", "VAILS", "ROODS", "MOREL", "SWARE", "ELANS", + "LATUS", "GULES", "RAZER", "DOXIE", "BUENA", "OVERS", "GUTTA", "ZINCS", "NATES", + "KIRKS", "TIKES", "DONEE", "JERRY", "MOHEL", "CEDER", "DOGES", "UNMAP", "FOLIA", + "RAWLY", "SNARK", "TOPOI", "CEILS", "IMMIX", "YORES", "DIEST", "BUBBA", "POMPS", + "FORKY", "TURDY", "LAWZY", "POOHS", "WORTS", "GLOMS", "BEANO", "MULEY", "BARKY", + "TUNNY", "AURIC", "FUNKS", "GAFFS", "CORDY", "CURDY", "LISLE", "TORIC", "SOYAS", + "REMAN", "MUNGY", "CARPY", "APISH", "OATEN", "GAPPY", "AURAE", "BRACT", "ROOKY", + "AXLED", "BURRY", "SIZER", "PROEM", "TURFY", "IMPRO", "MASHY", "MIENS", "NONNY", + "OLIOS", "GROOK", "SATES", "AGLEY", "CORGI", "DASHY", "DOSER", "DILDO", "APSOS", + "XORED", "LAKER", "PLAYA", "SELAH", "MALTY", "DULSE", "FRIGS", "DEMIT", "WHOSO", + "RIALS", "SAWER", "BEDIM", "SNUGS", "FANIN", "AZOIC", "ICERS", "SUERS", "WIZEN", + "KOINE", "TOPOS", "SHIRR", "RIFER", "FERAL", "LADED", "LASED", "TURDS", "SWEDE", + "EASTS", "COZEN", "UNHIT", "PALLY", "AITCH", "SEDUM", "COPER", "RUCHE", "GEEKS", + "SWAGS", "ETEXT", "ALGIN", "OFFED", "NINJA", "HOLER", "DOTER", "TOTER", "BESOT", + "DICUT", "MACER", "PEENS", "PEWIT", "REDOX", "POLER", "YECCH", "FLUKY", "DOETH", + "TWATS", "CRUDS", "BEBUG", "BIDER", "STELE", "HEXER", "WESTS", "GLUER", "PILAU", + "ABAFT", "WHELM", "LACER", "INODE", "TABUS", "GATOR", "CUING", "REFLY", "LUTED", + "CUKES", "BAIRN", "BIGHT", "ARSES", "CRUMP", "LOGGY", "BLINI", "SPOOR", "TOYON", + "HARKS", "WAZOO", "FENNY", "NAVES", "KEYER", "TUFAS", "MORPH", "RAJAS", "TYPAL", + "SPIFF", "OXLIP", "UNBAN", "MUSSY", "FINNY", "RIMER", "LOGIN", "MOLAS", "CIRRI", + "HUZZA", "AGONE", "UNSEX", "UNWON", "PEATS", "TOILE", "ZOMBI", "DEWED", "NOOKY", + "ALKYL", "IXNAY", "DOVEY", "HOLEY", "CUBER", "AMYLS", "PODIA", "CHINO", "APNEA", + "PRIMS", "LYCRA", "JOHNS", "PRIMO", "FATWA", "EGGER", "HEMPY", "SNOOK", "HYING", + "FUZED", "BARMS", "CRINK", "MOOTS", "YERBA", "RHUMB", "UNARC", "DIRER", "MUNGE", + "ELAND", "NARES", "WRIER", "NODDY", "ATILT", "JUKES", "ENDER", "THENS", "UNFIX", + "DOGGO", "ZOOKS", "DIDDY", "SHMOO", "BRUSK", "PREST", "CURER", "PASTS", "KELPY", + "BOCCE", "KICKY", "TAROS", "LINGS", "DICKY", "NERDY", "ABEND", "STELA", "BIGGY", + "LAVED", "BALDY", "PUBIS", "GOOKS", "WONKY", "STIED", "HYPOS", "ASSED", "SPUMY", + "OSIER", "ROBLE", "RUMBA", "BIFFY", "PUPAL" +] + +alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] + +def most_used_letters(): + dicto = {} + for i in alphabet: + count = 0 + for word in words: + for letter in word: + if i.upper() == letter.upper(): + count+=1 + break + dicto[i] = count + dicto = dict(sorted(dicto.items(), key=lambda item: item[1], reverse=True)) + print("Letter | Usage") + print("--------------") + for k in dicto: + print(f"{k.upper()} | {dicto[k]}") + + +def list_of_valid_words(): + letters = ['s', 'e', 'a', 'r', 'o', 'i', 'L', 'n', 'p', 'c'] + for i, letter in enumerate(letters): # Force all letters to be capitalized + letters[i] = letter.upper() + + legal_words = [] + for word in words: + valid_word = True + for letter in word: + if letter.upper() not in letters: + valid_word = False + break + if valid_word and word not in legal_words: + legal_words.append(word) + + for i,word in enumerate(legal_words): + legal_words[i] = word.upper().replace("D","d") + + print(f"Number of words found: {len(legal_words)}") + items_per_row = 9 + i = 0 + print("static const char _legal_words[][WORDLE_LENGTH + 1] = {") + while i < len(legal_words): + print(" ", end='') + for j in range(min(items_per_row, len(legal_words)-i)): + print(f'"{legal_words[i]}", ', end='') + i+=1 + print('') + print("};") + +if __name__ == "__main__": + #most_used_letters() + list_of_valid_words() + From ee53e83ae7d9d920add1af6d0faad01ce1b017ef Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 14 Aug 2024 23:31:00 -0400 Subject: [PATCH 096/220] Changed dict to a smaller and simpler one --- .../watch_faces/complication/wordle_face.c | 115 +-- utils/wordle_list.py | 969 ++++++------------ 2 files changed, 374 insertions(+), 710 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f6f63fd..4052ef7 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -36,77 +36,60 @@ TODO: */ -// From: https://github.com/charlesreid1/five-letter-words/blob/master/sgb-words.txt +// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff /* -Letter | Usage in sgb-words.txt -S | 2674 -E | 2658 -A | 2181 -R | 1799 -O | 1683 -I | 1539 -T | 1462 But looks bad across all positions -L | 1434 -N | 1219 -D | 1100 lowercase d looks like a in certain positions -U | 1068 C has more words with the other letters here (457 total vs 390) -C | 920 -P | 895 +Letter | Usage +E | 1519 +S | 1490 +A | 1213 +R | 1026 +O | 852 +L | 850 +I | 843 +T | 819 But looks bad across all positions +N | 681 +D | 619 lowercase d looks like a in certain positions +C | 525 +U | 514 P has more words with the other letters here (281 vs 198) +P | 448 */ -static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; +static const char _valid_letters[] = {'E', 'S', 'A', 'R', 'O', 'L', 'I', 'N', 'C', 'P'}; +// Number of words found: 281 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "PLACE", "SINCE", "PAPER", "LINES", "LEARN", "SPACE", "CLOSE", "CLASS", "PIECE", - "COLOR", "ALONE", "PLANE", "SPELL", "CLEAR", "AREAS", "SENSE", "OCEAN", "SCALE", - "CELLS", "SLEEP", "LOCAL", "CLEAN", "PEACE", "CROSS", "CASES", "CROPS", "PLAIN", - "PAIRS", "SCORE", "NOISE", "PIANO", "PLANS", "PRICE", "RAISE", "SCENE", "PRESS", - "APPLE", "CALLS", "POLES", "LOOSE", "OPERA", "INNER", "SOLAR", "RISES", "SALES", - "ACRES", "ERROR", "NAILS", "COINS", "SLOPE", "CANOE", "CANAL", "LIONS", "AROSE", - "RACES", "SPARE", "PIPES", "RAINS", "ROLLS", "SAILS", "ARISE", "ROPES", "CRIES", - "OPENS", "APRON", "SPEAR", "SLICE", "SPOON", "ROSES", "LINEN", "POLAR", "PEARL", - "LOSES", "CORAL", "SPOIL", "PANEL", "SELLS", "COOLS", "SOILS", "POOLS", "RISEN", - "PILES", "SLAIN", "PANIC", "CRISP", "RAILS", "SCOPE", "CONES", "COCOA", "REINS", - "NOSES", "SCARE", "CEASE", "PRIOR", "POLIO", "SEALS", "COALS", "LOANS", "SPINS", - "PAINS", "ONION", "SCRAP", "ROLES", "SNAIL", "LOOPS", "CREEP", "CARES", "ALIEN", - "CRANE", "SLIPS", "SPINE", "LEAPS", "PROSE", "SNARE", "PINES", "SCALP", "LEASE", - "COILS", "ARENA", "PEARS", "CLIPS", "LANES", "RESIN", "SONAR", "CORPS", "NIECE", - "CELLO", "REPEL", "SPICE", "OASIS", "PACES", "COLON", "CACAO", "RINSE", "SPOOL", - "SPILL", "SNAPS", "EERIE", "EARNS", "PERIL", "LINER", "SCARS", "SOLES", "PAILS", - "CAROL", "CANON", "POLLS", "NASAL", "SCORN", "OASES", "ASSES", "PILLS", "SPORE", - "SPIES", "ERASE", "AISLE", "LOINS", "LEANS", "LANCE", "PANES", "PORES", "POISE", - "IRONS", "ROARS", "SCOOP", "LASSO", "PRONE", "NICER", "SNARL", "CORES", "RIPEN", - "PEERS", "EASEL", "LACES", "SOAPS", "SNIPS", "PIERS", "LASER", "LILAC", "SIREN", - "CLASP", "POSSE", "POSES", "SLOOP", "SLAPS", "SOLOS", "SISAL", "SEEPS", "SPANS", - "CLAPS", "ACORN", "LOSER", "LAPSE", "ASPEN", "SNORE", "PROPS", "PESOS", "SONIC", - "SPARS", "SORES", "RELIC", "NOOSE", "NEARS", "CAPES", "CANES", "SPIRE", "ISLES", - "SNEER", "RARER", "NINES", "PECAN", "PENCE", "SILLS", "COPRA", "POPES", "SPREE", - "SCANS", "REELS", "LIARS", "LEPER", "SOARS", "PLEAS", "PALER", "EPICS", "CAPER", - "CONIC", "OPALS", "EASES", "ARSON", "CLANS", "PLIES", "CROON", "PREEN", "SEERS", - "COCCI", "SILOS", "SNIPE", "SANER", "RILLS", "CORNS", "PRIES", "LOONS", "EARLS", - "PEONS", "PALES", "LAIRS", "PEELS", "PEALS", "CRONE", "ENROL", "COOPS", "LAPEL", - "RASPS", "ASPIC", "PENIS", "INANE", "SLOPS", "COCOS", "LEERS", "LOPES", "ALIAS", - "RACER", "COPSE", "PALLS", "COPES", "ICONS", "REAPS", "SNOOP", "LORES", "REARS", - "COLIC", "PLOPS", "SIRES", "CARPS", "LISPS", "PEEPS", "SORER", "LOLLS", "PARES", - "ACNES", "NEONS", "NOONS", "PAPAS", "RIPER", "ELOPE", "CRESS", "NAPES", "ICIER", - "CILIA", "SEARS", "SARIS", "PAPAL", "ROSIN", "CREPE", "RISER", "PACER", "SALON", - "CRIER", "LOESS", "PIPER", "COONS", "SEINE", "IONIC", "SCRIP", "PENAL", "ALOES", - "APACE", "SIREE", "ROPER", "ANION", "LONER", "CIRCA", "CARNE", "ANISE", "SPECS", - "ANNAS", "PICAS", "REALS", "AERIE", "ORLON", "CRASS", "SPIEL", "LAPIS", "ARIAS", - "PAEAN", "SINES", "SCION", "RAPES", "SCARP", "SEPIA", "POSER", "LIENS", "RILES", - "APSES", "NONCE", "ANOLE", "RECAP", "CARON", "PORNO", "CREEL", "CAPOS", "OPINE", - "NISEI", "RERAN", "NARCO", "CLOPS", "ESSES", "SCONE", "SALSA", "ROANS", "RAPER", - "CORER", "COLAS", "CAIRN", "CRAPS", "CLONE", "NOELS", "ORCAS", "PARSE", "EPEES", - "LANAI", "SEPAL", "CAPON", "PREPS", "NARCS", "REPRO", "ORALS", "ROILS", "ILIAC", - "CILLS", "LOCOS", "RENAL", "CROCS", "PARAS", "SECCO", "PIONS", "PARER", "PLEIN", - "AREAL", "SOLON", "PSOAS", "SCOPS", "SLOES", "NOIRE", "POLIS", "PASSE", "NONES", - "SARAN", "POLOS", "APERS", "ARRAS", "PRISE", "SPIER", "AIRER", "APIAN", "CASAS", - "CARER", "POOPS", "SPINA", "PENES", "RILLE", "CANER", "LIRAS", "PRIER", "LOPER", - "CALLA", "PONES", "COCAS", "OILER", "ASSAI", "LAPIN", "ASANA", "OLEOS", "LIERS", - "ANILE", "PLENA", "AIOLI", "SLIER", "CANNA", "PEASE", "LASES", "RASAE", "PAREN", - "SOCLE", "RICER", "RICES", "ELANS", "CEILS", "LISLE", "OLIOS", "APSOS", "RIALS", - "ICERS", "COPER", "PEENS", "POLER", "LACER", "ARSES", "SPOOR", "CIRRI", "APNEA", - "NARES", "OSIER", + "SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", + "POLES", "ROLLS", "ALOES", "LOSES", "SLICE", "PEACE", "POLLS", "POSES", "LANES", + "COPRA", "SPANS", "CANAL", "LOSER", "PAPER", "PILES", "CLASS", "RACER", "POOLS", + "PLAIN", "SPEAR", "SPARE", "INNER", "ALIEN", "NOSES", "EARLS", "SEALS", "LEARN", + "COLIC", "OPERA", "LOOSE", "SPOOR", "SCALE", "SOARS", "PAILS", "PRONE", "OPALS", + "PIPER", "RILLS", "CAIRN", "POISE", "LEAPS", "ELOPE", "NICER", "SLOOP", "PANES", + "SOLES", "CROSS", "NIECE", "LAIRS", "LEASE", "SALES", "SCENE", "SORES", "SNARL", + "SPIRE", "LASSO", "CLOSE", "OSIER", "SPOOL", "PRICE", "LOANS", "POSSE", "PENAL", + "SLAPS", "RELIC", "SINCE", "CIRCA", "LIARS", "RISES", "OPENS", "ROARS", "PACES", + "ARISE", "RISEN", "PENIS", "LAPEL", "CROPS", "CANON", "LAPSE", "SCION", "ARSON", + "AREAS", "SLAIN", "CANOE", "EERIE", "NOOSE", "PIANO", "PLANE", "CLASP", "SCARE", + "COCOA", "CRESS", "NASAL", "LOCAL", "RINSE", "SCARS", "PROPS", "OASES", "SLEEP", + "SNAPS", "SIRES", "CANES", "RAILS", "RESIN", "COLON", "PEASE", "POPES", "PENCE", + "AROSE", "REELS", "SALSA", "OCEAN", "PESOS", "OPINE", "RACES", "RAINS", "PRIES", + "CRIES", "CALLS", "PIERS", "CELLS", "SCRAP", "EARNS", "IRONS", "SPACE", "LOONS", + "SILLS", "COALS", "PIECE", "PALER", "REINS", "APACE", "SLOPE", "CREPE", "CONES", + "CAPER", "SEERS", "CAPES", "OASIS", "REAPS", "PALES", "CLAPS", "PLEAS", "INANE", + "COINS", "SNAIL", "CLEAR", "ROSIN", "LILAC", "SPARS", "SPINE", "NONCE", "CRISP", + "CRAPE", "AISLE", "CRONE", "SPOIL", "SPOON", "ARENA", "PARSE", "CASES", "SPICE", + "RIPER", "PILLS", "SOLOS", "SPINS", "PEERS", "RARER", "CONIC", "REARS", "CACAO", + "PAPAS", "ACRES", "ROPES", "CORAL", "CLEAN", "EASES", "SPILL", "SENSE", "PIPES", + "CLANS", "PRESS", "LOINS", "PAPAL", "APPLE", "PAIRS", "SCORN", "ALONE", "PEEPS", + "SPREE", "SNARE", "CLIPS", "EASEL", "CAROL", "ASPEN", "SALON", "LOOPS", "PEALS", + "SNEER", "PLACE", "SELLS", "LINEN", "CRIER", "ACORN", "SLIPS", "ERASE", "LIONS", + "NAILS", "REPEL", "CORES", "LEPER", "APPAL", "ROSES", "SCORE", "RISER", "CREEP", + "CAPON", "ERROR", "NOISE", "CARES", "APRON", "SOILS", "SLOPS", "PAINS", "EPICS", + "SANER", "SAILS", "PRIOR", "ASSES", "COILS", "SCOOP", "LACES", "SCALP", "CRANE", + "PLANS", "ISLES", "SPORE", "PANIC", "COOLS", "SPELL", "ALIAS", "PORES", "SCRIP", + "PEARL", "PANEL", "ENROL", "LANCE", "CORPS", "LINES", "COPSE", "ONION", "NEARS", + "RIPEN", "LINER", "SCOPE", "SCANS", "SNIPE", "CEASE", "LEANS", "AEONS", "PINES", + "POPPA", "ROLES", "REALS", "PERIL", "POSER", "PROSE", "POLAR", "CORNS", "LIENS", + "SIREN", "PEONS", }; static const uint32_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); diff --git a/utils/wordle_list.py b/utils/wordle_list.py index 30da4bd..87e6066 100644 --- a/utils/wordle_list.py +++ b/utils/wordle_list.py @@ -1,645 +1,318 @@ -# From: https://github.com/charlesreid1/five-letter-words/blob/master/sgb-words.txt +import random + +# From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff words = [ - "WHICH", "THERE", "THEIR", "ABOUT", "WOULD", "THESE", "OTHER", "WORDS", "COULD", - "WRITE", "FIRST", "WATER", "AFTER", "WHERE", "RIGHT", "THINK", "THREE", "YEARS", - "PLACE", "SOUND", "GREAT", "AGAIN", "STILL", "EVERY", "SMALL", "FOUND", "THOSE", - "NEVER", "UNDER", "MIGHT", "WHILE", "HOUSE", "WORLD", "BELOW", "ASKED", "GOING", - "LARGE", "UNTIL", "ALONG", "SHALL", "BEING", "OFTEN", "EARTH", "BEGAN", "SINCE", - "STUDY", "NIGHT", "LIGHT", "ABOVE", "PAPER", "PARTS", "YOUNG", "STORY", "POINT", - "TIMES", "HEARD", "WHOLE", "WHITE", "GIVEN", "MEANS", "MUSIC", "MILES", "THING", - "TODAY", "LATER", "USING", "MONEY", "LINES", "ORDER", "GROUP", "AMONG", "LEARN", - "KNOWN", "SPACE", "TABLE", "EARLY", "TREES", "SHORT", "HANDS", "STATE", "BLACK", - "SHOWN", "STOOD", "FRONT", "VOICE", "KINDS", "MAKES", "COMES", "CLOSE", "POWER", - "LIVED", "VOWEL", "TAKEN", "BUILT", "HEART", "READY", "QUITE", "CLASS", "BRING", - "ROUND", "HORSE", "SHOWS", "PIECE", "GREEN", "STAND", "BIRDS", "START", "RIVER", - "TRIED", "LEAST", "FIELD", "WHOSE", "GIRLS", "LEAVE", "ADDED", "COLOR", "THIRD", - "HOURS", "MOVED", "PLANT", "DOING", "NAMES", "FORMS", "HEAVY", "IDEAS", "CRIED", - "CHECK", "FLOOR", "BEGIN", "WOMAN", "ALONE", "PLANE", "SPELL", "WATCH", "CARRY", - "WROTE", "CLEAR", "NAMED", "BOOKS", "CHILD", "GLASS", "HUMAN", "TAKES", "PARTY", - "BUILD", "SEEMS", "BLOOD", "SIDES", "SEVEN", "MOUTH", "SOLVE", "NORTH", "VALUE", - "DEATH", "MAYBE", "HAPPY", "TELLS", "GIVES", "LOOKS", "SHAPE", "LIVES", "STEPS", - "AREAS", "SENSE", "SPEAK", "FORCE", "OCEAN", "SPEED", "WOMEN", "METAL", "SOUTH", - "GRASS", "SCALE", "CELLS", "LOWER", "SLEEP", "WRONG", "PAGES", "SHIPS", "NEEDS", - "ROCKS", "EIGHT", "MAJOR", "LEVEL", "TOTAL", "AHEAD", "REACH", "STARS", "STORE", - "SIGHT", "TERMS", "CATCH", "WORKS", "BOARD", "COVER", "SONGS", "EQUAL", "STONE", - "WAVES", "GUESS", "DANCE", "SPOKE", "BREAK", "CAUSE", "RADIO", "WEEKS", "LANDS", - "BASIC", "LIKED", "TRADE", "FRESH", "FINAL", "FIGHT", "MEANT", "DRIVE", "SPENT", - "LOCAL", "WAXES", "KNOWS", "TRAIN", "BREAD", "HOMES", "TEETH", "COAST", "THICK", - "BROWN", "CLEAN", "QUIET", "SUGAR", "FACTS", "STEEL", "FORTH", "RULES", "NOTES", - "UNITS", "PEACE", "MONTH", "VERBS", "SEEDS", "HELPS", "SHARP", "VISIT", "WOODS", - "CHIEF", "WALLS", "CROSS", "WINGS", "GROWN", "CASES", "FOODS", "CROPS", "FRUIT", - "STICK", "WANTS", "STAGE", "SHEEP", "NOUNS", "PLAIN", "DRINK", "BONES", "APART", - "TURNS", "MOVES", "TOUCH", "ANGLE", "BASED", "RANGE", "MARKS", "TIRED", "OLDER", - "FARMS", "SPEND", "SHOES", "GOODS", "CHAIR", "TWICE", "CENTS", "EMPTY", "ALIKE", - "STYLE", "BROKE", "PAIRS", "COUNT", "ENJOY", "SCORE", "SHORE", "ROOTS", "PAINT", - "HEADS", "SHOOK", "SERVE", "ANGRY", "CROWD", "WHEEL", "QUICK", "DRESS", "SHARE", - "ALIVE", "NOISE", "SOLID", "CLOTH", "SIGNS", "HILLS", "TYPES", "DRAWN", "WORTH", - "TRUCK", "PIANO", "UPPER", "LOVED", "USUAL", "FACES", "DROVE", "CABIN", "BOATS", - "TOWNS", "PROUD", "COURT", "MODEL", "PRIME", "FIFTY", "PLANS", "YARDS", "PROVE", - "TOOLS", "PRICE", "SHEET", "SMELL", "BOXES", "RAISE", "MATCH", "TRUTH", "ROADS", - "THREW", "ENEMY", "LUNCH", "CHART", "SCENE", "GRAPH", "DOUBT", "GUIDE", "WINDS", - "BLOCK", "GRAIN", "SMOKE", "MIXED", "GAMES", "WAGON", "SWEET", "TOPIC", "EXTRA", - "PLATE", "TITLE", "KNIFE", "FENCE", "FALLS", "CLOUD", "WHEAT", "PLAYS", "ENTER", - "BROAD", "STEAM", "ATOMS", "PRESS", "LYING", "BASIS", "CLOCK", "TASTE", "GROWS", - "THANK", "STORM", "AGREE", "BRAIN", "TRACK", "SMILE", "FUNNY", "BEACH", "STOCK", - "HURRY", "SAVED", "SORRY", "GIANT", "TRAIL", "OFFER", "OUGHT", "ROUGH", "DAILY", - "AVOID", "KEEPS", "THROW", "ALLOW", "CREAM", "LAUGH", "EDGES", "TEACH", "FRAME", - "BELLS", "DREAM", "MAGIC", "OCCUR", "ENDED", "CHORD", "FALSE", "SKILL", "HOLES", - "DOZEN", "BRAVE", "APPLE", "CLIMB", "OUTER", "PITCH", "RULER", "HOLDS", "FIXED", - "COSTS", "CALLS", "BLANK", "STAFF", "LABOR", "EATEN", "YOUTH", "TONES", "HONOR", - "GLOBE", "GASES", "DOORS", "POLES", "LOOSE", "APPLY", "TEARS", "EXACT", "BRUSH", - "CHEST", "LAYER", "WHALE", "MINOR", "FAITH", "TESTS", "JUDGE", "ITEMS", "WORRY", - "WASTE", "HOPED", "STRIP", "BEGUN", "ASIDE", "LAKES", "BOUND", "DEPTH", "CANDY", - "EVENT", "WORSE", "AWARE", "SHELL", "ROOMS", "RANCH", "IMAGE", "SNAKE", "ALOUD", - "DRIED", "LIKES", "MOTOR", "POUND", "KNEES", "REFER", "FULLY", "CHAIN", "SHIRT", - "FLOUR", "DROPS", "SPITE", "ORBIT", "BANKS", "SHOOT", "CURVE", "TRIBE", "TIGHT", - "BLIND", "SLEPT", "SHADE", "CLAIM", "FLIES", "THEME", "QUEEN", "FIFTH", "UNION", - "HENCE", "STRAW", "ENTRY", "ISSUE", "BIRTH", "FEELS", "ANGER", "BRIEF", "RHYME", - "GLORY", "GUARD", "FLOWS", "FLESH", "OWNED", "TRICK", "YOURS", "SIZES", "NOTED", - "WIDTH", "BURST", "ROUTE", "LUNGS", "UNCLE", "BEARS", "ROYAL", "KINGS", "FORTY", - "TRIAL", "CARDS", "BRASS", "OPERA", "CHOSE", "OWNER", "VAPOR", "BEATS", "MOUSE", - "TOUGH", "WIRES", "METER", "TOWER", "FINDS", "INNER", "STUCK", "ARROW", "POEMS", - "LABEL", "SWING", "SOLAR", "TRULY", "TENSE", "BEANS", "SPLIT", "RISES", "WEIGH", - "HOTEL", "STEMS", "PRIDE", "SWUNG", "GRADE", "DIGIT", "BADLY", "BOOTS", "PILOT", - "SALES", "SWEPT", "LUCKY", "PRIZE", "STOVE", "TUBES", "ACRES", "WOUND", "STEEP", - "SLIDE", "TRUNK", "ERROR", "PORCH", "SLAVE", "EXIST", "FACED", "MINES", "MARRY", - "JUICE", "RACED", "WAVED", "GOOSE", "TRUST", "FEWER", "FAVOR", "MILLS", "VIEWS", - "JOINT", "EAGER", "SPOTS", "BLEND", "RINGS", "ADULT", "INDEX", "NAILS", "HORNS", - "BALLS", "FLAME", "RATES", "DRILL", "TRACE", "SKINS", "WAXED", "SEATS", "STUFF", - "RATIO", "MINDS", "DIRTY", "SILLY", "COINS", "HELLO", "TRIPS", "LEADS", "RIFLE", - "HOPES", "BASES", "SHINE", "BENCH", "MORAL", "FIRES", "MEALS", "SHAKE", "SHOPS", - "CYCLE", "MOVIE", "SLOPE", "CANOE", "TEAMS", "FOLKS", "FIRED", "BANDS", "THUMB", - "SHOUT", "CANAL", "HABIT", "REPLY", "RULED", "FEVER", "CRUST", "SHELF", "WALKS", - "MIDST", "CRACK", "PRINT", "TALES", "COACH", "STIFF", "FLOOD", "VERSE", "AWAKE", - "ROCKY", "MARCH", "FAULT", "SWIFT", "FAINT", "CIVIL", "GHOST", "FEAST", "BLADE", - "LIMIT", "GERMS", "READS", "DUCKS", "DAIRY", "WORST", "GIFTS", "LISTS", "STOPS", - "RAPID", "BRICK", "CLAWS", "BEADS", "BEAST", "SKIRT", "CAKES", "LIONS", "FROGS", - "TRIES", "NERVE", "GRAND", "ARMED", "TREAT", "HONEY", "MOIST", "LEGAL", "PENNY", - "CROWN", "SHOCK", "TAXES", "SIXTY", "ALTAR", "PULLS", "SPORT", "DRUMS", "TALKS", - "DYING", "DATES", "DRANK", "BLOWS", "LEVER", "WAGES", "PROOF", "DRUGS", "TANKS", - "SINGS", "TAILS", "PAUSE", "HERDS", "AROSE", "HATED", "CLUES", "NOVEL", "SHAME", - "BURNT", "RACES", "FLASH", "WEARY", "HEELS", "TOKEN", "COATS", "SPARE", "SHINY", - "ALARM", "DIMES", "SIXTH", "CLERK", "MERCY", "SUNNY", "GUEST", "FLOAT", "SHONE", - "PIPES", "WORMS", "BILLS", "SWEAT", "SUITS", "SMART", "UPSET", "RAINS", "SANDY", - "RAINY", "PARKS", "SADLY", "FANCY", "RIDER", "UNITY", "BUNCH", "ROLLS", "CRASH", - "CRAFT", "NEWLY", "GATES", "HATCH", "PATHS", "FUNDS", "WIDER", "GRACE", "GRAVE", - "TIDES", "ADMIT", "SHIFT", "SAILS", "PUPIL", "TIGER", "ANGEL", "CRUEL", "AGENT", - "DRAMA", "URGED", "PATCH", "NESTS", "VITAL", "SWORD", "BLAME", "WEEDS", "SCREW", - "VOCAL", "BACON", "CHALK", "CARGO", "CRAZY", "ACTED", "GOATS", "ARISE", "WITCH", - "LOVES", "QUEER", "DWELL", "BACKS", "ROPES", "SHOTS", "MERRY", "PHONE", "CHEEK", - "PEAKS", "IDEAL", "BEARD", "EAGLE", "CREEK", "CRIES", "ASHES", "STALL", "YIELD", - "MAYOR", "OPENS", "INPUT", "FLEET", "TOOTH", "CUBIC", "WIVES", "BURNS", "POETS", - "APRON", "SPEAR", "ORGAN", "CLIFF", "STAMP", "PASTE", "RURAL", "BAKED", "CHASE", - "SLICE", "SLANT", "KNOCK", "NOISY", "SORTS", "STAYS", "WIPED", "BLOWN", "PILED", - "CLUBS", "CHEER", "WIDOW", "TWIST", "TENTH", "HIDES", "COMMA", "SWEEP", "SPOON", - "STERN", "CREPT", "MAPLE", "DEEDS", "RIDES", "MUDDY", "CRIME", "JELLY", "RIDGE", - "DRIFT", "DUSTY", "DEVIL", "TEMPO", "HUMOR", "SENDS", "STEAL", "TENTS", "WAIST", - "ROSES", "REIGN", "NOBLE", "CHEAP", "DENSE", "LINEN", "GEESE", "WOVEN", "POSTS", - "HIRED", "WRATH", "SALAD", "BOWED", "TIRES", "SHARK", "BELTS", "GRASP", "BLAST", - "POLAR", "FUNGI", "TENDS", "PEARL", "LOADS", "JOKES", "VEINS", "FROST", "HEARS", - "LOSES", "HOSTS", "DIVER", "PHASE", "TOADS", "ALERT", "TASKS", "SEAMS", "CORAL", - "FOCUS", "NAKED", "PUPPY", "JUMPS", "SPOIL", "QUART", "MACRO", "FEARS", "FLUNG", - "SPARK", "VIVID", "BROOK", "STEER", "SPRAY", "DECAY", "PORTS", "SOCKS", "URBAN", - "GOALS", "GRANT", "MINUS", "FILMS", "TUNES", "SHAFT", "FIRMS", "SKIES", "BRIDE", - "WRECK", "FLOCK", "STARE", "HOBBY", "BONDS", "DARED", "FADED", "THIEF", "CRUDE", - "PANTS", "FLUTE", "VOTES", "TONAL", "RADAR", "WELLS", "SKULL", "HAIRS", "ARGUE", - "WEARS", "DOLLS", "VOTED", "CAVES", "CARED", "BROOM", "SCENT", "PANEL", "FAIRY", - "OLIVE", "BENDS", "PRISM", "LAMPS", "CABLE", "PEACH", "RUINS", "RALLY", "SCHWA", - "LAMBS", "SELLS", "COOLS", "DRAFT", "CHARM", "LIMBS", "BRAKE", "GAZED", "CUBES", - "DELAY", "BEAMS", "FETCH", "RANKS", "ARRAY", "HARSH", "CAMEL", "VINES", "PICKS", - "NAVAL", "PURSE", "RIGID", "CRAWL", "TOAST", "SOILS", "SAUCE", "BASIN", "PONDS", - "TWINS", "WRIST", "FLUID", "POOLS", "BRAND", "STALK", "ROBOT", "REEDS", "HOOFS", - "BUSES", "SHEER", "GRIEF", "BLOOM", "DWELT", "MELTS", "RISEN", "FLAGS", "KNELT", - "FIBER", "ROOFS", "FREED", "ARMOR", "PILES", "AIMED", "ALGAE", "TWIGS", "LEMON", - "DITCH", "DRUNK", "RESTS", "CHILL", "SLAIN", "PANIC", "CORDS", "TUNED", "CRISP", - "LEDGE", "DIVED", "SWAMP", "CLUNG", "STOLE", "MOLDS", "YARNS", "LIVER", "GAUGE", - "BREED", "STOOL", "GULLS", "AWOKE", "GROSS", "DIARY", "RAILS", "BELLY", "TREND", - "FLASK", "STAKE", "FRIED", "DRAWS", "ACTOR", "HANDY", "BOWLS", "HASTE", "SCOPE", - "DEALS", "KNOTS", "MOONS", "ESSAY", "THUMP", "HANGS", "BLISS", "DEALT", "GAINS", - "BOMBS", "CLOWN", "PALMS", "CONES", "ROAST", "TIDAL", "BORED", "CHANT", "ACIDS", - "DOUGH", "CAMPS", "SWORE", "LOVER", "HOOKS", "MALES", "COCOA", "PUNCH", "AWARD", - "REINS", "NINTH", "NOSES", "LINKS", "DRAIN", "FILLS", "NYLON", "LUNAR", "PULSE", - "FLOWN", "ELBOW", "FATAL", "SITES", "MOTHS", "MEATS", "FOXES", "MINED", "ATTIC", - "FIERY", "MOUNT", "USAGE", "SWEAR", "SNOWY", "RUSTY", "SCARE", "TRAPS", "RELAX", - "REACT", "VALID", "ROBIN", "CEASE", "GILLS", "PRIOR", "SAFER", "POLIO", "LOYAL", - "SWELL", "SALTY", "MARSH", "VAGUE", "WEAVE", "MOUND", "SEALS", "MULES", "VIRUS", - "SCOUT", "ACUTE", "WINDY", "STOUT", "FOLDS", "SEIZE", "HILLY", "JOINS", "PLUCK", - "STACK", "LORDS", "DUNES", "BURRO", "HAWKS", "TROUT", "FEEDS", "SCARF", "HALLS", - "COALS", "TOWEL", "SOULS", "ELECT", "BUGGY", "PUMPS", "LOANS", "SPINS", "FILES", - "OXIDE", "PAINS", "PHOTO", "RIVAL", "FLATS", "SYRUP", "RODEO", "SANDS", "MOOSE", - "PINTS", "CURLY", "COMIC", "CLOAK", "ONION", "CLAMS", "SCRAP", "DIDST", "COUCH", - "CODES", "FAILS", "OUNCE", "LODGE", "GREET", "GYPSY", "UTTER", "PAVED", "ZONES", - "FOURS", "ALLEY", "TILES", "BLESS", "CREST", "ELDER", "KILLS", "YEAST", "ERECT", - "BUGLE", "MEDAL", "ROLES", "HOUND", "SNAIL", "ALTER", "ANKLE", "RELAY", "LOOPS", - "ZEROS", "BITES", "MODES", "DEBTS", "REALM", "GLOVE", "RAYON", "SWIMS", "POKED", - "STRAY", "LIFTS", "MAKER", "LUMPS", "GRAZE", "DREAD", "BARNS", "DOCKS", "MASTS", - "POURS", "WHARF", "CURSE", "PLUMP", "ROBES", "SEEKS", "CEDAR", "CURLS", "JOLLY", - "MYTHS", "CAGES", "GLOOM", "LOCKS", "PEDAL", "BEETS", "CROWS", "ANODE", "SLASH", - "CREEP", "ROWED", "CHIPS", "FISTS", "WINES", "CARES", "VALVE", "NEWER", "MOTEL", - "IVORY", "NECKS", "CLAMP", "BARGE", "BLUES", "ALIEN", "FROWN", "STRAP", "CREWS", - "SHACK", "GONNA", "SAVES", "STUMP", "FERRY", "IDOLS", "COOKS", "JUICY", "GLARE", - "CARTS", "ALLOY", "BULBS", "LAWNS", "LASTS", "FUELS", "ODDLY", "CRANE", "FILED", - "WEIRD", "SHAWL", "SLIPS", "TROOP", "BOLTS", "SUITE", "SLEEK", "QUILT", "TRAMP", - "BLAZE", "ATLAS", "ODORS", "SCRUB", "CRABS", "PROBE", "LOGIC", "ADOBE", "EXILE", - "REBEL", "GRIND", "STING", "SPINE", "CLING", "DESKS", "GROVE", "LEAPS", "PROSE", - "LOFTY", "AGONY", "SNARE", "TUSKS", "BULLS", "MOODS", "HUMID", "FINER", "DIMLY", - "PLANK", "CHINA", "PINES", "GUILT", "SACKS", "BRACE", "QUOTE", "LATHE", "GAILY", - "FONTS", "SCALP", "ADOPT", "FOGGY", "FERNS", "GRAMS", "CLUMP", "PERCH", "TUMOR", - "TEENS", "CRANK", "FABLE", "HEDGE", "GENES", "SOBER", "BOAST", "TRACT", "CIGAR", - "UNITE", "OWING", "THIGH", "HAIKU", "SWISH", "DIKES", "WEDGE", "BOOTH", "EASED", - "FRAIL", "COUGH", "TOMBS", "DARTS", "FORTS", "CHOIR", "POUCH", "PINCH", "HAIRY", - "BUYER", "TORCH", "VIGOR", "WALTZ", "HEATS", "HERBS", "USERS", "FLINT", "CLICK", - "MADAM", "BLEAK", "BLUNT", "AIDED", "LACKS", "MASKS", "WADED", "RISKS", "NURSE", - "CHAOS", "SEWED", "CURED", "AMPLE", "LEASE", "STEAK", "SINKS", "MERIT", "BLUFF", - "BATHE", "GLEAM", "BONUS", "COLTS", "SHEAR", "GLAND", "SILKY", "SKATE", "BIRCH", - "ANVIL", "SLEDS", "GROAN", "MAIDS", "MEETS", "SPECK", "HYMNS", "HINTS", "DROWN", - "BOSOM", "SLICK", "QUEST", "COILS", "SPIED", "SNOWS", "STEAD", "SNACK", "PLOWS", - "BLOND", "TAMED", "THORN", "WAITS", "GLUED", "BANJO", "TEASE", "ARENA", "BULKY", - "CARVE", "STUNT", "WARMS", "SHADY", "RAZOR", "FOLLY", "LEAFY", "NOTCH", "FOOLS", - "OTTER", "PEARS", "FLUSH", "GENUS", "ACHED", "FIVES", "FLAPS", "SPOUT", "SMOTE", - "FUMES", "ADAPT", "CUFFS", "TASTY", "STOOP", "CLIPS", "DISKS", "SNIFF", "LANES", - "BRISK", "IMPLY", "DEMON", "SUPER", "FURRY", "RAGED", "GROWL", "TEXTS", "HARDY", - "STUNG", "TYPED", "HATES", "WISER", "TIMID", "SERUM", "BEAKS", "ROTOR", "CASTS", - "BATHS", "GLIDE", "PLOTS", "TRAIT", "RESIN", "SLUMS", "LYRIC", "PUFFS", "DECKS", - "BROOD", "MOURN", "ALOFT", "ABUSE", "WHIRL", "EDGED", "OVARY", "QUACK", "HEAPS", - "SLANG", "AWAIT", "CIVIC", "SAINT", "BEVEL", "SONAR", "AUNTS", "PACKS", "FROZE", - "TONIC", "CORPS", "SWARM", "FRANK", "REPAY", "GAUNT", "WIRED", "NIECE", "CELLO", - "NEEDY", "CHUCK", "STONY", "MEDIA", "SURGE", "HURTS", "REPEL", "HUSKY", "DATED", - "HUNTS", "MISTS", "EXERT", "DRIES", "MATES", "SWORN", "BAKER", "SPICE", "OASIS", - "BOILS", "SPURS", "DOVES", "SNEAK", "PACES", "COLON", "SIEGE", "STRUM", "DRIER", - "CACAO", "HUMUS", "BALES", "PIPED", "NASTY", "RINSE", "BOXER", "SHRUB", "AMUSE", - "TACKS", "CITED", "SLUNG", "DELTA", "LADEN", "LARVA", "RENTS", "YELLS", "SPOOL", - "SPILL", "CRUSH", "JEWEL", "SNAPS", "STAIN", "KICKS", "TYING", "SLITS", "RATED", - "EERIE", "SMASH", "PLUMS", "ZEBRA", "EARNS", "BUSHY", "SCARY", "SQUAD", "TUTOR", - "SILKS", "SLABS", "BUMPS", "EVILS", "FANGS", "SNOUT", "PERIL", "PIVOT", "YACHT", - "LOBBY", "JEANS", "GRINS", "VIOLA", "LINER", "COMET", "SCARS", "CHOPS", "RAIDS", - "EATER", "SLATE", "SKIPS", "SOLES", "MISTY", "URINE", "KNOBS", "SLEET", "HOLLY", - "PESTS", "FORKS", "GRILL", "TRAYS", "PAILS", "BORNE", "TENOR", "WARES", "CAROL", - "WOODY", "CANON", "WAKES", "KITTY", "MINER", "POLLS", "SHAKY", "NASAL", "SCORN", - "CHESS", "TAXIS", "CRATE", "SHYLY", "TULIP", "FORGE", "NYMPH", "BUDGE", "LOWLY", - "ABIDE", "DEPOT", "OASES", "ASSES", "SHEDS", "FUDGE", "PILLS", "RIVET", "THINE", - "GROOM", "LANKY", "BOOST", "BROTH", "HEAVE", "GRAVY", "BEECH", "TIMED", "QUAIL", - "INERT", "GEARS", "CHICK", "HINGE", "TRASH", "CLASH", "SIGHS", "RENEW", "BOUGH", - "DWARF", "SLOWS", "QUILL", "SHAVE", "SPORE", "SIXES", "CHUNK", "MADLY", "PACED", - "BRAID", "FUZZY", "MOTTO", "SPIES", "SLACK", "MUCUS", "MAGMA", "AWFUL", "DISCS", - "ERASE", "POSED", "ASSET", "CIDER", "TAPER", "THEFT", "CHURN", "SATIN", "SLOTS", - "TAXED", "BULLY", "SLOTH", "SHALE", "TREAD", "RAKED", "CURDS", "MANOR", "AISLE", - "BULGE", "LOINS", "STAIR", "TAPES", "LEANS", "BUNKS", "SQUAT", "TOWED", "LANCE", - "PANES", "SAKES", "HEIRS", "CASTE", "DUMMY", "PORES", "FAUNA", "CROOK", "POISE", - "EPOCH", "RISKY", "WARNS", "FLING", "BERRY", "GRAPE", "FLANK", "DRAGS", "SQUID", - "PELTS", "ICING", "IRONY", "IRONS", "BARKS", "WHOOP", "CHOKE", "DIETS", "WHIPS", - "TALLY", "DOZED", "TWINE", "KITES", "BIKES", "TICKS", "RIOTS", "ROARS", "VAULT", - "LOOMS", "SCOLD", "BLINK", "DANDY", "PUPAE", "SIEVE", "SPIKE", "DUCTS", "LENDS", - "PIZZA", "BRINK", "WIDEN", "PLUMB", "PAGAN", "FEATS", "BISON", "SOGGY", "SCOOP", - "ARGON", "NUDGE", "SKIFF", "AMBER", "SEXES", "ROUSE", "SALTS", "HITCH", "EXALT", - "LEASH", "DINED", "CHUTE", "SNORT", "GUSTS", "MELON", "CHEAT", "REEFS", "LLAMA", - "LASSO", "DEBUT", "QUOTA", "OATHS", "PRONE", "MIXES", "RAFTS", "DIVES", "STALE", - "INLET", "FLICK", "PINTO", "BROWS", "UNTIE", "BATCH", "GREED", "CHORE", "STIRS", - "BLUSH", "ONSET", "BARBS", "VOLTS", "BEIGE", "SWOOP", "PADDY", "LACED", "SHOVE", - "JERKY", "POPPY", "LEAKS", "FARES", "DODGE", "GODLY", "SQUAW", "AFFIX", "BRUTE", - "NICER", "UNDUE", "SNARL", "MERGE", "DOSES", "SHOWY", "DADDY", "ROOST", "VASES", - "SWIRL", "PETTY", "COLDS", "CURRY", "COBRA", "GENIE", "FLARE", "MESSY", "CORES", - "SOAKS", "RIPEN", "WHINE", "AMINO", "PLAID", "SPINY", "MOWED", "BATON", "PEERS", - "VOWED", "PIOUS", "SWANS", "EXITS", "AFOOT", "PLUGS", "IDIOM", "CHILI", "RITES", - "SERFS", "CLEFT", "BERTH", "GRUBS", "ANNEX", "DIZZY", "HASTY", "LATCH", "WASPS", - "MIRTH", "BARON", "PLEAD", "ALOOF", "AGING", "PIXEL", "BARED", "MUMMY", "HOTLY", - "AUGER", "BUDDY", "CHAPS", "BADGE", "STARK", "FAIRS", "GULLY", "MUMPS", "EMERY", - "FILLY", "OVENS", "DRONE", "GAUZE", "IDIOT", "FUSSY", "ANNOY", "SHANK", "GOUGE", - "BLEED", "ELVES", "ROPED", "UNFIT", "BAGGY", "MOWER", "SCANT", "GRABS", "FLEAS", - "LOUSY", "ALBUM", "SAWED", "COOKY", "MURKY", "INFER", "BURLY", "WAGED", "DINGY", - "BRINE", "KNEEL", "CREAK", "VANES", "SMOKY", "SPURT", "COMBS", "EASEL", "LACES", - "HUMPS", "RUMOR", "AROMA", "HORDE", "SWISS", "LEAPT", "OPIUM", "SLIME", "AFIRE", - "PANSY", "MARES", "SOAPS", "HUSKS", "SNIPS", "HAZEL", "LINED", "CAFES", "NAIVE", - "WRAPS", "SIZED", "PIERS", "BESET", "AGILE", "TONGS", "STEED", "FRAUD", "BOOTY", - "VALOR", "DOWNY", "WITTY", "MOSSY", "PSALM", "SCUBA", "TOURS", "POLKA", "MILKY", - "GAUDY", "SHRUG", "TUFTS", "WILDS", "LASER", "TRUSS", "HARES", "CREED", "LILAC", - "SIREN", "TARRY", "BRIBE", "SWINE", "MUTED", "FLIPS", "CURES", "SINEW", "BOXED", - "HOOPS", "GASPS", "HOODS", "NICHE", "YUCCA", "GLOWS", "SEWER", "WHACK", "FUSES", - "GOWNS", "DROOP", "BUCKS", "PANGS", "MAILS", "WHISK", "HAVEN", "CLASP", "SLING", - "STINT", "URGES", "CHAMP", "PIETY", "CHIRP", "PLEAT", "POSSE", "SUNUP", "MENUS", - "HOWLS", "QUAKE", "KNACK", "PLAZA", "FIEND", "CAKED", "BANGS", "ERUPT", "POKER", - "OLDEN", "CRAMP", "VOTER", "POSES", "MANLY", "SLUMP", "FINED", "GRIPS", "GAPED", - "PURGE", "HIKED", "MAIZE", "FLUFF", "STRUT", "SLOOP", "PROWL", "ROACH", "COCKS", - "BLAND", "DIALS", "PLUME", "SLAPS", "SOUPS", "DULLY", "WILLS", "FOAMS", "SOLOS", - "SKIER", "EAVES", "TOTEM", "FUSED", "LATEX", "VEILS", "MUSED", "MAINS", "MYRRH", - "RACKS", "GALLS", "GNATS", "BOUTS", "SISAL", "SHUTS", "HOSES", "DRYLY", "HOVER", - "GLOSS", "SEEPS", "DENIM", "PUTTY", "GUPPY", "LEAKY", "DUSKY", "FILTH", "OBOES", - "SPANS", "FOWLS", "ADORN", "GLAZE", "HAUNT", "DARES", "OBEYS", "BAKES", "ABYSS", - "SMELT", "GANGS", "ACHES", "TRAWL", "CLAPS", "UNDID", "SPICY", "HOIST", "FADES", - "VICAR", "ACORN", "PUSSY", "GRUFF", "MUSTY", "TARTS", "SNUFF", "HUNCH", "TRUCE", - "TWEED", "DRYER", "LOSER", "SHEAF", "MOLES", "LAPSE", "TAWNY", "VEXED", "AUTOS", - "WAGER", "DOMES", "SHEEN", "CLANG", "SPADE", "SOWED", "BROIL", "SLYLY", "STUDS", - "GRUNT", "DONOR", "SLUGS", "ASPEN", "HOMER", "CROAK", "TITHE", "HALTS", "AVERT", - "HAVOC", "HOGAN", "GLINT", "RUDDY", "JEEPS", "FLAKY", "LADLE", "TAUNT", "SNORE", - "FINES", "PROPS", "PRUNE", "PESOS", "RADII", "POKES", "TILED", "DAISY", "HERON", - "VILLA", "FARCE", "BINDS", "CITES", "FIXES", "JERKS", "LIVID", "WAKED", "INKED", - "BOOMS", "CHEWS", "LICKS", "HYENA", "SCOFF", "LUSTY", "SONIC", "SMITH", "USHER", - "TUCKS", "VIGIL", "MOLTS", "SECTS", "SPARS", "DUMPS", "SCALY", "WISPS", "SORES", - "MINCE", "PANDA", "FLIER", "AXLES", "PLIED", "BOOBY", "PATIO", "RABBI", "PETAL", - "POLYP", "TINTS", "GRATE", "TROLL", "TOLLS", "RELIC", "PHONY", "BLEAT", "FLAWS", - "FLAKE", "SNAGS", "APTLY", "DRAWL", "ULCER", "SOAPY", "BOSSY", "MONKS", "CRAGS", - "CAGED", "TWANG", "DINER", "TAPED", "CADET", "GRIDS", "SPAWN", "GUILE", "NOOSE", - "MORES", "GIRTH", "SLIMY", "AIDES", "SPASM", "BURRS", "ALIBI", "LYMPH", "SAUCY", - "MUGGY", "LITER", "JOKED", "GOOFY", "EXAMS", "ENACT", "STORK", "LURED", "TOXIC", - "OMENS", "NEARS", "COVET", "WRUNG", "FORUM", "VENOM", "MOODY", "ALDER", "SASSY", - "FLAIR", "GUILD", "PRAYS", "WRENS", "HAULS", "STAVE", "TILTS", "PECKS", "STOMP", - "GALES", "TEMPT", "CAPES", "MESAS", "OMITS", "TEPEE", "HARRY", "WRING", "EVOKE", - "LIMES", "CLUCK", "LUNGE", "HIGHS", "CANES", "GIDDY", "LITHE", "VERGE", "KHAKI", - "QUEUE", "LOATH", "FOYER", "OUTDO", "FARED", "DETER", "CRUMB", "ASTIR", "SPIRE", - "JUMPY", "EXTOL", "BUOYS", "STUBS", "LUCID", "THONG", "AFORE", "WHIFF", "MAXIM", - "HULLS", "CLOGS", "SLATS", "JIFFY", "ARBOR", "CINCH", "IGLOO", "GOODY", "GAZES", - "DOWEL", "CALMS", "BITCH", "SCOWL", "GULPS", "CODED", "WAVER", "MASON", "LOBES", - "EBONY", "FLAIL", "ISLES", "CLODS", "DAZED", "ADEPT", "OOZED", "SEDAN", "CLAYS", - "WARTS", "KETCH", "SKUNK", "MANES", "ADORE", "SNEER", "MANGO", "FIORD", "FLORA", - "ROOMY", "MINKS", "THAWS", "WATTS", "FREER", "EXULT", "PLUSH", "PALED", "TWAIN", - "CLINK", "SCAMP", "PAWED", "GROPE", "BRAVO", "GABLE", "STINK", "SEVER", "WANED", - "RARER", "REGAL", "WARDS", "FAWNS", "BABES", "UNIFY", "AMEND", "OAKEN", "GLADE", - "VISOR", "HEFTY", "NINES", "THROB", "PECAN", "BUTTS", "PENCE", "SILLS", "JAILS", - "FLYER", "SABER", "NOMAD", "MITER", "BEEPS", "DOMED", "GULFS", "CURBS", "HEATH", - "MOORS", "AORTA", "LARKS", "TANGY", "WRYLY", "CHEEP", "RAGES", "EVADE", "LURES", - "FREAK", "VOGUE", "TUNIC", "SLAMS", "KNITS", "DUMPY", "MANIA", "SPITS", "FIRTH", - "HIKES", "TROTS", "NOSED", "CLANK", "DOGMA", "BLOAT", "BALSA", "GRAFT", "MIDDY", - "STILE", "KEYED", "FINCH", "SPERM", "CHAFF", "WILES", "AMIGO", "COPRA", "AMISS", - "EYING", "TWIRL", "LURCH", "POPES", "CHINS", "SMOCK", "TINES", "GUISE", "GRITS", - "JUNKS", "SHOAL", "CACHE", "TAPIR", "ATOLL", "DEITY", "TOILS", "SPREE", "MOCKS", - "SCANS", "SHORN", "REVEL", "RAVEN", "HOARY", "REELS", "SCUFF", "MIMIC", "WEEDY", - "CORNY", "TRUER", "ROUGE", "EMBER", "FLOES", "TORSO", "WIPES", "EDICT", "SULKY", - "RECUR", "GROIN", "BASTE", "KINKS", "SURER", "PIGGY", "MOLDY", "FRANC", "LIARS", - "INEPT", "GUSTY", "FACET", "JETTY", "EQUIP", "LEPER", "SLINK", "SOARS", "CATER", - "DOWRY", "SIDED", "YEARN", "DECOY", "TABOO", "OVALS", "HEALS", "PLEAS", "BERET", - "SPILT", "GAYLY", "ROVER", "ENDOW", "PYGMY", "CARAT", "ABBEY", "VENTS", "WAKEN", - "CHIMP", "FUMED", "SODAS", "VINYL", "CLOUT", "WADES", "MITES", "SMIRK", "BORES", - "BUNNY", "SURLY", "FROCK", "FORAY", "PURER", "MILKS", "QUERY", "MIRED", "BLARE", - "FROTH", "GRUEL", "NAVEL", "PALER", "PUFFY", "CASKS", "GRIME", "DERBY", "MAMMA", - "GAVEL", "TEDDY", "VOMIT", "MOANS", "ALLOT", "DEFER", "WIELD", "VIPER", "LOUSE", - "ERRED", "HEWED", "ABHOR", "WREST", "WAXEN", "ADAGE", "ARDOR", "STABS", "PORED", - "RONDO", "LOPED", "FISHY", "BIBLE", "HIRES", "FOALS", "FEUDS", "JAMBS", "THUDS", - "JEERS", "KNEAD", "QUIRK", "RUGBY", "EXPEL", "GREYS", "RIGOR", "ESTER", "LYRES", - "ABACK", "GLUES", "LOTUS", "LURID", "RUNGS", "HUTCH", "THYME", "VALET", "TOMMY", - "YOKES", "EPICS", "TRILL", "PIKES", "OZONE", "CAPER", "CHIME", "FREES", "FAMED", - "LEECH", "SMITE", "NEIGH", "ERODE", "ROBED", "HOARD", "SALVE", "CONIC", "GAWKY", - "CRAZE", "JACKS", "GLOAT", "MUSHY", "RUMPS", "FETUS", "WINCE", "PINKS", "SHALT", - "TOOTS", "GLENS", "COOED", "RUSTS", "STEWS", "SHRED", "PARKA", "CHUGS", "WINKS", - "CLOTS", "SHREW", "BOOED", "FILMY", "JUROR", "DENTS", "GUMMY", "GRAYS", "HOOKY", - "BUTTE", "DOGIE", "POLED", "REAMS", "FIFES", "SPANK", "GAYER", "TEPID", "SPOOK", - "TAINT", "FLIRT", "ROGUE", "SPIKY", "OPALS", "MISER", "COCKY", "COYLY", "BALMY", - "SLOSH", "BRAWL", "APHID", "FAKED", "HYDRA", "BRAGS", "CHIDE", "YANKS", "ALLAY", - "VIDEO", "ALTOS", "EASES", "METED", "CHASM", "LONGS", "EXCEL", "TAFFY", "IMPEL", - "SAVOR", "KOALA", "QUAYS", "DAWNS", "PROXY", "CLOVE", "DUETS", "DREGS", "TARDY", - "BRIAR", "GRIMY", "ULTRA", "MEATY", "HALVE", "WAILS", "SUEDE", "MAUVE", "ENVOY", - "ARSON", "COVES", "GOOEY", "BREWS", "SOFAS", "CHUMS", "AMAZE", "ZOOMS", "ABBOT", - "HALOS", "SCOUR", "SUING", "CRIBS", "SAGAS", "ENEMA", "WORDY", "HARPS", "COUPE", - "MOLAR", "FLOPS", "WEEPS", "MINTS", "ASHEN", "FELTS", "ASKEW", "MUNCH", "MEWED", - "DIVAN", "VICES", "JUMBO", "BLOBS", "BLOTS", "SPUNK", "ACRID", "TOPAZ", "CUBED", - "CLANS", "FLEES", "SLURS", "GNAWS", "WELDS", "FORDS", "EMITS", "AGATE", "PUMAS", - "MENDS", "DARKS", "DUKES", "PLIES", "CANNY", "HOOTS", "OOZES", "LAMED", "FOULS", - "CLEFS", "NICKS", "MATED", "SKIMS", "BRUNT", "TUBER", "TINGE", "FATES", "DITTY", - "THINS", "FRETS", "EIDER", "BAYOU", "MULCH", "FASTS", "AMASS", "DAMPS", "MORNS", - "FRIAR", "PALSY", "VISTA", "CROON", "CONCH", "UDDER", "TACOS", "SKITS", "MIKES", - "QUITS", "PREEN", "ASTER", "ADDER", "ELEGY", "PULPY", "SCOWS", "BALED", "HOVEL", - "LAVAS", "CRAVE", "OPTIC", "WELTS", "BUSTS", "KNAVE", "RAZED", "SHINS", "TOTES", - "SCOOT", "DEARS", "CROCK", "MUTES", "TRIMS", "SKEIN", "DOTED", "SHUNS", "VEERS", - "FAKES", "YOKED", "WOOED", "HACKS", "SPRIG", "WANDS", "LULLS", "SEERS", "SNOBS", - "NOOKS", "PINED", "PERKY", "MOOED", "FRILL", "DINES", "BOOZE", "TRIPE", "PRONG", - "DRIPS", "ODDER", "LEVEE", "ANTIC", "SIDLE", "PITHY", "CORKS", "YELPS", "JOKER", - "FLECK", "BUFFS", "SCRAM", "TIERS", "BOGEY", "DOLED", "IRATE", "VALES", "COPED", - "HAILS", "ELUDE", "BULKS", "AIRED", "VYING", "STAGS", "STREW", "COCCI", "PACTS", - "SCABS", "SILOS", "DUSTS", "YODEL", "TERSE", "JADED", "BASER", "JIBES", "FOILS", - "SWAYS", "FORGO", "SLAYS", "PREYS", "TREKS", "QUELL", "PEEKS", "ASSAY", "LURKS", - "EJECT", "BOARS", "TRITE", "BELCH", "GNASH", "WANES", "LUTES", "WHIMS", "DOSED", - "CHEWY", "SNIPE", "UMBRA", "TEEMS", "DOZES", "KELPS", "UPPED", "BRAWN", "DOPED", - "SHUSH", "RINDS", "SLUSH", "MORON", "VOILE", "WOKEN", "FJORD", "SHEIK", "JESTS", - "KAYAK", "SLEWS", "TOTED", "SANER", "DRAPE", "PATTY", "RAVES", "SULFA", "GRIST", - "SKIED", "VIXEN", "CIVET", "VOUCH", "TIARA", "HOMEY", "MOPED", "RUNTS", "SERGE", - "KINKY", "RILLS", "CORNS", "BRATS", "PRIES", "AMBLE", "FRIES", "LOONS", "TSARS", - "DATUM", "MUSKY", "PIGMY", "GNOME", "RAVEL", "OVULE", "ICILY", "LIKEN", "LEMUR", - "FRAYS", "SILTS", "SIFTS", "PLODS", "RAMPS", "TRESS", "EARLS", "DUDES", "WAIVE", - "KARAT", "JOLTS", "PEONS", "BEERS", "HORNY", "PALES", "WREAK", "LAIRS", "LYNCH", - "STANK", "SWOON", "IDLER", "ABORT", "BLITZ", "ENSUE", "ATONE", "BINGO", "ROVES", - "KILTS", "SCALD", "ADIOS", "CYNIC", "DULLS", "MEMOS", "ELFIN", "DALES", "PEELS", - "PEALS", "BARES", "SINUS", "CRONE", "SABLE", "HINDS", "SHIRK", "ENROL", "WILTS", - "ROAMS", "DUPED", "CYSTS", "MITTS", "SAFES", "SPATS", "COOPS", "FILET", "KNELL", - "REFIT", "COVEY", "PUNKS", "KILNS", "FITLY", "ABATE", "TALCS", "HEEDS", "DUELS", - "WANLY", "RUFFS", "GAUSS", "LAPEL", "JAUNT", "WHELP", "CLEAT", "GAUZY", "DIRGE", - "EDITS", "WORMY", "MOATS", "SMEAR", "PRODS", "BOWEL", "FRISK", "VESTS", "BAYED", - "RASPS", "TAMES", "DELVE", "EMBED", "BEFIT", "WAFER", "CEDED", "NOVAS", "FEIGN", - "SPEWS", "LARCH", "HUFFS", "DOLES", "MAMAS", "HULKS", "PRIED", "BRIMS", "IRKED", - "ASPIC", "SWIPE", "MEALY", "SKIMP", "BLUER", "SLAKE", "DOWDY", "PENIS", "BRAYS", - "PUPAS", "EGRET", "FLUNK", "PHLOX", "GRIPE", "PEONY", "DOUSE", "BLURS", "DARNS", - "SLUNK", "LEFTS", "CHATS", "INANE", "VIALS", "STILT", "RINKS", "WOOFS", "WOWED", - "BONGS", "FROND", "INGOT", "EVICT", "SINGE", "SHYER", "FLIED", "SLOPS", "DOLTS", - "DROOL", "DELLS", "WHELK", "HIPPY", "FETED", "ETHER", "COCOS", "HIVES", "JIBED", - "MAZES", "TRIOS", "SIRUP", "SQUAB", "LATHS", "LEERS", "PASTA", "RIFTS", "LOPES", - "ALIAS", "WHIRS", "DICED", "SLAGS", "LODES", "FOXED", "IDLED", "PROWS", "PLAIT", - "MALTS", "CHAFE", "COWER", "TOYED", "CHEFS", "KEELS", "STIES", "RACER", "ETUDE", - "SUCKS", "SULKS", "MICAS", "CZARS", "COPSE", "AILED", "ABLER", "RABID", "GOLDS", - "CROUP", "SNAKY", "VISAS", "PALLS", "MOPES", "BONED", "WISPY", "RAVED", "SWAPS", - "JUNKY", "DOILY", "PAWNS", "TAMER", "POACH", "BAITS", "DAMNS", "GUMBO", "DAUNT", - "PRANK", "HUNKS", "BUXOM", "HERES", "HONKS", "STOWS", "UNBAR", "IDLES", "ROUTS", - "SAGES", "GOADS", "REMIT", "COPES", "DEIGN", "CULLS", "GIRDS", "HAVES", "LUCKS", - "STUNK", "DODOS", "SHAMS", "SNUBS", "ICONS", "USURP", "DOOMS", "HELLS", "SOLED", - "COMAS", "PAVES", "MATHS", "PERKS", "LIMPS", "WOMBS", "BLURB", "DAUBS", "COKES", - "SOURS", "STUNS", "CASED", "MUSTS", "COEDS", "COWED", "APING", "ZONED", "RUMMY", - "FETES", "SKULK", "QUAFF", "RAJAH", "DEANS", "REAPS", "GALAS", "TILLS", "ROVED", - "KUDOS", "TONED", "PARED", "SCULL", "VEXES", "PUNTS", "SNOOP", "BAILS", "DAMES", - "HAZES", "LORES", "MARTS", "VOIDS", "AMEBA", "RAKES", "ADZES", "HARMS", "REARS", - "SATYR", "SWILL", "HEXES", "COLIC", "LEEKS", "HURLS", "YOWLS", "IVIES", "PLOPS", - "MUSKS", "PAPAW", "JELLS", "BUSED", "CRUET", "BIDED", "FILCH", "ZESTS", "ROOKS", - "LAXLY", "RENDS", "LOAMS", "BASKS", "SIRES", "CARPS", "POKEY", "FLITS", "MUSES", - "BAWLS", "SHUCK", "VILER", "LISPS", "PEEPS", "SORER", "LOLLS", "PRUDE", "DIKED", - "FLOSS", "FLOGS", "SCUMS", "DOPES", "BOGIE", "PINKY", "LEAFS", "TUBAS", "SCADS", - "LOWED", "YESES", "BIKED", "QUALM", "EVENS", "CANED", "GAWKS", "WHITS", "WOOLY", - "GLUTS", "ROMPS", "BESTS", "DUNCE", "CRONY", "JOIST", "TUNAS", "BONER", "MALLS", - "PARCH", "AVERS", "CRAMS", "PARES", "DALLY", "BIGOT", "KALES", "FLAYS", "LEACH", - "GUSHY", "POOCH", "HUGER", "SLYER", "GOLFS", "MIRES", "FLUES", "LOAFS", "ARCED", - "ACNES", "NEONS", "FIEFS", "DINTS", "DAZES", "POUTS", "CORED", "YULES", "LILTS", - "BEEFS", "MUTTS", "FELLS", "COWLS", "SPUDS", "LAMES", "JAWED", "DUPES", "DEADS", - "BYLAW", "NOONS", "NIFTY", "CLUED", "VIREO", "GAPES", "METES", "CUTER", "MAIMS", - "DROLL", "CUPID", "MAULS", "SEDGE", "PAPAS", "WHEYS", "EKING", "LOOTS", "HILTS", - "MEOWS", "BEAUS", "DICES", "PEPPY", "RIPER", "FOGEY", "GISTS", "YOGAS", "GILTS", - "SKEWS", "CEDES", "ZEALS", "ALUMS", "OKAYS", "ELOPE", "GRUMP", "WAFTS", "SOOTS", - "BLIMP", "HEFTS", "MULLS", "HOSED", "CRESS", "DOFFS", "RUDER", "PIXIE", "WAIFS", - "OUSTS", "PUCKS", "BIERS", "GULCH", "SUETS", "HOBOS", "LINTS", "BRANS", "TEALS", - "GARBS", "PEWEE", "HELMS", "TURFS", "QUIPS", "WENDS", "BANES", "NAPES", "ICIER", - "SWATS", "BAGEL", "HEXED", "OGRES", "GONER", "GILDS", "PYRES", "LARDS", "BIDES", - "PAGED", "TALON", "FLOUT", "MEDIC", "VEALS", "PUTTS", "DIRKS", "DOTES", "TIPPY", - "BLURT", "PITHS", "ACING", "BARER", "WHETS", "GAITS", "WOOLS", "DUNKS", "HEROS", - "SWABS", "DIRTS", "JUTES", "HEMPS", "SURFS", "OKAPI", "CHOWS", "SHOOS", "DUSKS", - "PARRY", "DECAL", "FURLS", "CILIA", "SEARS", "NOVAE", "MURKS", "WARPS", "SLUES", - "LAMER", "SARIS", "WEANS", "PURRS", "DILLS", "TOGAS", "NEWTS", "MEANY", "BUNTS", - "RAZES", "GOONS", "WICKS", "RUSES", "VENDS", "GEODE", "DRAKE", "JUDOS", "LOFTS", - "PULPS", "LAUDS", "MUCKS", "VISES", "MOCHA", "OILED", "ROMAN", "ETHYL", "GOTTA", - "FUGUE", "SMACK", "GOURD", "BUMPY", "RADIX", "FATTY", "BORAX", "CUBIT", "CACTI", - "GAMMA", "FOCAL", "AVAIL", "PAPAL", "GOLLY", "ELITE", "VERSA", "BILLY", "ADIEU", - "ANNUM", "HOWDY", "RHINO", "NORMS", "BOBBY", "AXIOM", "SETUP", "YOLKS", "TERNS", - "MIXER", "GENRE", "KNOLL", "ABODE", "JUNTA", "GORGE", "COMBO", "ALPHA", "OVERT", - "KINDA", "SPELT", "PRICK", "NOBLY", "EPHOD", "AUDIO", "MODAL", "VELDT", "WARTY", - "FLUKE", "BONNY", "BREAM", "ROSIN", "BOLLS", "DOERS", "DOWNS", "BEADY", "MOTIF", - "HUMPH", "FELLA", "MOULD", "CREPE", "KERNS", "ALOHA", "GLYPH", "AZURE", "RISER", - "BLEST", "LOCUS", "LUMPY", "BERYL", "WANNA", "BRIER", "TUNER", "ROWDY", "MURAL", - "TIMER", "CANST", "KRILL", "QUOTH", "LEMME", "TRIAD", "TENON", "AMPLY", "DEEPS", - "PADRE", "LEANT", "PACER", "OCTAL", "DOLLY", "TRANS", "SUMAC", "FOAMY", "LOLLY", - "GIVER", "QUIPU", "CODEX", "MANNA", "UNWED", "VODKA", "FERNY", "SALON", "DUPLE", - "BORON", "REVUE", "CRIER", "ALACK", "INTER", "DILLY", "WHIST", "CULTS", "SPAKE", - "RESET", "LOESS", "DECOR", "MOVER", "VERVE", "ETHIC", "GAMUT", "LINGO", "DUNNO", - "ALIGN", "SISSY", "INCUR", "REEDY", "AVANT", "PIPER", "WAXER", "CALYX", "BASIL", - "COONS", "SEINE", "PINEY", "LEMMA", "TRAMS", "WINCH", "WHIRR", "SAITH", "IONIC", - "HEADY", "HAREM", "TUMMY", "SALLY", "SHIED", "DROSS", "FARAD", "SAVER", "TILDE", - "JINGO", "BOWER", "SERIF", "FACTO", "BELLE", "INSET", "BOGUS", "CAVED", "FORTE", - "SOOTY", "BONGO", "TOVES", "CREDO", "BASAL", "YELLA", "AGLOW", "GLEAN", "GUSTO", - "HYMEN", "ETHOS", "TERRA", "BRASH", "SCRIP", "SWASH", "ALEPH", "TINNY", "ITCHY", - "WANTA", "TRICE", "JOWLS", "GONGS", "GARDE", "BORIC", "TWILL", "SOWER", "HENRY", - "AWASH", "LIBEL", "SPURN", "SABRE", "REBUT", "PENAL", "OBESE", "SONNY", "QUIRT", - "MEBBE", "TACIT", "GREEK", "XENON", "HULLO", "PIQUE", "ROGER", "NEGRO", "HADST", - "GECKO", "BEGET", "UNCUT", "ALOES", "LOUIS", "QUINT", "CLUNK", "RAPED", "SALVO", - "DIODE", "MATEY", "HERTZ", "XYLEM", "KIOSK", "APACE", "CAWED", "PETER", "WENCH", - "COHOS", "SORTA", "GAMBA", "BYTES", "TANGO", "NUTTY", "AXIAL", "ALECK", "NATAL", - "CLOMP", "GORED", "SIREE", "BANDY", "GUNNY", "RUNIC", "WHIZZ", "RUPEE", "FATED", - "WIPER", "BARDS", "BRINY", "STAID", "HOCKS", "OCHRE", "YUMMY", "GENTS", "SOUPY", - "ROPER", "SWATH", "CAMEO", "EDGER", "SPATE", "GIMME", "EBBED", "BREVE", "THETA", - "DEEMS", "DYKES", "SERVO", "TELLY", "TABBY", "TARES", "BLOCS", "WELCH", "GHOUL", - "VITAE", "CUMIN", "DINKY", "BRONC", "TABOR", "TEENY", "COMER", "BORER", "SIRED", - "PRIVY", "MAMMY", "DEARY", "GYROS", "SPRIT", "CONGA", "QUIRE", "THUGS", "FUROR", - "BLOKE", "RUNES", "BAWDY", "CADRE", "TOXIN", "ANNUL", "EGGED", "ANION", "NODES", - "PICKY", "STEIN", "JELLO", "AUDIT", "ECHOS", "FAGOT", "LETUP", "EYRIE", "FOUNT", - "CAPED", "AXONS", "AMUCK", "BANAL", "RILED", "PETIT", "UMBER", "MILER", "FIBRE", - "AGAVE", "BATED", "BILGE", "VITRO", "FEINT", "PUDGY", "MATER", "MANIC", "UMPED", - "PESKY", "STREP", "SLURP", "PYLON", "PUREE", "CARET", "TEMPS", "NEWEL", "YAWNS", - "SEEDY", "TREED", "COUPS", "RANGY", "BRADS", "MANGY", "LONER", "CIRCA", "TIBIA", - "AFOUL", "MOMMY", "TITER", "CARNE", "KOOKY", "MOTES", "AMITY", "SUAVE", "HIPPO", - "CURVY", "SAMBA", "NEWSY", "ANISE", "IMAMS", "TULLE", "AWAYS", "LIVEN", "HALLO", - "WALES", "OPTED", "CANTO", "IDYLL", "BODES", "CURIO", "WRACK", "HIKER", "CHIVE", - "YOKEL", "DOTTY", "DEMUR", "CUSPS", "SPECS", "QUADS", "LAITY", "TONER", "DECRY", - "WRITS", "SAUTE", "CLACK", "AUGHT", "LOGOS", "TIPSY", "NATTY", "DUCAL", "BIDET", - "BULGY", "METRE", "LUSTS", "UNARY", "GOETH", "BALER", "SITED", "SHIES", "HASPS", - "BRUNG", "HOLED", "SWANK", "LOOKY", "MELEE", "HUFFY", "LOAMY", "PIMPS", "TITAN", - "BINGE", "SHUNT", "FEMUR", "LIBRA", "SEDER", "HONED", "ANNAS", "COYPU", "SHIMS", - "ZOWIE", "JIHAD", "SAVVY", "NADIR", "BASSO", "MONIC", "MANED", "MOUSY", "OMEGA", - "LAVER", "PRIMA", "PICAS", "FOLIO", "MECCA", "REALS", "TROTH", "TESTY", "BALKY", - "CRIMP", "CHINK", "ABETS", "SPLAT", "ABACI", "VAUNT", "CUTIE", "PASTY", "MORAY", - "LEVIS", "RATTY", "ISLET", "JOUST", "MOTET", "VIRAL", "NUKES", "GRADS", "COMFY", - "VOILA", "WOOZY", "BLUED", "WHOMP", "SWARD", "METRO", "SKEET", "CHINE", "AERIE", - "BOWIE", "TUBBY", "EMIRS", "COATI", "UNZIP", "SLOBS", "TRIKE", "FUNKY", "DUCAT", - "DEWEY", "SKOAL", "WADIS", "OOMPH", "TAKER", "MINIM", "GETUP", "STOIC", "SYNOD", - "RUNTY", "FLYBY", "BRAZE", "INLAY", "VENUE", "LOUTS", "PEATY", "ORLON", "HUMPY", - "RADON", "BEAUT", "RASPY", "UNFED", "CRICK", "NAPPY", "VIZOR", "YIPES", "REBUS", - "DIVOT", "KIWIS", "VETCH", "SQUIB", "SITAR", "KIDDO", "DYERS", "COTTA", "MATZO", - "LAGER", "ZEBUS", "CRASS", "DACHA", "KNEED", "DICTA", "FAKIR", "KNURL", "RUNNY", - "UNPIN", "JULEP", "GLOBS", "NUDES", "SUSHI", "TACKY", "STOKE", "KAPUT", "BUTCH", - "HULAS", "CROFT", "ACHOO", "GENII", "NODAL", "OUTGO", "SPIEL", "VIOLS", "FETID", - "CAGEY", "FUDGY", "EPOXY", "LEGGY", "HANKY", "LAPIS", "FELON", "BEEFY", "COOTS", - "MELBA", "CADDY", "SEGUE", "BETEL", "FRIZZ", "DREAR", "KOOKS", "TURBO", "HOAGY", - "MOULT", "HELIX", "ZONAL", "ARIAS", "NOSEY", "PAEAN", "LACEY", "BANNS", "SWAIN", - "FRYER", "RETCH", "TENET", "GIGAS", "WHINY", "OGLED", "RUMEN", "BEGOT", "CRUSE", - "ABUTS", "RIVEN", "BALKS", "SINES", "SIGMA", "ABASE", "ENNUI", "GORES", "UNSET", - "AUGUR", "SATED", "ODIUM", "LATIN", "DINGS", "MOIRE", "SCION", "HENNA", "KRAUT", - "DICKS", "LIFER", "PRIGS", "BEBOP", "GAGES", "GAZER", "FANNY", "GIBES", "AURAL", - "TEMPI", "HOOCH", "RAPES", "SNUCK", "HARTS", "TECHS", "EMEND", "NINNY", "GUAVA", - "SCARP", "LIEGE", "TUFTY", "SEPIA", "TOMES", "CAROB", "EMCEE", "PRAMS", "POSER", - "VERSO", "HUBBA", "JOULE", "BAIZE", "BLIPS", "SCRIM", "CUBBY", "CLAVE", "WINOS", - "REARM", "LIENS", "LUMEN", "CHUMP", "NANNY", "TRUMP", "FICHU", "CHOMP", "HOMOS", - "PURTY", "MASER", "WOOSH", "PATSY", "SHILL", "RUSKS", "AVAST", "SWAMI", "BODED", - "AHHHH", "LOBED", "NATCH", "SHISH", "TANSY", "SNOOT", "PAYER", "ALTHO", "SAPPY", - "LAXER", "HUBBY", "AEGIS", "RILES", "DITTO", "JAZZY", "DINGO", "QUASI", "SEPTA", - "PEAKY", "LORRY", "HEERD", "BITTY", "PAYEE", "SEAMY", "APSES", "IMBUE", "BELIE", - "CHARY", "SPOOF", "PHYLA", "CLIME", "BABEL", "WACKY", "SUMPS", "SKIDS", "KHANS", - "CRYPT", "INURE", "NONCE", "OUTEN", "FAIRE", "HOOEY", "ANOLE", "KAZOO", "CALVE", - "LIMBO", "ARGOT", "DUCKY", "FAKER", "VIBES", "GASSY", "UNLIT", "NERVY", "FEMME", - "BITER", "FICHE", "BOORS", "GAFFE", "SAXES", "RECAP", "SYNCH", "FACIE", "DICEY", - "OUIJA", "HEWER", "LEGIT", "GURUS", "EDIFY", "TWEAK", "CARON", "TYPOS", "RERUN", - "POLLY", "SURDS", "HAMZA", "NULLS", "HATER", "LEFTY", "MOGUL", "MAFIA", "DEBUG", - "PATES", "BLABS", "SPLAY", "TALUS", "PORNO", "MOOLA", "NIXED", "KILOS", "SNIDE", - "HORSY", "GESSO", "JAGGY", "TROVE", "NIXES", "CREEL", "PATER", "IOTAS", "CADGE", - "SKYED", "HOKUM", "FURZE", "ANKHS", "CURIE", "NUTSY", "HILUM", "REMIX", "ANGST", - "BURLS", "JIMMY", "VEINY", "TRYST", "CODON", "BEFOG", "GAMED", "FLUME", "AXMAN", - "DOOZY", "LUBES", "RHEAS", "BOZOS", "BUTYL", "KELLY", "MYNAH", "JOCKS", "DONUT", - "AVIAN", "WURST", "CHOCK", "QUASH", "QUALS", "HAYED", "BOMBE", "CUSHY", "SPACY", - "PUKED", "LEERY", "THEWS", "PRINK", "AMENS", "TESLA", "INTRO", "FIVER", "FRUMP", - "CAPOS", "OPINE", "CODER", "NAMER", "JOWLY", "PUKES", "HALED", "CHARD", "DUFFS", - "BRUIN", "REUSE", "WHANG", "TOONS", "FRATS", "SILTY", "TELEX", "CUTUP", "NISEI", - "NEATO", "DECAF", "SOFTY", "BIMBO", "ADLIB", "LOONY", "SHOED", "AGUES", "PEEVE", - "NOWAY", "GAMEY", "SARGE", "RERAN", "EPACT", "POTTY", "CONED", "UPEND", "NARCO", - "IKATS", "WHORL", "JINKS", "TIZZY", "WEEPY", "POSIT", "MARGE", "VEGAN", "CLOPS", - "NUMBS", "REEKS", "RUBES", "ROWER", "BIPED", "TIFFS", "HOCUS", "HAMMY", "BUNCO", - "FIXIT", "TYKES", "CHAWS", "YUCKY", "HOKEY", "RESEW", "MAVEN", "ADMAN", "SCUZZ", - "SLOGS", "SOUSE", "NACHO", "MIMED", "MELDS", "BOFFO", "DEBIT", "PINUP", "VAGUS", - "GULAG", "RANDY", "BOSUN", "EDUCE", "FAXES", "AURAS", "PESTO", "ANTSY", "BETAS", - "FIZZY", "DORKY", "SNITS", "MOXIE", "THANE", "MYLAR", "NOBBY", "GAMIN", "GOUTY", - "ESSES", "GOYIM", "PANED", "DRUID", "JADES", "REHAB", "GOFER", "TZARS", "OCTET", - "HOMED", "SOCKO", "DORKS", "EARED", "ANTED", "ELIDE", "FAZES", "OXBOW", "DOWSE", - "SITUS", "MACAW", "SCONE", "DRILY", "HYPER", "SALSA", "MOOCH", "GATED", "UNJAM", - "LIPID", "MITRE", "VENAL", "KNISH", "RITZY", "DIVAS", "TORUS", "MANGE", "DIMER", - "RECUT", "MESON", "WINED", "FENDS", "PHAGE", "FIATS", "CAULK", "CAVIL", "PANTY", - "ROANS", "BILKS", "HONES", "BOTCH", "ESTOP", "SULLY", "SOOTH", "GELDS", "AHOLD", - "RAPER", "PAGER", "FIXER", "INFIX", "HICKS", "TUXES", "PLEBE", "TWITS", "ABASH", - "TWIXT", "WACKO", "PRIMP", "NABLA", "GIRTS", "MIFFS", "EMOTE", "XEROX", "REBID", - "SHAHS", "RUTTY", "GROUT", "GRIFT", "DEIFY", "BIDDY", "KOPEK", "SEMIS", "BRIES", - "ACMES", "PITON", "HUSSY", "TORTS", "DISCO", "WHORE", "BOOZY", "GIBED", "VAMPS", - "AMOUR", "SOPPY", "GONZO", "DURST", "WADER", "TUTUS", "PERMS", "CATTY", "GLITZ", - "BRIGS", "NERDS", "BARMY", "GIZMO", "OWLET", "SAYER", "MOLLS", "SHARD", "WHOPS", - "COMPS", "CORER", "COLAS", "MATTE", "DROID", "PLOYS", "VAPID", "CAIRN", "DEISM", - "MIXUP", "YIKES", "PROSY", "RAKER", "FLUBS", "WHISH", "REIFY", "CRAPS", "SHAGS", - "CLONE", "HAZED", "MACHO", "RECTO", "REFIX", "DRAMS", "BIKER", "AQUAS", "PORKY", - "DOYEN", "EXUDE", "GOOFS", "DIVVY", "NOELS", "JIVED", "HULKY", "CAGER", "HARPY", - "OLDIE", "VIVAS", "ADMIX", "CODAS", "ZILCH", "DEIST", "ORCAS", "RETRO", "PILAF", - "PARSE", "RANTS", "ZINGY", "TODDY", "CHIFF", "MICRO", "VEEPS", "GIRLY", "NEXUS", - "DEMOS", "BIBBS", "ANTES", "LULUS", "GNARL", "ZIPPY", "IVIED", "EPEES", "WIMPS", - "TROMP", "GRAIL", "YOYOS", "POUFS", "HALES", "ROUST", "CABAL", "RAWER", "PAMPA", - "MOSEY", "KEFIR", "BURGS", "UNMET", "CUSPY", "BOOBS", "BOONS", "HYPES", "DYNES", - "NARDS", "LANAI", "YOGIS", "SEPAL", "QUARK", "TOKED", "PRATE", "AYINS", "HAWED", - "SWIGS", "VITAS", "TOKER", "DOPER", "BOSSA", "LINTY", "FOIST", "MONDO", "STASH", - "KAYOS", "TWERP", "ZESTY", "CAPON", "WIMPY", "REWED", "FUNGO", "TAROT", "FROSH", - "KABOB", "PINKO", "REDID", "MIMEO", "HEIST", "TARPS", "LAMAS", "SUTRA", "DINAR", - "WHAMS", "BUSTY", "SPAYS", "MAMBO", "NABOB", "PREPS", "ODOUR", "CABBY", "CONKS", - "SLUFF", "DADOS", "HOURI", "SWART", "BALMS", "GUTSY", "FAXED", "EGADS", "PUSHY", - "RETRY", "AGORA", "DRUBS", "DAFFY", "CHITS", "MUFTI", "KARMA", "LOTTO", "TOFFS", - "BURPS", "DEUCE", "ZINGS", "KAPPA", "CLADS", "DOGGY", "DUPER", "SCAMS", "OGLER", - "MIMES", "THROE", "ZETAS", "WALED", "PROMO", "BLATS", "MUFFS", "OINKS", "VIAND", - "COSET", "FINKS", "FADDY", "MINIS", "SNAFU", "SAUNA", "USURY", "MUXES", "CRAWS", - "STATS", "CONDO", "COXES", "LOOPY", "DORMS", "ASCOT", "DIPPY", "EXECS", "DOPEY", - "ENVOI", "UMPTY", "GISMO", "FAZED", "STROP", "JIVES", "SLIMS", "BATIK", "PINGS", - "SONLY", "LEGGO", "PEKOE", "PRAWN", "LUAUS", "CAMPY", "OODLE", "PREXY", "PROMS", - "TOUTS", "OGLES", "TWEET", "TOADY", "NAIAD", "HIDER", "NUKED", "FATSO", "SLUTS", - "OBITS", "NARCS", "TYROS", "DELIS", "WOOER", "HYPED", "POSET", "BYWAY", "TEXAS", - "SCROD", "AVOWS", "FUTON", "TORTE", "TUPLE", "CAROM", "KEBAB", "TAMPS", "JILTS", - "DUALS", "ARTSY", "REPRO", "MODEM", "TOPED", "PSYCH", "SICKO", "KLUTZ", "TARNS", - "COXED", "DRAYS", "CLOYS", "ANDED", "PIKER", "AIMER", "SURAS", "LIMOS", "FLACK", - "HAPAX", "DUTCH", "MUCKY", "SHIRE", "KLIEG", "STAPH", "LAYUP", "TOKES", "AXING", - "TOPER", "DUVET", "COWRY", "PROFS", "BLAHS", "ADDLE", "SUDSY", "BATTY", "COIFS", - "SUETY", "GABBY", "HAFTA", "PITAS", "GOUDA", "DEICE", "TAUPE", "TOPES", "DUCHY", - "NITRO", "CARNY", "LIMEY", "ORALS", "HIRER", "TAXER", "ROILS", "RUBLE", "ELATE", - "DOLOR", "WRYER", "SNOTS", "QUAIS", "COKED", "GIMEL", "GORSE", "MINAS", "GOEST", - "AGAPE", "MANTA", "JINGS", "ILIAC", "ADMEN", "OFFEN", "CILLS", "OFFAL", "LOTTA", - "BOLAS", "THWAP", "ALWAY", "BOGGY", "DONNA", "LOCOS", "BELAY", "GLUEY", "BITSY", - "MIMSY", "HILAR", "OUTTA", "VROOM", "FETAL", "RATHS", "RENAL", "DYADS", "CROCS", - "VIRES", "CULPA", "KIVAS", "FEIST", "TEATS", "THATS", "YAWLS", "WHENS", "ABACA", - "OHHHH", "APHIS", "FUSTY", "ECLAT", "PERDU", "MAYST", "EXEAT", "MOLLY", "SUPRA", - "WETLY", "PLASM", "BUFFA", "SEMEN", "PUKKA", "TAGUA", "PARAS", "STOAT", "SECCO", - "CARTE", "HAUTE", "MOLAL", "SHADS", "FORMA", "OVOID", "PIONS", "MODUS", "BUENO", - "RHEUM", "SCURF", "PARER", "EPHAH", "DOEST", "SPRUE", "FLAMS", "MOLTO", "DIETH", - "CHOOS", "MIKED", "BRONX", "GOOPY", "BALLY", "PLUMY", "MOONY", "MORTS", "YOURN", - "BIPOD", "SPUME", "ALGAL", "AMBIT", "MUCHO", "SPUED", "DOZER", "HARUM", "GROAT", - "SKINT", "LAUDE", "THRUM", "PAPPY", "ONCET", "RIMED", "GIGUE", "LIMED", "PLEIN", - "REDLY", "HUMPF", "LITES", "SEEST", "GREBE", "ABSIT", "THANX", "PSHAW", "YAWPS", - "PLATS", "PAYED", "AREAL", "TILTH", "YOUSE", "GWINE", "THEES", "WATSA", "LENTO", - "SPITZ", "YAWED", "GIPSY", "SPRAT", "CORNU", "AMAHS", "BLOWY", "WAHOO", "LUBRA", - "MECUM", "WHOOO", "COQUI", "SABRA", "EDEMA", "MRADS", "DICOT", "ASTRO", "KITED", - "OUZEL", "DIDOS", "GRATA", "BONNE", "AXMEN", "KLUNK", "SUMMA", "LAVES", "PURLS", - "YAWNY", "TEARY", "MASSE", "LARGO", "BAZAR", "PSSST", "SYLPH", "LULAB", "TOQUE", - "FUGIT", "PLUNK", "ORTHO", "LUCRE", "COOCH", "WHIPT", "FOLKY", "TYRES", "WHEEE", - "CORKY", "INJUN", "SOLON", "DIDOT", "KERFS", "RAYED", "WASSA", "CHILE", "BEGAT", - "NIPPY", "LITRE", "MAGNA", "REBOX", "HYDRO", "MILCH", "BRENT", "GYVES", "LAZED", - "FEUED", "MAVIS", "INAPT", "BAULK", "CASUS", "SCRUM", "WISED", "FOSSA", "DOWER", - "KYRIE", "BHOYS", "SCUSE", "FEUAR", "OHMIC", "JUSTE", "UKASE", "BEAUX", "TUSKY", - "ORATE", "MUSTA", "LARDY", "INTRA", "QUIFF", "EPSOM", "NEATH", "OCHER", "TARED", - "HOMME", "MEZZO", "CORMS", "PSOAS", "BEAKY", "TERRY", "INFRA", "SPIVS", "TUANS", - "BELLI", "BERGS", "ANIMA", "WEIRS", "MAHUA", "SCOPS", "MANSE", "TITRE", "CURIA", - "KEBOB", "CYCAD", "TALKY", "FUCKS", "TAPIS", "AMIDE", "DOLCE", "SLOES", "JAKES", - "RUSSE", "BLASH", "TUTTI", "PRUTA", "PANGA", "BLEBS", "TENCH", "SWARF", "HEREM", - "MISSY", "MERSE", "PAWKY", "LIMEN", "VIVRE", "CHERT", "UNSEE", "TIROS", "BRACK", - "FOOTS", "WELSH", "FOSSE", "KNOPS", "ILEUM", "NOIRE", "FIRMA", "PODGY", "LAIRD", - "THUNK", "SHUTE", "ROWAN", "SHOJI", "POESY", "UNCAP", "FAMES", "GLEES", "COSTA", - "TURPS", "FORES", "SOLUM", "IMAGO", "BYRES", "FONDU", "CONEY", "POLIS", "DICTU", - "KRAAL", "SHERD", "MUMBO", "WROTH", "CHARS", "UNBOX", "VACUO", "SLUED", "WEEST", - "HADES", "WILED", "SYNCS", "MUSER", "EXCON", "HOARS", "SIBYL", "PASSE", "JOEYS", - "LOTSA", "LEPTA", "SHAYS", "BOCKS", "ENDUE", "DARER", "NONES", "ILEUS", "PLASH", - "BUSBY", "WHEAL", "BUFFO", "YOBBO", "BILES", "POXES", "ROOTY", "LICIT", "TERCE", - "BROMO", "HAYEY", "DWEEB", "IMBED", "SARAN", "BRUIT", "PUNKY", "SOFTS", "BIFFS", - "LOPPY", "AGARS", "AQUAE", "LIVRE", "BIOME", "BUNDS", "SHEWS", "DIEMS", "GINNY", - "DEGUM", "POLOS", "DESEX", "UNMAN", "DUNGY", "VITAM", "WEDGY", "GLEBE", "APERS", - "RIDGY", "ROIDS", "WIFEY", "VAPES", "WHOAS", "BUNKO", "YOLKY", "ULNAS", "REEKY", - "BODGE", "BRANT", "DAVIT", "DEQUE", "LIKER", "JENNY", "TACTS", "FULLS", "TREAP", - "LIGNE", "ACKED", "REFRY", "VOWER", "AARGH", "CHURL", "MOMMA", "GAOLS", "WHUMP", - "ARRAS", "MARLS", "TILER", "GROGS", "MEMES", "MIDIS", "TIDED", "HALER", "DUCES", - "TWINY", "POSTE", "UNRIG", "PRISE", "DRABS", "QUIDS", "FACER", "SPIER", "BARIC", - "GEOID", "REMAP", "TRIER", "GUNKS", "STENO", "STOMA", "AIRER", "OVATE", "TORAH", - "APIAN", "SMUTS", "POCKS", "YURTS", "EXURB", "DEFOG", "NUDER", "BOSKY", "NIMBI", - "MOTHY", "JOYED", "LABIA", "PARDS", "JAMMY", "BIGLY", "FAXER", "HOPPY", "NURBS", - "COTES", "DISHY", "VISED", "CELEB", "PISMO", "CASAS", "WITHS", "DODGY", "SCUDI", - "MUNGS", "MUONS", "UREAS", "IOCTL", "UNHIP", "KRONE", "SAGER", "VERST", "EXPAT", - "GRONK", "UVULA", "SHAWM", "BILGY", "BRAES", "CENTO", "WEBBY", "LIPPY", "GAMIC", - "LORDY", "MAZED", "TINGS", "SHOAT", "FAERY", "WIRER", "DIAZO", "CARER", "RATER", - "GREPS", "RENTE", "ZLOTY", "VIERS", "UNAPT", "POOPS", "FECAL", "KEPIS", "TAXON", - "EYERS", "WONTS", "SPINA", "STOAE", "YENTA", "POOEY", "BURET", "JAPAN", "BEDEW", - "HAFTS", "SELFS", "OARED", "HERBY", "PRYER", "OAKUM", "DINKS", "TITTY", "SEPOY", - "PENES", "FUSEE", "WINEY", "GIMPS", "NIHIL", "RILLE", "GIBER", "OUSEL", "UMIAK", - "CUPPY", "HAMES", "SHITS", "AZINE", "GLADS", "TACET", "BUMPH", "COYER", "HONKY", - "GAMER", "GOOKY", "WASPY", "SEDGY", "BENTS", "VARIA", "DJINN", "JUNCO", "PUBIC", - "WILCO", "LAZES", "IDYLS", "LUPUS", "RIVES", "SNOOD", "SCHMO", "SPAZZ", "FINIS", - "NOTER", "PAVAN", "ORBED", "BATES", "PIPET", "BADDY", "GOERS", "SHAKO", "STETS", - "SEBUM", "SEETH", "LOBAR", "RAVER", "AJUGA", "RICED", "VELDS", "DRIBS", "VILLE", - "DHOWS", "UNSEW", "HALMA", "KRONA", "LIMBY", "JIFFS", "TREYS", "BAUDS", "PFFFT", - "MIMER", "PLEBS", "CANER", "JIBER", "CUPPA", "WASHY", "CHUFF", "UNARM", "YUKKY", - "STYES", "WAKER", "FLAKS", "MACES", "RIMES", "GIMPY", "GUANO", "LIRAS", "KAPOK", - "SCUDS", "BWANA", "ORING", "AIDER", "PRIER", "KLUGY", "MONTE", "GOLEM", "VELAR", - "FIRER", "PIETA", "UMBEL", "CAMPO", "UNPEG", "FOVEA", "ABEAM", "BOSON", "ASKER", - "GOTHS", "VOCAB", "VINED", "TROWS", "TIKIS", "LOPER", "INDIE", "BOFFS", "SPANG", - "GRAPY", "TATER", "ICHOR", "KILTY", "LOCHS", "SUPES", "DEGAS", "FLICS", "TORSI", - "BETHS", "WEBER", "RESAW", "LAWNY", "COVEN", "MUJIK", "RELET", "THERM", "HEIGH", - "SHNOR", "TRUED", "ZAYIN", "LIEST", "BARFS", "BASSI", "QOPHS", "ROILY", "FLABS", - "PUNNY", "OKRAS", "HANKS", "DIPSO", "NERFS", "FAUNS", "CALLA", "PSEUD", "LURER", - "MAGUS", "OBEAH", "ATRIA", "TWINK", "PALMY", "POCKY", "PENDS", "RECTA", "PLONK", - "SLAWS", "KEENS", "NICAD", "PONES", "INKER", "WHEWS", "GROKS", "MOSTS", "TREWS", - "ULNAR", "GYPPY", "COCAS", "EXPOS", "ERUCT", "OILER", "VACUA", "DRECK", "DATER", - "ARUMS", "TUBAL", "VOXEL", "DIXIT", "BEERY", "ASSAI", "LADES", "ACTIN", "GHOTI", - "BUZZY", "MEADS", "GRODY", "RIBBY", "CLEWS", "CREME", "EMAIL", "PYXIE", "KULAK", - "BOCCI", "RIVED", "DUDDY", "HOPER", "LAPIN", "WONKS", "PETRI", "PHIAL", "FUGAL", - "HOLON", "BOOMY", "DUOMO", "MUSOS", "SHIER", "HAYER", "PORGY", "HIVED", "LITHO", - "FISTY", "STAGY", "LUVYA", "MARIA", "SMOGS", "ASANA", "YOGIC", "SLOMO", "FAWNY", - "AMINE", "WEFTS", "GONAD", "TWIRP", "BRAVA", "PLYER", "FERMI", "LOGES", "NITER", - "REVET", "UNATE", "GYVED", "TOTTY", "ZAPPY", "HONER", "GIROS", "DICER", "CALKS", - "LUXES", "MONAD", "CRUFT", "QUOIN", "FUMER", "AMPED", "SHLEP", "VINCA", "YAHOO", - "VULVA", "ZOOEY", "DRYAD", "NIXIE", "MOPER", "IAMBS", "LUNES", "NUDIE", "LIMNS", - "WEALS", "NOHOW", "MIAOW", "GOUTS", "MYNAS", "MAZER", "KIKES", "OXEYE", "STOUP", - "JUJUS", "DEBAR", "PUBES", "TAELS", "DEFUN", "RANDS", "BLEAR", "PAVER", "GOOSY", - "SPROG", "OLEOS", "TOFFY", "PAWER", "MACED", "CRITS", "KLUGE", "TUBED", "SAHIB", - "GANEF", "SCATS", "SPUTA", "VANED", "ACNED", "TAXOL", "PLINK", "OWETH", "TRIBS", - "RESAY", "BOULE", "THOUS", "HAPLY", "GLANS", "MAXIS", "BEZEL", "ANTIS", "PORKS", - "QUOIT", "ALKYD", "GLARY", "BEAMY", "HEXAD", "BONKS", "TECUM", "KERBS", "FILAR", - "FRIER", "REDUX", "ABUZZ", "FADER", "SHOER", "COUTH", "TRUES", "GUYED", "GOONY", - "BOOKY", "FUZES", "HURLY", "GENET", "HODAD", "CALIX", "FILER", "PAWLS", "IODIC", - "UTERO", "HENGE", "UNSAY", "LIERS", "PIING", "WEALD", "SEXED", "FOLIC", "POXED", - "CUNTS", "ANILE", "KITHS", "BECKS", "TATTY", "PLENA", "REBAR", "ABLED", "TOYER", - "ATTAR", "TEAKS", "AIOLI", "AWING", "ANENT", "FECES", "REDIP", "WISTS", "PRATS", - "MESNE", "MUTER", "SMURF", "OWEST", "BAHTS", "LOSSY", "FTPED", "HUNKY", "HOERS", - "SLIER", "SICKS", "FATLY", "DELFT", "HIVER", "HIMBO", "PENGO", "BUSKS", "LOXES", - "ZONKS", "ILIUM", "APORT", "IKONS", "MULCT", "REEVE", "CIVVY", "CANNA", "BARFY", - "KAIAK", "SCUDO", "KNOUT", "GAPER", "BHANG", "PEASE", "UTERI", "LASES", "PATEN", - "RASAE", "AXELS", "STOAS", "OMBRE", "STYLI", "GUNKY", "HAZER", "KENAF", "AHOYS", - "AMMOS", "WEENY", "URGER", "KUDZU", "PAREN", "BOLOS", "FETOR", "NITTY", "TECHY", - "LIETH", "SOMAS", "DARKY", "VILLI", "GLUON", "JANES", "CANTS", "FARTS", "SOCLE", - "JINNS", "RUING", "SLILY", "RICER", "HADDA", "WOWEE", "RICES", "NERTS", "CAULS", - "SWIVE", "LILTY", "MICKS", "ARITY", "PASHA", "FINIF", "OINKY", "GUTTY", "TETRA", - "WISES", "WOLDS", "BALDS", "PICOT", "WHATS", "SHIKI", "BUNGS", "SNARF", "LEGOS", - "DUNGS", "STOGY", "BERMS", "TANGS", "VAILS", "ROODS", "MOREL", "SWARE", "ELANS", - "LATUS", "GULES", "RAZER", "DOXIE", "BUENA", "OVERS", "GUTTA", "ZINCS", "NATES", - "KIRKS", "TIKES", "DONEE", "JERRY", "MOHEL", "CEDER", "DOGES", "UNMAP", "FOLIA", - "RAWLY", "SNARK", "TOPOI", "CEILS", "IMMIX", "YORES", "DIEST", "BUBBA", "POMPS", - "FORKY", "TURDY", "LAWZY", "POOHS", "WORTS", "GLOMS", "BEANO", "MULEY", "BARKY", - "TUNNY", "AURIC", "FUNKS", "GAFFS", "CORDY", "CURDY", "LISLE", "TORIC", "SOYAS", - "REMAN", "MUNGY", "CARPY", "APISH", "OATEN", "GAPPY", "AURAE", "BRACT", "ROOKY", - "AXLED", "BURRY", "SIZER", "PROEM", "TURFY", "IMPRO", "MASHY", "MIENS", "NONNY", - "OLIOS", "GROOK", "SATES", "AGLEY", "CORGI", "DASHY", "DOSER", "DILDO", "APSOS", - "XORED", "LAKER", "PLAYA", "SELAH", "MALTY", "DULSE", "FRIGS", "DEMIT", "WHOSO", - "RIALS", "SAWER", "BEDIM", "SNUGS", "FANIN", "AZOIC", "ICERS", "SUERS", "WIZEN", - "KOINE", "TOPOS", "SHIRR", "RIFER", "FERAL", "LADED", "LASED", "TURDS", "SWEDE", - "EASTS", "COZEN", "UNHIT", "PALLY", "AITCH", "SEDUM", "COPER", "RUCHE", "GEEKS", - "SWAGS", "ETEXT", "ALGIN", "OFFED", "NINJA", "HOLER", "DOTER", "TOTER", "BESOT", - "DICUT", "MACER", "PEENS", "PEWIT", "REDOX", "POLER", "YECCH", "FLUKY", "DOETH", - "TWATS", "CRUDS", "BEBUG", "BIDER", "STELE", "HEXER", "WESTS", "GLUER", "PILAU", - "ABAFT", "WHELM", "LACER", "INODE", "TABUS", "GATOR", "CUING", "REFLY", "LUTED", - "CUKES", "BAIRN", "BIGHT", "ARSES", "CRUMP", "LOGGY", "BLINI", "SPOOR", "TOYON", - "HARKS", "WAZOO", "FENNY", "NAVES", "KEYER", "TUFAS", "MORPH", "RAJAS", "TYPAL", - "SPIFF", "OXLIP", "UNBAN", "MUSSY", "FINNY", "RIMER", "LOGIN", "MOLAS", "CIRRI", - "HUZZA", "AGONE", "UNSEX", "UNWON", "PEATS", "TOILE", "ZOMBI", "DEWED", "NOOKY", - "ALKYL", "IXNAY", "DOVEY", "HOLEY", "CUBER", "AMYLS", "PODIA", "CHINO", "APNEA", - "PRIMS", "LYCRA", "JOHNS", "PRIMO", "FATWA", "EGGER", "HEMPY", "SNOOK", "HYING", - "FUZED", "BARMS", "CRINK", "MOOTS", "YERBA", "RHUMB", "UNARC", "DIRER", "MUNGE", - "ELAND", "NARES", "WRIER", "NODDY", "ATILT", "JUKES", "ENDER", "THENS", "UNFIX", - "DOGGO", "ZOOKS", "DIDDY", "SHMOO", "BRUSK", "PREST", "CURER", "PASTS", "KELPY", - "BOCCE", "KICKY", "TAROS", "LINGS", "DICKY", "NERDY", "ABEND", "STELA", "BIGGY", - "LAVED", "BALDY", "PUBIS", "GOOKS", "WONKY", "STIED", "HYPOS", "ASSED", "SPUMY", - "OSIER", "ROBLE", "RUMBA", "BIFFY", "PUPAL" + "ABACK", "ABAFT", "ABASE", "ABATE", "ABBEY", "ABBOT", "ABHOR", "ABIDE", "ABLER", "ABODE", + "ABOUT", "ABOVE", "ABUSE", "ABYSS", "ACHED", "ACHES", "ACIDS", "ACORN", "ACRES", "ACRID", + "ACTED", "ACTOR", "ACUTE", "ADAGE", "ADAPT", "ADDED", "ADDER", "ADEPT", "ADIEU", "ADMIT", + "ADOBE", "ADOPT", "ADORE", "ADORN", "ADULT", "AEGIS", "AEONS", "AFFIX", "AFIRE", "AFOOT", + "AFTER", "AGAIN", "AGAPE", "AGATE", "AGENT", "AGILE", "AGING", "AGLOW", "AGONY", "AGREE", + "AHEAD", "AIDED", "AIDES", "AILED", "AIMED", "AIRED", "AISLE", "ALARM", "ALBUM", "ALDER", + "ALERT", "ALIAS", "ALIBI", "ALIEN", "ALIKE", "ALIVE", "ALLAY", "ALLEY", "ALLOT", "ALLOW", + "ALLOY", "ALOES", "ALOFT", "ALONE", "ALONG", "ALOOF", "ALOUD", "ALPHA", "ALTAR", "ALTER", + "ALTOS", "AMASS", "AMAZE", "AMBER", "AMBLE", "AMEND", "AMIGO", "AMISS", "AMITY", "AMONG", + "AMOUR", "AMPLE", "AMPLY", "AMUSE", "ANGEL", "ANGER", "ANGLE", "ANGRY", "ANGST", "ANIME", + "ANKLE", "ANNEX", "ANNOY", "ANNUL", "ANTES", "ANTIC", "ANVIL", "APACE", "APART", "APING", + "APPAL", "APPLE", "APPLY", "APRON", "APTLY", "AREAS", "ARENA", "ARGUE", "ARISE", "ARMED", + "AROMA", "AROSE", "ARRAY", "ARROW", "ARSON", "ASHEN", "ASHES", "ASIDE", "ASKED", "ASKEW", + "ASPEN", "ASSAY", "ASSES", "ASSET", "ASTER", "ASTIR", "ATLAS", "ATOLL", "ATOMS", "ATONE", + "ATTAR", "ATTIC", "AUDIO", "AUDIT", "AUGER", "AUGHT", "AUGUR", "AUNTS", "AURAS", "AUTOS", + "AVAIL", "AVERS", "AVERT", "AVOID", "AVOWS", "AWAIT", "AWAKE", "AWARD", "AWARE", "AWFUL", + "AWOKE", "AXIOM", "AXLES", "AZURE", "BABEL", "BABES", "BACKS", "BACON", "BADGE", "BADLY", + "BAGGY", "BAITS", "BAIZE", "BAKED", "BAKER", "BALES", "BALLS", "BALMY", "BANAL", "BANDS", + "BANDY", "BANGS", "BANJO", "BANKS", "BANNS", "BARBS", "BARDS", "BARED", "BARGE", "BARKS", + "BARNS", "BARON", "BASAL", "BASED", "BASER", "BASES", "BASIC", "BASIL", "BASIN", "BASIS", + "BASSO", "BASTE", "BATCH", "BATED", "BATHE", "BATHS", "BATON", "BAYOU", "BEACH", "BEADS", + "BEADY", "BEAKS", "BEAMS", "BEANS", "BEARD", "BEARS", "BEAST", "BEAUX", "BEECH", "BEETS", + "BEFIT", "BEGAN", "BEGAT", "BEGET", "BEGIN", "BEGOT", "BEGUN", "BEING", "BELIE", "BELLE", + "BELLS", "BELLY", "BELOW", "BELTS", "BENCH", "BENDS", "BERGS", "BERRY", "BERTH", "BERYL", + "BESET", "BESOM", "BEVEL", "BIBLE", "BIDED", "BIDES", "BIGHT", "BIGOT", "BILGE", "BILLS", + "BILLY", "BINDS", "BIPED", "BIRCH", "BIRDS", "BIRTH", "BISON", "BITCH", "BITES", "BLACK", + "BLADE", "BLAME", "BLAND", "BLANK", "BLARE", "BLAST", "BLAZE", "BLEAK", "BLEAT", "BLEED", + "BLEND", "BLENT", "BLESS", "BLEST", "BLIND", "BLINK", "BLISS", "BLOCK", "BLOCS", "BLOND", + "BLOOD", "BLOOM", "BLOTS", "BLOWN", "BLOWS", "BLUER", "BLUES", "BLUFF", "BLUNT", "BLURT", + "BLUSH", "BOARD", "BOARS", "BOAST", "BOATS", "BODED", "BODES", "BOGGY", "BOGUS", "BOILS", + "BOLES", "BOLTS", "BOMBS", "BONDS", "BONED", "BONES", "BONNY", "BONUS", "BOOBY", "BOOKS", + "BOOMS", "BOONS", "BOORS", "BOOST", "BOOTH", "BOOTS", "BOOTY", "BOOZE", "BORAX", "BORED", + "BORES", "BORNE", "BOSOM", "BOUGH", "BOUND", "BOUTS", "BOWED", "BOWEL", "BOWER", "BOWLS", + "BOXED", "BOXER", "BOXES", "BRACE", "BRAGS", "BRAID", "BRAIN", "BRAKE", "BRAND", "BRASS", + "BRATS", "BRAVE", "BRAVO", "BRAWL", "BRAWN", "BREAD", "BREAK", "BREED", "BRIAR", "BRIBE", + "BRICK", "BRIDE", "BRIEF", "BRIER", "BRIGS", "BRIMS", "BRINE", "BRING", "BRINK", "BRINY", + "BRISK", "BROAD", "BROIL", "BROKE", "BROOD", "BROOK", "BROOM", "BROTH", "BROWN", "BROWS", + "BRUIN", "BRUNT", "BRUSH", "BRUTE", "BUCKS", "BUDGE", "BUGGY", "BUGLE", "BUILD", "BUILT", + "BULBS", "BULGE", "BULKS", "BULKY", "BULLS", "BULLY", "BUMPS", "BUNCH", "BUNKS", "BUOYS", + "BURLY", "BURNS", "BURNT", "BURRO", "BURRS", "BURST", "BUSHY", "BUSTS", "BUTTE", "BUTTS", + "BUXOM", "BUYER", "CABAL", "CABBY", "CABIN", "CABLE", "CACAO", "CACHE", "CADET", "CADRE", + "CAGED", "CAGES", "CAIRN", "CAKED", "CAKES", "CALLS", "CALMS", "CALYX", "CAMEL", "CAMEO", + "CAMPS", "CANAL", "CANDY", "CANES", "CANNY", "CANOE", "CANON", "CANTO", "CAPER", "CAPES", + "CAPON", "CARDS", "CARED", "CARES", "CARGO", "CAROL", "CARRY", "CARTS", "CARVE", "CASED", + "CASES", "CASKS", "CASTE", "CASTS", "CATCH", "CATER", "CAUSE", "CAVED", "CAVES", "CAVIL", + "CEASE", "CEDAR", "CEDED", "CELLS", "CENTS", "CHAFE", "CHAFF", "CHAIN", "CHAIR", "CHALK", + "CHAMP", "CHANT", "CHAOS", "CHAPS", "CHARM", "CHART", "CHARY", "CHASE", "CHASM", "CHATS", + "CHEAP", "CHEAT", "CHECK", "CHEEK", "CHEER", "CHEFS", "CHESS", "CHEST", "CHICK", "CHIDE", + "CHIEF", "CHILD", "CHILL", "CHIME", "CHINA", "CHINK", "CHINS", "CHIPS", "CHIRP", "CHOIR", + "CHOKE", "CHOPS", "CHORD", "CHOSE", "CHUCK", "CHUMP", "CHUMS", "CHUNK", "CHURL", "CHURN", + "CHUTE", "CIDER", "CIGAR", "CINCH", "CIRCA", "CITED", "CITES", "CIVET", "CIVIC", "CIVIL", + "CLACK", "CLAIM", "CLAMP", "CLAMS", "CLANG", "CLANK", "CLANS", "CLAPS", "CLASH", "CLASP", + "CLASS", "CLAWS", "CLEAN", "CLEAR", "CLEFS", "CLEFT", "CLERK", "CLEWS", "CLICK", "CLIFF", + "CLIMB", "CLIME", "CLING", "CLINK", "CLIPS", "CLOAK", "CLOCK", "CLODS", "CLOGS", "CLOSE", + "CLOTH", "CLOUD", "CLOUT", "CLOVE", "CLOWN", "CLUBS", "CLUCK", "CLUES", "CLUMP", "CLUNG", + "COACH", "COALS", "COAST", "COATS", "COBRA", "COCKS", "COCOA", "CODES", "COILS", "COINS", + "COLDS", "COLIC", "COLON", "COLTS", "COMBS", "COMER", "COMES", "COMET", "COMIC", "COMMA", + "CONCH", "CONES", "CONIC", "COOED", "COOKS", "COOLS", "COPRA", "COPSE", "CORAL", "CORDS", + "CORES", "CORKS", "CORNS", "CORPS", "COSTS", "COTES", "COUCH", "COUGH", "COULD", "COUNT", + "COUPE", "COUPS", "COURT", "COVER", "COVES", "COVET", "COVEY", "COWED", "COWER", "COYLY", + "COZEN", "CRABS", "CRACK", "CRAFT", "CRAGS", "CRAMP", "CRANE", "CRANK", "CRAPE", "CRASH", + "CRASS", "CRATE", "CRAVE", "CRAWL", "CRAZE", "CRAZY", "CREAK", "CREAM", "CREDO", "CREED", + "CREEK", "CREEP", "CREPE", "CREPT", "CRESS", "CREST", "CREWS", "CRIBS", "CRICK", "CRIED", + "CRIER", "CRIES", "CRIME", "CRIMP", "CRISP", "CROAK", "CROCK", "CRONE", "CRONY", "CROOK", + "CROPS", "CROSS", "CROUP", "CROWD", "CROWN", "CROWS", "CRUDE", "CRUEL", "CRUMB", "CRUSH", + "CRUST", "CRYPT", "CUBES", "CUBIC", "CUBIT", "CUFFS", "CULTS", "CURDS", "CURED", "CURES", + "CURLS", "CURLY", "CURRY", "CURSE", "CURST", "CURVE", "CYCLE", "CYNIC", "DADDY", "DAILY", + "DAIRY", "DAISY", "DALES", "DALLY", "DAMES", "DAMPS", "DANCE", "DANDY", "DARED", "DARES", + "DARTS", "DATED", "DATES", "DATUM", "DAUBS", "DAUNT", "DAWNS", "DAZED", "DEALS", "DEALT", + "DEANS", "DEARS", "DEATH", "DEBAR", "DEBIT", "DEBTS", "DEBUT", "DECAY", "DECKS", "DECOY", + "DECRY", "DEEDS", "DEEMS", "DEEPS", "DEFER", "DEIGN", "DEITY", "DELAY", "DELLS", "DELTA", + "DELVE", "DEMON", "DEMUR", "DENSE", "DENTS", "DEPOT", "DEPTH", "DERBY", "DESKS", "DETER", + "DEUCE", "DEVIL", "DIARY", "DICED", "DICES", "DICTA", "DIETS", "DIGIT", "DIKES", "DIMES", + "DIMLY", "DINED", "DINER", "DINES", "DINGY", "DIRGE", "DIRTY", "DISCS", "DISKS", "DITCH", + "DITTO", "DITTY", "DIVAN", "DIVED", "DIVER", "DIVES", "DIZZY", "DOCKS", "DODGE", "DOERS", + "DOGMA", "DOING", "DOLED", "DOLLS", "DOMED", "DOMES", "DONOR", "DOOMS", "DOORS", "DOSED", + "DOSES", "DOTED", "DOTES", "DOUBT", "DOUGH", "DOVES", "DOWDY", "DOWNS", "DOWNY", "DOWRY", + "DOZED", "DOZEN", "DRAFT", "DRAGS", "DRAIN", "DRAKE", "DRAMA", "DRAMS", "DRANK", "DRAPE", + "DRAWL", "DRAWN", "DRAWS", "DRAYS", "DREAD", "DREAM", "DREGS", "DRESS", "DRIED", "DRIER", + "DRIES", "DRIFT", "DRILL", "DRILY", "DRINK", "DRIPS", "DRIVE", "DROLL", "DRONE", "DROOP", + "DROPS", "DROSS", "DROVE", "DROWN", "DRUGS", "DRUMS", "DRUNK", "DRYLY", "DUCAL", "DUCAT", + "DUCHY", "DUCKS", "DUCTS", "DUELS", "DUETS", "DUKES", "DULLY", "DUMMY", "DUMPS", "DUMPY", + "DUNCE", "DUNES", "DUNNO", "DUPED", "DUPES", "DUSKY", "DUSTY", "DWARF", "DWELL", "DWELT", + "DYING", "DYKES", "EAGER", "EAGLE", "EARLS", "EARLY", "EARNS", "EARTH", "EASED", "EASEL", + "EASES", "EATEN", "EATER", "EAVES", "EBBED", "EBONY", "EDGED", "EDGES", "EDICT", "EDIFY", + "EERIE", "EGGED", "EIGHT", "EJECT", "ELATE", "ELBOW", "ELDER", "ELECT", "ELEGY", "ELFIN", + "ELITE", "ELOPE", "ELUDE", "ELVES", "EMAIL", "EMITS", "EMPTY", "ENACT", "ENDED", "ENDOW", + "ENEMY", "ENJOY", "ENNUI", "ENROL", "ENSUE", "ENTER", "ENTRY", "ENVOY", "EPICS", "EPOCH", + "EQUAL", "EQUIP", "ERASE", "ERECT", "ERRED", "ERROR", "ESSAY", "ETHER", "ETHIC", "EVADE", + "EVENT", "EVERY", "EVILS", "EVOKE", "EXACT", "EXALT", "EXCEL", "EXERT", "EXILE", "EXIST", + "EXITS", "EXPEL", "EXTOL", "EXTRA", "EXULT", "EYING", "EYRIE", "FABLE", "FACED", "FACES", + "FACTS", "FADED", "FADES", "FAILS", "FAINT", "FAIRS", "FAIRY", "FAITH", "FAKIR", "FALLS", + "FALSE", "FAMED", "FANCY", "FANGS", "FARCE", "FARED", "FARES", "FARMS", "FASTS", "FATAL", + "FATED", "FATES", "FATTY", "FAULT", "FAUNA", "FAUNS", "FAWNS", "FEARS", "FEAST", "FEATS", + "FEEDS", "FEELS", "FEIGN", "FEINT", "FELLS", "FELON", "FENCE", "FERAL", "FERNS", "FERRY", + "FETCH", "FETED", "FETID", "FETUS", "FEUDS", "FEVER", "FEWER", "FICHE", "FIEFS", "FIELD", + "FIEND", "FIERY", "FIFES", "FIFTH", "FIFTY", "FIGHT", "FILCH", "FILED", "FILES", "FILET", + "FILLS", "FILLY", "FILMS", "FILMY", "FILTH", "FINAL", "FINCH", "FINDS", "FINED", "FINER", + "FINES", "FINIS", "FINNY", "FIORD", "FIRED", "FIRES", "FIRMS", "FIRST", "FISHY", "FISTS", + "FITLY", "FIVES", "FIXED", "FIXER", "FIXES", "FJORD", "FLAGS", "FLAIL", "FLAIR", "FLAKE", + "FLAKY", "FLAME", "FLANK", "FLAPS", "FLARE", "FLASH", "FLASK", "FLATS", "FLAWS", "FLEAS", + "FLECK", "FLEES", "FLEET", "FLESH", "FLICK", "FLIER", "FLIES", "FLING", "FLINT", "FLIRT", + "FLITS", "FLOAT", "FLOCK", "FLOES", "FLOOD", "FLOOR", "FLORA", "FLOSS", "FLOUR", "FLOUT", + "FLOWN", "FLOWS", "FLUES", "FLUFF", "FLUID", "FLUKE", "FLUME", "FLUNG", "FLUSH", "FLUTE", + "FLYER", "FOAMS", "FOAMY", "FOCAL", "FOCUS", "FOGGY", "FOILS", "FOIST", "FOLDS", "FOLIO", + "FOLKS", "FOLLY", "FOODS", "FOOLS", "FORAY", "FORCE", "FORDS", "FORGE", "FORGO", "FORKS", + "FORMS", "FORTE", "FORTH", "FORTS", "FORTY", "FORUM", "FOUND", "FOUNT", "FOURS", "FOWLS", + "FOXES", "FOYER", "FRAIL", "FRAME", "FRANC", "FRANK", "FRAUD", "FREAK", "FREED", "FREER", + "FREES", "FRESH", "FRETS", "FRIAR", "FRIED", "FRILL", "FRISK", "FROCK", "FROGS", "FROND", + "FRONT", "FROST", "FROTH", "FROWN", "FROZE", "FRUIT", "FUDGE", "FUELS", "FUGUE", "FULLY", + "FUMED", "FUMES", "FUNDS", "FUNGI", "FUNNY", "FURRY", "FURZE", "FUSED", "FUSES", "FUSSY", + "FUZZY", "GABLE", "GAILY", "GAINS", "GALES", "GALLS", "GAMES", "GAMIN", "GAMMA", "GAMUT", + "GANGS", "GAPED", "GAPES", "GASES", "GASPS", "GATES", "GAUDY", "GAUGE", "GAUNT", "GAUZE", + "GAUZY", "GAVEL", "GAWKY", "GAYER", "GAYLY", "GAZED", "GAZER", "GAZES", "GEARS", "GEESE", + "GENIE", "GENII", "GENRE", "GENTS", "GENUS", "GERMS", "GHOST", "GIANT", "GIBES", "GIDDY", + "GIFTS", "GILDS", "GILLS", "GIMME", "GIPSY", "GIRDS", "GIRLS", "GIRTH", "GIVEN", "GIVES", + "GLADE", "GLAND", "GLARE", "GLASS", "GLAZE", "GLEAM", "GLEAN", "GLENS", "GLIDE", "GLINT", + "GLOAT", "GLOBE", "GLOOM", "GLORY", "GLOSS", "GLOVE", "GLOWS", "GLUED", "GNASH", "GNATS", + "GNAWS", "GNOME", "GOADS", "GOALS", "GOATS", "GODLY", "GOING", "GOLLY", "GONGS", "GONNA", + "GOODS", "GOODY", "GOOSE", "GORED", "GORGE", "GORSE", "GOTTA", "GOUGE", "GOURD", "GOUTY", + "GOWNS", "GRABS", "GRACE", "GRADE", "GRAFT", "GRAIN", "GRAMS", "GRAND", "GRANT", "GRAPE", + "GRAPH", "GRASP", "GRASS", "GRATE", "GRAVE", "GRAVY", "GRAZE", "GREAT", "GREED", "GREEN", + "GREET", "GREYS", "GRIEF", "GRILL", "GRIME", "GRIMY", "GRIND", "GRINS", "GRIPE", "GRIPS", + "GRIST", "GROAN", "GROIN", "GROOM", "GROPE", "GROSS", "GROUP", "GROVE", "GROWL", "GROWN", + "GROWS", "GRUBS", "GRUEL", "GRUFF", "GRUNT", "GUANO", "GUARD", "GUESS", "GUEST", "GUIDE", + "GUILD", "GUILE", "GUILT", "GUISE", "GULCH", "GULFS", "GULLS", "GULLY", "GUMMY", "GUSTO", + "GUSTS", "GUSTY", "GYPSY", "HABIT", "HACKS", "HAILS", "HAIRS", "HAIRY", "HALED", "HALLS", + "HALTS", "HALVE", "HANDS", "HANDY", "HANGS", "HAPPY", "HARDY", "HAREM", "HARES", "HARMS", + "HARPS", "HARPY", "HARRY", "HARSH", "HARTS", "HASTE", "HASTY", "HATCH", "HATED", "HATER", + "HAULS", "HAVEN", "HAVOC", "HAWKS", "HAZEL", "HEADS", "HEADY", "HEALS", "HEAPS", "HEARD", + "HEARS", "HEART", "HEATH", "HEATS", "HEAVE", "HEAVY", "HEDGE", "HEEDS", "HEELS", "HEIRS", + "HELIX", "HELLO", "HELMS", "HELPS", "HENCE", "HERBS", "HERDS", "HERON", "HEROS", "HEWED", + "HIDES", "HILLS", "HILLY", "HILTS", "HINDS", "HINGE", "HINTS", "HIRED", "HIRES", "HITCH", + "HIVES", "HOARD", "HOARY", "HOBBY", "HOIST", "HOLDS", "HOLES", "HOLLY", "HOMES", "HONEY", + "HOODS", "HOOFS", "HOOKS", "HOOPS", "HOOTS", "HOPED", "HOPES", "HORDE", "HORNS", "HORNY", + "HORSE", "HOSTS", "HOTEL", "HOTLY", "HOUND", "HOURS", "HOUSE", "HOVEL", "HOVER", "HOWLS", + "HULKS", "HULLS", "HUMAN", "HUMID", "HUMPS", "HUMUS", "HUNCH", "HUNTS", "HURLS", "HURRY", + "HURTS", "HUSKS", "HUSKY", "HUSSY", "HYDRA", "HYENA", "HYMNS", "ICILY", "ICING", "IDEAL", + "IDEAS", "IDIOM", "IDIOT", "IDLED", "IDLER", "IDOLS", "IDYLL", "IGLOO", "IMAGE", "IMBUE", + "IMPEL", "IMPLY", "INANE", "INCUR", "INDEX", "INEPT", "INERT", "INFER", "INGOT", "INLET", + "INNER", "INTER", "INURE", "IRATE", "IRKED", "IRONS", "IRONY", "ISLES", "ISLET", "ISSUE", + "ITEMS", "IVORY", "JACKS", "JADED", "JAILS", "JAUNT", "JEANS", "JEERS", "JELLY", "JERKS", + "JERKY", "JESTS", "JETTY", "JEWEL", "JIFFY", "JOINS", "JOINT", "JOKED", "JOKER", "JOKES", + "JOLLY", "JOUST", "JOYED", "JUDGE", "JUICE", "JUICY", "JUMPS", "JUNKS", "JUNTA", "JUROR", + "KARMA", "KEELS", "KEEPS", "KETCH", "KEYED", "KHAKI", "KICKS", "KILLS", "KINDA", "KINDS", + "KINGS", "KIOSK", "KITES", "KNACK", "KNAVE", "KNEAD", "KNEEL", "KNEES", "KNELL", "KNELT", + "KNIFE", "KNITS", "KNOBS", "KNOCK", "KNOLL", "KNOTS", "KNOWN", "KNOWS", "LABEL", "LACED", + "LACES", "LACKS", "LADEN", "LADLE", "LAGER", "LAIRS", "LAITY", "LAKES", "LAMBS", "LAMED", + "LAMES", "LAMPS", "LANCE", "LANDS", "LANES", "LANKY", "LAPEL", "LAPSE", "LARCH", "LARGE", + "LARGO", "LARKS", "LARVA", "LASSO", "LASTS", "LATCH", "LATER", "LATHE", "LATHS", "LAUGH", + "LAWNS", "LAYER", "LEADS", "LEAFY", "LEAKS", "LEAKY", "LEANS", "LEAPS", "LEAPT", "LEARN", + "LEASE", "LEASH", "LEAST", "LEAVE", "LEDGE", "LEECH", "LEEKS", "LEGAL", "LEMME", "LEMON", + "LENDS", "LEPER", "LEVEE", "LEVEL", "LEVER", "LIARS", "LIBEL", "LICKS", "LIEGE", "LIENS", + "LIFTS", "LIGHT", "LIKED", "LIKEN", "LIKER", "LIKES", "LILAC", "LIMBO", "LIMBS", "LIMES", + "LIMIT", "LINED", "LINEN", "LINER", "LINES", "LINGO", "LINKS", "LIONS", "LISTS", "LITHE", + "LIVED", "LIVER", "LIVES", "LIVID", "LLAMA", "LOADS", "LOAMY", "LOANS", "LOATH", "LOBBY", + "LOBES", "LOCAL", "LOCKS", "LOCUS", "LODGE", "LOFTY", "LOGES", "LOGIC", "LOGIN", "LOINS", + "LONGS", "LOOKS", "LOOMS", "LOONS", "LOOPS", "LOOSE", "LORDS", "LOSER", "LOSES", "LOTUS", + "LOUSE", "LOUSY", "LOVED", "LOVER", "LOVES", "LOWED", "LOWER", "LOWLY", "LOYAL", "LUCID", + "LUCKY", "LULLS", "LUMPS", "LUMPY", "LUNAR", "LUNCH", "LUNGE", "LUNGS", "LURCH", "LURED", + "LURES", "LURID", "LURKS", "LUSTS", "LUSTY", "LUTES", "LYING", "LYMPH", "LYNCH", "LYRIC", + "MACES", "MADAM", "MADLY", "MAGIC", "MAIDS", "MAILS", "MAINS", "MAIZE", "MAJOR", "MAKER", + "MAKES", "MALES", "MAMMA", "MANES", "MANGA", "MANGE", "MANGO", "MANGY", "MANIA", "MANLY", + "MANNA", "MANOR", "MANSE", "MAPLE", "MARCH", "MARES", "MARKS", "MARRY", "MARSH", "MARTS", + "MASKS", "MASON", "MASTS", "MATCH", "MATED", "MATES", "MAUVE", "MAXIM", "MAYBE", "MAYOR", + "MAZES", "MEALS", "MEALY", "MEANS", "MEANT", "MEATS", "MEDAL", "MEDIA", "MEETS", "MELON", + "MELTS", "MEMES", "MENDS", "MENUS", "MERCY", "MERES", "MERGE", "MERIT", "MERRY", "MESAS", + "METAL", "METED", "METER", "MEWED", "MIDST", "MIENS", "MIGHT", "MILCH", "MILES", "MILKY", + "MILLS", "MIMES", "MIMIC", "MINCE", "MINDS", "MINED", "MINER", "MINES", "MINOR", "MINTS", + "MINUS", "MIRTH", "MISER", "MISTS", "MITES", "MIXED", "MIXES", "MOANS", "MOATS", "MOCKS", + "MODEL", "MODEM", "MODES", "MOIST", "MOLAR", "MOLES", "MOMMA", "MONEY", "MONKS", "MONTH", + "MOODS", "MOODY", "MOONS", "MOORS", "MOOSE", "MOPED", "MORAL", "MORES", "MOSSY", "MOTES", + "MOTHS", "MOTIF", "MOTOR", "MOTTO", "MOUND", "MOUNT", "MOURN", "MOUSE", "MOUTH", "MOVED", + "MOVER", "MOVES", "MOVIE", "MOWED", "MOWER", "MUCUS", "MUDDY", "MULES", "MULTI", "MUMMY", + "MUMPS", "MUNCH", "MURAL", "MURKY", "MUSED", "MUSES", "MUSIC", "MUSKY", "MUSTY", "MUTED", + "MUTES", "MYRRH", "MYTHS", "NABOB", "NAILS", "NAIVE", "NAKED", "NAMED", "NAMES", "NASAL", + "NASTY", "NATAL", "NATTY", "NAVAL", "NAVEL", "NAVES", "NEARS", "NECKS", "NEEDS", "NEEDY", + "NEIGH", "NERVE", "NESTS", "NEVER", "NEWER", "NEWLY", "NICER", "NICHE", "NIECE", "NIGHT", + "NINNY", "NOBLE", "NOBLY", "NOISE", "NOISY", "NOMAD", "NONCE", "NOOKS", "NOOSE", "NORTH", + "NOSED", "NOSES", "NOTCH", "NOTED", "NOTES", "NOUNS", "NOVEL", "NUDGE", "NURSE", "NYMPH", + "OAKEN", "OAKUM", "OASES", "OASIS", "OATEN", "OATHS", "OBESE", "OBEYS", "OCCUR", "OCEAN", + "OCHRE", "ODDER", "ODDLY", "ODIUM", "OFFAL", "OFFER", "OFTEN", "OILED", "OLDEN", "OLDER", + "OMENS", "OMITS", "ONION", "ONSET", "OOZED", "OOZES", "OPALS", "OPENS", "OPERA", "OPINE", + "OPIUM", "OPTIC", "ORBIT", "ORDER", "ORGAN", "OSIER", "OTHER", "OTTER", "OUGHT", "OUNCE", + "OUTDO", "OUTER", "OVALS", "OVARY", "OVENS", "OVERT", "OWING", "OWNED", "OWNER", "OXIDE", + "OZONE", "PACES", "PACKS", "PADDY", "PADRE", "PAEAN", "PAGAN", "PAGES", "PAILS", "PAINS", + "PAINT", "PAIRS", "PALED", "PALER", "PALES", "PALMS", "PALMY", "PALSY", "PANEL", "PANES", + "PANGS", "PANIC", "PANSY", "PANTS", "PAPAL", "PAPAS", "PAPER", "PARED", "PARKA", "PARKS", + "PARRY", "PARSE", "PARTS", "PARTY", "PASHA", "PASTE", "PASTY", "PATCH", "PATES", "PATHS", + "PATIO", "PAUSE", "PAVED", "PAWED", "PAWNS", "PAYED", "PAYER", "PEACE", "PEACH", "PEAKS", + "PEALS", "PEARL", "PEARS", "PEASE", "PECKS", "PEDAL", "PEEPS", "PEERS", "PELTS", "PENAL", + "PENCE", "PENIS", "PENNY", "PEONS", "PERCH", "PERIL", "PESKY", "PESOS", "PESTS", "PETAL", + "PETTY", "PHASE", "PHIAL", "PHONE", "PHOTO", "PIANO", "PICKS", "PIECE", "PIERS", "PIETY", + "PIGMY", "PIKES", "PILED", "PILES", "PILLS", "PILOT", "PINCH", "PINED", "PINES", "PINKS", + "PINTO", "PINTS", "PIOUS", "PIPED", "PIPER", "PIPES", "PIQUE", "PITCH", "PITHY", "PIVOT", + "PLACE", "PLAID", "PLAIN", "PLAIT", "PLANE", "PLANK", "PLANS", "PLANT", "PLATE", "PLAYS", + "PLAZA", "PLEAD", "PLEAS", "PLIED", "PLIES", "PLOTS", "PLUCK", "PLUGS", "PLUMB", "PLUME", + "PLUMS", "PLUSH", "PODIA", "POEMS", "POESY", "POETS", "POINT", "POISE", "POKED", "POKER", + "POKES", "POLAR", "POLES", "POLKA", "POLLS", "PONDS", "POOLS", "POPES", "POPPA", "POPPY", + "PORCH", "PORED", "PORES", "PORTS", "POSED", "POSER", "POSES", "POSSE", "POSTS", "POUCH", + "POUND", "POURS", "POWER", "PRANK", "PRATE", "PRAYS", "PRESS", "PREYS", "PRICE", "PRICK", + "PRIDE", "PRIED", "PRIES", "PRIME", "PRINT", "PRIOR", "PRISM", "PRIVY", "PRIZE", "PROBE", + "PRONE", "PROOF", "PROPS", "PROSE", "PROSY", "PROUD", "PROVE", "PROWL", "PROWS", "PROXY", + "PRUDE", "PRUNE", "PSALM", "PSHAW", "PUDGY", "PUFFS", "PUFFY", "PULLS", "PULPY", "PULSE", + "PUMPS", "PUNCH", "PUPIL", "PUPPY", "PUREE", "PURER", "PURGE", "PURSE", "PUSSY", "PUTTY", + "QUACK", "QUAFF", "QUAIL", "QUAKE", "QUALM", "QUART", "QUASI", "QUAYS", "QUEEN", "QUEER", + "QUELL", "QUERY", "QUEST", "QUEUE", "QUICK", "QUIET", "QUILL", "QUILT", "QUIPS", "QUIRE", + "QUITE", "QUITS", "QUOTA", "QUOTE", "QUOTH", "RABBI", "RABID", "RACED", "RACER", "RACES", + "RACKS", "RADII", "RADIO", "RAFTS", "RAGED", "RAGES", "RAIDS", "RAILS", "RAINS", "RAINY", + "RAISE", "RAJAH", "RAKED", "RAKES", "RALLY", "RANCH", "RANGE", "RANKS", "RAPID", "RARER", + "RARES", "RATED", "RATES", "RATIO", "RAVED", "RAVEN", "RAVES", "RAYON", "RAZED", "RAZOR", + "REACH", "REACT", "READS", "READY", "REALM", "REALS", "REAMS", "REAPS", "REARS", "REBEL", + "REBUS", "REBUT", "RECUR", "REEDS", "REEDY", "REEFS", "REEKS", "REELS", "REEVE", "REFER", + "REFIT", "REGAL", "REIGN", "REINS", "RELAX", "RELAY", "RELIC", "REMIT", "RENDS", "RENEW", + "RENTS", "REPAY", "REPEL", "REPLY", "RESET", "RESIN", "RESTS", "REVEL", "REVUE", "RHEUM", + "RHYME", "RICKS", "RIDER", "RIDES", "RIDGE", "RIFLE", "RIFTS", "RIGHT", "RIGID", "RILED", + "RILLS", "RIMES", "RINGS", "RINSE", "RIOTS", "RIPEN", "RIPER", "RISEN", "RISER", "RISES", + "RISKS", "RISKY", "RITES", "RIVAL", "RIVEN", "RIVER", "RIVET", "ROADS", "ROAMS", "ROARS", + "ROAST", "ROBED", "ROBES", "ROBIN", "ROCKS", "ROCKY", "ROGUE", "ROLES", "ROLLS", "ROMAN", + "ROOFS", "ROOKS", "ROOMS", "ROOMY", "ROOST", "ROOTS", "ROPED", "ROPES", "ROSES", "ROSIN", + "ROUGE", "ROUGH", "ROUND", "ROUSE", "ROUTE", "ROUTS", "ROVED", "ROVER", "ROWDY", "ROWED", + "ROYAL", "RUDER", "RUFFS", "RUINS", "RULED", "RULER", "RULES", "RUNES", "RUNGS", "RUPEE", + "RURAL", "RUSES", "SABLE", "SABRE", "SACKS", "SADLY", "SAFER", "SAGAS", "SAGES", "SAHIB", + "SAILS", "SAINT", "SAITH", "SALAD", "SALES", "SALLY", "SALON", "SALSA", "SALTS", "SALTY", + "SALVE", "SALVO", "SANDS", "SANDY", "SANER", "SATED", "SATIN", "SATYR", "SAUCE", "SAUCY", + "SAVED", "SAVES", "SAWED", "SCALD", "SCALE", "SCALP", "SCALY", "SCAMP", "SCANS", "SCANT", + "SCARE", "SCARF", "SCARS", "SCENE", "SCENT", "SCION", "SCOFF", "SCOLD", "SCOOP", "SCOPE", + "SCORE", "SCORN", "SCOUR", "SCOUT", "SCOWL", "SCRAP", "SCREW", "SCRIP", "SCRUB", "SCULL", + "SEALS", "SEAMS", "SEAMY", "SEATS", "SECTS", "SEDAN", "SEDGE", "SEEDS", "SEEDY", "SEEKS", + "SEEMS", "SEERS", "SEIZE", "SELLS", "SEMEN", "SENDS", "SENSE", "SERFS", "SERGE", "SERUM", + "SERVE", "SEVEN", "SEVER", "SEWED", "SEWER", "SEXES", "SHACK", "SHADE", "SHADY", "SHAFT", + "SHAKE", "SHAKY", "SHALE", "SHALL", "SHALT", "SHAME", "SHAMS", "SHANK", "SHAPE", "SHARE", + "SHARK", "SHARP", "SHAVE", "SHAWL", "SHEAF", "SHEAR", "SHEDS", "SHEEN", "SHEEP", "SHEER", + "SHEET", "SHEIK", "SHELF", "SHELL", "SHIED", "SHIFT", "SHINE", "SHINS", "SHINY", "SHIPS", + "SHIRE", "SHIRK", "SHIRT", "SHOAL", "SHOCK", "SHOES", "SHONE", "SHOOK", "SHOON", "SHOOT", + "SHOPS", "SHORE", "SHORN", "SHORT", "SHOTS", "SHOUT", "SHOVE", "SHOWN", "SHOWS", "SHOWY", + "SHRED", "SHREW", "SHRUB", "SHRUG", "SHUNS", "SHUTS", "SHYLY", "SIBYL", "SIDED", "SIDES", + "SIEGE", "SIEVE", "SIGHS", "SIGHT", "SIGMA", "SIGNS", "SILKS", "SILKY", "SILLS", "SILLY", + "SINCE", "SINEW", "SINGE", "SINGS", "SINKS", "SIREN", "SIRES", "SITES", "SIXES", "SIXTH", + "SIXTY", "SIZED", "SIZES", "SKATE", "SKEIN", "SKIES", "SKIFF", "SKILL", "SKIMS", "SKINS", + "SKIPS", "SKIRT", "SKULK", "SKULL", "SKUNK", "SLABS", "SLACK", "SLAGS", "SLAIN", "SLAKE", + "SLANG", "SLANT", "SLAPS", "SLASH", "SLATE", "SLATS", "SLAVE", "SLAYS", "SLEDS", "SLEEK", + "SLEEP", "SLEET", "SLEPT", "SLICE", "SLICK", "SLIDE", "SLILY", "SLIME", "SLIMY", "SLING", + "SLINK", "SLIPS", "SLITS", "SLOOP", "SLOPE", "SLOPS", "SLOTH", "SLUGS", "SLUMP", "SLUMS", + "SLUNG", "SLUNK", "SLUSH", "SLYLY", "SMACK", "SMALL", "SMART", "SMASH", "SMEAR", "SMELL", + "SMELT", "SMILE", "SMIRK", "SMITE", "SMITH", "SMOCK", "SMOKE", "SMOKY", "SMOTE", "SNACK", + "SNAGS", "SNAIL", "SNAKE", "SNAKY", "SNAPS", "SNARE", "SNARL", "SNEAK", "SNEER", "SNIFF", + "SNIPE", "SNOBS", "SNORE", "SNORT", "SNOUT", "SNOWS", "SNOWY", "SNUFF", "SOAPY", "SOARS", + "SOBER", "SOCKS", "SOFAS", "SOGGY", "SOILS", "SOLAR", "SOLES", "SOLID", "SOLOS", "SOLVE", + "SONGS", "SONNY", "SOOTH", "SOOTY", "SORES", "SORRY", "SORTS", "SOUGH", "SOULS", "SOUND", + "SOUPS", "SOUSE", "SOUTH", "SOWED", "SOWER", "SPACE", "SPADE", "SPAKE", "SPANK", "SPANS", + "SPARE", "SPARK", "SPARS", "SPASM", "SPAWN", "SPEAK", "SPEAR", "SPECK", "SPEED", "SPELL", + "SPELT", "SPEND", "SPENT", "SPERM", "SPICE", "SPICY", "SPIED", "SPIES", "SPIKE", "SPILL", + "SPILT", "SPINE", "SPINS", "SPINY", "SPIRE", "SPITE", "SPITS", "SPLIT", "SPOIL", "SPOKE", + "SPOOK", "SPOOL", "SPOON", "SPOOR", "SPORE", "SPORT", "SPOTS", "SPOUT", "SPRAY", "SPREE", + "SPRIG", "SPUNK", "SPURN", "SPURS", "SPURT", "SQUAD", "SQUAT", "SQUAW", "STABS", "STACK", + "STAFF", "STAGE", "STAGS", "STAID", "STAIN", "STAIR", "STAKE", "STALE", "STALK", "STALL", + "STAMP", "STAND", "STANK", "STARE", "STARK", "STARS", "START", "STATE", "STAVE", "STAYS", + "STEAD", "STEAK", "STEAL", "STEAM", "STEED", "STEEL", "STEEP", "STEER", "STEMS", "STEPS", + "STERN", "STEWS", "STICK", "STIFF", "STILE", "STILL", "STING", "STINK", "STINT", "STIRS", + "STOCK", "STOIC", "STOLE", "STONE", "STONY", "STOOD", "STOOL", "STOOP", "STOPS", "STORE", + "STORK", "STORM", "STORY", "STOUT", "STOVE", "STRAP", "STRAW", "STRAY", "STREW", "STRIP", + "STRUT", "STUCK", "STUDS", "STUDY", "STUFF", "STUMP", "STUNG", "STUNT", "STYLE", "SUAVE", + "SUCKS", "SUGAR", "SUING", "SUITE", "SUITS", "SULKS", "SULKY", "SULLY", "SUNNY", "SUPER", + "SURER", "SURGE", "SURLY", "SWAIN", "SWAMP", "SWANS", "SWARD", "SWARM", "SWAYS", "SWEAR", + "SWEAT", "SWEEP", "SWEET", "SWELL", "SWEPT", "SWIFT", "SWILL", "SWIMS", "SWINE", "SWING", + "SWIRL", "SWISH", "SWOON", "SWOOP", "SWORD", "SWORE", "SWORN", "SWUNG", "SYNOD", "SYRUP", + "TABBY", "TABLE", "TABOO", "TACIT", "TACKS", "TAILS", "TAINT", "TAKEN", "TAKES", "TALES", + "TALKS", "TALLY", "TALON", "TAMED", "TAMER", "TANKS", "TAPER", "TAPES", "TARDY", "TARES", + "TARRY", "TARTS", "TASKS", "TASTE", "TASTY", "TAUNT", "TAWNY", "TAXED", "TAXES", "TEACH", + "TEAMS", "TEARS", "TEASE", "TEEMS", "TEENS", "TEETH", "TELLS", "TEMPI", "TEMPO", "TEMPS", + "TENDS", "TENET", "TENOR", "TENSE", "TENTH", "TENTS", "TEPEE", "TEPID", "TERMS", "TERSE", + "TESTS", "TESTY", "TEXTS", "THANK", "THEFT", "THEIR", "THEME", "THERE", "THESE", "THICK", + "THIEF", "THIGH", "THINE", "THING", "THINK", "THIRD", "THONG", "THORN", "THOSE", "THREE", + "THREW", "THROB", "THROE", "THROW", "THUMB", "THUMP", "THYME", "TIARA", "TIBIA", "TICKS", + "TIDAL", "TIDES", "TIERS", "TIGER", "TIGHT", "TILDE", "TILED", "TILES", "TILLS", "TILTS", + "TIMED", "TIMES", "TIMID", "TINGE", "TINTS", "TIPSY", "TIRED", "TIRES", "TITHE", "TITLE", + "TOADS", "TOAST", "TODAY", "TODDY", "TOILS", "TOKEN", "TOLLS", "TOMBS", "TOMES", "TONED", + "TONES", "TONGS", "TONIC", "TOOLS", "TOOTH", "TOPAZ", "TOPIC", "TOQUE", "TORCH", "TORSO", + "TORTS", "TOTAL", "TOTEM", "TOUCH", "TOUGH", "TOURS", "TOWED", "TOWEL", "TOWER", "TOWNS", + "TOXIC", "TOYED", "TRACE", "TRACK", "TRACT", "TRADE", "TRAIL", "TRAIN", "TRAIT", "TRAMP", + "TRAMS", "TRAPS", "TRASH", "TRAYS", "TREAD", "TREAT", "TREED", "TREES", "TREND", "TRESS", + "TRIAD", "TRIAL", "TRIBE", "TRICE", "TRICK", "TRIED", "TRIES", "TRILL", "TRIPE", "TRIPS", + "TRITE", "TROLL", "TROOP", "TROTH", "TROTS", "TROUT", "TRUCE", "TRUCK", "TRUER", "TRULY", + "TRUMP", "TRUNK", "TRUSS", "TRUST", "TRUTH", "TRYST", "TUBES", "TUFTS", "TULIP", "TULLE", + "TUNED", "TUNES", "TUNIC", "TURNS", "TUSKS", "TUTOR", "TWAIN", "TWANG", "TWEED", "TWICE", + "TWIGS", "TWINE", "TWINS", "TWIRL", "TWIST", "TYING", "TYPED", "TYPES", "UDDER", "ULCER", + "ULTRA", "UNCLE", "UNCUT", "UNDER", "UNDID", "UNDUE", "UNFIT", "UNION", "UNITE", "UNITS", + "UNITY", "UNSAY", "UNTIE", "UNTIL", "UPPER", "UPSET", "URBAN", "URGED", "URGES", "URINE", + "USAGE", "USERS", "USHER", "USING", "USUAL", "USURP", "USURY", "UTTER", "VAGUE", "VALES", + "VALET", "VALID", "VALUE", "VALVE", "VANES", "VAPID", "VASES", "VAULT", "VAUNT", "VEILS", + "VEINS", "VELDT", "VENAL", "VENOM", "VENTS", "VENUE", "VERBS", "VERGE", "VERSE", "VERVE", + "VESTS", "VEXED", "VEXES", "VIALS", "VICAR", "VICES", "VIDEO", "VIEWS", "VIGIL", "VILER", + "VILLA", "VINES", "VIOLA", "VIPER", "VIRUS", "VISIT", "VISOR", "VISTA", "VITAL", "VIVID", + "VIXEN", "VIZOR", "VOCAL", "VODKA", "VOGUE", "VOICE", "VOILE", "VOLTS", "VOMIT", "VOTED", + "VOTER", "VOTES", "VOUCH", "VOWED", "VOWEL", "VYING", "WADED", "WAFER", "WAFTS", "WAGED", + "WAGER", "WAGES", "WAGON", "WAIFS", "WAILS", "WAIST", "WAITS", "WAIVE", "WAKED", "WAKEN", + "WAKES", "WALKS", "WALLS", "WALTZ", "WANDS", "WANED", "WANES", "WANTS", "WARDS", "WARES", + "WARMS", "WARNS", "WARTS", "WASPS", "WASTE", "WATCH", "WATER", "WAVED", "WAVER", "WAVES", + "WAXED", "WAXEN", "WAXES", "WEARS", "WEARY", "WEAVE", "WEDGE", "WEEDS", "WEEDY", "WEEKS", + "WEEPS", "WEIGH", "WEIRD", "WELCH", "WELLS", "WENCH", "WHACK", "WHALE", "WHARF", "WHEAT", + "WHEEL", "WHELP", "WHERE", "WHICH", "WHIFF", "WHILE", "WHIMS", "WHINE", "WHIPS", "WHIRL", + "WHIRR", "WHISK", "WHIST", "WHITE", "WHOLE", "WHOOP", "WHORE", "WHOSE", "WICKS", "WIDEN", + "WIDER", "WIDOW", "WIDTH", "WIELD", "WIGHT", "WILDS", "WILES", "WILLS", "WINCE", "WINCH", + "WINDS", "WINDY", "WINES", "WINGS", "WINKS", "WIPED", "WIPES", "WIRED", "WIRES", "WISER", + "WISPS", "WITCH", "WITTY", "WIVES", "WOMAN", "WOMEN", "WOODS", "WOODY", "WOOED", "WOOER", + "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", + "WOUND", "WRACK", "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", + "WRITE", "WRITS", "WRONG", "WROTE", "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", + "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", "YOKES", "YOLKS", "YOUNG", "YOURS", + "YOUTH", "ZEBRA", "ZONES", ] alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] @@ -662,7 +335,7 @@ def most_used_letters(): def list_of_valid_words(): - letters = ['s', 'e', 'a', 'r', 'o', 'i', 'L', 'n', 'p', 'c'] + letters = ['e', 's', 'a', 'r', 'o', 'l', 'i', 'n', 'c', 'p'] for i, letter in enumerate(letters): # Force all letters to be capitalized letters[i] = letter.upper() @@ -678,8 +351,16 @@ def list_of_valid_words(): for i,word in enumerate(legal_words): legal_words[i] = word.upper().replace("D","d") + random.shuffle(legal_words) + # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter - print(f"Number of words found: {len(legal_words)}") + print("static const char _valid_letters[] = {", end='') + for letter in letters[:-1]: + print(f"'{letter}', ", end='') + print(f"'{letters[-1]}'" + "};") + print("") + + print(f"// Number of words found: {len(legal_words)}") items_per_row = 9 i = 0 print("static const char _legal_words[][WORDLE_LENGTH + 1] = {") @@ -692,6 +373,6 @@ def list_of_valid_words(): print("};") if __name__ == "__main__": - #most_used_letters() + most_used_letters() list_of_valid_words() From 3a24ede3de5467f3418704a88b7a32ef2c64b2e1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 08:17:10 -0400 Subject: [PATCH 097/220] day streak and further wordle dev --- .../watch_faces/complication/wordle_face.c | 92 ++++++++++++++----- .../watch_faces/complication/wordle_face.h | 5 +- utils/wordle_list.py | 1 + 3 files changed, 72 insertions(+), 26 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 4052ef7..62141cb 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -25,14 +25,15 @@ #include #include #include "wordle_face.h" +#if USE_DAILY_STREAK +#include "watch_utility.h" +#endif /* TODO: * Add quick iteration (8x freq to get to the letter we want) * Fix the word matching (if answer is AAAAA and we put in AACAA, the C blinks) -* Verify pressing back always work when the board is G_G_G -* Add daily streak and wait for next day -* Add a way tpo recount previous attempts +* Add a way to recount previous attempts */ @@ -54,7 +55,7 @@ C | 525 U | 514 P has more words with the other letters here (281 vs 198) P | 448 */ -static const char _valid_letters[] = {'E', 'S', 'A', 'R', 'O', 'L', 'I', 'N', 'C', 'P'}; +static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; // Number of words found: 281 static const char _legal_words[][WORDLE_LENGTH + 1] = { @@ -126,12 +127,13 @@ static void display_all_letters(wordle_state_t *state) { } static bool check_word(wordle_state_t *state) { - + WordleLetterResult checked_letter_in_answer[WORDLE_LENGTH] = {WORDLE_LETTER_WRONG}; // Exact bool is_exact_match = true; for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) - state->word_elements_result[i] = WORDLE_LETTER_CORRECT; + if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) { + state->word_elements_result[i] = checked_letter_in_answer[i] = WORDLE_LETTER_CORRECT; + } else { state->word_elements_result[i] = WORDLE_LETTER_WRONG; is_exact_match = false; @@ -141,10 +143,10 @@ static bool check_word(wordle_state_t *state) { // Wrong Location for (size_t i = 0; i < WORDLE_LENGTH; i++) { for (size_t j = 0; j < WORDLE_LENGTH; j++) { - if (state->word_elements_result[j] != WORDLE_LETTER_WRONG) continue; + if (checked_letter_in_answer[j] != WORDLE_LETTER_WRONG) continue; if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { printf("me: %c them: %c\r\n", _valid_letters[state->word_elements[i]], _legal_words[state->curr_answer][j]); - state->word_elements_result[j] = WORDLE_LETTER_WRONG_LOC; + state->word_elements_result[i] = checked_letter_in_answer[j] = WORDLE_LETTER_WRONG_LOC; break; } } @@ -190,12 +192,29 @@ static void display_title(wordle_state_t *state) { static void display_streak(wordle_state_t *state) { char buf[12]; state->curr_screen = SCREEN_STREAK; - printf("streak %d \r\n", state->streak); +#if USE_DAILY_STREAK sprintf(buf, "WO St%2ddy", state->streak); +#else + sprintf(buf, "WO St%4d", state->streak); +#endif watch_display_string(buf, 0); watch_set_colon(); } +#if USE_DAILY_STREAK +static void display_wait(wordle_state_t *state) { + state->curr_screen = SCREEN_WAIT; + if (state->streak < 40) { + char buf[13]; + sprintf(buf,"WO%2d WaIt ", state->streak); + watch_display_string(buf, 0); + } + else { // Streak too long to display in top-right + watch_display_string("WO WaIt ", 0); + } +} +#endif + static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; sprintf(buf," L %s", subsecond % 2 ? _legal_words[state->curr_answer] : " "); @@ -218,23 +237,30 @@ static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { } static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { - uint8_t pos = curr_pos; - do { - pos++; - if (pos > WORDLE_LENGTH) return WORDLE_LENGTH + 1; - } while (word_elements_result[pos] == WORDLE_LETTER_CORRECT); - return pos; + for (size_t pos = curr_pos+1; pos < WORDLE_LENGTH; pos++) { + if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + return pos; + } + return WORDLE_LENGTH; } static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { - int8_t pos = curr_pos; - do { - pos--; - if (pos < 0) return curr_pos; - } while (word_elements_result[pos] == WORDLE_LETTER_CORRECT); - return pos; + if (curr_pos == 0) return 0; + for (int8_t pos = curr_pos-1; pos >= 0; pos--) { + if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + return pos; + } + return curr_pos; } +#if USE_DAILY_STREAK +static uint32_t get_day_unix_time(void) { + watch_date_time now = watch_rtc_get_date_time(); + now.unit.hour = now.unit.minute = now.unit.second = 0; + return watch_utility_date_time_to_unix_time(now, 0); +} +#endif + static void display_result(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 1]; for (size_t i = 0; i < WORDLE_LENGTH; i++) @@ -273,6 +299,12 @@ static bool act_on_btn(wordle_state_t *state) { state->curr_screen = SCREEN_PLAYING; return true; case SCREEN_TITLE: +#if USE_DAILY_STREAK + if (state->prev_day == get_day_unix_time()) { + display_wait(state); + return true; + } +#endif display_streak(state); return true; case SCREEN_STREAK: @@ -281,7 +313,12 @@ static bool act_on_btn(wordle_state_t *state) { case SCREEN_WIN: case SCREEN_LOSE: display_title(state); - return true; + return true; +#if USE_DAILY_STREAK + case SCREEN_WAIT: + display_title(state); + return true; +#endif default: return false; } @@ -296,7 +333,6 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, memset(*context_ptr, 0, sizeof(wordle_state_t)); wordle_state_t *state = (wordle_state_t *)*context_ptr; state->curr_screen = SCREEN_TITLE; - } // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. } @@ -305,6 +341,9 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; movement_request_tick_frequency(2); +#if USE_DAILY_STREAK + if (state->prev_day <= (get_day_unix_time() + (60 *60 * 24))) state->streak = 0; +#endif if (state->curr_screen == SCREEN_TITLE) display_title(state); } @@ -356,11 +395,14 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; state->position = get_next_pos(state->position, state->word_elements_result); - if(WORDLE_LENGTH == (state->position)) { + if (state->position >= WORDLE_LENGTH) { bool exact_match = check_word(state); if (exact_match) { state->curr_screen = SCREEN_WIN; state->streak++; +#if USE_DAILY_STREAK + state->prev_day = get_day_unix_time(); +#endif break; } if (state->attempt++ >= WORDLE_MAX_ATTEMPTS) { diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index b8e5d0f..15b2a94 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -50,6 +50,9 @@ typedef enum { SCREEN_RESULT, SCREEN_TITLE, SCREEN_STREAK, +#if USE_DAILY_STREAK + SCREEN_WAIT, +#endif SCREEN_WIN, SCREEN_LOSE, SCREEN_COUNT @@ -66,7 +69,7 @@ typedef struct { uint8_t streak; WordleScreen curr_screen; #if USE_DAILY_STREAK - // For the day info + uint32_t prev_day; #endif } wordle_state_t; diff --git a/utils/wordle_list.py b/utils/wordle_list.py index 87e6066..1b5baf0 100644 --- a/utils/wordle_list.py +++ b/utils/wordle_list.py @@ -336,6 +336,7 @@ def most_used_letters(): def list_of_valid_words(): letters = ['e', 's', 'a', 'r', 'o', 'l', 'i', 'n', 'c', 'p'] + letters = sorted(letters) for i, letter in enumerate(letters): # Force all letters to be capitalized letters[i] = letter.upper() From 676f50d1943f15fbb0c7b2544a32a21492d32fd5 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 08:56:49 -0400 Subject: [PATCH 098/220] Added fast cycle --- .../watch_faces/complication/wordle_face.c | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 62141cb..4fa6720 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -29,6 +29,9 @@ #include "watch_utility.h" #endif +#define FREQ_FAST 4 +#define FREQ 2 + /* TODO: * Add quick iteration (8x freq to get to the letter we want) @@ -104,6 +107,17 @@ static uint32_t get_random(uint32_t max) { #endif } +static bool _quick_ticks_running; +static void start_quick_cyc(void){ + _quick_ticks_running = true; + movement_request_tick_frequency(FREQ_FAST); +} + +static void stop_quick_cyc(void){ + _quick_ticks_running = false; + movement_request_tick_frequency(FREQ); +} + static void display_letter(wordle_state_t *state, bool display_dash) { char buf[1 + 1]; if (state->word_elements[state->position] >= _num_valid_letters) { @@ -145,19 +159,11 @@ static bool check_word(wordle_state_t *state) { for (size_t j = 0; j < WORDLE_LENGTH; j++) { if (checked_letter_in_answer[j] != WORDLE_LETTER_WRONG) continue; if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { - printf("me: %c them: %c\r\n", _valid_letters[state->word_elements[i]], _legal_words[state->curr_answer][j]); state->word_elements_result[i] = checked_letter_in_answer[j] = WORDLE_LETTER_WRONG_LOC; break; } } } - - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - printf("%d : %d\r\n", i, state->word_elements_result[i]); - } - - - return is_exact_match; } @@ -181,7 +187,9 @@ static void reset_board(wordle_state_t *state) { display_attempt(state->attempt); display_all_letters(state); watch_display_string("-", 5); - printf("rand: %s\r\n", _legal_words[state->curr_answer]); +#if __EMSCRIPTEN__ + printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); +#endif } static void display_title(wordle_state_t *state) { @@ -253,6 +261,16 @@ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ return curr_pos; } +static void get_next_letter(uint8_t curr_pos, uint8_t *word_elements) { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; + else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; +} + +static void get_prev_letter(uint8_t curr_pos, uint8_t *word_elements) { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; + else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; +} + #if USE_DAILY_STREAK static uint32_t get_day_unix_time(void) { watch_date_time now = watch_rtc_get_date_time(); @@ -340,7 +358,7 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; - movement_request_tick_frequency(2); + movement_request_tick_frequency(FREQ); #if USE_DAILY_STREAK if (state->prev_day <= (get_day_unix_time() + (60 *60 * 24))) state->streak = 0; #endif @@ -360,10 +378,19 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi switch (state->curr_screen) { case SCREEN_PLAYING: - if (event.subsecond % 2) { - display_letter(state, true); - } else { - watch_display_string(" ", state->position + 5); + if (_quick_ticks_running) { + if (watch_get_pin_level(BTN_ALARM)){ + get_next_letter(state->position, state->word_elements); + display_letter(state, true); + } + else stop_quick_cyc(); + } + else { + if (event.subsecond % 2) { + display_letter(state, true); + } else { + watch_display_string(" ", state->position + 5); + } } break; case SCREEN_RESULT: @@ -379,18 +406,23 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; } break; - case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_BUTTON_UP: if (act_on_btn(state)) break; - if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = 0; - else state->word_elements[state->position] = (state->word_elements[state->position] + 1) % _num_valid_letters; + get_next_letter(state->position, state->word_elements); display_letter(state, true); break; - case EVENT_LIGHT_LONG_PRESS: - if (state->word_elements[state->position] >= _num_valid_letters) state->word_elements[state->position] = _num_valid_letters - 1; - else state->word_elements[state->position] = (state->word_elements[state->position] + _num_valid_letters - 1) % _num_valid_letters; + case EVENT_ALARM_LONG_PRESS: + if (state->curr_screen != SCREEN_PLAYING) break; + get_prev_letter(state->position, state->word_elements); display_letter(state, true); + break; + case EVENT_ALARM_LONGER_PRESS: + if (state->curr_screen != SCREEN_PLAYING) break; + get_next_letter(state->position, state->word_elements); + display_letter(state, true); + start_quick_cyc(); break; - case EVENT_ALARM_BUTTON_UP: + case EVENT_LIGHT_BUTTON_UP: if (act_on_btn(state)) break; display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; @@ -414,7 +446,8 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; } break; - case EVENT_ALARM_LONG_PRESS: + case EVENT_LIGHT_LONG_PRESS: + if (state->curr_screen != SCREEN_PLAYING) break; display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); break; From 0c86be4a40ef338ee7641dc4e405af9525ae54b1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 08:59:22 -0400 Subject: [PATCH 099/220] Swapped button mapping and removed fast iteration --- .../watch_faces/complication/wordle_face.c | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 4fa6720..f701fc4 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -29,7 +29,6 @@ #include "watch_utility.h" #endif -#define FREQ_FAST 4 #define FREQ 2 /* @@ -107,17 +106,6 @@ static uint32_t get_random(uint32_t max) { #endif } -static bool _quick_ticks_running; -static void start_quick_cyc(void){ - _quick_ticks_running = true; - movement_request_tick_frequency(FREQ_FAST); -} - -static void stop_quick_cyc(void){ - _quick_ticks_running = false; - movement_request_tick_frequency(FREQ); -} - static void display_letter(wordle_state_t *state, bool display_dash) { char buf[1 + 1]; if (state->word_elements[state->position] >= _num_valid_letters) { @@ -378,19 +366,10 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi switch (state->curr_screen) { case SCREEN_PLAYING: - if (_quick_ticks_running) { - if (watch_get_pin_level(BTN_ALARM)){ - get_next_letter(state->position, state->word_elements); - display_letter(state, true); - } - else stop_quick_cyc(); - } - else { - if (event.subsecond % 2) { - display_letter(state, true); - } else { - watch_display_string(" ", state->position + 5); - } + if (event.subsecond % 2) { + display_letter(state, true); + } else { + watch_display_string(" ", state->position + 5); } break; case SCREEN_RESULT: @@ -406,23 +385,17 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; } break; - case EVENT_ALARM_BUTTON_UP: + case EVENT_LIGHT_BUTTON_UP: if (act_on_btn(state)) break; get_next_letter(state->position, state->word_elements); display_letter(state, true); break; - case EVENT_ALARM_LONG_PRESS: + case EVENT_LIGHT_LONG_PRESS: if (state->curr_screen != SCREEN_PLAYING) break; get_prev_letter(state->position, state->word_elements); display_letter(state, true); - break; - case EVENT_ALARM_LONGER_PRESS: - if (state->curr_screen != SCREEN_PLAYING) break; - get_next_letter(state->position, state->word_elements); - display_letter(state, true); - start_quick_cyc(); break; - case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_BUTTON_UP: if (act_on_btn(state)) break; display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; @@ -446,7 +419,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; } break; - case EVENT_LIGHT_LONG_PRESS: + case EVENT_ALARM_LONG_PRESS: if (state->curr_screen != SCREEN_PLAYING) break; display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); From 8ea779874f0405fbe5dd99e13208833ecae4eb42 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 17:02:14 -0400 Subject: [PATCH 100/220] Face compares the values correctly now and does a dict lookup first --- .../watch_faces/complication/wordle_face.c | 177 +++++++++++------- .../watch_faces/complication/wordle_face.h | 7 +- 2 files changed, 113 insertions(+), 71 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f701fc4..13b4b6f 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -33,9 +33,8 @@ /* TODO: -* Add quick iteration (8x freq to get to the letter we want) -* Fix the word matching (if answer is AAAAA and we put in AACAA, the C blinks) * Add a way to recount previous attempts +* Only allow dictionary attempts - Show "nodict" otherwise */ @@ -106,6 +105,41 @@ static uint32_t get_random(uint32_t max) { #endif } +static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (word_elements_result[i] != WORDLE_LETTER_CORRECT) + return i; + } + return 0; +} + +static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { + for (size_t pos = curr_pos+1; pos < WORDLE_LENGTH; pos++) { + if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + return pos; + } + return WORDLE_LENGTH; +} + +static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { + if (curr_pos == 0) return 0; + for (size_t pos = curr_pos-1; pos > 0; pos--) { + if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + return pos; + } + return curr_pos; +} + +static void get_next_letter(uint8_t curr_pos, uint8_t *word_elements) { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; + else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; +} + +static void get_prev_letter(uint8_t curr_pos, uint8_t *word_elements) { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; + else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; +} + static void display_letter(wordle_state_t *state, bool display_dash) { char buf[1 + 1]; if (state->word_elements[state->position] >= _num_valid_letters) { @@ -121,6 +155,7 @@ static void display_letter(wordle_state_t *state, bool display_dash) { static void display_all_letters(wordle_state_t *state) { uint8_t prev_pos = state->position; + watch_display_string(" ", 4); for (size_t i = 0; i < WORDLE_LENGTH; i++) { state->position = i; display_letter(state, false); @@ -128,31 +163,47 @@ static void display_all_letters(wordle_state_t *state) { state->position = prev_pos; } +static bool check_word_in_dict(uint8_t *word_elements) { + bool is_exact_match; + for (uint16_t i = 0; i < _num_words; i++) { + is_exact_match = true; + for (size_t j = 0; j < WORDLE_LENGTH; j++) { + if (_valid_letters[word_elements[j]] != _legal_words[i][j]) { + is_exact_match = false; + break; + } + } + if (is_exact_match) return true; + } + return false; +} + static bool check_word(wordle_state_t *state) { - WordleLetterResult checked_letter_in_answer[WORDLE_LENGTH] = {WORDLE_LETTER_WRONG}; // Exact bool is_exact_match = true; for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) { - state->word_elements_result[i] = checked_letter_in_answer[i] = WORDLE_LETTER_CORRECT; - } + if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) + state->word_elements_result[i] = WORDLE_LETTER_CORRECT; else { state->word_elements_result[i] = WORDLE_LETTER_WRONG; is_exact_match = false; } } - + if (is_exact_match) return true; // Wrong Location - for (size_t i = 0; i < WORDLE_LENGTH; i++) { + bool answer_found_wrong_loc[WORDLE_LENGTH] = { false }; + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_WRONG) continue; for (size_t j = 0; j < WORDLE_LENGTH; j++) { - if (checked_letter_in_answer[j] != WORDLE_LETTER_WRONG) continue; + if (answer_found_wrong_loc[j]) continue; if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { - state->word_elements_result[i] = checked_letter_in_answer[j] = WORDLE_LETTER_WRONG_LOC; + state->word_elements_result[i] = WORDLE_LETTER_WRONG_LOC; + answer_found_wrong_loc[j] = true; break; } } } - return is_exact_match; + return false; } static void display_attempt(uint8_t attempt) { @@ -161,19 +212,27 @@ static void display_attempt(uint8_t attempt) { watch_display_string(buf, 3); } +static void show_start_of_attempt(wordle_state_t *state) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) + state->word_elements[i] = _num_valid_letters; + } + display_attempt(state->attempt); + display_all_letters(state); + state->position = get_first_pos(state->word_elements_result); + state->curr_screen = SCREEN_PLAYING; +} + static void reset_board(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { state->word_elements[i] = _num_valid_letters; state->word_elements_result[i] = WORDLE_LETTER_WRONG; } state->curr_answer = get_random(_num_words); - state->position = 0; state->attempt = 1; - state->curr_screen = SCREEN_PLAYING; watch_clear_colon(); watch_display_string(" ", 4); - display_attempt(state->attempt); - display_all_letters(state); + show_start_of_attempt(state); watch_display_string("-", 5); #if __EMSCRIPTEN__ printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); @@ -182,7 +241,7 @@ static void reset_board(wordle_state_t *state) { static void display_title(wordle_state_t *state) { state->curr_screen = SCREEN_TITLE; - watch_display_string("WO WORDLE", 0); + watch_display_string("WO WordLE", 0); } static void display_streak(wordle_state_t *state) { @@ -211,6 +270,11 @@ static void display_wait(wordle_state_t *state) { } #endif +static void display_not_in_dict(wordle_state_t *state) { + state->curr_screen = SCREEN_NO_DICT; + watch_display_string("WO nodict", 0); +} + static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; sprintf(buf," L %s", subsecond % 2 ? _legal_words[state->curr_answer] : " "); @@ -224,41 +288,6 @@ static void display_win(wordle_state_t *state, uint8_t subsecond) { watch_display_string(buf, 0); } -static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (word_elements_result[i] != WORDLE_LETTER_CORRECT) - return i; - } - return 0; -} - -static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { - for (size_t pos = curr_pos+1; pos < WORDLE_LENGTH; pos++) { - if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) - return pos; - } - return WORDLE_LENGTH; -} - -static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { - if (curr_pos == 0) return 0; - for (int8_t pos = curr_pos-1; pos >= 0; pos--) { - if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) - return pos; - } - return curr_pos; -} - -static void get_next_letter(uint8_t curr_pos, uint8_t *word_elements) { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; - else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; -} - -static void get_prev_letter(uint8_t curr_pos, uint8_t *word_elements) { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; - else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; -} - #if USE_DAILY_STREAK static uint32_t get_day_unix_time(void) { watch_date_time now = watch_rtc_get_date_time(); @@ -295,14 +324,7 @@ static bool act_on_btn(wordle_state_t *state) { switch (state->curr_screen) { case SCREEN_RESULT: - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) - state->word_elements[i] = _num_valid_letters; - } - display_attempt(state->attempt); - display_all_letters(state); - state->position = get_first_pos(state->word_elements_result); - state->curr_screen = SCREEN_PLAYING; + show_start_of_attempt(state); return true; case SCREEN_TITLE: #if USE_DAILY_STREAK @@ -311,15 +333,24 @@ static bool act_on_btn(wordle_state_t *state) { return true; } #endif - display_streak(state); + if (state->playing) + show_start_of_attempt(state); + else + display_streak(state); return true; case SCREEN_STREAK: +#if USE_DAILY_STREAK + state->curr_day = get_day_unix_time(); +#endif reset_board(state); return true; case SCREEN_WIN: case SCREEN_LOSE: display_title(state); return true; + case SCREEN_NO_DICT: + show_start_of_attempt(state); + return true; #if USE_DAILY_STREAK case SCREEN_WAIT: display_title(state); @@ -346,22 +377,19 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; - movement_request_tick_frequency(FREQ); #if USE_DAILY_STREAK - if (state->prev_day <= (get_day_unix_time() + (60 *60 * 24))) state->streak = 0; + uint32_t now = get_day_unix_time() ; + if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; + if (state->curr_day != now) state->playing = false; #endif - if (state->curr_screen == SCREEN_TITLE) - display_title(state); + movement_request_tick_frequency(FREQ); + display_title(state); } bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { wordle_state_t *state = (wordle_state_t *)context; switch (event.event_type) { - case EVENT_ACTIVATE: - if (state->curr_screen == SCREEN_PLAYING) - display_all_letters(state); - break; case EVENT_TICK: switch (state->curr_screen) { @@ -399,10 +427,17 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi if (act_on_btn(state)) break; display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; + state->playing = true; state->position = get_next_pos(state->position, state->word_elements_result); if (state->position >= WORDLE_LENGTH) { + bool in_dict = check_word_in_dict(state->word_elements); + if (!in_dict) { + display_not_in_dict(state); + break; + } bool exact_match = check_word(state); if (exact_match) { + state->playing = false; state->curr_screen = SCREEN_WIN; state->streak++; #if USE_DAILY_STREAK @@ -411,6 +446,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; } if (state->attempt++ >= WORDLE_MAX_ATTEMPTS) { + state->playing = false; state->curr_screen = SCREEN_LOSE; state->streak = 0; break; @@ -425,9 +461,12 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi state->position = get_prev_pos(state->position, state->word_elements_result); break; case EVENT_LIGHT_BUTTON_DOWN: - break; + case EVENT_ACTIVATE: case EVENT_TIMEOUT: + break; case EVENT_LOW_ENERGY_UPDATE: + if (state->curr_screen == SCREEN_TITLE) + display_title(state); break; default: return movement_default_loop_handler(event, settings); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 15b2a94..633d3a2 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -35,7 +35,7 @@ */ #define WORDLE_LENGTH 5 -#define WORDLE_MAX_ATTEMPTS 5 +#define WORDLE_MAX_ATTEMPTS 6 #define USE_DAILY_STREAK true typedef enum { @@ -55,6 +55,7 @@ typedef enum { #endif SCREEN_WIN, SCREEN_LOSE, + SCREEN_NO_DICT, SCREEN_COUNT } WordleScreen; @@ -64,12 +65,14 @@ typedef struct { WordleLetterResult word_elements_result[WORDLE_LENGTH]; uint8_t attempt : 3; uint8_t position : 3; - uint8_t unused : 2; + bool playing : 1; + bool unused : 1; uint16_t curr_answer; uint8_t streak; WordleScreen curr_screen; #if USE_DAILY_STREAK uint32_t prev_day; + uint32_t curr_day; #endif } wordle_state_t; From cef0d8836a9f4b2e3907b0e243c7dcfa8037c960 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 17:40:04 -0400 Subject: [PATCH 101/220] Don't allow readding already guessed items --- .../watch_faces/complication/wordle_face.c | 108 +++++++++++------- .../watch_faces/complication/wordle_face.h | 2 + 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 13b4b6f..08ad478 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2024 <#author_name#> + * Copyright (c) 2024 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,7 +34,6 @@ /* TODO: * Add a way to recount previous attempts -* Only allow dictionary attempts - Show "nodict" otherwise */ @@ -42,6 +41,7 @@ TODO: /* Letter | Usage +_______|______ E | 1519 S | 1490 A | 1213 @@ -60,7 +60,7 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R // Number of words found: 281 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", + "AAAAA","SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", "POLES", "ROLLS", "ALOES", "LOSES", "SLICE", "PEACE", "POLLS", "POSES", "LANES", "COPRA", "SPANS", "CANAL", "LOSER", "PAPER", "PILES", "CLASS", "RACER", "POOLS", "PLAIN", "SPEAR", "SPARE", "INNER", "ALIEN", "NOSES", "EARLS", "SEALS", "LEARN", @@ -94,7 +94,7 @@ static const char _legal_words[][WORDLE_LENGTH + 1] = { "SIREN", "PEONS", }; -static const uint32_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); +static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); static uint32_t get_random(uint32_t max) { @@ -163,7 +163,7 @@ static void display_all_letters(wordle_state_t *state) { state->position = prev_pos; } -static bool check_word_in_dict(uint8_t *word_elements) { +static uint32_t check_word_in_dict(uint8_t *word_elements) { bool is_exact_match; for (uint16_t i = 0; i < _num_words; i++) { is_exact_match = true; @@ -173,9 +173,9 @@ static bool check_word_in_dict(uint8_t *word_elements) { break; } } - if (is_exact_match) return true; + if (is_exact_match) return i; } - return false; + return _num_words; } static bool check_word(wordle_state_t *state) { @@ -208,7 +208,7 @@ static bool check_word(wordle_state_t *state) { static void display_attempt(uint8_t attempt) { char buf[2]; - sprintf(buf, "%d", attempt); + sprintf(buf, "%d", attempt+1); watch_display_string(buf, 3); } @@ -228,8 +228,11 @@ static void reset_board(wordle_state_t *state) { state->word_elements[i] = _num_valid_letters; state->word_elements_result[i] = WORDLE_LETTER_WRONG; } + for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { + state->guessed_words[i] = _num_words; + } state->curr_answer = get_random(_num_words); - state->attempt = 1; + state->attempt = 0; watch_clear_colon(); watch_display_string(" ", 4); show_start_of_attempt(state); @@ -268,11 +271,22 @@ static void display_wait(wordle_state_t *state) { watch_display_string("WO WaIt ", 0); } } + +static uint32_t get_day_unix_time(void) { + watch_date_time now = watch_rtc_get_date_time(); + now.unit.hour = now.unit.minute = now.unit.second = 0; + return watch_utility_date_time_to_unix_time(now, 0); +} #endif static void display_not_in_dict(wordle_state_t *state) { state->curr_screen = SCREEN_NO_DICT; - watch_display_string("WO nodict", 0); + watch_display_string("nodict", 4); +} + +static void display_already_guessed(wordle_state_t *state) { + state->curr_screen = SCREEN_ALREADY_GUESSED; + watch_display_string("GUESSD", 4); } static void display_lose(wordle_state_t *state, uint8_t subsecond) { @@ -288,14 +302,6 @@ static void display_win(wordle_state_t *state, uint8_t subsecond) { watch_display_string(buf, 0); } -#if USE_DAILY_STREAK -static uint32_t get_day_unix_time(void) { - watch_date_time now = watch_rtc_get_date_time(); - now.unit.hour = now.unit.minute = now.unit.second = 0; - return watch_utility_date_time_to_unix_time(now, 0); -} -#endif - static void display_result(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 1]; for (size_t i = 0; i < WORDLE_LENGTH; i++) @@ -349,6 +355,7 @@ static bool act_on_btn(wordle_state_t *state) { display_title(state); return true; case SCREEN_NO_DICT: + case SCREEN_ALREADY_GUESSED: show_start_of_attempt(state); return true; #if USE_DAILY_STREAK @@ -362,6 +369,44 @@ static bool act_on_btn(wordle_state_t *state) { return false; } +static void get_result(wordle_state_t *state) { + // Check if it's in the dict + uint16_t in_dict = check_word_in_dict(state->word_elements); + if (in_dict == _num_words) { + display_not_in_dict(state); + return; + } + + // Check if already guessed + for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { + printf("%d %d \r\n",state->guessed_words[state->attempt], state->guessed_words[i]); + if(in_dict == state->guessed_words[i]) { + display_already_guessed(state); + return; + } + } + + state->guessed_words[state->attempt] = in_dict; + bool exact_match = check_word(state); + if (exact_match) { + state->playing = false; + state->curr_screen = SCREEN_WIN; + state->streak++; +#if USE_DAILY_STREAK + state->prev_day = get_day_unix_time(); +#endif + return; + } + if (state->attempt++ > WORDLE_MAX_ATTEMPTS) { + state->playing = false; + state->curr_screen = SCREEN_LOSE; + state->streak = 0; + return; + } + state->curr_screen = SCREEN_RESULT; + return; +} + void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; @@ -429,31 +474,8 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi if (state->word_elements[state->position] == _num_valid_letters) break; state->playing = true; state->position = get_next_pos(state->position, state->word_elements_result); - if (state->position >= WORDLE_LENGTH) { - bool in_dict = check_word_in_dict(state->word_elements); - if (!in_dict) { - display_not_in_dict(state); - break; - } - bool exact_match = check_word(state); - if (exact_match) { - state->playing = false; - state->curr_screen = SCREEN_WIN; - state->streak++; -#if USE_DAILY_STREAK - state->prev_day = get_day_unix_time(); -#endif - break; - } - if (state->attempt++ >= WORDLE_MAX_ATTEMPTS) { - state->playing = false; - state->curr_screen = SCREEN_LOSE; - state->streak = 0; - break; - } - state->curr_screen = SCREEN_RESULT; - break; - } + if (state->position >= WORDLE_LENGTH) + get_result(state); break; case EVENT_ALARM_LONG_PRESS: if (state->curr_screen != SCREEN_PLAYING) break; diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 633d3a2..9117334 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -56,6 +56,7 @@ typedef enum { SCREEN_WIN, SCREEN_LOSE, SCREEN_NO_DICT, + SCREEN_ALREADY_GUESSED, SCREEN_COUNT } WordleScreen; @@ -63,6 +64,7 @@ typedef struct { // Anything you need to keep track of, put it here! uint8_t word_elements[WORDLE_LENGTH]; WordleLetterResult word_elements_result[WORDLE_LENGTH]; + uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; uint8_t attempt : 3; uint8_t position : 3; bool playing : 1; From 3e327eb7fdc7afef9f3c9afd7d20f44d43625c40 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 15 Aug 2024 18:12:54 -0400 Subject: [PATCH 102/220] Another fix on the word_check --- movement/watch_faces/complication/wordle_face.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 08ad478..3cf4722 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -60,7 +60,7 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R // Number of words found: 281 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "AAAAA","SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", + "SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", "POLES", "ROLLS", "ALOES", "LOSES", "SLICE", "PEACE", "POLLS", "POSES", "LANES", "COPRA", "SPANS", "CANAL", "LOSER", "PAPER", "PILES", "CLASS", "RACER", "POOLS", "PLAIN", "SPEAR", "SPARE", "INNER", "ALIEN", "NOSES", "EARLS", "SEALS", "LEARN", @@ -181,9 +181,12 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { static bool check_word(wordle_state_t *state) { // Exact bool is_exact_match = true; + bool answer_already_accounted[WORDLE_LENGTH] = { false }; for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) + if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) { state->word_elements_result[i] = WORDLE_LETTER_CORRECT; + answer_already_accounted[i] = true; + } else { state->word_elements_result[i] = WORDLE_LETTER_WRONG; is_exact_match = false; @@ -191,14 +194,13 @@ static bool check_word(wordle_state_t *state) { } if (is_exact_match) return true; // Wrong Location - bool answer_found_wrong_loc[WORDLE_LENGTH] = { false }; for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] != WORDLE_LETTER_WRONG) continue; for (size_t j = 0; j < WORDLE_LENGTH; j++) { - if (answer_found_wrong_loc[j]) continue; + if (answer_already_accounted[j]) continue; if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { state->word_elements_result[i] = WORDLE_LETTER_WRONG_LOC; - answer_found_wrong_loc[j] = true; + answer_already_accounted[j] = true; break; } } @@ -379,7 +381,6 @@ static void get_result(wordle_state_t *state) { // Check if already guessed for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { - printf("%d %d \r\n",state->guessed_words[state->attempt], state->guessed_words[i]); if(in_dict == state->guessed_words[i]) { display_already_guessed(state); return; From 02f6a3256cf0a243bf54c0b365e5894feef5af25 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 02:13:26 -0400 Subject: [PATCH 103/220] Added documentation for Wordle face --- .../watch_faces/complication/wordle_face.c | 38 +++----- .../watch_faces/complication/wordle_face.h | 32 +++++-- utils/{ => wordle_face}/wordle_list.py | 89 +++++++++++++++++-- 3 files changed, 119 insertions(+), 40 deletions(-) rename utils/{ => wordle_face}/wordle_list.py (88%) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 3cf4722..1d40252 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -29,33 +29,8 @@ #include "watch_utility.h" #endif -#define FREQ 2 - -/* -TODO: -* Add a way to recount previous attempts -*/ - // From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff - -/* -Letter | Usage -_______|______ -E | 1519 -S | 1490 -A | 1213 -R | 1026 -O | 852 -L | 850 -I | 843 -T | 819 But looks bad across all positions -N | 681 -D | 619 lowercase d looks like a in certain positions -C | 525 -U | 514 P has more words with the other letters here (281 vs 198) -P | 448 -*/ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; // Number of words found: 281 @@ -253,7 +228,10 @@ static void display_streak(wordle_state_t *state) { char buf[12]; state->curr_screen = SCREEN_STREAK; #if USE_DAILY_STREAK - sprintf(buf, "WO St%2ddy", state->streak); + if (state->streak > 99) + sprintf(buf, "WO St--dy"); + else + sprintf(buf, "WO St%2ddy", state->streak); #else sprintf(buf, "WO St%4d", state->streak); #endif @@ -392,7 +370,8 @@ static void get_result(wordle_state_t *state) { if (exact_match) { state->playing = false; state->curr_screen = SCREEN_WIN; - state->streak++; + if (state->streak < 0x7F) + state->streak++; #if USE_DAILY_STREAK state->prev_day = get_day_unix_time(); #endif @@ -428,7 +407,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; if (state->curr_day != now) state->playing = false; #endif - movement_request_tick_frequency(FREQ); + movement_request_tick_frequency(2); display_title(state); } @@ -485,7 +464,10 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ACTIVATE: + break; case EVENT_TIMEOUT: + if (state->curr_screen >= SCREEN_WIN) + display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: if (state->curr_screen == SCREEN_TITLE) diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 9117334..b137512 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2024 <#author_name#> + * Copyright (c) 2024 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,15 +28,37 @@ #include "movement.h" /* - * A DESCRIPTION OF YOUR WATCH FACE - * - * and a description of how use it + * Wordle Face + * A port of NY Times' Wordle game (https://www.nytimes.com/games/wordle/index.html) + * A random 5 letter word is chosen and you have WORDLE_MAX_ATTEMPTS attempts to guess it. + * Each guess must be a valid 5-letter word found in _legal_words in the C file. + * The only letters used are _valid_letters, also found in the C file. + * After a guess, the letters in the correct spot will remain, + * and the letters found in the word, but in the incorrect spot will blink. + * The screen after the title screen if a new game is started shows the streak of games won in a row. + * + * If USE_DAILY_STREAK is set to True, then the game can only be played once per day, + * and the streak resets to 0 if a day goes by without playing the game. + * + * Controls: + * Light Press + * If Playing: Next letter + * Else: Next screen + * Light Hold + * If Playing: Previous letter + * Else: None * + * Alarm Press + * If Playing: Next position + * Else: Next screen + * Alarm Hold + * If Playing: Previous position + * Else: None */ #define WORDLE_LENGTH 5 #define WORDLE_MAX_ATTEMPTS 6 -#define USE_DAILY_STREAK true +#define USE_DAILY_STREAK false typedef enum { WORDLE_LETTER_WRONG = 0, diff --git a/utils/wordle_list.py b/utils/wordle_face/wordle_list.py similarity index 88% rename from utils/wordle_list.py rename to utils/wordle_face/wordle_list.py index 1b5baf0..f0c894b 100644 --- a/utils/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1,4 +1,4 @@ -import random +import random, itertools, time, ast # From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff words = [ @@ -315,9 +315,12 @@ words = [ "YOUTH", "ZEBRA", "ZONES", ] -alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] +alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] def most_used_letters(): + ''' + Outputs how many times each letter is used in the words array. + ''' dicto = {} for i in alphabet: count = 0 @@ -334,12 +337,13 @@ def most_used_letters(): print(f"{k.upper()} | {dicto[k]}") -def list_of_valid_words(): - letters = ['e', 's', 'a', 'r', 'o', 'l', 'i', 'n', 'c', 'p'] +def list_of_valid_words(letters): + ''' + Outputs the array of valid words that can be made with the combination of letters. + ''' letters = sorted(letters) for i, letter in enumerate(letters): # Force all letters to be capitalized letters[i] = letter.upper() - legal_words = [] for word in words: valid_word = True @@ -349,7 +353,14 @@ def list_of_valid_words(): break if valid_word and word not in legal_words: legal_words.append(word) + return legal_words + +def print_valid_words(letters): + ''' + Prints the array of valid words that the wordle_face.c can use + ''' + legal_words = list_of_valid_words(letters) for i,word in enumerate(legal_words): legal_words[i] = word.upper().replace("D","d") random.shuffle(legal_words) @@ -373,7 +384,71 @@ def list_of_valid_words(): print('') print("};") + +def get_sec_val_and_units(seconds): + if seconds < 1: + return f"{round(seconds * 1000)} ms" + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + secs = int(seconds % 60) + if hours > 0: + return f"{hours} hr {minutes} min {secs} sec" + elif minutes > 0: + return f"{minutes} min {secs} sec" + else: + return f"{secs} sec" + + +def txt_of_all_letter_combos(num_letters_in_set): + ''' + Creates a txt file that shows every combination of letters and how many words + their combo can make. + num_letters_in_set - How many letters should be in each combination + ''' + num_status_prints = 100 + dict_combos_counts = {} + print_iter = 0 + prev = time.time() + start = prev + letters_to_ignore = ['D','T'] # Don't diplay well on the watch + legal_letters = [item for item in alphabet if item not in letters_to_ignore] + print(f"Finding all {num_letters_in_set} letter combinations with the following letters: {legal_letters}") + all_combos = list(itertools.combinations(legal_letters, num_letters_in_set)) + len_all_combos = len(all_combos) + to_print = max(1, int(len_all_combos/ num_status_prints)) + print(f"Amount of Combos: {len_all_combos}") + estimated_prints = round(len_all_combos / to_print) + for i, letters in enumerate(all_combos): + letters = sorted(letters) + dict_combos_counts[repr(letters)] = len(list_of_valid_words(letters)) + print_iter+=1 + if print_iter >= to_print: + curr = time.time() + delta = curr - prev + time_passed = curr - start + total_time_estimate = delta * estimated_prints + time_left_estimate = (delta * estimated_prints) - time_passed + output = f"Time Passed: {get_sec_val_and_units(time_passed)} | " + output+= f"Amount of time for {to_print} items: {get_sec_val_and_units(delta)} | " + output+= f"Estimate for total: {get_sec_val_and_units(total_time_estimate)} | " + output+= f"items Left {len_all_combos - i} | " + output+= f"Percent Complete {round((100 * i) / len_all_combos)}% | " + output+= f"Estimated Time Left : {get_sec_val_and_units(time_left_estimate)}" + print(output) + prev = curr + print_iter = 0 + dict_combos_counts = dict(sorted(dict_combos_counts.items(), key=lambda item: item[1], reverse=True)) + + most_common_key = next(iter(dict_combos_counts)) + print(f"The Most Common Combo is: {most_common_key}") + print_valid_words(ast.literal_eval(most_common_key)) + + with open('output.txt', 'w') as file: + for key, value in dict_combos_counts.items(): + file.write(f'{key}: {value}\n') + + if __name__ == "__main__": most_used_letters() - list_of_valid_words() - + print_valid_words(['A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S']) + #txt_of_all_letter_combos(10) \ No newline at end of file From 580f8bf8eece137f2262686df60f482f037f4dd0 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 06:33:14 -0400 Subject: [PATCH 104/220] bugfix on iterating to previous position --- movement/watch_faces/complication/wordle_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 1d40252..98da99c 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -98,7 +98,7 @@ static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { if (curr_pos == 0) return 0; - for (size_t pos = curr_pos-1; pos > 0; pos--) { + for (int8_t pos = curr_pos-1; pos >= 0; pos--) { if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) return pos; } From 10eda8b2080d3a353a9d1a0d99c3d313a421dd61 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 06:19:31 -0400 Subject: [PATCH 105/220] Added expanded dictionary to check against --- .../watch_faces/complication/wordle_face.c | 144 +- utils/wordle_face/wordle_list.py | 1306 +++++++++++++---- 2 files changed, 1090 insertions(+), 360 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 98da99c..9b1db1b 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -30,46 +30,106 @@ #endif -// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; -// Number of words found: 281 +// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff +// Number of words found: 283 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "SPIES", "SOLAR", "RAISE", "RARES", "PAEAN", "PLIES", "CRASS", "PEARS", "SNORE", - "POLES", "ROLLS", "ALOES", "LOSES", "SLICE", "PEACE", "POLLS", "POSES", "LANES", - "COPRA", "SPANS", "CANAL", "LOSER", "PAPER", "PILES", "CLASS", "RACER", "POOLS", - "PLAIN", "SPEAR", "SPARE", "INNER", "ALIEN", "NOSES", "EARLS", "SEALS", "LEARN", - "COLIC", "OPERA", "LOOSE", "SPOOR", "SCALE", "SOARS", "PAILS", "PRONE", "OPALS", - "PIPER", "RILLS", "CAIRN", "POISE", "LEAPS", "ELOPE", "NICER", "SLOOP", "PANES", - "SOLES", "CROSS", "NIECE", "LAIRS", "LEASE", "SALES", "SCENE", "SORES", "SNARL", - "SPIRE", "LASSO", "CLOSE", "OSIER", "SPOOL", "PRICE", "LOANS", "POSSE", "PENAL", - "SLAPS", "RELIC", "SINCE", "CIRCA", "LIARS", "RISES", "OPENS", "ROARS", "PACES", - "ARISE", "RISEN", "PENIS", "LAPEL", "CROPS", "CANON", "LAPSE", "SCION", "ARSON", - "AREAS", "SLAIN", "CANOE", "EERIE", "NOOSE", "PIANO", "PLANE", "CLASP", "SCARE", - "COCOA", "CRESS", "NASAL", "LOCAL", "RINSE", "SCARS", "PROPS", "OASES", "SLEEP", - "SNAPS", "SIRES", "CANES", "RAILS", "RESIN", "COLON", "PEASE", "POPES", "PENCE", - "AROSE", "REELS", "SALSA", "OCEAN", "PESOS", "OPINE", "RACES", "RAINS", "PRIES", - "CRIES", "CALLS", "PIERS", "CELLS", "SCRAP", "EARNS", "IRONS", "SPACE", "LOONS", - "SILLS", "COALS", "PIECE", "PALER", "REINS", "APACE", "SLOPE", "CREPE", "CONES", - "CAPER", "SEERS", "CAPES", "OASIS", "REAPS", "PALES", "CLAPS", "PLEAS", "INANE", - "COINS", "SNAIL", "CLEAR", "ROSIN", "LILAC", "SPARS", "SPINE", "NONCE", "CRISP", - "CRAPE", "AISLE", "CRONE", "SPOIL", "SPOON", "ARENA", "PARSE", "CASES", "SPICE", - "RIPER", "PILLS", "SOLOS", "SPINS", "PEERS", "RARER", "CONIC", "REARS", "CACAO", - "PAPAS", "ACRES", "ROPES", "CORAL", "CLEAN", "EASES", "SPILL", "SENSE", "PIPES", - "CLANS", "PRESS", "LOINS", "PAPAL", "APPLE", "PAIRS", "SCORN", "ALONE", "PEEPS", - "SPREE", "SNARE", "CLIPS", "EASEL", "CAROL", "ASPEN", "SALON", "LOOPS", "PEALS", - "SNEER", "PLACE", "SELLS", "LINEN", "CRIER", "ACORN", "SLIPS", "ERASE", "LIONS", - "NAILS", "REPEL", "CORES", "LEPER", "APPAL", "ROSES", "SCORE", "RISER", "CREEP", - "CAPON", "ERROR", "NOISE", "CARES", "APRON", "SOILS", "SLOPS", "PAINS", "EPICS", - "SANER", "SAILS", "PRIOR", "ASSES", "COILS", "SCOOP", "LACES", "SCALP", "CRANE", - "PLANS", "ISLES", "SPORE", "PANIC", "COOLS", "SPELL", "ALIAS", "PORES", "SCRIP", - "PEARL", "PANEL", "ENROL", "LANCE", "CORPS", "LINES", "COPSE", "ONION", "NEARS", - "RIPEN", "LINER", "SCOPE", "SCANS", "SNIPE", "CEASE", "LEANS", "AEONS", "PINES", - "POPPA", "ROLES", "REALS", "PERIL", "POSER", "PROSE", "POLAR", "CORNS", "LIENS", - "SIREN", "PEONS", + "PROSE", "SLOPE", "CLAPS", "CAIRN", "PLANE", "SPACE", "ARISE", "NEARS", "OPERA", + "EPICS", "LAIRS", "AISLE", "APRON", "SCRIP", "CARES", "PERIL", "PILES", "CLEAN", + "CLANS", "CANOE", "COALS", "RELIC", "CRANE", "SLICE", "RESIN", "NAILS", "LIARS", + "RISEN", "COILS", "RINSE", "PRIES", "PLEAS", "CAPON", "PARSE", "ROLES", "SPICE", + "CORAL", "LEARN", "SNIPE", "CAPER", "SIREN", "CORES", "CRAPE", "SPOIL", "EARNS", + "LANES", "PEALS", "RAINS", "ACRES", "SINCE", "LACES", "ROPES", "CLEAR", "PANIC", + "SCALE", "SANER", "CLOSE", "LINER", "CORNS", "SPINE", "SCARE", "ACORN", "PLANS", + "POSER", "SCORN", "AROSE", "LINES", "LIONS", "OCEAN", "SNAIL", "OSIER", "SCOPE", + "OPINE", "REINS", "COINS", "LOSER", "PANES", "RAISE", "RAILS", "ALIEN", "PLAIN", + "SNARE", "LOANS", "IRONS", "COPRA", "PLIES", "COPSE", "PEARS", "PALER", "SLAIN", + "LAPSE", "CONES", "ARSON", "POISE", "OPENS", "SPORE", "CAROL", "PACES", "LEAPS", + "ALONE", "ASPEN", "SNORE", "CRIES", "OPALS", "PENAL", "PAINS", "ENROL", "POLAR", + "SPEAR", "SCION", "POLES", "SCORE", "LEANS", "PEONS", "ROSIN", "REAPS", "SOLAR", + "EARLS", "PENIS", "PRICE", "REALS", "SCALP", "CLASP", "NOISE", "PANEL", "NICER", + "CRISP", "SPARE", "PEARL", "CORPS", "SALON", "RACES", "LOINS", "RIPEN", "PLACE", + "CRONE", "CANES", "LANCE", "LIENS", "ALOES", "PIANO", "PRONE", "PIERS", "SNARL", + "AEONS", "PINES", "SPIRE", "PAILS", "CAPES", "CLIPS", "PALES", "CROPS", "PAIRS", + "SCRAP", "PORES", "SALES", "COOLS", "SLOPS", "APACE", "CRIER", "ROLLS", "PIPES", + "SLIPS", "EASES", "PEACE", "CELLS", "POLLS", "POPPA", "SPREE", "RILLS", "SPILL", + "RIPER", "ONION", "ISLES", "CROSS", "PAPAS", "NOSES", "PAPAL", "SLEEP", "EERIE", + "SEALS", "PAPER", "COLOR", "SORES", "SPELL", "SNAPS", "SEERS", "SPARS", "PIPER", + "POSES", "LOOPS", "APPAL", "LOSES", "LINEN", "ROARS", "RARER", "NIECE", "LOCAL", + "NONCE", "CREEP", "SCARS", "SPOOR", "PEASE", "SIRES", "PRESS", "CLASS", "PAEAN", + "SPOOL", "LOOSE", "CRASS", "LILAC", "COLIC", "RACER", "CREPE", "NASAL", "SOLOS", + "ROSES", "COLON", "REPEL", "ASSES", "INANE", "SCENE", "SLAPS", "PEEPS", "LAPEL", + "PIECE", "LEPER", "REELS", "INNER", "PROPS", "SILLS", "LEASE", "SOLES", "CIRCA", + "CRESS", "SCANS", "SPOON", "REARS", "CACAO", "ERASE", "CANON", "SOARS", "SLOOP", + "AREAS", "SPINS", "OASIS", "OASES", "POPES", "ELOPE", "CEASE", "CINCO", "APPLE", + "NOOSE", "PEERS", "SENSE", "POOLS", "RISER", "PENCE", "POSSE", "ALIAS", "PESOS", + "SCOOP", "EASEL", "LOONS", "CONIC", "SPANS", "SPIES", "PRIOR", "SALSA", "SELLS", + "PILLS", "RISES", "RARES", "SNEER", "SOILS", "ARENA", "CASES", "CANAL", "SAILS", + "LASSO", "COCOA", "ERROR", "CALLS", +}; +// These are words that'll never be used, but still need to be in the dictionary for guesses. +// Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236 +// Number of words found: 469 +static const char _expanded_words[][WORDLE_LENGTH + 1] = { + "PARIS", "APRIL", "SPAIN", "EINEN", "ASCII", "EINER", "SEINE", "AINSI", "ALICE", + "ALLES", "ALORS", "EINES", "ALLER", "PEINE", "PARCE", "CELLE", "CLARA", "ELLES", + "ELLEN", "OLISI", "ALLEN", "ISAAC", "APRES", "CROIS", "SANOI", "PASSE", "ELSIE", + "REINE", "ELLER", "AARON", "CLARE", "IRENE", "ANNIE", "ELLOS", "PARLE", "ALLAN", + "PELLE", "CAIRO", "SENOR", "PENSE", "CECIL", "SEELE", "ORION", "SELON", "COSAS", + "PASSA", "ELLIS", "CARLO", "ENNEN", "SILAS", "EENEN", "OSCAR", "ONCLE", "CESSE", + "SONNE", "ASSIS", "PRISE", "SERAI", "CELIA", "NOIRE", "NORSE", "SINNE", "LIESS", + "ELIAS", "REPOS", "COLIN", "NOIRS", "CLAIR", "CIELO", "PARLA", "SOINS", "LASSE", + "NELLA", "PAOLO", "SOLON", "REPAS", "NANCE", "PAINE", "SAISI", "ELISE", "CESAR", + "CANNA", "SALLE", "SINON", "SINAI", "LOIRE", "PENSA", "LEILA", "REISE", "ELLAS", + "POORE", "EARLE", "CERCA", "LEISE", "POOLE", "AILES", "SANOA", "LEONE", "LILLE", + "PROIE", "CARNE", "SPIEL", "CERES", "ENSIN", "ROLLO", "ARRAS", "SEIEN", "PRIER", + "ANNAN", "CALLE", "LIISA", "SALIR", "LESSE", "LESEN", "LIIAN", "NEERE", "ARIEL", + "PIENI", "PIERO", "ERANO", "ELENA", "SILLE", "NEALE", "SEENE", "ROLLE", "NELLE", + "SOLLE", "ESSER", "PASAR", "PREIS", "ASIAN", "SENCE", "ANSON", "SERRA", "CONAN", + "SERAS", "SIENA", "SOPRA", "RENEE", "ALINE", "CORSE", "ASSAI", "INSEL", "ROSIE", + "SONIA", "APPEL", "CRISE", "CIRCE", "LINIE", "RENAN", "CAIRE", "COLLA", "SANOO", + "EENER", "ANCOR", "NEPAL", "REINO", "LAINE", "SOONE", "ALAIN", "LAPSI", "INCAS", + "INNES", "CARON", "ROSEN", "CASAS", "NOLAN", "SERRE", "PESAR", "SEARS", "LEPIC", + "LISLE", "LOSSE", "CINNA", "SERIE", "RIRES", "CORSO", "SOIRS", "CREER", "POCOS", + "SIENS", "ARLES", "CROCE", "IONIC", "PONER", "ESSEN", "SANON", "CESSA", "SERIA", + "ALPES", "NINON", "LILLA", "AINOA", "CORPO", "LESER", "ILLIS", "ROPER", "ANNEE", + "PAIRE", "PEPIN", "ORIEL", "CANNE", "AIRES", "ARCIS", "EASIE", "ANNOS", "COLLE", + "SELLE", "EILEN", "CAPRI", "ERICA", "ROCCO", "ARIAN", "CLEON", "ALLIE", "PONCE", + "COPIE", "INNAN", "NOCES", "NAPPE", "CORNE", "ESIIN", "ENCOR", "LORNA", "SACRE", + "PAPEL", "SAILE", "SAEPE", "CREON", "LLENO", "ELISA", "PASSO", "ASILE", "LORCS", + "ASIAA", "SANIN", "ONNEN", "SONNA", "AILIE", "ALIIS", "ECOLE", "CREES", "PRESO", + "CLARO", "EARES", "ROSSI", "COREA", "SANAN", "AESOP", "SAPOR", "EISEN", "ACASO", + "PARAS", "NANON", "LAPIS", "ARRAN", "CLLIA", "SACRA", "PRINS", "CENCI", "CLAES", + "SLAAP", "ROLLA", "COLES", "LORNE", "OLELO", "CASSE", "NILES", "PASOS", "ESSAI", + "ROSAS", "LLENA", "LEERE", "CLASE", "CALOR", "ROSSE", "ALLEE", "SOREL", "SANAA", + "SLONE", "OLSEN", "OOREN", "PARER", "PASSI", "POSSA", "PLAIE", "OPERE", "SCAPE", + "POLEN", "RIPON", "SCALA", "AILLE", "PALOS", "CLAPP", "ESCAP", "ELLEI", "IONIA", + "NICOL", "PAESE", "PERON", "ORSON", "INNEN", "AISNE", "RANCE", "SLAAN", "PAOLI", + "COLLO", "ANNAS", "ERROL", "CLERC", "SAINE", "RAINA", "PRESE", "PARIA", "PERLE", + "RECAL", "SINAE", "PESER", "OISIN", "PLENA", "CARLE", "PERES", "SACAR", "ALPEN", + "CORRE", "ACCES", "RILLA", "ANNAL", "PERSE", "SAALE", "PERRO", "AILSA", "POCAS", + "SOLEN", "PLASE", "SOLIS", "PAPPI", "COPIA", "ARIES", "ROCCA", "ALIOS", "ROCAS", + "PELOS", "NEPOS", "COLPA", "ISORA", "LECON", "SOANE", "SNELL", "ILLOS", "PILAR", + "ECLAC", "PRESA", "SILLA", "NIELS", "LIPPO", "CROLL", "PONEN", "POSEE", "PIPPA", + "ILLAN", "CENIS", "SANNA", "RASSI", "CERRO", "SCENA", "CASOS", "COLPO", "POSSO", + "POSEN", "EINAR", "ISLAS", "IPSIS", "SALEN", "ASIEN", "CREAN", "LENIN", "LOCIS", + "NENNE", "ILION", "NARES", "ONNEA", "PALAA", "PIPPO", "POLIS", "RICOS", "ELSON", + "SNOOP", "ANNIS", "PROPE", "ELLIE", "ERNIE", "PLIER", "SERES", "REINA", "LIPPE", + "OLINE", "PARIE", "ARLEE", "NIAIS", "LEONI", "RAINE", "LARES", "SEINS", "CARRE", + "POILS", "ALENE", "LINEA", "NEARE", "PENSO", "PRISA", "CAPEL", "PAREA", "PEECE", + "SALIO", "COELO", "SCIRE", "NELLO", "LIENE", "ORICE", "EPAIS", "PERCE", "ALLIS", + "PEPLE", "LARNE", "NEILL", "OLLEN", "CASCA", "LAPIN", "OLLIE", "SALIC", "LIANE", + "REESE", "ELSLI", "SPION", "RIENS", "LILAS", "PAPPA", "ERRER", "SPISE", "CELIE", + "OLSON", "IRREN", "ARIAS", "ARION", "PASEO", "CAERE", "PISAN", "CARRO", "PAROI", + "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", "CONOR", + "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", "CARIA", + "PIENA", }; +static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); +static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); static uint32_t get_random(uint32_t max) { @@ -150,7 +210,17 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { } if (is_exact_match) return i; } - return _num_words; + for (uint16_t i = 0; i < _num_expanded_words; i++) { + is_exact_match = true; + for (size_t j = 0; j < WORDLE_LENGTH; j++) { + if (_valid_letters[word_elements[j]] != _expanded_words[i][j]) { + is_exact_match = false; + break; + } + } + if (is_exact_match) return _num_words + i; + } + return _num_words + _num_expanded_words; } static bool check_word(wordle_state_t *state) { @@ -206,7 +276,7 @@ static void reset_board(wordle_state_t *state) { state->word_elements_result[i] = WORDLE_LETTER_WRONG; } for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { - state->guessed_words[i] = _num_words; + state->guessed_words[i] = _num_words + _num_expanded_words; } state->curr_answer = get_random(_num_words); state->attempt = 0; @@ -352,7 +422,7 @@ static bool act_on_btn(wordle_state_t *state) { static void get_result(wordle_state_t *state) { // Check if it's in the dict uint16_t in_dict = check_word_in_dict(state->word_elements); - if (in_dict == _num_words) { + if (in_dict == _num_words + _num_expanded_words) { display_not_in_dict(state); return; } diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index f0c894b..d508a8b 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1,323 +1,949 @@ import random, itertools, time, ast # From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff -words = [ - "ABACK", "ABAFT", "ABASE", "ABATE", "ABBEY", "ABBOT", "ABHOR", "ABIDE", "ABLER", "ABODE", - "ABOUT", "ABOVE", "ABUSE", "ABYSS", "ACHED", "ACHES", "ACIDS", "ACORN", "ACRES", "ACRID", - "ACTED", "ACTOR", "ACUTE", "ADAGE", "ADAPT", "ADDED", "ADDER", "ADEPT", "ADIEU", "ADMIT", - "ADOBE", "ADOPT", "ADORE", "ADORN", "ADULT", "AEGIS", "AEONS", "AFFIX", "AFIRE", "AFOOT", - "AFTER", "AGAIN", "AGAPE", "AGATE", "AGENT", "AGILE", "AGING", "AGLOW", "AGONY", "AGREE", - "AHEAD", "AIDED", "AIDES", "AILED", "AIMED", "AIRED", "AISLE", "ALARM", "ALBUM", "ALDER", - "ALERT", "ALIAS", "ALIBI", "ALIEN", "ALIKE", "ALIVE", "ALLAY", "ALLEY", "ALLOT", "ALLOW", - "ALLOY", "ALOES", "ALOFT", "ALONE", "ALONG", "ALOOF", "ALOUD", "ALPHA", "ALTAR", "ALTER", - "ALTOS", "AMASS", "AMAZE", "AMBER", "AMBLE", "AMEND", "AMIGO", "AMISS", "AMITY", "AMONG", - "AMOUR", "AMPLE", "AMPLY", "AMUSE", "ANGEL", "ANGER", "ANGLE", "ANGRY", "ANGST", "ANIME", - "ANKLE", "ANNEX", "ANNOY", "ANNUL", "ANTES", "ANTIC", "ANVIL", "APACE", "APART", "APING", - "APPAL", "APPLE", "APPLY", "APRON", "APTLY", "AREAS", "ARENA", "ARGUE", "ARISE", "ARMED", - "AROMA", "AROSE", "ARRAY", "ARROW", "ARSON", "ASHEN", "ASHES", "ASIDE", "ASKED", "ASKEW", - "ASPEN", "ASSAY", "ASSES", "ASSET", "ASTER", "ASTIR", "ATLAS", "ATOLL", "ATOMS", "ATONE", - "ATTAR", "ATTIC", "AUDIO", "AUDIT", "AUGER", "AUGHT", "AUGUR", "AUNTS", "AURAS", "AUTOS", - "AVAIL", "AVERS", "AVERT", "AVOID", "AVOWS", "AWAIT", "AWAKE", "AWARD", "AWARE", "AWFUL", - "AWOKE", "AXIOM", "AXLES", "AZURE", "BABEL", "BABES", "BACKS", "BACON", "BADGE", "BADLY", - "BAGGY", "BAITS", "BAIZE", "BAKED", "BAKER", "BALES", "BALLS", "BALMY", "BANAL", "BANDS", - "BANDY", "BANGS", "BANJO", "BANKS", "BANNS", "BARBS", "BARDS", "BARED", "BARGE", "BARKS", - "BARNS", "BARON", "BASAL", "BASED", "BASER", "BASES", "BASIC", "BASIL", "BASIN", "BASIS", - "BASSO", "BASTE", "BATCH", "BATED", "BATHE", "BATHS", "BATON", "BAYOU", "BEACH", "BEADS", - "BEADY", "BEAKS", "BEAMS", "BEANS", "BEARD", "BEARS", "BEAST", "BEAUX", "BEECH", "BEETS", - "BEFIT", "BEGAN", "BEGAT", "BEGET", "BEGIN", "BEGOT", "BEGUN", "BEING", "BELIE", "BELLE", - "BELLS", "BELLY", "BELOW", "BELTS", "BENCH", "BENDS", "BERGS", "BERRY", "BERTH", "BERYL", - "BESET", "BESOM", "BEVEL", "BIBLE", "BIDED", "BIDES", "BIGHT", "BIGOT", "BILGE", "BILLS", - "BILLY", "BINDS", "BIPED", "BIRCH", "BIRDS", "BIRTH", "BISON", "BITCH", "BITES", "BLACK", - "BLADE", "BLAME", "BLAND", "BLANK", "BLARE", "BLAST", "BLAZE", "BLEAK", "BLEAT", "BLEED", - "BLEND", "BLENT", "BLESS", "BLEST", "BLIND", "BLINK", "BLISS", "BLOCK", "BLOCS", "BLOND", - "BLOOD", "BLOOM", "BLOTS", "BLOWN", "BLOWS", "BLUER", "BLUES", "BLUFF", "BLUNT", "BLURT", - "BLUSH", "BOARD", "BOARS", "BOAST", "BOATS", "BODED", "BODES", "BOGGY", "BOGUS", "BOILS", - "BOLES", "BOLTS", "BOMBS", "BONDS", "BONED", "BONES", "BONNY", "BONUS", "BOOBY", "BOOKS", - "BOOMS", "BOONS", "BOORS", "BOOST", "BOOTH", "BOOTS", "BOOTY", "BOOZE", "BORAX", "BORED", - "BORES", "BORNE", "BOSOM", "BOUGH", "BOUND", "BOUTS", "BOWED", "BOWEL", "BOWER", "BOWLS", - "BOXED", "BOXER", "BOXES", "BRACE", "BRAGS", "BRAID", "BRAIN", "BRAKE", "BRAND", "BRASS", - "BRATS", "BRAVE", "BRAVO", "BRAWL", "BRAWN", "BREAD", "BREAK", "BREED", "BRIAR", "BRIBE", - "BRICK", "BRIDE", "BRIEF", "BRIER", "BRIGS", "BRIMS", "BRINE", "BRING", "BRINK", "BRINY", - "BRISK", "BROAD", "BROIL", "BROKE", "BROOD", "BROOK", "BROOM", "BROTH", "BROWN", "BROWS", - "BRUIN", "BRUNT", "BRUSH", "BRUTE", "BUCKS", "BUDGE", "BUGGY", "BUGLE", "BUILD", "BUILT", - "BULBS", "BULGE", "BULKS", "BULKY", "BULLS", "BULLY", "BUMPS", "BUNCH", "BUNKS", "BUOYS", - "BURLY", "BURNS", "BURNT", "BURRO", "BURRS", "BURST", "BUSHY", "BUSTS", "BUTTE", "BUTTS", - "BUXOM", "BUYER", "CABAL", "CABBY", "CABIN", "CABLE", "CACAO", "CACHE", "CADET", "CADRE", - "CAGED", "CAGES", "CAIRN", "CAKED", "CAKES", "CALLS", "CALMS", "CALYX", "CAMEL", "CAMEO", - "CAMPS", "CANAL", "CANDY", "CANES", "CANNY", "CANOE", "CANON", "CANTO", "CAPER", "CAPES", - "CAPON", "CARDS", "CARED", "CARES", "CARGO", "CAROL", "CARRY", "CARTS", "CARVE", "CASED", - "CASES", "CASKS", "CASTE", "CASTS", "CATCH", "CATER", "CAUSE", "CAVED", "CAVES", "CAVIL", - "CEASE", "CEDAR", "CEDED", "CELLS", "CENTS", "CHAFE", "CHAFF", "CHAIN", "CHAIR", "CHALK", - "CHAMP", "CHANT", "CHAOS", "CHAPS", "CHARM", "CHART", "CHARY", "CHASE", "CHASM", "CHATS", - "CHEAP", "CHEAT", "CHECK", "CHEEK", "CHEER", "CHEFS", "CHESS", "CHEST", "CHICK", "CHIDE", - "CHIEF", "CHILD", "CHILL", "CHIME", "CHINA", "CHINK", "CHINS", "CHIPS", "CHIRP", "CHOIR", - "CHOKE", "CHOPS", "CHORD", "CHOSE", "CHUCK", "CHUMP", "CHUMS", "CHUNK", "CHURL", "CHURN", - "CHUTE", "CIDER", "CIGAR", "CINCH", "CIRCA", "CITED", "CITES", "CIVET", "CIVIC", "CIVIL", - "CLACK", "CLAIM", "CLAMP", "CLAMS", "CLANG", "CLANK", "CLANS", "CLAPS", "CLASH", "CLASP", - "CLASS", "CLAWS", "CLEAN", "CLEAR", "CLEFS", "CLEFT", "CLERK", "CLEWS", "CLICK", "CLIFF", - "CLIMB", "CLIME", "CLING", "CLINK", "CLIPS", "CLOAK", "CLOCK", "CLODS", "CLOGS", "CLOSE", - "CLOTH", "CLOUD", "CLOUT", "CLOVE", "CLOWN", "CLUBS", "CLUCK", "CLUES", "CLUMP", "CLUNG", - "COACH", "COALS", "COAST", "COATS", "COBRA", "COCKS", "COCOA", "CODES", "COILS", "COINS", - "COLDS", "COLIC", "COLON", "COLTS", "COMBS", "COMER", "COMES", "COMET", "COMIC", "COMMA", - "CONCH", "CONES", "CONIC", "COOED", "COOKS", "COOLS", "COPRA", "COPSE", "CORAL", "CORDS", - "CORES", "CORKS", "CORNS", "CORPS", "COSTS", "COTES", "COUCH", "COUGH", "COULD", "COUNT", - "COUPE", "COUPS", "COURT", "COVER", "COVES", "COVET", "COVEY", "COWED", "COWER", "COYLY", - "COZEN", "CRABS", "CRACK", "CRAFT", "CRAGS", "CRAMP", "CRANE", "CRANK", "CRAPE", "CRASH", - "CRASS", "CRATE", "CRAVE", "CRAWL", "CRAZE", "CRAZY", "CREAK", "CREAM", "CREDO", "CREED", - "CREEK", "CREEP", "CREPE", "CREPT", "CRESS", "CREST", "CREWS", "CRIBS", "CRICK", "CRIED", - "CRIER", "CRIES", "CRIME", "CRIMP", "CRISP", "CROAK", "CROCK", "CRONE", "CRONY", "CROOK", - "CROPS", "CROSS", "CROUP", "CROWD", "CROWN", "CROWS", "CRUDE", "CRUEL", "CRUMB", "CRUSH", - "CRUST", "CRYPT", "CUBES", "CUBIC", "CUBIT", "CUFFS", "CULTS", "CURDS", "CURED", "CURES", - "CURLS", "CURLY", "CURRY", "CURSE", "CURST", "CURVE", "CYCLE", "CYNIC", "DADDY", "DAILY", - "DAIRY", "DAISY", "DALES", "DALLY", "DAMES", "DAMPS", "DANCE", "DANDY", "DARED", "DARES", - "DARTS", "DATED", "DATES", "DATUM", "DAUBS", "DAUNT", "DAWNS", "DAZED", "DEALS", "DEALT", - "DEANS", "DEARS", "DEATH", "DEBAR", "DEBIT", "DEBTS", "DEBUT", "DECAY", "DECKS", "DECOY", - "DECRY", "DEEDS", "DEEMS", "DEEPS", "DEFER", "DEIGN", "DEITY", "DELAY", "DELLS", "DELTA", - "DELVE", "DEMON", "DEMUR", "DENSE", "DENTS", "DEPOT", "DEPTH", "DERBY", "DESKS", "DETER", - "DEUCE", "DEVIL", "DIARY", "DICED", "DICES", "DICTA", "DIETS", "DIGIT", "DIKES", "DIMES", - "DIMLY", "DINED", "DINER", "DINES", "DINGY", "DIRGE", "DIRTY", "DISCS", "DISKS", "DITCH", - "DITTO", "DITTY", "DIVAN", "DIVED", "DIVER", "DIVES", "DIZZY", "DOCKS", "DODGE", "DOERS", - "DOGMA", "DOING", "DOLED", "DOLLS", "DOMED", "DOMES", "DONOR", "DOOMS", "DOORS", "DOSED", - "DOSES", "DOTED", "DOTES", "DOUBT", "DOUGH", "DOVES", "DOWDY", "DOWNS", "DOWNY", "DOWRY", - "DOZED", "DOZEN", "DRAFT", "DRAGS", "DRAIN", "DRAKE", "DRAMA", "DRAMS", "DRANK", "DRAPE", - "DRAWL", "DRAWN", "DRAWS", "DRAYS", "DREAD", "DREAM", "DREGS", "DRESS", "DRIED", "DRIER", - "DRIES", "DRIFT", "DRILL", "DRILY", "DRINK", "DRIPS", "DRIVE", "DROLL", "DRONE", "DROOP", - "DROPS", "DROSS", "DROVE", "DROWN", "DRUGS", "DRUMS", "DRUNK", "DRYLY", "DUCAL", "DUCAT", - "DUCHY", "DUCKS", "DUCTS", "DUELS", "DUETS", "DUKES", "DULLY", "DUMMY", "DUMPS", "DUMPY", - "DUNCE", "DUNES", "DUNNO", "DUPED", "DUPES", "DUSKY", "DUSTY", "DWARF", "DWELL", "DWELT", - "DYING", "DYKES", "EAGER", "EAGLE", "EARLS", "EARLY", "EARNS", "EARTH", "EASED", "EASEL", - "EASES", "EATEN", "EATER", "EAVES", "EBBED", "EBONY", "EDGED", "EDGES", "EDICT", "EDIFY", - "EERIE", "EGGED", "EIGHT", "EJECT", "ELATE", "ELBOW", "ELDER", "ELECT", "ELEGY", "ELFIN", - "ELITE", "ELOPE", "ELUDE", "ELVES", "EMAIL", "EMITS", "EMPTY", "ENACT", "ENDED", "ENDOW", - "ENEMY", "ENJOY", "ENNUI", "ENROL", "ENSUE", "ENTER", "ENTRY", "ENVOY", "EPICS", "EPOCH", - "EQUAL", "EQUIP", "ERASE", "ERECT", "ERRED", "ERROR", "ESSAY", "ETHER", "ETHIC", "EVADE", - "EVENT", "EVERY", "EVILS", "EVOKE", "EXACT", "EXALT", "EXCEL", "EXERT", "EXILE", "EXIST", - "EXITS", "EXPEL", "EXTOL", "EXTRA", "EXULT", "EYING", "EYRIE", "FABLE", "FACED", "FACES", - "FACTS", "FADED", "FADES", "FAILS", "FAINT", "FAIRS", "FAIRY", "FAITH", "FAKIR", "FALLS", - "FALSE", "FAMED", "FANCY", "FANGS", "FARCE", "FARED", "FARES", "FARMS", "FASTS", "FATAL", - "FATED", "FATES", "FATTY", "FAULT", "FAUNA", "FAUNS", "FAWNS", "FEARS", "FEAST", "FEATS", - "FEEDS", "FEELS", "FEIGN", "FEINT", "FELLS", "FELON", "FENCE", "FERAL", "FERNS", "FERRY", - "FETCH", "FETED", "FETID", "FETUS", "FEUDS", "FEVER", "FEWER", "FICHE", "FIEFS", "FIELD", - "FIEND", "FIERY", "FIFES", "FIFTH", "FIFTY", "FIGHT", "FILCH", "FILED", "FILES", "FILET", - "FILLS", "FILLY", "FILMS", "FILMY", "FILTH", "FINAL", "FINCH", "FINDS", "FINED", "FINER", - "FINES", "FINIS", "FINNY", "FIORD", "FIRED", "FIRES", "FIRMS", "FIRST", "FISHY", "FISTS", - "FITLY", "FIVES", "FIXED", "FIXER", "FIXES", "FJORD", "FLAGS", "FLAIL", "FLAIR", "FLAKE", - "FLAKY", "FLAME", "FLANK", "FLAPS", "FLARE", "FLASH", "FLASK", "FLATS", "FLAWS", "FLEAS", - "FLECK", "FLEES", "FLEET", "FLESH", "FLICK", "FLIER", "FLIES", "FLING", "FLINT", "FLIRT", - "FLITS", "FLOAT", "FLOCK", "FLOES", "FLOOD", "FLOOR", "FLORA", "FLOSS", "FLOUR", "FLOUT", - "FLOWN", "FLOWS", "FLUES", "FLUFF", "FLUID", "FLUKE", "FLUME", "FLUNG", "FLUSH", "FLUTE", - "FLYER", "FOAMS", "FOAMY", "FOCAL", "FOCUS", "FOGGY", "FOILS", "FOIST", "FOLDS", "FOLIO", - "FOLKS", "FOLLY", "FOODS", "FOOLS", "FORAY", "FORCE", "FORDS", "FORGE", "FORGO", "FORKS", - "FORMS", "FORTE", "FORTH", "FORTS", "FORTY", "FORUM", "FOUND", "FOUNT", "FOURS", "FOWLS", - "FOXES", "FOYER", "FRAIL", "FRAME", "FRANC", "FRANK", "FRAUD", "FREAK", "FREED", "FREER", - "FREES", "FRESH", "FRETS", "FRIAR", "FRIED", "FRILL", "FRISK", "FROCK", "FROGS", "FROND", - "FRONT", "FROST", "FROTH", "FROWN", "FROZE", "FRUIT", "FUDGE", "FUELS", "FUGUE", "FULLY", - "FUMED", "FUMES", "FUNDS", "FUNGI", "FUNNY", "FURRY", "FURZE", "FUSED", "FUSES", "FUSSY", - "FUZZY", "GABLE", "GAILY", "GAINS", "GALES", "GALLS", "GAMES", "GAMIN", "GAMMA", "GAMUT", - "GANGS", "GAPED", "GAPES", "GASES", "GASPS", "GATES", "GAUDY", "GAUGE", "GAUNT", "GAUZE", - "GAUZY", "GAVEL", "GAWKY", "GAYER", "GAYLY", "GAZED", "GAZER", "GAZES", "GEARS", "GEESE", - "GENIE", "GENII", "GENRE", "GENTS", "GENUS", "GERMS", "GHOST", "GIANT", "GIBES", "GIDDY", - "GIFTS", "GILDS", "GILLS", "GIMME", "GIPSY", "GIRDS", "GIRLS", "GIRTH", "GIVEN", "GIVES", - "GLADE", "GLAND", "GLARE", "GLASS", "GLAZE", "GLEAM", "GLEAN", "GLENS", "GLIDE", "GLINT", - "GLOAT", "GLOBE", "GLOOM", "GLORY", "GLOSS", "GLOVE", "GLOWS", "GLUED", "GNASH", "GNATS", - "GNAWS", "GNOME", "GOADS", "GOALS", "GOATS", "GODLY", "GOING", "GOLLY", "GONGS", "GONNA", - "GOODS", "GOODY", "GOOSE", "GORED", "GORGE", "GORSE", "GOTTA", "GOUGE", "GOURD", "GOUTY", - "GOWNS", "GRABS", "GRACE", "GRADE", "GRAFT", "GRAIN", "GRAMS", "GRAND", "GRANT", "GRAPE", - "GRAPH", "GRASP", "GRASS", "GRATE", "GRAVE", "GRAVY", "GRAZE", "GREAT", "GREED", "GREEN", - "GREET", "GREYS", "GRIEF", "GRILL", "GRIME", "GRIMY", "GRIND", "GRINS", "GRIPE", "GRIPS", - "GRIST", "GROAN", "GROIN", "GROOM", "GROPE", "GROSS", "GROUP", "GROVE", "GROWL", "GROWN", - "GROWS", "GRUBS", "GRUEL", "GRUFF", "GRUNT", "GUANO", "GUARD", "GUESS", "GUEST", "GUIDE", - "GUILD", "GUILE", "GUILT", "GUISE", "GULCH", "GULFS", "GULLS", "GULLY", "GUMMY", "GUSTO", - "GUSTS", "GUSTY", "GYPSY", "HABIT", "HACKS", "HAILS", "HAIRS", "HAIRY", "HALED", "HALLS", - "HALTS", "HALVE", "HANDS", "HANDY", "HANGS", "HAPPY", "HARDY", "HAREM", "HARES", "HARMS", - "HARPS", "HARPY", "HARRY", "HARSH", "HARTS", "HASTE", "HASTY", "HATCH", "HATED", "HATER", - "HAULS", "HAVEN", "HAVOC", "HAWKS", "HAZEL", "HEADS", "HEADY", "HEALS", "HEAPS", "HEARD", - "HEARS", "HEART", "HEATH", "HEATS", "HEAVE", "HEAVY", "HEDGE", "HEEDS", "HEELS", "HEIRS", - "HELIX", "HELLO", "HELMS", "HELPS", "HENCE", "HERBS", "HERDS", "HERON", "HEROS", "HEWED", - "HIDES", "HILLS", "HILLY", "HILTS", "HINDS", "HINGE", "HINTS", "HIRED", "HIRES", "HITCH", - "HIVES", "HOARD", "HOARY", "HOBBY", "HOIST", "HOLDS", "HOLES", "HOLLY", "HOMES", "HONEY", - "HOODS", "HOOFS", "HOOKS", "HOOPS", "HOOTS", "HOPED", "HOPES", "HORDE", "HORNS", "HORNY", - "HORSE", "HOSTS", "HOTEL", "HOTLY", "HOUND", "HOURS", "HOUSE", "HOVEL", "HOVER", "HOWLS", - "HULKS", "HULLS", "HUMAN", "HUMID", "HUMPS", "HUMUS", "HUNCH", "HUNTS", "HURLS", "HURRY", - "HURTS", "HUSKS", "HUSKY", "HUSSY", "HYDRA", "HYENA", "HYMNS", "ICILY", "ICING", "IDEAL", - "IDEAS", "IDIOM", "IDIOT", "IDLED", "IDLER", "IDOLS", "IDYLL", "IGLOO", "IMAGE", "IMBUE", - "IMPEL", "IMPLY", "INANE", "INCUR", "INDEX", "INEPT", "INERT", "INFER", "INGOT", "INLET", - "INNER", "INTER", "INURE", "IRATE", "IRKED", "IRONS", "IRONY", "ISLES", "ISLET", "ISSUE", - "ITEMS", "IVORY", "JACKS", "JADED", "JAILS", "JAUNT", "JEANS", "JEERS", "JELLY", "JERKS", - "JERKY", "JESTS", "JETTY", "JEWEL", "JIFFY", "JOINS", "JOINT", "JOKED", "JOKER", "JOKES", - "JOLLY", "JOUST", "JOYED", "JUDGE", "JUICE", "JUICY", "JUMPS", "JUNKS", "JUNTA", "JUROR", - "KARMA", "KEELS", "KEEPS", "KETCH", "KEYED", "KHAKI", "KICKS", "KILLS", "KINDA", "KINDS", - "KINGS", "KIOSK", "KITES", "KNACK", "KNAVE", "KNEAD", "KNEEL", "KNEES", "KNELL", "KNELT", - "KNIFE", "KNITS", "KNOBS", "KNOCK", "KNOLL", "KNOTS", "KNOWN", "KNOWS", "LABEL", "LACED", - "LACES", "LACKS", "LADEN", "LADLE", "LAGER", "LAIRS", "LAITY", "LAKES", "LAMBS", "LAMED", - "LAMES", "LAMPS", "LANCE", "LANDS", "LANES", "LANKY", "LAPEL", "LAPSE", "LARCH", "LARGE", - "LARGO", "LARKS", "LARVA", "LASSO", "LASTS", "LATCH", "LATER", "LATHE", "LATHS", "LAUGH", - "LAWNS", "LAYER", "LEADS", "LEAFY", "LEAKS", "LEAKY", "LEANS", "LEAPS", "LEAPT", "LEARN", - "LEASE", "LEASH", "LEAST", "LEAVE", "LEDGE", "LEECH", "LEEKS", "LEGAL", "LEMME", "LEMON", - "LENDS", "LEPER", "LEVEE", "LEVEL", "LEVER", "LIARS", "LIBEL", "LICKS", "LIEGE", "LIENS", - "LIFTS", "LIGHT", "LIKED", "LIKEN", "LIKER", "LIKES", "LILAC", "LIMBO", "LIMBS", "LIMES", - "LIMIT", "LINED", "LINEN", "LINER", "LINES", "LINGO", "LINKS", "LIONS", "LISTS", "LITHE", - "LIVED", "LIVER", "LIVES", "LIVID", "LLAMA", "LOADS", "LOAMY", "LOANS", "LOATH", "LOBBY", - "LOBES", "LOCAL", "LOCKS", "LOCUS", "LODGE", "LOFTY", "LOGES", "LOGIC", "LOGIN", "LOINS", - "LONGS", "LOOKS", "LOOMS", "LOONS", "LOOPS", "LOOSE", "LORDS", "LOSER", "LOSES", "LOTUS", - "LOUSE", "LOUSY", "LOVED", "LOVER", "LOVES", "LOWED", "LOWER", "LOWLY", "LOYAL", "LUCID", - "LUCKY", "LULLS", "LUMPS", "LUMPY", "LUNAR", "LUNCH", "LUNGE", "LUNGS", "LURCH", "LURED", - "LURES", "LURID", "LURKS", "LUSTS", "LUSTY", "LUTES", "LYING", "LYMPH", "LYNCH", "LYRIC", - "MACES", "MADAM", "MADLY", "MAGIC", "MAIDS", "MAILS", "MAINS", "MAIZE", "MAJOR", "MAKER", - "MAKES", "MALES", "MAMMA", "MANES", "MANGA", "MANGE", "MANGO", "MANGY", "MANIA", "MANLY", - "MANNA", "MANOR", "MANSE", "MAPLE", "MARCH", "MARES", "MARKS", "MARRY", "MARSH", "MARTS", - "MASKS", "MASON", "MASTS", "MATCH", "MATED", "MATES", "MAUVE", "MAXIM", "MAYBE", "MAYOR", - "MAZES", "MEALS", "MEALY", "MEANS", "MEANT", "MEATS", "MEDAL", "MEDIA", "MEETS", "MELON", - "MELTS", "MEMES", "MENDS", "MENUS", "MERCY", "MERES", "MERGE", "MERIT", "MERRY", "MESAS", - "METAL", "METED", "METER", "MEWED", "MIDST", "MIENS", "MIGHT", "MILCH", "MILES", "MILKY", - "MILLS", "MIMES", "MIMIC", "MINCE", "MINDS", "MINED", "MINER", "MINES", "MINOR", "MINTS", - "MINUS", "MIRTH", "MISER", "MISTS", "MITES", "MIXED", "MIXES", "MOANS", "MOATS", "MOCKS", - "MODEL", "MODEM", "MODES", "MOIST", "MOLAR", "MOLES", "MOMMA", "MONEY", "MONKS", "MONTH", - "MOODS", "MOODY", "MOONS", "MOORS", "MOOSE", "MOPED", "MORAL", "MORES", "MOSSY", "MOTES", - "MOTHS", "MOTIF", "MOTOR", "MOTTO", "MOUND", "MOUNT", "MOURN", "MOUSE", "MOUTH", "MOVED", - "MOVER", "MOVES", "MOVIE", "MOWED", "MOWER", "MUCUS", "MUDDY", "MULES", "MULTI", "MUMMY", - "MUMPS", "MUNCH", "MURAL", "MURKY", "MUSED", "MUSES", "MUSIC", "MUSKY", "MUSTY", "MUTED", - "MUTES", "MYRRH", "MYTHS", "NABOB", "NAILS", "NAIVE", "NAKED", "NAMED", "NAMES", "NASAL", - "NASTY", "NATAL", "NATTY", "NAVAL", "NAVEL", "NAVES", "NEARS", "NECKS", "NEEDS", "NEEDY", - "NEIGH", "NERVE", "NESTS", "NEVER", "NEWER", "NEWLY", "NICER", "NICHE", "NIECE", "NIGHT", - "NINNY", "NOBLE", "NOBLY", "NOISE", "NOISY", "NOMAD", "NONCE", "NOOKS", "NOOSE", "NORTH", - "NOSED", "NOSES", "NOTCH", "NOTED", "NOTES", "NOUNS", "NOVEL", "NUDGE", "NURSE", "NYMPH", - "OAKEN", "OAKUM", "OASES", "OASIS", "OATEN", "OATHS", "OBESE", "OBEYS", "OCCUR", "OCEAN", - "OCHRE", "ODDER", "ODDLY", "ODIUM", "OFFAL", "OFFER", "OFTEN", "OILED", "OLDEN", "OLDER", - "OMENS", "OMITS", "ONION", "ONSET", "OOZED", "OOZES", "OPALS", "OPENS", "OPERA", "OPINE", - "OPIUM", "OPTIC", "ORBIT", "ORDER", "ORGAN", "OSIER", "OTHER", "OTTER", "OUGHT", "OUNCE", - "OUTDO", "OUTER", "OVALS", "OVARY", "OVENS", "OVERT", "OWING", "OWNED", "OWNER", "OXIDE", - "OZONE", "PACES", "PACKS", "PADDY", "PADRE", "PAEAN", "PAGAN", "PAGES", "PAILS", "PAINS", - "PAINT", "PAIRS", "PALED", "PALER", "PALES", "PALMS", "PALMY", "PALSY", "PANEL", "PANES", - "PANGS", "PANIC", "PANSY", "PANTS", "PAPAL", "PAPAS", "PAPER", "PARED", "PARKA", "PARKS", - "PARRY", "PARSE", "PARTS", "PARTY", "PASHA", "PASTE", "PASTY", "PATCH", "PATES", "PATHS", - "PATIO", "PAUSE", "PAVED", "PAWED", "PAWNS", "PAYED", "PAYER", "PEACE", "PEACH", "PEAKS", - "PEALS", "PEARL", "PEARS", "PEASE", "PECKS", "PEDAL", "PEEPS", "PEERS", "PELTS", "PENAL", - "PENCE", "PENIS", "PENNY", "PEONS", "PERCH", "PERIL", "PESKY", "PESOS", "PESTS", "PETAL", - "PETTY", "PHASE", "PHIAL", "PHONE", "PHOTO", "PIANO", "PICKS", "PIECE", "PIERS", "PIETY", - "PIGMY", "PIKES", "PILED", "PILES", "PILLS", "PILOT", "PINCH", "PINED", "PINES", "PINKS", - "PINTO", "PINTS", "PIOUS", "PIPED", "PIPER", "PIPES", "PIQUE", "PITCH", "PITHY", "PIVOT", - "PLACE", "PLAID", "PLAIN", "PLAIT", "PLANE", "PLANK", "PLANS", "PLANT", "PLATE", "PLAYS", - "PLAZA", "PLEAD", "PLEAS", "PLIED", "PLIES", "PLOTS", "PLUCK", "PLUGS", "PLUMB", "PLUME", - "PLUMS", "PLUSH", "PODIA", "POEMS", "POESY", "POETS", "POINT", "POISE", "POKED", "POKER", - "POKES", "POLAR", "POLES", "POLKA", "POLLS", "PONDS", "POOLS", "POPES", "POPPA", "POPPY", - "PORCH", "PORED", "PORES", "PORTS", "POSED", "POSER", "POSES", "POSSE", "POSTS", "POUCH", - "POUND", "POURS", "POWER", "PRANK", "PRATE", "PRAYS", "PRESS", "PREYS", "PRICE", "PRICK", - "PRIDE", "PRIED", "PRIES", "PRIME", "PRINT", "PRIOR", "PRISM", "PRIVY", "PRIZE", "PROBE", - "PRONE", "PROOF", "PROPS", "PROSE", "PROSY", "PROUD", "PROVE", "PROWL", "PROWS", "PROXY", - "PRUDE", "PRUNE", "PSALM", "PSHAW", "PUDGY", "PUFFS", "PUFFY", "PULLS", "PULPY", "PULSE", - "PUMPS", "PUNCH", "PUPIL", "PUPPY", "PUREE", "PURER", "PURGE", "PURSE", "PUSSY", "PUTTY", - "QUACK", "QUAFF", "QUAIL", "QUAKE", "QUALM", "QUART", "QUASI", "QUAYS", "QUEEN", "QUEER", - "QUELL", "QUERY", "QUEST", "QUEUE", "QUICK", "QUIET", "QUILL", "QUILT", "QUIPS", "QUIRE", - "QUITE", "QUITS", "QUOTA", "QUOTE", "QUOTH", "RABBI", "RABID", "RACED", "RACER", "RACES", - "RACKS", "RADII", "RADIO", "RAFTS", "RAGED", "RAGES", "RAIDS", "RAILS", "RAINS", "RAINY", - "RAISE", "RAJAH", "RAKED", "RAKES", "RALLY", "RANCH", "RANGE", "RANKS", "RAPID", "RARER", - "RARES", "RATED", "RATES", "RATIO", "RAVED", "RAVEN", "RAVES", "RAYON", "RAZED", "RAZOR", - "REACH", "REACT", "READS", "READY", "REALM", "REALS", "REAMS", "REAPS", "REARS", "REBEL", - "REBUS", "REBUT", "RECUR", "REEDS", "REEDY", "REEFS", "REEKS", "REELS", "REEVE", "REFER", - "REFIT", "REGAL", "REIGN", "REINS", "RELAX", "RELAY", "RELIC", "REMIT", "RENDS", "RENEW", - "RENTS", "REPAY", "REPEL", "REPLY", "RESET", "RESIN", "RESTS", "REVEL", "REVUE", "RHEUM", - "RHYME", "RICKS", "RIDER", "RIDES", "RIDGE", "RIFLE", "RIFTS", "RIGHT", "RIGID", "RILED", - "RILLS", "RIMES", "RINGS", "RINSE", "RIOTS", "RIPEN", "RIPER", "RISEN", "RISER", "RISES", - "RISKS", "RISKY", "RITES", "RIVAL", "RIVEN", "RIVER", "RIVET", "ROADS", "ROAMS", "ROARS", - "ROAST", "ROBED", "ROBES", "ROBIN", "ROCKS", "ROCKY", "ROGUE", "ROLES", "ROLLS", "ROMAN", - "ROOFS", "ROOKS", "ROOMS", "ROOMY", "ROOST", "ROOTS", "ROPED", "ROPES", "ROSES", "ROSIN", - "ROUGE", "ROUGH", "ROUND", "ROUSE", "ROUTE", "ROUTS", "ROVED", "ROVER", "ROWDY", "ROWED", - "ROYAL", "RUDER", "RUFFS", "RUINS", "RULED", "RULER", "RULES", "RUNES", "RUNGS", "RUPEE", - "RURAL", "RUSES", "SABLE", "SABRE", "SACKS", "SADLY", "SAFER", "SAGAS", "SAGES", "SAHIB", - "SAILS", "SAINT", "SAITH", "SALAD", "SALES", "SALLY", "SALON", "SALSA", "SALTS", "SALTY", - "SALVE", "SALVO", "SANDS", "SANDY", "SANER", "SATED", "SATIN", "SATYR", "SAUCE", "SAUCY", - "SAVED", "SAVES", "SAWED", "SCALD", "SCALE", "SCALP", "SCALY", "SCAMP", "SCANS", "SCANT", - "SCARE", "SCARF", "SCARS", "SCENE", "SCENT", "SCION", "SCOFF", "SCOLD", "SCOOP", "SCOPE", - "SCORE", "SCORN", "SCOUR", "SCOUT", "SCOWL", "SCRAP", "SCREW", "SCRIP", "SCRUB", "SCULL", - "SEALS", "SEAMS", "SEAMY", "SEATS", "SECTS", "SEDAN", "SEDGE", "SEEDS", "SEEDY", "SEEKS", - "SEEMS", "SEERS", "SEIZE", "SELLS", "SEMEN", "SENDS", "SENSE", "SERFS", "SERGE", "SERUM", - "SERVE", "SEVEN", "SEVER", "SEWED", "SEWER", "SEXES", "SHACK", "SHADE", "SHADY", "SHAFT", - "SHAKE", "SHAKY", "SHALE", "SHALL", "SHALT", "SHAME", "SHAMS", "SHANK", "SHAPE", "SHARE", - "SHARK", "SHARP", "SHAVE", "SHAWL", "SHEAF", "SHEAR", "SHEDS", "SHEEN", "SHEEP", "SHEER", - "SHEET", "SHEIK", "SHELF", "SHELL", "SHIED", "SHIFT", "SHINE", "SHINS", "SHINY", "SHIPS", - "SHIRE", "SHIRK", "SHIRT", "SHOAL", "SHOCK", "SHOES", "SHONE", "SHOOK", "SHOON", "SHOOT", - "SHOPS", "SHORE", "SHORN", "SHORT", "SHOTS", "SHOUT", "SHOVE", "SHOWN", "SHOWS", "SHOWY", - "SHRED", "SHREW", "SHRUB", "SHRUG", "SHUNS", "SHUTS", "SHYLY", "SIBYL", "SIDED", "SIDES", - "SIEGE", "SIEVE", "SIGHS", "SIGHT", "SIGMA", "SIGNS", "SILKS", "SILKY", "SILLS", "SILLY", - "SINCE", "SINEW", "SINGE", "SINGS", "SINKS", "SIREN", "SIRES", "SITES", "SIXES", "SIXTH", - "SIXTY", "SIZED", "SIZES", "SKATE", "SKEIN", "SKIES", "SKIFF", "SKILL", "SKIMS", "SKINS", - "SKIPS", "SKIRT", "SKULK", "SKULL", "SKUNK", "SLABS", "SLACK", "SLAGS", "SLAIN", "SLAKE", - "SLANG", "SLANT", "SLAPS", "SLASH", "SLATE", "SLATS", "SLAVE", "SLAYS", "SLEDS", "SLEEK", - "SLEEP", "SLEET", "SLEPT", "SLICE", "SLICK", "SLIDE", "SLILY", "SLIME", "SLIMY", "SLING", - "SLINK", "SLIPS", "SLITS", "SLOOP", "SLOPE", "SLOPS", "SLOTH", "SLUGS", "SLUMP", "SLUMS", - "SLUNG", "SLUNK", "SLUSH", "SLYLY", "SMACK", "SMALL", "SMART", "SMASH", "SMEAR", "SMELL", - "SMELT", "SMILE", "SMIRK", "SMITE", "SMITH", "SMOCK", "SMOKE", "SMOKY", "SMOTE", "SNACK", - "SNAGS", "SNAIL", "SNAKE", "SNAKY", "SNAPS", "SNARE", "SNARL", "SNEAK", "SNEER", "SNIFF", - "SNIPE", "SNOBS", "SNORE", "SNORT", "SNOUT", "SNOWS", "SNOWY", "SNUFF", "SOAPY", "SOARS", - "SOBER", "SOCKS", "SOFAS", "SOGGY", "SOILS", "SOLAR", "SOLES", "SOLID", "SOLOS", "SOLVE", - "SONGS", "SONNY", "SOOTH", "SOOTY", "SORES", "SORRY", "SORTS", "SOUGH", "SOULS", "SOUND", - "SOUPS", "SOUSE", "SOUTH", "SOWED", "SOWER", "SPACE", "SPADE", "SPAKE", "SPANK", "SPANS", - "SPARE", "SPARK", "SPARS", "SPASM", "SPAWN", "SPEAK", "SPEAR", "SPECK", "SPEED", "SPELL", - "SPELT", "SPEND", "SPENT", "SPERM", "SPICE", "SPICY", "SPIED", "SPIES", "SPIKE", "SPILL", - "SPILT", "SPINE", "SPINS", "SPINY", "SPIRE", "SPITE", "SPITS", "SPLIT", "SPOIL", "SPOKE", - "SPOOK", "SPOOL", "SPOON", "SPOOR", "SPORE", "SPORT", "SPOTS", "SPOUT", "SPRAY", "SPREE", - "SPRIG", "SPUNK", "SPURN", "SPURS", "SPURT", "SQUAD", "SQUAT", "SQUAW", "STABS", "STACK", - "STAFF", "STAGE", "STAGS", "STAID", "STAIN", "STAIR", "STAKE", "STALE", "STALK", "STALL", - "STAMP", "STAND", "STANK", "STARE", "STARK", "STARS", "START", "STATE", "STAVE", "STAYS", - "STEAD", "STEAK", "STEAL", "STEAM", "STEED", "STEEL", "STEEP", "STEER", "STEMS", "STEPS", - "STERN", "STEWS", "STICK", "STIFF", "STILE", "STILL", "STING", "STINK", "STINT", "STIRS", - "STOCK", "STOIC", "STOLE", "STONE", "STONY", "STOOD", "STOOL", "STOOP", "STOPS", "STORE", - "STORK", "STORM", "STORY", "STOUT", "STOVE", "STRAP", "STRAW", "STRAY", "STREW", "STRIP", - "STRUT", "STUCK", "STUDS", "STUDY", "STUFF", "STUMP", "STUNG", "STUNT", "STYLE", "SUAVE", - "SUCKS", "SUGAR", "SUING", "SUITE", "SUITS", "SULKS", "SULKY", "SULLY", "SUNNY", "SUPER", - "SURER", "SURGE", "SURLY", "SWAIN", "SWAMP", "SWANS", "SWARD", "SWARM", "SWAYS", "SWEAR", - "SWEAT", "SWEEP", "SWEET", "SWELL", "SWEPT", "SWIFT", "SWILL", "SWIMS", "SWINE", "SWING", - "SWIRL", "SWISH", "SWOON", "SWOOP", "SWORD", "SWORE", "SWORN", "SWUNG", "SYNOD", "SYRUP", - "TABBY", "TABLE", "TABOO", "TACIT", "TACKS", "TAILS", "TAINT", "TAKEN", "TAKES", "TALES", - "TALKS", "TALLY", "TALON", "TAMED", "TAMER", "TANKS", "TAPER", "TAPES", "TARDY", "TARES", - "TARRY", "TARTS", "TASKS", "TASTE", "TASTY", "TAUNT", "TAWNY", "TAXED", "TAXES", "TEACH", - "TEAMS", "TEARS", "TEASE", "TEEMS", "TEENS", "TEETH", "TELLS", "TEMPI", "TEMPO", "TEMPS", - "TENDS", "TENET", "TENOR", "TENSE", "TENTH", "TENTS", "TEPEE", "TEPID", "TERMS", "TERSE", - "TESTS", "TESTY", "TEXTS", "THANK", "THEFT", "THEIR", "THEME", "THERE", "THESE", "THICK", - "THIEF", "THIGH", "THINE", "THING", "THINK", "THIRD", "THONG", "THORN", "THOSE", "THREE", - "THREW", "THROB", "THROE", "THROW", "THUMB", "THUMP", "THYME", "TIARA", "TIBIA", "TICKS", - "TIDAL", "TIDES", "TIERS", "TIGER", "TIGHT", "TILDE", "TILED", "TILES", "TILLS", "TILTS", - "TIMED", "TIMES", "TIMID", "TINGE", "TINTS", "TIPSY", "TIRED", "TIRES", "TITHE", "TITLE", - "TOADS", "TOAST", "TODAY", "TODDY", "TOILS", "TOKEN", "TOLLS", "TOMBS", "TOMES", "TONED", - "TONES", "TONGS", "TONIC", "TOOLS", "TOOTH", "TOPAZ", "TOPIC", "TOQUE", "TORCH", "TORSO", - "TORTS", "TOTAL", "TOTEM", "TOUCH", "TOUGH", "TOURS", "TOWED", "TOWEL", "TOWER", "TOWNS", - "TOXIC", "TOYED", "TRACE", "TRACK", "TRACT", "TRADE", "TRAIL", "TRAIN", "TRAIT", "TRAMP", - "TRAMS", "TRAPS", "TRASH", "TRAYS", "TREAD", "TREAT", "TREED", "TREES", "TREND", "TRESS", - "TRIAD", "TRIAL", "TRIBE", "TRICE", "TRICK", "TRIED", "TRIES", "TRILL", "TRIPE", "TRIPS", - "TRITE", "TROLL", "TROOP", "TROTH", "TROTS", "TROUT", "TRUCE", "TRUCK", "TRUER", "TRULY", - "TRUMP", "TRUNK", "TRUSS", "TRUST", "TRUTH", "TRYST", "TUBES", "TUFTS", "TULIP", "TULLE", - "TUNED", "TUNES", "TUNIC", "TURNS", "TUSKS", "TUTOR", "TWAIN", "TWANG", "TWEED", "TWICE", - "TWIGS", "TWINE", "TWINS", "TWIRL", "TWIST", "TYING", "TYPED", "TYPES", "UDDER", "ULCER", - "ULTRA", "UNCLE", "UNCUT", "UNDER", "UNDID", "UNDUE", "UNFIT", "UNION", "UNITE", "UNITS", - "UNITY", "UNSAY", "UNTIE", "UNTIL", "UPPER", "UPSET", "URBAN", "URGED", "URGES", "URINE", - "USAGE", "USERS", "USHER", "USING", "USUAL", "USURP", "USURY", "UTTER", "VAGUE", "VALES", - "VALET", "VALID", "VALUE", "VALVE", "VANES", "VAPID", "VASES", "VAULT", "VAUNT", "VEILS", - "VEINS", "VELDT", "VENAL", "VENOM", "VENTS", "VENUE", "VERBS", "VERGE", "VERSE", "VERVE", - "VESTS", "VEXED", "VEXES", "VIALS", "VICAR", "VICES", "VIDEO", "VIEWS", "VIGIL", "VILER", - "VILLA", "VINES", "VIOLA", "VIPER", "VIRUS", "VISIT", "VISOR", "VISTA", "VITAL", "VIVID", - "VIXEN", "VIZOR", "VOCAL", "VODKA", "VOGUE", "VOICE", "VOILE", "VOLTS", "VOMIT", "VOTED", - "VOTER", "VOTES", "VOUCH", "VOWED", "VOWEL", "VYING", "WADED", "WAFER", "WAFTS", "WAGED", - "WAGER", "WAGES", "WAGON", "WAIFS", "WAILS", "WAIST", "WAITS", "WAIVE", "WAKED", "WAKEN", - "WAKES", "WALKS", "WALLS", "WALTZ", "WANDS", "WANED", "WANES", "WANTS", "WARDS", "WARES", - "WARMS", "WARNS", "WARTS", "WASPS", "WASTE", "WATCH", "WATER", "WAVED", "WAVER", "WAVES", - "WAXED", "WAXEN", "WAXES", "WEARS", "WEARY", "WEAVE", "WEDGE", "WEEDS", "WEEDY", "WEEKS", - "WEEPS", "WEIGH", "WEIRD", "WELCH", "WELLS", "WENCH", "WHACK", "WHALE", "WHARF", "WHEAT", - "WHEEL", "WHELP", "WHERE", "WHICH", "WHIFF", "WHILE", "WHIMS", "WHINE", "WHIPS", "WHIRL", - "WHIRR", "WHISK", "WHIST", "WHITE", "WHOLE", "WHOOP", "WHORE", "WHOSE", "WICKS", "WIDEN", - "WIDER", "WIDOW", "WIDTH", "WIELD", "WIGHT", "WILDS", "WILES", "WILLS", "WINCE", "WINCH", - "WINDS", "WINDY", "WINES", "WINGS", "WINKS", "WIPED", "WIPES", "WIRED", "WIRES", "WISER", - "WISPS", "WITCH", "WITTY", "WIVES", "WOMAN", "WOMEN", "WOODS", "WOODY", "WOOED", "WOOER", - "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", - "WOUND", "WRACK", "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", - "WRITE", "WRITS", "WRONG", "WROTE", "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", - "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", "YOKES", "YOLKS", "YOUNG", "YOURS", - "YOUTH", "ZEBRA", "ZONES", +legal_list = [ + "ABACK", "ABAFT", "ABASE", "ABATE", "ABBEY", "ABBOT", "ABHOR", "ABIDE", "ABLER", "ABODE", "ABOUT", "ABOVE", + "ABUSE", "ABYSS", "ACHED", "ACHES", "ACIDS", "ACORN", "ACRES", "ACRID", "ACTED", "ACTOR", "ACUTE", "ADAGE", + "ADAPT", "ADDED", "ADDER", "ADEPT", "ADIEU", "ADMIT", "ADOBE", "ADOPT", "ADORE", "ADORN", "ADULT", "AEGIS", + "AEONS", "AFFIX", "AFIRE", "AFOOT", "AFTER", "AGAIN", "AGAPE", "AGATE", "AGENT", "AGILE", "AGING", "AGLOW", + "AGONY", "AGREE", "AHEAD", "AIDED", "AIDES", "AILED", "AIMED", "AIRED", "AISLE", "ALARM", "ALBUM", "ALDER", + "ALERT", "ALIAS", "ALIBI", "ALIEN", "ALIKE", "ALIVE", "ALLAY", "ALLEY", "ALLOT", "ALLOW", "ALLOY", "ALOES", + "ALOFT", "ALONE", "ALONG", "ALOOF", "ALOUD", "ALPHA", "ALTAR", "ALTER", "ALTOS", "AMASS", "AMAZE", "AMBER", + "AMBLE", "AMEND", "AMIGO", "AMISS", "AMITY", "AMONG", "AMOUR", "AMPLE", "AMPLY", "AMUSE", "ANGEL", "ANGER", + "ANGLE", "ANGRY", "ANGST", "ANIME", "ANKLE", "ANNEX", "ANNOY", "ANNUL", "ANTES", "ANTIC", "ANVIL", "APACE", + "APART", "APING", "APPAL", "APPLE", "APPLY", "APRON", "APTLY", "AREAS", "ARENA", "ARGUE", "ARISE", "ARMED", + "AROMA", "AROSE", "ARRAY", "ARROW", "ARSON", "ASHEN", "ASHES", "ASIDE", "ASKED", "ASKEW", "ASPEN", "ASSAY", + "ASSES", "ASSET", "ASTER", "ASTIR", "ATLAS", "ATOLL", "ATOMS", "ATONE", "ATTAR", "ATTIC", "AUDIO", "AUDIT", + "AUGER", "AUGHT", "AUGUR", "AUNTS", "AURAS", "AUTOS", "AVAIL", "AVERS", "AVERT", "AVOID", "AVOWS", "AWAIT", + "AWAKE", "AWARD", "AWARE", "AWFUL", "AWOKE", "AXIOM", "AXLES", "AZURE", "BABEL", "BABES", "BACKS", "BACON", + "BADGE", "BADLY", "BAGGY", "BAITS", "BAIZE", "BAKED", "BAKER", "BALES", "BALLS", "BALMY", "BANAL", "BANDS", + "BANDY", "BANGS", "BANJO", "BANKS", "BANNS", "BARBS", "BARDS", "BARED", "BARGE", "BARKS", "BARNS", "BARON", + "BASAL", "BASED", "BASER", "BASES", "BASIC", "BASIL", "BASIN", "BASIS", "BASSO", "BASTE", "BATCH", "BATED", + "BATHE", "BATHS", "BATON", "BAYOU", "BEACH", "BEADS", "BEADY", "BEAKS", "BEAMS", "BEANS", "BEARD", "BEARS", + "BEAST", "BEAUX", "BEECH", "BEETS", "BEFIT", "BEGAN", "BEGAT", "BEGET", "BEGIN", "BEGOT", "BEGUN", "BEING", + "BELIE", "BELLE", "BELLS", "BELLY", "BELOW", "BELTS", "BENCH", "BENDS", "BERGS", "BERRY", "BERTH", "BERYL", + "BESET", "BESOM", "BEVEL", "BIBLE", "BIDED", "BIDES", "BIGHT", "BIGOT", "BILGE", "BILLS", "BILLY", "BINDS", + "BIPED", "BIRCH", "BIRDS", "BIRTH", "BISON", "BITCH", "BITES", "BLACK", "BLADE", "BLAME", "BLAND", "BLANK", + "BLARE", "BLAST", "BLAZE", "BLEAK", "BLEAT", "BLEED", "BLEND", "BLENT", "BLESS", "BLEST", "BLIND", "BLINK", + "BLISS", "BLOCK", "BLOCS", "BLOND", "BLOOD", "BLOOM", "BLOTS", "BLOWN", "BLOWS", "BLUER", "BLUES", "BLUFF", + "BLUNT", "BLURT", "BLUSH", "BOARD", "BOARS", "BOAST", "BOATS", "BODED", "BODES", "BOGGY", "BOGUS", "BOILS", + "BOLES", "BOLTS", "BOMBS", "BONDS", "BONED", "BONES", "BONNY", "BONUS", "BOOBY", "BOOKS", "BOOMS", "BOONS", + "BOORS", "BOOST", "BOOTH", "BOOTS", "BOOTY", "BOOZE", "BORAX", "BORED", "BORES", "BORNE", "BOSOM", "BOUGH", + "BOUND", "BOUTS", "BOWED", "BOWEL", "BOWER", "BOWLS", "BOXED", "BOXER", "BOXES", "BRACE", "BRAGS", "BRAID", + "BRAIN", "BRAKE", "BRAND", "BRASS", "BRATS", "BRAVE", "BRAVO", "BRAWL", "BRAWN", "BREAD", "BREAK", "BREED", + "BRIAR", "BRIBE", "BRICK", "BRIDE", "BRIEF", "BRIER", "BRIGS", "BRIMS", "BRINE", "BRING", "BRINK", "BRINY", + "BRISK", "BROAD", "BROIL", "BROKE", "BROOD", "BROOK", "BROOM", "BROTH", "BROWN", "BROWS", "BRUIN", "BRUNT", + "BRUSH", "BRUTE", "BUCKS", "BUDGE", "BUGGY", "BUGLE", "BUILD", "BUILT", "BULBS", "BULGE", "BULKS", "BULKY", + "BULLS", "BULLY", "BUMPS", "BUNCH", "BUNKS", "BUOYS", "BURLY", "BURNS", "BURNT", "BURRO", "BURRS", "BURST", + "BUSHY", "BUSTS", "BUTTE", "BUTTS", "BUXOM", "BUYER", "CABAL", "CABBY", "CABIN", "CABLE", "CACAO", "CACHE", + "CADET", "CADRE", "CAGED", "CAGES", "CAIRN", "CAKED", "CAKES", "CALLS", "CALMS", "CALYX", "CAMEL", "CAMEO", + "CAMPS", "CANAL", "CANDY", "CANES", "CANNY", "CANOE", "CANON", "CANTO", "CAPER", "CAPES", "CAPON", "CARDS", + "CARED", "CARES", "CARGO", "CAROL", "CARRY", "CARTS", "CARVE", "CASED", "CASES", "CASKS", "CASTE", "CASTS", + "CATCH", "CATER", "CAUSE", "CAVED", "CAVES", "CAVIL", "CEASE", "CEDAR", "CEDED", "CELLS", "CENTS", "CHAFE", + "CHAFF", "CHAIN", "CHAIR", "CHALK", "CHAMP", "CHANT", "CHAOS", "CHAPS", "CHARM", "CHART", "CHARY", "CHASE", + "CHASM", "CHATS", "CHEAP", "CHEAT", "CHECK", "CHEEK", "CHEER", "CHEFS", "CHESS", "CHEST", "CHICK", "CHIDE", + "CHIEF", "CHILD", "CHILL", "CHIME", "CHINA", "CHINK", "CHINS", "CHIPS", "CHIRP", "CHOIR", "CHOKE", "CHOPS", + "CHORD", "CHOSE", "CHUCK", "CHUMP", "CHUMS", "CHUNK", "CHURL", "CHURN", "CHUTE", "CIDER", "CIGAR", "CINCH", + "CIRCA", "CITED", "CITES", "CIVET", "CIVIC", "CIVIL", "CLACK", "CLAIM", "CLAMP", "CLAMS", "CLANG", "CLANK", + "CLANS", "CLAPS", "CLASH", "CLASP", "CLASS", "CLAWS", "CLEAN", "CLEAR", "CLEFS", "CLEFT", "CLERK", "CLEWS", + "CLICK", "CLIFF", "CLIMB", "CLIME", "CLING", "CLINK", "CLIPS", "CLOAK", "CLOCK", "CLODS", "CLOGS", "CLOSE", + "CLOTH", "CLOUD", "CLOUT", "CLOVE", "CLOWN", "CLUBS", "CLUCK", "CLUES", "CLUMP", "CLUNG", "COACH", "COALS", + "COAST", "COATS", "COBRA", "COCKS", "COCOA", "CODES", "COILS", "COINS", "COLDS", "COLIC", "COLON", "COLTS", + "COMBS", "COMER", "COMES", "COMET", "COMIC", "COMMA", "CONCH", "CONES", "CONIC", "COOED", "COOKS", "COOLS", + "COPRA", "COPSE", "CORAL", "CORDS", "CORES", "CORKS", "CORNS", "CORPS", "COSTS", "COTES", "COUCH", "COUGH", + "COULD", "COUNT", "COUPE", "COUPS", "COURT", "COVER", "COVES", "COVET", "COVEY", "COWED", "COWER", "COYLY", + "COZEN", "CRABS", "CRACK", "CRAFT", "CRAGS", "CRAMP", "CRANE", "CRANK", "CRAPE", "CRASH", "CRASS", "CRATE", + "CRAVE", "CRAWL", "CRAZE", "CRAZY", "CREAK", "CREAM", "CREDO", "CREED", "CREEK", "CREEP", "CREPE", "CREPT", + "CRESS", "CREST", "CREWS", "CRIBS", "CRICK", "CRIED", "CRIER", "CRIES", "CRIME", "CRIMP", "CRISP", "CROAK", + "CROCK", "CRONE", "CRONY", "CROOK", "CROPS", "CROSS", "CROUP", "CROWD", "CROWN", "CROWS", "CRUDE", "CRUEL", + "CRUMB", "CRUSH", "CRUST", "CRYPT", "CUBES", "CUBIC", "CUBIT", "CUFFS", "CULTS", "CURDS", "CURED", "CURES", + "CURLS", "CURLY", "CURRY", "CURSE", "CURST", "CURVE", "CYCLE", "CYNIC", "DADDY", "DAILY", "DAIRY", "DAISY", + "DALES", "DALLY", "DAMES", "DAMPS", "DANCE", "DANDY", "DARED", "DARES", "DARTS", "DATED", "DATES", "DATUM", + "DAUBS", "DAUNT", "DAWNS", "DAZED", "DEALS", "DEALT", "DEANS", "DEARS", "DEATH", "DEBAR", "DEBIT", "DEBTS", + "DEBUT", "DECAY", "DECKS", "DECOY", "DECRY", "DEEDS", "DEEMS", "DEEPS", "DEFER", "DEIGN", "DEITY", "DELAY", + "DELLS", "DELTA", "DELVE", "DEMON", "DEMUR", "DENSE", "DENTS", "DEPOT", "DEPTH", "DERBY", "DESKS", "DETER", + "DEUCE", "DEVIL", "DIARY", "DICED", "DICES", "DICTA", "DIETS", "DIGIT", "DIKES", "DIMES", "DIMLY", "DINED", + "DINER", "DINES", "DINGY", "DIRGE", "DIRTY", "DISCS", "DISKS", "DITCH", "DITTO", "DITTY", "DIVAN", "DIVED", + "DIVER", "DIVES", "DIZZY", "DOCKS", "DODGE", "DOERS", "DOGMA", "DOING", "DOLED", "DOLLS", "DOMED", "DOMES", + "DONOR", "DOOMS", "DOORS", "DOSED", "DOSES", "DOTED", "DOTES", "DOUBT", "DOUGH", "DOVES", "DOWDY", "DOWNS", + "DOWNY", "DOWRY", "DOZED", "DOZEN", "DRAFT", "DRAGS", "DRAIN", "DRAKE", "DRAMA", "DRAMS", "DRANK", "DRAPE", + "DRAWL", "DRAWN", "DRAWS", "DRAYS", "DREAD", "DREAM", "DREGS", "DRESS", "DRIED", "DRIER", "DRIES", "DRIFT", + "DRILL", "DRILY", "DRINK", "DRIPS", "DRIVE", "DROLL", "DRONE", "DROOP", "DROPS", "DROSS", "DROVE", "DROWN", + "DRUGS", "DRUMS", "DRUNK", "DRYLY", "DUCAL", "DUCAT", "DUCHY", "DUCKS", "DUCTS", "DUELS", "DUETS", "DUKES", + "DULLY", "DUMMY", "DUMPS", "DUMPY", "DUNCE", "DUNES", "DUNNO", "DUPED", "DUPES", "DUSKY", "DUSTY", "DWARF", + "DWELL", "DWELT", "DYING", "DYKES", "EAGER", "EAGLE", "EARLS", "EARLY", "EARNS", "EARTH", "EASED", "EASEL", + "EASES", "EATEN", "EATER", "EAVES", "EBBED", "EBONY", "EDGED", "EDGES", "EDICT", "EDIFY", "EERIE", "EGGED", + "EIGHT", "EJECT", "ELATE", "ELBOW", "ELDER", "ELECT", "ELEGY", "ELFIN", "ELITE", "ELOPE", "ELUDE", "ELVES", + "EMAIL", "EMITS", "EMPTY", "ENACT", "ENDED", "ENDOW", "ENEMY", "ENJOY", "ENNUI", "ENROL", "ENSUE", "ENTER", + "ENTRY", "ENVOY", "EPICS", "EPOCH", "EQUAL", "EQUIP", "ERASE", "ERECT", "ERRED", "ERROR", "ESSAY", "ETHER", + "ETHIC", "EVADE", "EVENT", "EVERY", "EVILS", "EVOKE", "EXACT", "EXALT", "EXCEL", "EXERT", "EXILE", "EXIST", + "EXITS", "EXPEL", "EXTOL", "EXTRA", "EXULT", "EYING", "EYRIE", "FABLE", "FACED", "FACES", "FACTS", "FADED", + "FADES", "FAILS", "FAINT", "FAIRS", "FAIRY", "FAITH", "FAKIR", "FALLS", "FALSE", "FAMED", "FANCY", "FANGS", + "FARCE", "FARED", "FARES", "FARMS", "FASTS", "FATAL", "FATED", "FATES", "FATTY", "FAULT", "FAUNA", "FAUNS", + "FAWNS", "FEARS", "FEAST", "FEATS", "FEEDS", "FEELS", "FEIGN", "FEINT", "FELLS", "FELON", "FENCE", "FERAL", + "FERNS", "FERRY", "FETCH", "FETED", "FETID", "FETUS", "FEUDS", "FEVER", "FEWER", "FICHE", "FIEFS", "FIELD", + "FIEND", "FIERY", "FIFES", "FIFTH", "FIFTY", "FIGHT", "FILCH", "FILED", "FILES", "FILET", "FILLS", "FILLY", + "FILMS", "FILMY", "FILTH", "FINAL", "FINCH", "FINDS", "FINED", "FINER", "FINES", "FINIS", "FINNY", "FIORD", + "FIRED", "FIRES", "FIRMS", "FIRST", "FISHY", "FISTS", "FITLY", "FIVES", "FIXED", "FIXER", "FIXES", "FJORD", + "FLAGS", "FLAIL", "FLAIR", "FLAKE", "FLAKY", "FLAME", "FLANK", "FLAPS", "FLARE", "FLASH", "FLASK", "FLATS", + "FLAWS", "FLEAS", "FLECK", "FLEES", "FLEET", "FLESH", "FLICK", "FLIER", "FLIES", "FLING", "FLINT", "FLIRT", + "FLITS", "FLOAT", "FLOCK", "FLOES", "FLOOD", "FLOOR", "FLORA", "FLOSS", "FLOUR", "FLOUT", "FLOWN", "FLOWS", + "FLUES", "FLUFF", "FLUID", "FLUKE", "FLUME", "FLUNG", "FLUSH", "FLUTE", "FLYER", "FOAMS", "FOAMY", "FOCAL", + "FOCUS", "FOGGY", "FOILS", "FOIST", "FOLDS", "FOLIO", "FOLKS", "FOLLY", "FOODS", "FOOLS", "FORAY", "FORCE", + "FORDS", "FORGE", "FORGO", "FORKS", "FORMS", "FORTE", "FORTH", "FORTS", "FORTY", "FORUM", "FOUND", "FOUNT", + "FOURS", "FOWLS", "FOXES", "FOYER", "FRAIL", "FRAME", "FRANC", "FRANK", "FRAUD", "FREAK", "FREED", "FREER", + "FREES", "FRESH", "FRETS", "FRIAR", "FRIED", "FRILL", "FRISK", "FROCK", "FROGS", "FROND", "FRONT", "FROST", + "FROTH", "FROWN", "FROZE", "FRUIT", "FUDGE", "FUELS", "FUGUE", "FULLY", "FUMED", "FUMES", "FUNDS", "FUNGI", + "FUNNY", "FURRY", "FURZE", "FUSED", "FUSES", "FUSSY", "FUZZY", "GABLE", "GAILY", "GAINS", "GALES", "GALLS", + "GAMES", "GAMIN", "GAMMA", "GAMUT", "GANGS", "GAPED", "GAPES", "GASES", "GASPS", "GATES", "GAUDY", "GAUGE", + "GAUNT", "GAUZE", "GAUZY", "GAVEL", "GAWKY", "GAYER", "GAYLY", "GAZED", "GAZER", "GAZES", "GEARS", "GEESE", + "GENIE", "GENII", "GENRE", "GENTS", "GENUS", "GERMS", "GHOST", "GIANT", "GIBES", "GIDDY", "GIFTS", "GILDS", + "GILLS", "GIMME", "GIPSY", "GIRDS", "GIRLS", "GIRTH", "GIVEN", "GIVES", "GLADE", "GLAND", "GLARE", "GLASS", + "GLAZE", "GLEAM", "GLEAN", "GLENS", "GLIDE", "GLINT", "GLOAT", "GLOBE", "GLOOM", "GLORY", "GLOSS", "GLOVE", + "GLOWS", "GLUED", "GNASH", "GNATS", "GNAWS", "GNOME", "GOADS", "GOALS", "GOATS", "GODLY", "GOING", "GOLLY", + "GONGS", "GONNA", "GOODS", "GOODY", "GOOSE", "GORED", "GORGE", "GORSE", "GOTTA", "GOUGE", "GOURD", "GOUTY", + "GOWNS", "GRABS", "GRACE", "GRADE", "GRAFT", "GRAIN", "GRAMS", "GRAND", "GRANT", "GRAPE", "GRAPH", "GRASP", + "GRASS", "GRATE", "GRAVE", "GRAVY", "GRAZE", "GREAT", "GREED", "GREEN", "GREET", "GREYS", "GRIEF", "GRILL", + "GRIME", "GRIMY", "GRIND", "GRINS", "GRIPE", "GRIPS", "GRIST", "GROAN", "GROIN", "GROOM", "GROPE", "GROSS", + "GROUP", "GROVE", "GROWL", "GROWN", "GROWS", "GRUBS", "GRUEL", "GRUFF", "GRUNT", "GUANO", "GUARD", "GUESS", + "GUEST", "GUIDE", "GUILD", "GUILE", "GUILT", "GUISE", "GULCH", "GULFS", "GULLS", "GULLY", "GUMMY", "GUSTO", + "GUSTS", "GUSTY", "GYPSY", "HABIT", "HACKS", "HAILS", "HAIRS", "HAIRY", "HALED", "HALLS", "HALTS", "HALVE", + "HANDS", "HANDY", "HANGS", "HAPPY", "HARDY", "HAREM", "HARES", "HARMS", "HARPS", "HARPY", "HARRY", "HARSH", + "HARTS", "HASTE", "HASTY", "HATCH", "HATED", "HATER", "HAULS", "HAVEN", "HAVOC", "HAWKS", "HAZEL", "HEADS", + "HEADY", "HEALS", "HEAPS", "HEARD", "HEARS", "HEART", "HEATH", "HEATS", "HEAVE", "HEAVY", "HEDGE", "HEEDS", + "HEELS", "HEIRS", "HELIX", "HELLO", "HELMS", "HELPS", "HENCE", "HERBS", "HERDS", "HERON", "HEROS", "HEWED", + "HIDES", "HILLS", "HILLY", "HILTS", "HINDS", "HINGE", "HINTS", "HIRED", "HIRES", "HITCH", "HIVES", "HOARD", + "HOARY", "HOBBY", "HOIST", "HOLDS", "HOLES", "HOLLY", "HOMES", "HONEY", "HOODS", "HOOFS", "HOOKS", "HOOPS", + "HOOTS", "HOPED", "HOPES", "HORDE", "HORNS", "HORNY", "HORSE", "HOSTS", "HOTEL", "HOTLY", "HOUND", "HOURS", + "HOUSE", "HOVEL", "HOVER", "HOWLS", "HULKS", "HULLS", "HUMAN", "HUMID", "HUMPS", "HUMUS", "HUNCH", "HUNTS", + "HURLS", "HURRY", "HURTS", "HUSKS", "HUSKY", "HUSSY", "HYDRA", "HYENA", "HYMNS", "ICILY", "ICING", "IDEAL", + "IDEAS", "IDIOM", "IDIOT", "IDLED", "IDLER", "IDOLS", "IDYLL", "IGLOO", "IMAGE", "IMBUE", "IMPEL", "IMPLY", + "INANE", "INCUR", "INDEX", "INEPT", "INERT", "INFER", "INGOT", "INLET", "INNER", "INTER", "INURE", "IRATE", + "IRKED", "IRONS", "IRONY", "ISLES", "ISLET", "ISSUE", "ITEMS", "IVORY", "JACKS", "JADED", "JAILS", "JAUNT", + "JEANS", "JEERS", "JELLY", "JERKS", "JERKY", "JESTS", "JETTY", "JEWEL", "JIFFY", "JOINS", "JOINT", "JOKED", + "JOKER", "JOKES", "JOLLY", "JOUST", "JOYED", "JUDGE", "JUICE", "JUICY", "JUMPS", "JUNKS", "JUNTA", "JUROR", + "KARMA", "KEELS", "KEEPS", "KETCH", "KEYED", "KHAKI", "KICKS", "KILLS", "KINDA", "KINDS", "KINGS", "KIOSK", + "KITES", "KNACK", "KNAVE", "KNEAD", "KNEEL", "KNEES", "KNELL", "KNELT", "KNIFE", "KNITS", "KNOBS", "KNOCK", + "KNOLL", "KNOTS", "KNOWN", "KNOWS", "LABEL", "LACED", "LACES", "LACKS", "LADEN", "LADLE", "LAGER", "LAIRS", + "LAITY", "LAKES", "LAMBS", "LAMED", "LAMES", "LAMPS", "LANCE", "LANDS", "LANES", "LANKY", "LAPEL", "LAPSE", + "LARCH", "LARGE", "LARGO", "LARKS", "LARVA", "LASSO", "LASTS", "LATCH", "LATER", "LATHE", "LATHS", "LAUGH", + "LAWNS", "LAYER", "LEADS", "LEAFY", "LEAKS", "LEAKY", "LEANS", "LEAPS", "LEAPT", "LEARN", "LEASE", "LEASH", + "LEAST", "LEAVE", "LEDGE", "LEECH", "LEEKS", "LEGAL", "LEMME", "LEMON", "LENDS", "LEPER", "LEVEE", "LEVEL", + "LEVER", "LIARS", "LIBEL", "LICKS", "LIEGE", "LIENS", "LIFTS", "LIGHT", "LIKED", "LIKEN", "LIKER", "LIKES", + "LILAC", "LIMBO", "LIMBS", "LIMES", "LIMIT", "LINED", "LINEN", "LINER", "LINES", "LINGO", "LINKS", "LIONS", + "LISTS", "LITHE", "LIVED", "LIVER", "LIVES", "LIVID", "LLAMA", "LOADS", "LOAMY", "LOANS", "LOATH", "LOBBY", + "LOBES", "LOCAL", "LOCKS", "LOCUS", "LODGE", "LOFTY", "LOGES", "LOGIC", "LOGIN", "LOINS", "LONGS", "LOOKS", + "LOOMS", "LOONS", "LOOPS", "LOOSE", "LORDS", "LOSER", "LOSES", "LOTUS", "LOUSE", "LOUSY", "LOVED", "LOVER", + "LOVES", "LOWED", "LOWER", "LOWLY", "LOYAL", "LUCID", "LUCKY", "LULLS", "LUMPS", "LUMPY", "LUNAR", "LUNCH", + "LUNGE", "LUNGS", "LURCH", "LURED", "LURES", "LURID", "LURKS", "LUSTS", "LUSTY", "LUTES", "LYING", "LYMPH", + "LYNCH", "LYRIC", "MACES", "MADAM", "MADLY", "MAGIC", "MAIDS", "MAILS", "MAINS", "MAIZE", "MAJOR", "MAKER", + "MAKES", "MALES", "MAMMA", "MANES", "MANGA", "MANGE", "MANGO", "MANGY", "MANIA", "MANLY", "MANNA", "MANOR", + "MANSE", "MAPLE", "MARCH", "MARES", "MARKS", "MARRY", "MARSH", "MARTS", "MASKS", "MASON", "MASTS", "MATCH", + "MATED", "MATES", "MAUVE", "MAXIM", "MAYBE", "MAYOR", "MAZES", "MEALS", "MEALY", "MEANS", "MEANT", "MEATS", + "MEDAL", "MEDIA", "MEETS", "MELON", "MELTS", "MEMES", "MENDS", "MENUS", "MERCY", "MERES", "MERGE", "MERIT", + "MERRY", "MESAS", "METAL", "METED", "METER", "MEWED", "MIDST", "MIENS", "MIGHT", "MILCH", "MILES", "MILKY", + "MILLS", "MIMES", "MIMIC", "MINCE", "MINDS", "MINED", "MINER", "MINES", "MINOR", "MINTS", "MINUS", "MIRTH", + "MISER", "MISTS", "MITES", "MIXED", "MIXES", "MOANS", "MOATS", "MOCKS", "MODEL", "MODEM", "MODES", "MOIST", + "MOLAR", "MOLES", "MOMMA", "MONEY", "MONKS", "MONTH", "MOODS", "MOODY", "MOONS", "MOORS", "MOOSE", "MOPED", + "MORAL", "MORES", "MOSSY", "MOTES", "MOTHS", "MOTIF", "MOTOR", "MOTTO", "MOUND", "MOUNT", "MOURN", "MOUSE", + "MOUTH", "MOVED", "MOVER", "MOVES", "MOVIE", "MOWED", "MOWER", "MUCUS", "MUDDY", "MULES", "MULTI", "MUMMY", + "MUMPS", "MUNCH", "MURAL", "MURKY", "MUSED", "MUSES", "MUSIC", "MUSKY", "MUSTY", "MUTED", "MUTES", "MYRRH", + "MYTHS", "NABOB", "NAILS", "NAIVE", "NAKED", "NAMED", "NAMES", "NASAL", "NASTY", "NATAL", "NATTY", "NAVAL", + "NAVEL", "NAVES", "NEARS", "NECKS", "NEEDS", "NEEDY", "NEIGH", "NERVE", "NESTS", "NEVER", "NEWER", "NEWLY", + "NICER", "NICHE", "NIECE", "NIGHT", "NINNY", "NOBLE", "NOBLY", "NOISE", "NOISY", "NOMAD", "NONCE", "NOOKS", + "NOOSE", "NORTH", "NOSED", "NOSES", "NOTCH", "NOTED", "NOTES", "NOUNS", "NOVEL", "NUDGE", "NURSE", "NYMPH", + "OAKEN", "OAKUM", "OASES", "OASIS", "OATEN", "OATHS", "OBESE", "OBEYS", "OCCUR", "OCEAN", "OCHRE", "ODDER", + "ODDLY", "ODIUM", "OFFAL", "OFFER", "OFTEN", "OILED", "OLDEN", "OLDER", "OMENS", "OMITS", "ONION", "ONSET", + "OOZED", "OOZES", "OPALS", "OPENS", "OPERA", "OPINE", "OPIUM", "OPTIC", "ORBIT", "ORDER", "ORGAN", "OSIER", + "OTHER", "OTTER", "OUGHT", "OUNCE", "OUTDO", "OUTER", "OVALS", "OVARY", "OVENS", "OVERT", "OWING", "OWNED", + "OWNER", "OXIDE", "OZONE", "PACES", "PACKS", "PADDY", "PADRE", "PAEAN", "PAGAN", "PAGES", "PAILS", "PAINS", + "PAINT", "PAIRS", "PALED", "PALER", "PALES", "PALMS", "PALMY", "PALSY", "PANEL", "PANES", "PANGS", "PANIC", + "PANSY", "PANTS", "PAPAL", "PAPAS", "PAPER", "PARED", "PARKA", "PARKS", "PARRY", "PARSE", "PARTS", "PARTY", + "PASHA", "PASTE", "PASTY", "PATCH", "PATES", "PATHS", "PATIO", "PAUSE", "PAVED", "PAWED", "PAWNS", "PAYED", + "PAYER", "PEACE", "PEACH", "PEAKS", "PEALS", "PEARL", "PEARS", "PEASE", "PECKS", "PEDAL", "PEEPS", "PEERS", + "PELTS", "PENAL", "PENCE", "PENIS", "PENNY", "PEONS", "PERCH", "PERIL", "PESKY", "PESOS", "PESTS", "PETAL", + "PETTY", "PHASE", "PHIAL", "PHONE", "PHOTO", "PIANO", "PICKS", "PIECE", "PIERS", "PIETY", "PIGMY", "PIKES", + "PILED", "PILES", "PILLS", "PILOT", "PINCH", "PINED", "PINES", "PINKS", "PINTO", "PINTS", "PIOUS", "PIPED", + "PIPER", "PIPES", "PIQUE", "PITCH", "PITHY", "PIVOT", "PLACE", "PLAID", "PLAIN", "PLAIT", "PLANE", "PLANK", + "PLANS", "PLANT", "PLATE", "PLAYS", "PLAZA", "PLEAD", "PLEAS", "PLIED", "PLIES", "PLOTS", "PLUCK", "PLUGS", + "PLUMB", "PLUME", "PLUMS", "PLUSH", "PODIA", "POEMS", "POESY", "POETS", "POINT", "POISE", "POKED", "POKER", + "POKES", "POLAR", "POLES", "POLKA", "POLLS", "PONDS", "POOLS", "POPES", "POPPA", "POPPY", "PORCH", "PORED", + "PORES", "PORTS", "POSED", "POSER", "POSES", "POSSE", "POSTS", "POUCH", "POUND", "POURS", "POWER", "PRANK", + "PRATE", "PRAYS", "PRESS", "PREYS", "PRICE", "PRICK", "PRIDE", "PRIED", "PRIES", "PRIME", "PRINT", "PRIOR", + "PRISM", "PRIVY", "PRIZE", "PROBE", "PRONE", "PROOF", "PROPS", "PROSE", "PROSY", "PROUD", "PROVE", "PROWL", + "PROWS", "PROXY", "PRUDE", "PRUNE", "PSALM", "PSHAW", "PUDGY", "PUFFS", "PUFFY", "PULLS", "PULPY", "PULSE", + "PUMPS", "PUNCH", "PUPIL", "PUPPY", "PUREE", "PURER", "PURGE", "PURSE", "PUSSY", "PUTTY", "QUACK", "QUAFF", + "QUAIL", "QUAKE", "QUALM", "QUART", "QUASI", "QUAYS", "QUEEN", "QUEER", "QUELL", "QUERY", "QUEST", "QUEUE", + "QUICK", "QUIET", "QUILL", "QUILT", "QUIPS", "QUIRE", "QUITE", "QUITS", "QUOTA", "QUOTE", "QUOTH", "RABBI", + "RABID", "RACED", "RACER", "RACES", "RACKS", "RADII", "RADIO", "RAFTS", "RAGED", "RAGES", "RAIDS", "RAILS", + "RAINS", "RAINY", "RAISE", "RAJAH", "RAKED", "RAKES", "RALLY", "RANCH", "RANGE", "RANKS", "RAPID", "RARER", + "RARES", "RATED", "RATES", "RATIO", "RAVED", "RAVEN", "RAVES", "RAYON", "RAZED", "RAZOR", "REACH", "REACT", + "READS", "READY", "REALM", "REALS", "REAMS", "REAPS", "REARS", "REBEL", "REBUS", "REBUT", "RECUR", "REEDS", + "REEDY", "REEFS", "REEKS", "REELS", "REEVE", "REFER", "REFIT", "REGAL", "REIGN", "REINS", "RELAX", "RELAY", + "RELIC", "REMIT", "RENDS", "RENEW", "RENTS", "REPAY", "REPEL", "REPLY", "RESET", "RESIN", "RESTS", "REVEL", + "REVUE", "RHEUM", "RHYME", "RICKS", "RIDER", "RIDES", "RIDGE", "RIFLE", "RIFTS", "RIGHT", "RIGID", "RILED", + "RILLS", "RIMES", "RINGS", "RINSE", "RIOTS", "RIPEN", "RIPER", "RISEN", "RISER", "RISES", "RISKS", "RISKY", + "RITES", "RIVAL", "RIVEN", "RIVER", "RIVET", "ROADS", "ROAMS", "ROARS", "ROAST", "ROBED", "ROBES", "ROBIN", + "ROCKS", "ROCKY", "ROGUE", "ROLES", "ROLLS", "ROMAN", "ROOFS", "ROOKS", "ROOMS", "ROOMY", "ROOST", "ROOTS", + "ROPED", "ROPES", "ROSES", "ROSIN", "ROUGE", "ROUGH", "ROUND", "ROUSE", "ROUTE", "ROUTS", "ROVED", "ROVER", + "ROWDY", "ROWED", "ROYAL", "RUDER", "RUFFS", "RUINS", "RULED", "RULER", "RULES", "RUNES", "RUNGS", "RUPEE", + "RURAL", "RUSES", "SABLE", "SABRE", "SACKS", "SADLY", "SAFER", "SAGAS", "SAGES", "SAHIB", "SAILS", "SAINT", + "SAITH", "SALAD", "SALES", "SALLY", "SALON", "SALSA", "SALTS", "SALTY", "SALVE", "SALVO", "SANDS", "SANDY", + "SANER", "SATED", "SATIN", "SATYR", "SAUCE", "SAUCY", "SAVED", "SAVES", "SAWED", "SCALD", "SCALE", "SCALP", + "SCALY", "SCAMP", "SCANS", "SCANT", "SCARE", "SCARF", "SCARS", "SCENE", "SCENT", "SCION", "SCOFF", "SCOLD", + "SCOOP", "SCOPE", "SCORE", "SCORN", "SCOUR", "SCOUT", "SCOWL", "SCRAP", "SCREW", "SCRIP", "SCRUB", "SCULL", + "SEALS", "SEAMS", "SEAMY", "SEATS", "SECTS", "SEDAN", "SEDGE", "SEEDS", "SEEDY", "SEEKS", "SEEMS", "SEERS", + "SEIZE", "SELLS", "SEMEN", "SENDS", "SENSE", "SERFS", "SERGE", "SERUM", "SERVE", "SEVEN", "SEVER", "SEWED", + "SEWER", "SEXES", "SHACK", "SHADE", "SHADY", "SHAFT", "SHAKE", "SHAKY", "SHALE", "SHALL", "SHALT", "SHAME", + "SHAMS", "SHANK", "SHAPE", "SHARE", "SHARK", "SHARP", "SHAVE", "SHAWL", "SHEAF", "SHEAR", "SHEDS", "SHEEN", + "SHEEP", "SHEER", "SHEET", "SHEIK", "SHELF", "SHELL", "SHIED", "SHIFT", "SHINE", "SHINS", "SHINY", "SHIPS", + "SHIRE", "SHIRK", "SHIRT", "SHOAL", "SHOCK", "SHOES", "SHONE", "SHOOK", "SHOON", "SHOOT", "SHOPS", "SHORE", + "SHORN", "SHORT", "SHOTS", "SHOUT", "SHOVE", "SHOWN", "SHOWS", "SHOWY", "SHRED", "SHREW", "SHRUB", "SHRUG", + "SHUNS", "SHUTS", "SHYLY", "SIBYL", "SIDED", "SIDES", "SIEGE", "SIEVE", "SIGHS", "SIGHT", "SIGMA", "SIGNS", + "SILKS", "SILKY", "SILLS", "SILLY", "SINCE", "SINEW", "SINGE", "SINGS", "SINKS", "SIREN", "SIRES", "SITES", + "SIXES", "SIXTH", "SIXTY", "SIZED", "SIZES", "SKATE", "SKEIN", "SKIES", "SKIFF", "SKILL", "SKIMS", "SKINS", + "SKIPS", "SKIRT", "SKULK", "SKULL", "SKUNK", "SLABS", "SLACK", "SLAGS", "SLAIN", "SLAKE", "SLANG", "SLANT", + "SLAPS", "SLASH", "SLATE", "SLATS", "SLAVE", "SLAYS", "SLEDS", "SLEEK", "SLEEP", "SLEET", "SLEPT", "SLICE", + "SLICK", "SLIDE", "SLILY", "SLIME", "SLIMY", "SLING", "SLINK", "SLIPS", "SLITS", "SLOOP", "SLOPE", "SLOPS", + "SLOTH", "SLUGS", "SLUMP", "SLUMS", "SLUNG", "SLUNK", "SLUSH", "SLYLY", "SMACK", "SMALL", "SMART", "SMASH", + "SMEAR", "SMELL", "SMELT", "SMILE", "SMIRK", "SMITE", "SMITH", "SMOCK", "SMOKE", "SMOKY", "SMOTE", "SNACK", + "SNAGS", "SNAIL", "SNAKE", "SNAKY", "SNAPS", "SNARE", "SNARL", "SNEAK", "SNEER", "SNIFF", "SNIPE", "SNOBS", + "SNORE", "SNORT", "SNOUT", "SNOWS", "SNOWY", "SNUFF", "SOAPY", "SOARS", "SOBER", "SOCKS", "SOFAS", "SOGGY", + "SOILS", "SOLAR", "SOLES", "SOLID", "SOLOS", "SOLVE", "SONGS", "SONNY", "SOOTH", "SOOTY", "SORES", "SORRY", + "SORTS", "SOUGH", "SOULS", "SOUND", "SOUPS", "SOUSE", "SOUTH", "SOWED", "SOWER", "SPACE", "SPADE", "SPAKE", + "SPANK", "SPANS", "SPARE", "SPARK", "SPARS", "SPASM", "SPAWN", "SPEAK", "SPEAR", "SPECK", "SPEED", "SPELL", + "SPELT", "SPEND", "SPENT", "SPERM", "SPICE", "SPICY", "SPIED", "SPIES", "SPIKE", "SPILL", "SPILT", "SPINE", + "SPINS", "SPINY", "SPIRE", "SPITE", "SPITS", "SPLIT", "SPOIL", "SPOKE", "SPOOK", "SPOOL", "SPOON", "SPOOR", + "SPORE", "SPORT", "SPOTS", "SPOUT", "SPRAY", "SPREE", "SPRIG", "SPUNK", "SPURN", "SPURS", "SPURT", "SQUAD", + "SQUAT", "SQUAW", "STABS", "STACK", "STAFF", "STAGE", "STAGS", "STAID", "STAIN", "STAIR", "STAKE", "STALE", + "STALK", "STALL", "STAMP", "STAND", "STANK", "STARE", "STARK", "STARS", "START", "STATE", "STAVE", "STAYS", + "STEAD", "STEAK", "STEAL", "STEAM", "STEED", "STEEL", "STEEP", "STEER", "STEMS", "STEPS", "STERN", "STEWS", + "STICK", "STIFF", "STILE", "STILL", "STING", "STINK", "STINT", "STIRS", "STOCK", "STOIC", "STOLE", "STONE", + "STONY", "STOOD", "STOOL", "STOOP", "STOPS", "STORE", "STORK", "STORM", "STORY", "STOUT", "STOVE", "STRAP", + "STRAW", "STRAY", "STREW", "STRIP", "STRUT", "STUCK", "STUDS", "STUDY", "STUFF", "STUMP", "STUNG", "STUNT", + "STYLE", "SUAVE", "SUCKS", "SUGAR", "SUING", "SUITE", "SUITS", "SULKS", "SULKY", "SULLY", "SUNNY", "SUPER", + "SURER", "SURGE", "SURLY", "SWAIN", "SWAMP", "SWANS", "SWARD", "SWARM", "SWAYS", "SWEAR", "SWEAT", "SWEEP", + "SWEET", "SWELL", "SWEPT", "SWIFT", "SWILL", "SWIMS", "SWINE", "SWING", "SWIRL", "SWISH", "SWOON", "SWOOP", + "SWORD", "SWORE", "SWORN", "SWUNG", "SYNOD", "SYRUP", "TABBY", "TABLE", "TABOO", "TACIT", "TACKS", "TAILS", + "TAINT", "TAKEN", "TAKES", "TALES", "TALKS", "TALLY", "TALON", "TAMED", "TAMER", "TANKS", "TAPER", "TAPES", + "TARDY", "TARES", "TARRY", "TARTS", "TASKS", "TASTE", "TASTY", "TAUNT", "TAWNY", "TAXED", "TAXES", "TEACH", + "TEAMS", "TEARS", "TEASE", "TEEMS", "TEENS", "TEETH", "TELLS", "TEMPI", "TEMPO", "TEMPS", "TENDS", "TENET", + "TENOR", "TENSE", "TENTH", "TENTS", "TEPEE", "TEPID", "TERMS", "TERSE", "TESTS", "TESTY", "TEXTS", "THANK", + "THEFT", "THEIR", "THEME", "THERE", "THESE", "THICK", "THIEF", "THIGH", "THINE", "THING", "THINK", "THIRD", + "THONG", "THORN", "THOSE", "THREE", "THREW", "THROB", "THROE", "THROW", "THUMB", "THUMP", "THYME", "TIARA", + "TIBIA", "TICKS", "TIDAL", "TIDES", "TIERS", "TIGER", "TIGHT", "TILDE", "TILED", "TILES", "TILLS", "TILTS", + "TIMED", "TIMES", "TIMID", "TINGE", "TINTS", "TIPSY", "TIRED", "TIRES", "TITHE", "TITLE", "TOADS", "TOAST", + "TODAY", "TODDY", "TOILS", "TOKEN", "TOLLS", "TOMBS", "TOMES", "TONED", "TONES", "TONGS", "TONIC", "TOOLS", + "TOOTH", "TOPAZ", "TOPIC", "TOQUE", "TORCH", "TORSO", "TORTS", "TOTAL", "TOTEM", "TOUCH", "TOUGH", "TOURS", + "TOWED", "TOWEL", "TOWER", "TOWNS", "TOXIC", "TOYED", "TRACE", "TRACK", "TRACT", "TRADE", "TRAIL", "TRAIN", + "TRAIT", "TRAMP", "TRAMS", "TRAPS", "TRASH", "TRAYS", "TREAD", "TREAT", "TREED", "TREES", "TREND", "TRESS", + "TRIAD", "TRIAL", "TRIBE", "TRICE", "TRICK", "TRIED", "TRIES", "TRILL", "TRIPE", "TRIPS", "TRITE", "TROLL", + "TROOP", "TROTH", "TROTS", "TROUT", "TRUCE", "TRUCK", "TRUER", "TRULY", "TRUMP", "TRUNK", "TRUSS", "TRUST", + "TRUTH", "TRYST", "TUBES", "TUFTS", "TULIP", "TULLE", "TUNED", "TUNES", "TUNIC", "TURNS", "TUSKS", "TUTOR", + "TWAIN", "TWANG", "TWEED", "TWICE", "TWIGS", "TWINE", "TWINS", "TWIRL", "TWIST", "TYING", "TYPED", "TYPES", + "UDDER", "ULCER", "ULTRA", "UNCLE", "UNCUT", "UNDER", "UNDID", "UNDUE", "UNFIT", "UNION", "UNITE", "UNITS", + "UNITY", "UNSAY", "UNTIE", "UNTIL", "UPPER", "UPSET", "URBAN", "URGED", "URGES", "URINE", "USAGE", "USERS", + "USHER", "USING", "USUAL", "USURP", "USURY", "UTTER", "VAGUE", "VALES", "VALET", "VALID", "VALUE", "VALVE", + "VANES", "VAPID", "VASES", "VAULT", "VAUNT", "VEILS", "VEINS", "VELDT", "VENAL", "VENOM", "VENTS", "VENUE", + "VERBS", "VERGE", "VERSE", "VERVE", "VESTS", "VEXED", "VEXES", "VIALS", "VICAR", "VICES", "VIDEO", "VIEWS", + "VIGIL", "VILER", "VILLA", "VINES", "VIOLA", "VIPER", "VIRUS", "VISIT", "VISOR", "VISTA", "VITAL", "VIVID", + "VIXEN", "VIZOR", "VOCAL", "VODKA", "VOGUE", "VOICE", "VOILE", "VOLTS", "VOMIT", "VOTED", "VOTER", "VOTES", + "VOUCH", "VOWED", "VOWEL", "VYING", "WADED", "WAFER", "WAFTS", "WAGED", "WAGER", "WAGES", "WAGON", "WAIFS", + "WAILS", "WAIST", "WAITS", "WAIVE", "WAKED", "WAKEN", "WAKES", "WALKS", "WALLS", "WALTZ", "WANDS", "WANED", + "WANES", "WANTS", "WARDS", "WARES", "WARMS", "WARNS", "WARTS", "WASPS", "WASTE", "WATCH", "WATER", "WAVED", + "WAVER", "WAVES", "WAXED", "WAXEN", "WAXES", "WEARS", "WEARY", "WEAVE", "WEDGE", "WEEDS", "WEEDY", "WEEKS", + "WEEPS", "WEIGH", "WEIRD", "WELCH", "WELLS", "WENCH", "WHACK", "WHALE", "WHARF", "WHEAT", "WHEEL", "WHELP", + "WHERE", "WHICH", "WHIFF", "WHILE", "WHIMS", "WHINE", "WHIPS", "WHIRL", "WHIRR", "WHISK", "WHIST", "WHITE", + "WHOLE", "WHOOP", "WHORE", "WHOSE", "WICKS", "WIDEN", "WIDER", "WIDOW", "WIDTH", "WIELD", "WIGHT", "WILDS", + "WILES", "WILLS", "WINCE", "WINCH", "WINDS", "WINDY", "WINES", "WINGS", "WINKS", "WIPED", "WIPES", "WIRED", + "WIRES", "WISER", "WISPS", "WITCH", "WITTY", "WIVES", "WOMAN", "WOMEN", "WOODS", "WOODY", "WOOED", "WOOER", + "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", "WOUND", "WRACK", + "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", "WRITE", "WRITS", "WRONG", "WROTE", + "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", + "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", "CINCO", +] + +expanded_list = [ + "WHICH", "THEIR", "WOULD", "THERE", "COULD", "OTHER", "ABOUT", "GREAT", "THESE", "AFTER", "FIRST", "NEVER", + "WHERE", "THOSE", "SHALL", "BEING", "MIGHT", "EVERY", "THINK", "UNDER", "FOUND", "STILL", "WHILE", "AGAIN", + "PLACE", "YOUNG", "YEARS", "THREE", "RIGHT", "HOUSE", "WHOLE", "WORLD", "THING", "NIGHT", "GOING", "HEARD", + "HEART", "AMONG", "ASKED", "SMALL", "WOMAN", "WHOSE", "QUITE", "WORDS", "GIVEN", "TAKEN", "HANDS", "UNTIL", + "SINCE", "LIGHT", "BEGAN", "LARGE", "WATER", "WORKS", "OFTEN", "STOOD", "POWER", "MONEY", "ORDER", "MEANS", + "ROUND", "VOICE", "WHITE", "POINT", "STATE", "ABOVE", "DEATH", "LEAST", "KNOWN", "ALONG", "LEAVE", "ALONE", + "WOMEN", "TIMES", "SPEAK", "FORTH", "TERMS", "CRIED", "CHILD", "HUMAN", "SHORT", "CAUSE", "SEEMS", "BRING", + "DOUBT", "BLACK", "SENSE", "CLOSE", "TRUTH", "OUGHT", "PARTY", "READY", "FORCE", "EARLY", "EARTH", "EBOOK", + "SIGHT", "SPOKE", "STORY", "LATER", "ADDED", "STAND", "NICHT", "MILES", "COMES", "TABLE", "HOURS", "RIVER", + "HAPPY", "CLEAR", "SOUND", "MAKES", "BLOOD", "COMME", "DOING", "AVAIT", "TRIED", "FRONT", "QUILL", "PEACE", + "LIVED", "HORSE", "WROTE", "PAPER", "CETTE", "CHIEF", "PARIS", "BOOKS", "VISIT", "HEAVY", "KNOWS", "LOVED", + "CARRY", "PLAIN", "SWEET", "WRITE", "TREES", "BELOW", "WRONG", "REACH", "NOBLE", "PARTS", "AGREE", "MOVED", + "ENEMY", "WORTH", "GREEN", "THIRD", "MOUTH", "SLEEP", "FRESH", "FAITH", "SMILE", "USUAL", "BOUND", "QUIET", + "ETEXT", "COURT", "YOUTH", "PIECE", "SOUTH", "MEANT", "SEVEN", "TEARS", "VALUE", "BROKE", "FIGHT", "STONE", + "BEGIN", "HENRY", "LEARN", "LINES", "GRAND", "TAKES", "MONTH", "GIRLS", "GIVES", "EIGHT", "SCENE", "LIVES", + "DRAWN", "FIFTY", "FIELD", "CHAIR", "NAMED", "ALLOW", "MUSIC", "FIXED", "STUDY", "SPENT", "ROMAN", "TRUST", + "BREAK", "EQUAL", "NORTH", "THREW", "WATCH", "LOOKS", "BUILT", "USING", "SPITE", "MORAL", "WALLS", "TOUCH", + "JAMES", "STEPS", "OFFER", "DRESS", "LYING", "GRAVE", "LEGAL", "QUEEN", "LOWER", "CASES", "SHOWN", "NAMES", + "GREEK", "BOARD", "FAIRE", "GLASS", "SHARE", "FORMS", "CLASS", "START", "SHOOK", "TRAIN", "ENTER", "PROVE", + "FLOOR", "XPAGE", "WORSE", "SORRY", "PRIDE", "MARCH", "MARRY", "CROWD", "SHORE", "DRINK", "JUDGE", "SERVE", + "LAUGH", "TRADE", "BROAD", "GRACE", "PETER", "JESUS", "ROYAL", "LOUIS", "HEADS", "PROUD", "SPACE", "FULLY", + "QUICK", "IDEAS", "FANCY", "TASTE", "SWORD", "SHIPS", "DAILY", "GLORY", "BRAVE", "HONOR", "DREAM", "WEEKS", + "THICK", "CLAIM", "CHECK", "ASIDE", "REPLY", "FALSE", "SIDES", "CROSS", "SHARP", "FACTS", "HILLS", "BREAD", + "COAST", "DAVID", "AWARE", "GROUP", "FACES", "GROWN", "BIRDS", "MIDST", "TELLS", "LIKED", "THROW", "HABIT", + "STAGE", "ANGRY", "BROWN", "OWNER", "TIRED", "TRULY", "RULES", "TOTAL", "GRASS", "STYLE", "SAVED", "DRIVE", + "TWICE", "GUARD", "BURST", "PRICE", "WANTS", "THANK", "BASED", "APRIL", "GUESS", "CHOSE", "HOPES", "UNCLE", + "WOODS", "FINAL", "FORTY", "DROVE", "SPAIN", "TITLE", "UPPER", "MINDS", "NOISE", "HOPED", "BEGUN", "ALIVE", + "ITALY", "CRUEL", "SHAPE", "SLAVE", "BIRTH", "YOURS", "STORM", "CATCH", "LOOSE", "EMPTY", "CIVIL", "DOZEN", + "SHOWS", "ADMIT", "SMOKE", "DYING", "BRIEF", "APPLY", "PROOF", "FLESH", "FRUIT", "ENJOY", "WORST", "SHAME", + "ROUGH", "COVER", "ROCKS", "COUNT", "CRIME", "GRIEF", "IRISH", "NOTES", "NEEDS", "LANDS", "BLIND", "BRAIN", + "PRINT", "CLEAN", "DURCH", "ETAIT", "LEVEL", "RAISE", "EAGER", "STARS", "FAINT", "TEETH", "LABOR", "ROOMS", + "OLDER", "EINEN", "KNEES", "MERCY", "AWFUL", "AVOID", "DOORS", "INDIA", "ENDED", "DEVIL", "WEARY", "AROSE", + "FAULT", "CROWN", "COLOR", "AUSSI", "EVENT", "GOODS", "QUAND", "YARDS", "UNION", "ASCII", "TEMPS", "FAVOR", + "VOTRE", "SOULS", "CALLS", "AHEAD", "HARRY", "SMITH", "ANGER", "PLANS", "LOCAL", "LOVER", "PAGES", "LEURS", + "VIEWS", "SIGNS", "TEACH", "STOCK", "KINDS", "APART", "GUIDE", "ARMED", "EXACT", "HOMME", "TOUTE", "LATIN", + "TRIAL", "HOTEL", "SPEND", "SKILL", "KINGS", "SHALT", "LINKS", "DANCE", "SWEPT", "FATAL", "EINER", "WOUND", + "STORE", "SLEPT", "RANGE", "HATTE", "HENCE", "IMAGE", "ARISE", "FILES", "EINEM", "BANKS", "RAPID", "WASTE", + "ENTRE", "DARED", "PLANT", "SHADE", "ACTED", "CLOUD", "PRESS", "LOVES", "UTTER", "WINGS", "FLUNG", "BOWED", + "EGYPT", "GAZED", "THINE", "STICK", "FRANK", "REIGN", "MAJOR", "URGED", "FLEET", "TURNS", "SUGAR", "SPARE", + "SOLID", "BLAME", "BORNE", "WAVES", "TOWNS", "BOSOM", "CORPS", "SPEED", "GRANT", "FINDS", "MOINS", "BUILD", + "COSTS", "ERROR", "CEASE", "BOATS", "MIXED", "DELAY", "AGENT", "CLOTH", "ISSUE", "CHARM", "TREAT", "EMAIL", + "FRAME", "SHEEP", "ALIKE", "DUTCH", "PAUSE", "SEINE", "MERRY", "FEMME", "CRIES", "VAGUE", "TRACE", "VERSE", + "NOTRE", "FLAME", "HELEN", "IDEAL", "AVOIR", "HASTE", "ROUTE", "DREAD", "BIBLE", "EXIST", "OWING", "STERN", + "SAINT", "SORTS", "FALLS", "YIELD", "OCCUR", "GATES", "BONES", "OCEAN", "JEUNE", "SHAKE", "CARED", "STAFF", + "HURRY", "BEAST", "LOFTY", "BLESS", "TROIS", "INNER", "SHONE", "DRANK", "NOTED", "WINDS", "CHEEK", "CHAIN", + "KNIFE", "FEARS", "SWIFT", "WIVES", "WIDOW", "HATED", "HOLDS", "SIXTY", "MERIT", "GROSS", "DIESE", "ANGEL", + "MARIE", "FEVER", "FIRED", "AINSI", "ROADS", "CHINA", "QUEER", "GIFTS", "SWEAR", "NURSE", "CABIN", "MARKS", + "TRIBE", "ALOUD", "PAINS", "ALARM", "SCOTT", "OUTER", "WALKS", "NAKED", "FOLKS", "ELDER", "POETS", "MATCH", + "FOLLY", "WRATH", "DWELL", "SHOES", "SLAIN", "WAREN", "COACH", "ALICE", "TOWER", "DEEDS", "HABEN", "STEEL", + "TRAIL", "DEPTH", "BEARS", "PORTE", "SHOCK", "MOSES", "GUEST", "SCHON", "PLATE", "SAITH", "FEELS", "CLERK", + "STUFF", "TRACK", "POEMS", "PLAYS", "MAGIC", "KEEPS", "MONDE", "SONGS", "LEADS", "DRUNK", "AWAKE", "COEUR", + "SHOOT", "SMELL", "PETIT", "ALTAR", "AUTRE", "LIMBS", "EATEN", "TONES", "BASIS", "STEEP", "FEAST", "NOVEL", + "SILLY", "GROWS", "BADLY", "STOUT", "ALLES", "MARIA", "RISEN", "CREPT", "SCALE", "RANKS", "FLASH", "BURNT", + "RIVAL", "BRIDE", "TALES", "BOOTS", "CHEST", "SEATS", "GRAIN", "PRIZE", "FETCH", "DIRTY", "MOUNT", "CURSE", + "STIFF", "BEARD", "BARON", "NEGRO", "ASSEZ", "UNTER", "MODEL", "GRASP", "FIRES", "FLOOD", "ALORS", "JOURS", + "FLOUR", "STOLE", "POUND", "SWUNG", "ALTER", "SHEET", "CENTS", "DWELT", "CLOAK", "ROSES", "ROOTS", "CLOCK", + "SCORE", "GLOOM", "AGONY", "SEIZE", "PIOUS", "WAGES", "BLOWS", "WHEEL", "SAGTE", "CARES", "VITAL", "RISES", + "BILLY", "AVANT", "OWNED", "SCORN", "METAL", "ABIDE", "IMMER", "JONES", "STUCK", "SHOUT", "PAINT", "DRIED", + "AMPLE", "RUINS", "MAYBE", "FILLE", "SIEGE", "STEAM", "BETTY", "MILLE", "TRICK", "TYPES", "SHELL", "REFER", + "GIANT", "LORDS", "ROGER", "FIFTH", "FACED", "DATES", "MEINE", "HOMES", "LINEN", "VIVID", "STRAW", "BRASS", + "NAVAL", "MADAM", "BONNE", "FADED", "SPORT", "DROPS", "DINED", "BEACH", "LOYAL", "CELUI", "LIKES", "CARDS", + "RACES", "CHEAP", "TRUNK", "RALPH", "CHEER", "JOINT", "EINES", "SPELL", "ABODE", "WAVED", "DRAMA", "ARRAY", + "CRAFT", "CANOE", "GEGEN", "RIFLE", "CODES", "FIERY", "MIEUX", "TAXES", "AWOKE", "LIMIT", "ASHES", "JACOB", + "GHOST", "BELLS", "STEAL", "ALLER", "CLIMB", "HEELS", "TEXAS", "TERRE", "MUTTA", "SOBER", "HARSH", "SHINE", + "KNOCK", "PEINE", "SPAKE", "VIRUS", "DEALT", "MINOR", "LAURA", "SLOPE", "HUMOR", "BENCH", "WHEAT", "MAMMA", + "PIANO", "FUNNY", "SIMON", "ROCKY", "SWEEP", "SADLY", "ERECT", "BELLE", "PURSE", "PARCE", "CLUNG", "SWORE", + "FENCE", "WORRY", "SUITE", "JAPAN", "WAGON", "POLLY", "EVILS", "CELLE", "HENRI", "JETZT", "PETTY", "BANDS", + "CHASE", "GLEAM", "UNITY", "HASTY", "ACRES", "MAINS", "LUCKY", "PERIL", "ADAMS", "HIRED", "FILED", "EXTRA", + "FLIES", "DOUTE", "TIGHT", "BLANK", "JULIA", "ENFIN", "SEEDS", "IHRER", "FAIRY", "SITES", "SMART", "ETWAS", + "WURDE", "SHIRT", "ELECT", "SPOIL", "GUILT", "ADOPT", "BILLS", "CLARA", "HOLES", "ELLES", "SAILS", "RINGS", + "EDITH", "FANNY", "BLOWN", "RIDGE", "DENSE", "TODAY", "SHEER", "ABUSE", "RESTE", "QUOTH", "LUNCH", "NIECE", + "TIMID", "MANLY", "SWORN", "PRIME", "PROSE", "BOXES", "VILLE", "LEWIS", "CREEK", "ORGAN", "BRUSH", "OPERA", + "FLOCK", "SUSAN", "BOAST", "PITCH", "ANGLE", "ELLEN", "SUNNY", "AUGHT", "FOOLS", "SWAMP", "VEINS", "NEWLY", + "COUCH", "VIEUX", "JOLLY", "OPENS", "SPEAR", "BRICK", "CREAM", "THIEF", "WAIST", "RULED", "KNELT", "SARAH", + "BRUTE", "SENDS", "MINES", "DATED", "PATHS", "MOVES", "SHOPS", "PORCH", "IHREN", "TENTS", "BACKS", "KITTY", + "SANDY", "PUPIL", "LODGE", "BLOOM", "BONDS", "BLOCK", "SATAN", "SWELL", "RIGID", "OLLUT", "GAMES", "SANTA", + "DEBTS", "FELIX", "SPOTS", "SCENT", "HEEFT", "CIGAR", "ALLAH", "ENTRY", "SAXON", "AVAIL", "MAINE", "EFFET", + "TREAD", "WIDER", "OLISI", "CHILL", "BROOK", "AMUSE", "ALLEN", "COMTE", "ROBIN", "SKALL", "VEXED", "BALLS", + "KEINE", "CROPS", "UEBER", "REALM", "EMILY", "MONKS", "REBEL", "WALES", "LAMPS", "EXILE", "HEARS", "UNITE", + "ROBES", "SKIES", "SIXTH", "SEULE", "CRAZY", "ISAAC", "IHNEN", "RULER", "WRECK", "DRAWS", "DIDST", "ARROW", + "STARE", "DROIT", "BLUSH", "DONNE", "SOBRE", "PRIOR", "VOTES", "MATIN", "THEME", "FINER", "PSEUD", "NERVE", + "APPLE", "RESTS", "PENNY", "STAKE", "BROWS", "READS", "BUNCH", "TWAIN", "TOKEN", "TALKS", "WISER", "HORNS", + "APRES", "BLAZE", "SORTE", "CLIFF", "STRIP", "TEXTS", "AWAIT", "ELBOW", "GLOBE", "FLUSH", "UPSET", "AVONS", + "TURKS", "JANET", "GENUS", "LOCKS", "HONEY", "CARGO", "LEBEN", "STOVE", "CANAL", "JERRY", "CRACK", "POSTS", + "PIETY", "LADEN", "BLADE", "LINED", "HONOM", "NOISY", "DRIFT", "DECAY", "FUNDS", "MIRTH", "TOOLS", "ACUTE", + "AUGEN", "PACES", "SPLIT", "BREED", "VENUS", "JUICE", "PLATO", "FREED", "FAILS", "PORTS", "BLISS", "USAGE", + "HEURE", "PILED", "RATES", "AIDED", "BYRON", "WIPED", "HOMER", "STAMP", "VICES", "VATER", "TOMMY", "PIEDS", + "TROOP", "SAGDE", "BLAST", "MOTOR", "RURAL", "WEARS", "CREED", "VOTED", "DELLA", "GLARE", "SKINS", "TODOS", + "SHAFT", "VENIR", "NANCY", "CROIS", "DAVIS", "QUOTE", "PLEAD", "ITEMS", "SWING", "SEEKS", "CREST", "BASIN", + "LAKES", "SNAKE", "SALES", "DUSTY", "CRUSH", "MEETS", "TENTH", "SALLY", "MEALS", "GROVE", "SUITS", "SMOTE", + "LANGE", "ACTOR", "ARGUE", "FLOWS", "DIMLY", "REEDS", "PLANE", "GREET", "SWEAT", "DIANA", "BRUIT", "ROOFS", + "SANOI", "BEAMS", "JIMMY", "PILOT", "ARABS", "FROST", "CELLS", "MOORE", "AYANT", "PERCY", "EDGAR", "PIPES", + "MOLLY", "QUEST", "FOLDS", "CURED", "PASSE", "RHINE", "MAIDS", "MULES", "REINS", "SALON", "CANST", "AUCUN", + "ALERT", "RENEW", "EDGES", "IHREM", "SWISS", "PATCH", "ALOFT", "CRUDE", "AIMED", "NAILS", "RECHT", "SAGEN", + "VIGOR", "TRIES", "FLANK", "TRACT", "VERGE", "ELSIE", "TRAMP", "IVORY", "CRASH", "CURVE", "CAKES", "EAGLE", + "HEDGE", "VOULU", "RITES", "LOGIC", "CLARK", "SPARK", "SINGS", "EPOCH", "MAYOR", "WEIGH", "ONDER", "ABBEY", + "KEITH", "AGNES", "ESSAY", "VALET", "HOSTS", "HANGS", "COMIC", "PANIC", "PEDRO", "COATS", "SCOPE", "FRAIL", + "BURKE", "GOALS", "MODES", "SKULL", "FLOAT", "RHODE", "PULSE", "LOSES", "DANTE", "TOPIC", "TIGER", "FEWER", + "PINES", "SHIFT", "HEAPS", "VIENT", "FILLS", "REINE", "FORTS", "LIONS", "MILAN", "ZIJNE", "LUNGS", "SANDS", + "PHASE", "HALLS", "ROUSE", "DAISY", "PARTE", "FARMS", "SKIRT", "SATIN", "RAINS", "HINTS", "CABLE", "GARDE", + "THUMB", "QUART", "HARDY", "ELLER", "POLES", "AARON", "SYRIA", "GROAN", "ROPES", "MOULD", "JENNY", "ROLLS", + "BACON", "MOIST", "FLING", "LANCE", "PARTI", "SHELF", "ETHEL", "MALES", "PLUCK", "SHOTS", "STOPS", "ALIEN", + "FRAUD", "CLUBS", "NEWBY", "CREEP", "TILDE", "SHAWL", "FORME", "AFORE", "RAOUL", "FROWN", "SNOWY", "LISTS", + "STONY", "FLAGS", "BORED", "MELAN", "PGLAF", "CURLS", "EFTER", "STAID", "TOUGH", "CHOIR", "HASTA", "DITCH", + "CLARE", "WITCH", "RIDER", "SEHEN", "MASON", "WEEDS", "TENDS", "PALMS", "FLUID", "VILLA", "STADT", "IRENE", + "SAUCE", "PAVED", "ANNIE", "MUDDY", "SEXES", "USERS", "STRAY", "DAMIT", "OATHS", "WIDTH", "STAIN", "STEED", + "UNFIT", "BEADS", "HERDS", "INDEM", "WENIG", "VINES", "BLAKE", "GORGE", "DEMON", "CANON", "BRISK", "GANZE", + "TUTOR", "SIGHS", "RAINY", "TANTO", "MOYEN", "HELPS", "GRADE", "PEAKS", "ELLOS", "PARMI", "PARLE", "ALLAN", + "FLORA", "PELLE", "APRON", "COINS", "CITED", "LIEBE", "DEREN", "WELSH", "HOOFS", "PAIRS", "DONNA", "VOWED", + "WEIRD", "IRONY", "BRUCE", "BEANS", "DOLLY", "CLING", "LAPSE", "GAILY", "DUSKY", "TEGEN", "WITTY", "WRIST", + "TORCH", "MONTE", "SHADY", "AMOUR", "BESET", "ATHOS", "COUPS", "TRUCE", "GOATS", "VAULT", "SPURS", "GAINS", + "TELLE", "TOAST", "VINGT", "LAYER", "ARMOR", "JUDAH", "KOMMT", "MOODS", "IMPLY", "LEVER", "JOTKA", "EXERT", + "FOULE", "SPIES", "YACHT", "ALTEN", "PAGAN", "TENSE", "ALOOF", "VALOR", "HYMNS", "ENVOY", "PINCH", "PRONE", + "HELLO", "BROOD", "MABEL", "FOWLS", "HERRN", "DROWN", "ROUGE", "BURNS", "MISMO", "CALME", "LIVRE", "SLIDE", + "LYONS", "CAIRO", "PATTY", "CASTE", "STOOP", "BAKER", "MILLS", "BLEST", "TAILS", "VOICI", "LOGIN", "FARED", + "CHANT", "SEITE", "FABLE", "MISTS", "DIEGO", "ROWED", "WORDT", "DUCKS", "MAXIM", "BOERS", "LEVEN", "SWARM", + "QUANT", "MUSED", "WAITS", "CHALK", "BEIDE", "FORTE", "CAVES", "JEDER", "SENOR", "INFER", "TRAIT", "TASKS", + "DIARY", "HOUND", "SAVEZ", "BLUFF", "PENSE", "CECIL", "JUDAS", "OUNCE", "STARK", "DRAIN", "LEAPT", "WEISE", + "CLAWS", "TWINS", "IDOLS", "SUJET", "ALTSP", "WILLS", "RAGED", "HAVDE", "BEAUX", "FRITZ", "DARES", "DEPOT", + "DAZED", "RODIN", "LOWLY", "STUNG", "NASTY", "DEITY", "BOBBY", "ELIZA", "SIZES", "GAUNT", "LYDIA", "KNEEL", + "RISKS", "DURST", "TENOR", "STEMS", "FAITE", "LIVER", "STEAD", "EVANS", "TESTS", "GILES", "HUNNE", "SEALS", + "PLANK", "SINKS", "WHIRL", "SCREW", "SPOON", "GEHEN", "HABIA", "JAHRE", "BARRY", "AMPLY", "FRANZ", "TENIR", + "SLICE", "CARTS", "EDWIN", "DEALS", "JUSTE", "WHALE", "VIVRE", "PARUT", "HAUSE", "WESEN", "GEESE", "NAMEN", + "PASTE", "NIETS", "SCRAP", "LIBRE", "ADORN", "NOSES", "MADLY", "USTED", "WELKE", "ARDOR", "TROUT", "WELLS", + "ZEIDE", "DINGY", "SOEUR", "KNAVE", "KNOTS", "TIENE", "SEELE", "ESSEX", "CRAWL", "THEFT", "WEDER", "AREAS", + "STAIR", "ORION", "HEUTE", "DADDY", "COUGH", "GUISE", "PARMA", "FISTS", "GENUG", "FAUST", "AQUEL", "SNEER", + "PLOTS", "DAVON", "JENER", "PLUME", "GEBEN", "HAUTE", "PADRE", "ABBOT", "VOILA", "SELON", "ODOUR", "MEDAL", + "OPIUM", "DRAKE", "AMISS", "ETTEI", "GLOVE", "BLEAK", "SACHE", "GENRE", "RATIO", "DRUGS", "WINES", "HINDU", + "POOLS", "VISTA", "SCANT", "CORDS", "RAILS", "STAYS", "GENOA", "WHARF", "COSAS", "STEHT", "PRIMA", "DEUCE", + "KILLS", "MADGE", "BLUNT", "PASSA", "WAKED", "PUNCH", "SABLE", "FLOWN", "DRILL", "LIFTS", "ILLUS", "CRISP", + "WORMS", "FLINT", "HANDY", "ALLEY", "MASTS", "FIEND", "STUMP", "ADULT", "LEUTE", "SCRUB", "DECKS", "PANGS", + "CAMEL", "DEINE", "TITUS", "INDEX", "HAVEN", "HILDA", "GEIST", "ORDRE", "THORN", "TOILS", "CEDAR", "LOADS", + "WAXED", "SOPHY", "DAHER", "GAUGE", "WAGER", "JULIE", "WILDE", "PEERS", "AISLE", "BLANC", "LONGS", "ELLIS", + "MINUA", "CARLO", "SCARE", "LEVIN", "KUNDE", "DINGE", "SNARE", "WEISS", "STALE", "FAITS", "AVERT", "HADST", + "BATHS", "XVIII", "TIDES", "TINTS", "BULLY", "AZURE", "WHIGS", "VIELE", "ATOMS", "PERRY", "BLICK", "GIDDY", + "SOLAR", "FARCE", "BOWER", "WARES", "FROID", "SLIPS", "RENTS", "FEATS", "LEASE", "BLAIR", "BLAND", "CONDE", + "MARSH", "RUMOR", "BERTH", "BATHE", "BOOTY", "GLIDE", "ENNEN", "BERRY", "PAULA", "AIVAN", "FRIED", "DANES", + "WORTE", "BOOTH", "OTROS", "TAXED", "TWIGS", "ERSTE", "UNDUE", "CLUMP", "RICHE", "LIEGT", "LYRIC", "IHRES", + "LASTS", "ANNOY", "WEGEN", "OOGEN", "SELFE", "DABEI", "CASTS", "BOUGH", "ROUEN", "DIGNE", "SILAS", "LOCKE", + "CHART", "MAKER", "PEGGY", "SNOWS", "DETTA", "CHRIS", "ARENA", "QUIEN", "RELIC", "SECTS", "DOUCE", "BRIBE", + "FRIAR", "TINGE", "COURS", "VIENS", "HACER", "PEPYS", "LANES", "ETHER", "VICAR", "STEVE", "JASON", "VOCAL", + "JOINS", "BASES", "UNSER", "LIGNE", "DINAH", "VASES", "TEDDY", "NEUEN", "CURLY", "VALID", "CLEFT", "CHAPS", + "BARGE", "HAGUE", "BOLTS", "DARIN", "SMELT", "TULEE", "VOYEZ", "PURER", "ENTRA", "ODDLY", "VAPOR", "EENEN", + "SPIED", "SLATE", "CREWE", "BRACE", "CYRIL", "OSCAR", "IRONS", "DESDE", "LAMBS", "VROEG", "VROUW", "DICKY", + "KRAFT", "RALLY", "RESTA", "CRAVE", "GAYLY", "LEAPS", "FLUTE", "ONCLE", "SCOUT", "GOWNS", "SWINE", "GREED", + "ONION", "SAGES", "AMBER", "TARRY", "GRATE", "GESTE", "ANDRE", "MOEST", "JUURI", "CESSE", "SHEWN", "THIGH", + "LEAFY", "BENDS", "FERME", "BRAKE", "HURTS", "GRIND", "SILKS", "CLASH", "JELLY", "SOOTH", "DIZZY", "NEBEN", + "INCUR", "DOVER", "VERRE", "HAIRY", "STALK", "TOMBE", "HEAVE", "MUCHO", "INTER", "SPADE", "CREWS", "DENIS", + "HIELT", "FOSSE", "BULLS", "ALLEZ", "SOMME", "SNUFF", "BLIEB", "HAVOC", "ALLEM", "SMITE", "LACKS", "THROB", + "GUTEN", "PUEDE", "CLICK", "TIRER", "KORAN", "FLASK", "GRUND", "TERRA", "CAUSA", "GAUDY", "HIRAM", "JETER", + "FIBRE", "VERBS", "FORGE", "WAERE", "SPECK", "OLDEN", "SHRUB", "FLEDA", "GODLY", "HOOFD", "SONNE", "EDICT", + "DAGEN", "ISLES", "CHASM", "EDGED", "TRAPS", "BIRCH", "TRUER", "PAPAL", "ASSIS", "MASSE", "MORTE", "HETTY", + "REPEL", "INGEN", "SHRUG", "KOMEN", "PRISE", "HENNE", "LIVID", "BRIAN", "SALAD", "MATES", "BOVEN", "DOUZE", + "MEATS", "FEEDS", "TANTE", "WENCH", "DARTS", "FLATS", "TUBES", "UNITS", "HECHO", "PERCH", "POURS", "TRIPS", + "CIVIC", "MUNDO", "SPRAK", "ANTES", "SCARF", "DELLE", "HYVIN", "VASTE", "FOLIO", "NEEDY", "YELLS", "FAUTE", + "REGAL", "HOOKS", "ELIOT", "PALER", "TRENT", "MALTA", "MAKEN", "LUCAS", "NOUNS", "ATTIC", "WINDY", "SPIRE", + "SIEHT", "DERBY", "SAVOY", "MAUDE", "ILIAD", "DITTO", "PLINY", "FERNS", "BASIC", "GRAPE", "SOGAR", "TYING", + "DOUGH", "DERAS", "LEANT", "BELLA", "LICHT", "FILTH", "PEARL", "LUSTY", "OLIVE", "DROLL", "BADGE", "WAGED", + "SEEST", "ANKLE", "FERRY", "PENAL", "NEUER", "ESTOS", "FUMES", "SHEDS", "SHOWY", "SALEM", "REDEN", "STALL", + "JOYCE", "SANTO", "ZELFS", "HOTLY", "SCOLD", "CANDY", "LUCIA", "WEAVE", "MONTA", "SLACK", "VOGUE", "RHODA", + "SLANG", "FOODS", "PRIVY", "SERAI", "RENDU", "VALVE", "KUNST", "CYCLE", "EXCEL", "QUASI", "CRAIG", "VERTU", + "AVEVA", "MAYST", "HEATH", "MEDIO", "CELIA", "DEEZE", "BATES", "ATONE", "VENUE", "LURID", "SKOLA", "MDLLE", + "CHOKE", "PREND", "DARAN", "SAVES", "MANOR", "FORKS", "GENTE", "DITES", "TEAMS", "GOETH", "STOND", "COOKS", + "CHIEN", "PANEL", "GROWL", "MURAT", "BODEN", "DERES", "CAIUS", "FRERE", "HEIDI", "BROTH", "TEMPO", "JONAS", + "BROOM", "RACED", "SACKS", "TERRY", "NOOIT", "GENOM", "RAVEN", "FROGS", "ANNUM", "ACHED", "HEROD", "NOIRE", + "SURLY", "EIGEN", "EVADE", "BUGGY", "CONNU", "ATQUE", "WAKES", "REMIT", "REEFS", "ASSES", "MORSE", "RUFUS", + "ROMEO", "CHORD", "SLOOP", "LOANS", "TAWNY", "ETIAM", "NOCHE", "CONGO", "GHENT", "BINDS", "SLUNG", "CUBIC", + "GROOT", "DARUM", "MUSES", "GERMS", "NYMPH", "CUPID", "LUTTE", "FINES", "LUEGO", "PSALM", "HOARY", "XXIII", + "MUJER", "CROWS", "DECIR", "SULLA", "TITRE", "MAPLE", "SALTS", "POSTE", "DODGE", "ARMEN", "DISSE", "MACHT", + "FOCUS", "LITHE", "PASHA", "HIJOS", "BUSSY", "FLEUR", "JOLIE", "SMASH", "NORAH", "DOGMA", "TUTTO", "BOWLS", + "JEDEN", "LOATH", "JESTS", "MALAY", "DIGIT", "PENCE", "JOUER", "NORSE", "SLEEK", "DOWRY", "DEFER", "SELLS", + "CALEB", "BLEED", "DOWNS", "BABES", "DENTS", "ALKOI", "JEDEM", "PANES", "SINNE", "LEIGH", "MENOS", "WILDS", + "WOLFE", "ANDRA", "IDAHO", "DOWNE", "BETSY", "ADAPT", "MIJNE", "LIESS", "DICHO", "NELLY", "TUDOR", "BLOND", + "GRAVY", "FRAGE", "AELIG", "TIENT", "STEIN", "PLIED", "SABRE", "SQUAD", "STAAT", "REVEL", "YOLKS", "SEWED", + "CRANE", "SYRUP", "GIVER", "SEULS", "HAINE", "CASEY", "HICKS", "KATIE", "LOINS", "ABEND", "EXALT", "DENNA", + "STIRS", "SMOKY", "EUERY", "NICHE", "ONELY", "SINUN", "RABBI", "BRUST", "ELIAS", "JOSSA", "PAYER", "VEILS", + "CHAFF", "SULKY", "JUNGE", "PEACH", "MAMAN", "TUNES", "KAMEN", "DIANE", "LUMPS", "SHAVE", "CLOWN", "SHINY", + "LATEN", "HUSKY", "TOWEL", "REPOS", "GLADE", "GEORG", "NUEVA", "MANIA", "SAKES", "KEATS", "PULLS", "SIOUX", + "TAINT", "SUIVI", "CROIT", "MUUTA", "TILES", "BUSHY", "BLEND", "HAPLY", "ANJOU", "TRITT", "TURIN", "PLATZ", + "FAMED", "BARNS", "VECES", "DAMON", "LANGS", "HUMPH", "RELAX", "AUNTS", "SALUT", "CRAGS", "SHOAL", "COLIN", + "MOSSY", "GAULS", "CASKS", "WRING", "AHORA", "SPICE", "BONNY", "CURES", "BRUNO", "BRAVO", "ANDER", "CHESS", + "ALIAS", "SAUCY", "NOIRS", "TUNIC", "TOMBA", "LURED", "POKER", "RADIO", "CHAMP", "FINDE", "HULLO", "MECCA", + "SCALP", "FORMA", "SCARS", "MORTS", "BASSE", "SPINE", "PEREZ", "DIVAN", "HONTE", "LINDA", "ENVIE", "TEASE", + "BARED", "MADRE", "TUTTI", "OUTRE", "OMDAT", "LUCID", "SURGE", "CLAIR", "ONSET", "PONDS", "AUTEM", "MOODY", + "BALES", "SEDAN", "GALES", "LATCH", "LENDS", "LUIGI", "CAREY", "HOIST", "OTRAS", "GWINE", "WARUM", "DIVED", + "STRAP", "KELLY", "BEECH", "TROTH", "CHLOE", "PUNTO", "CIELO", "FROZE", "BUGLE", "POISE", "SIEUR", "TRUCK", + "ZEKER", "FOXES", "BALMY", "PICKS", "ISLAM", "AMEND", "DROOP", "LOPEZ", "DAIRY", "WALTZ", "BLOSS", "KYSYI", + "NAIVE", "SHORN", "URGES", "MAIZE", "WIELD", "HOLLY", "ARTER", "YORKE", "FRAIS", "NIGEL", "PAYNE", "COMET", + "SHUTS", "SOCKS", "HORDE", "SKIFF", "HACIA", "BESTE", "PARKS", "INERT", "PARLA", "INLET", "ODORS", "PRICK", + "KOREA", "FORUM", "LIEGE", "JEDES", "LIETH", "GASES", "SENZA", "TISCH", "PRATT", "ABNER", "BURLY", "BRAND", + "MINER", "TRACY", "LUGAR", "LAWNS", "GOTHS", "TARDY", "WIGHT", "DRURY", "VADER", "COOKE", "LAIRD", "TUFTS", + "FIXES", "SOLES", "VOWEL", "CLIVE", "KOSKA", "TAPER", "GUSTS", "RIANT", "FERAI", "COILS", "GROND", "SOINS", + "ABATE", "AFOOT", "EMILE", "POCHE", "LASSE", "ADELA", "PORTA", "BULKY", "MASKS", "DORIS", "KOMMA", "TAMEN", + "CRIER", "LOBBY", "ERNST", "EXPEL", "MANGE", "PACKS", "FATED", "DOVES", "DOLLS", "METRE", "AVAIS", "NEWER", + "LOGAN", "FREER", "DENNE", "MILLY", "GENIE", "STILE", "WARDS", "GEVEN", "WROTH", "BOONE", "COMER", "ANGUS", + "USHER", "CEDED", "PUFFS", "EBONY", "MIMIC", "SIEVE", "MEDIA", "ATLAS", "DESSA", "DUKES", "TERUG", "GRIMM", + "HAYES", "INDRA", "SHEWS", "DUMAS", "MYTHS", "FACON", "LILAC", "MISER", "TACIT", "SABER", "TUNIS", "NELLA", + "PORTO", "EERST", "ENNUI", "CIDER", "POUCH", "JUIST", "SHYLY", "NEVIL", "FROTH", "GIPSY", "EINST", "HERRA", + "ALLAY", "DANDY", "TOWNE", "HARTE", "HOHEN", "PLUIE", "NEIGE", "FASSE", "WAKEN", "YEAST", "CADIZ", "MINCE", + "WOODY", "AVOIT", "BOIRE", "PIERS", "FANGS", "KRIEG", "SAHIB", "DRYLY", "BELTS", "LACED", "WAYNE", "KOMME", + "AWARD", "FRANC", "FUERA", "IDEES", "COUPE", "VIOLA", "QUERY", "VENOM", "WAZIR", "SLABS", "FATES", "DIXON", + "POKED", "TOURS", "CHEFS", "ABACK", "PAOLO", "BECKY", "OAKEN", "KLEIN", "MOONS", "POLAR", "PRAYS", "DEIGN", + "CROOK", "SOLON", "LEANS", "PUPPY", "FINIT", "XXVII", "FOLIE", "POTTS", "LABEL", "SUSIE", "STACK", "POIDS", + "JESSE", "BLEEF", "HILLY", "BITTE", "WADED", "AIKAA", "EAVES", "MINUS", "REPAS", "NANCE", "DELHI", "HAZEL", + "ORBIT", "SURER", "TYLER", "DELIA", "RAJAH", "JUDEA", "STATT", "MENGE", "WHIMS", "BOYNE", "WHIPS", "PAINE", + "LIBEL", "MANOS", "TAMED", "CHIPS", "REIHE", "SWANN", "QUALE", "SPASM", "VRAIE", "TWINE", "CLIME", "ABHOR", + "DOMES", "KNOLL", "VISTO", "WEDGE", "GRIMY", "ROVER", "WRAPS", "LEVEE", "LEGTE", "AMITY", "NIHIL", "HALLO", + "CARVE", "TREND", "DAYES", "HEARE", "DEZEN", "SAISI", "COCOA", "QUEUE", "SWOON", "WAGEN", "PUMPS", "TRASH", + "SHARK", "MEADE", "RECUR", "DREGS", "VOILE", "CANTO", "HAVRE", "FATTO", "SNEAK", "MISMA", "SHOVE", "SOYEZ", + "CRANK", "FINED", "JUMPS", "BRYAN", "VENEZ", "ENSUE", "WILES", "ROARS", "FREAK", "POSSE", "SINUA", "FUSIL", + "DAHIN", "BEGAT", "ELUDE", "CROIX", "CLANG", "HOARD", "HATCH", "SOILS", "SAMMY", "STARR", "POIKA", "ELISE", + "LISZT", "JENES", "WIRST", "RUHIG", "MEDEA", "RAIDS", "NOOSE", "LETTY", "CHERE", "EASED", "SERVI", "MEJOR", + "FEARE", "SMACK", "ALTRI", "HITCH", "GETAN", "MITEN", "MAVIS", "ACIDS", "BEGET", "GARTH", "TIERS", "ANTON", + "NICER", "TANKS", "CESAR", "CANES", "PLUMB", "VANCE", "BEGOT", "DETER", "FLARE", "BOMBS", "FITLY", "JENEN", + "FLIRT", "HABER", "JOITA", "CHUTE", "READE", "STETS", "STEAK", "GAUZE", "SPARS", "PEARS", "PIQUE", "GLACE", + "PATSY", "HOVER", "RIPEN", "SWANS", "VARRO", "MARSE", "SONGE", "GUILE", "CANNA", "NACHT", "SALLE", "CARTE", + "AMMON", "TASSO", "RAZOR", "WHIST", "OMENS", "NIMMT", "GOEDE", "GRUNT", "DIRAI", "SPIKE", "COCKS", "DIEUX", + "SUURI", "UTILE", "SEREZ", "SOTTO", "STAAN", "DACHT", "LUNAR", "WHOSO", "DINNA", "CRETE", "PADUA", "SERGE", + "CACHE", "CHILI", "TIBER", "TAGEN", "RIGOR", "BIDDY", "AIMER", "MOUSE", "OXIDE", "PESOS", "SLING", "SPILT", + "BOWEN", "SINON", "SINAI", "TULLA", "TONIC", "LOIRE", "JUGER", "HAUPT", "CADET", "COSMO", "OCCHI", "CLANS", + "MUSTY", "MUMMY", "NIGER", "GOULD", "SQUAW", "UNTEN", "PENSA", "BIJNA", "CONTI", "NASAL", "TAUNT", "SILKY", + "NUNCA", "KAKSI", "DUCHY", "MELTS", "VASTA", "DAVIE", "QUELS", "ANCHE", "LIEUT", "BARDS", "BUNNY", "RILEY", + "TIENS", "MOTHS", "OMNES", "TANTA", "WOOED", "BRENT", "RASCH", "JADED", "ERRED", "BRAGG", "GUIDO", "HURON", + "LEILA", "VEDAS", "DEEPS", "RHONE", "SOWED", "BADEN", "NOMME", "SLUNK", "DEARS", "GUTER", "HIELD", "FEUER", + "LUPIN", "HAGEN", "JAMIE", "PLUMS", "GUSTO", "WETEN", "TENER", "HAWKS", "FOLLE", "CHILE", "SAADA", "SQUAT", + "KNACK", "MOORS", "SCOWL", "JENEM", "PLAZA", "SHEEN", "UNSRE", "DETTE", "WEBER", "BITES", "MASSA", "TUOTA", + "WOORD", "DOCKS", "AGILE", "SERFS", "OASIS", "BEGON", "LACES", "CORDE", "MEYER", "REISE", "FOGGY", "DINER", + "HUMID", "QUAIL", "LILLY", "FADES", "WRAPT", "GRUFF", "WEEPS", "ETTAE", "MARAT", "REICH", "LUSTS", "DICHT", + "APACE", "BUENA", "SLIMY", "TRUMP", "JOUES", "DUCAL", "COVET", "GIEBT", "AMANT", "FEIGN", "MEIST", "RATED", + "DOIGT", "ELLAS", "CHOIX", "GLOWS", "PALED", "MOOSE", "SHEAF", "SWARD", "HAREM", "DORIA", "PIKES", "WREST", + "MITTE", "TOOKE", "CHUMS", "TENGO", "LOTUS", "CUFFS", "KIRBY", "BARKS", "GYPSY", "BLOIS", "URBAN", "SIBYL", + "TIRES", "JOSTA", "TEILS", "ODIUM", "PURGE", "DOSES", "WEZEN", "NEPHI", "LIBRO", "SLYLY", "VIGIL", "HADES", + "THUMP", "BUYER", "RYDER", "ARYAN", "SNARL", "CHIME", "ROBED", "AUREZ", "DESTO", "SLEET", "CUBAN", "RARER", + "SEVER", "WOLLE", "FOYER", "SHAKY", "CAGES", "VANHA", "BETER", "TARDE", "VNDER", "FOURS", "KARNA", "POORE", + "OMNIA", "ACTES", "GOWER", "NINTH", "GENAU", "CLAUS", "BIENS", "KOHTA", "NUITS", "BRAID", "FEUDS", "GANGS", + "ILMAN", "LEECH", "NEQUE", "ANTAA", "BARBE", "MAGNA", "PILLS", "PANTS", "EARLE", "MERCI", "PERTE", "BABEL", + "LUCHT", "TENIA", "SEUIL", "DEGLI", "HARES", "VILDE", "RIOTS", "GJORT", "PIPER", "ZEIGT", "TIBET", "KICKS", + "JADIS", "SPELT", "PLAID", "CERCA", "FALLU", "MURKY", "HEINE", "REVUE", "CAMPO", "AMAZE", "LOOMS", "COSTA", + "STYPE", "JONAH", "CHUCK", "RELAY", "FIBER", "GLINT", "SLOTH", "MUSIK", "MOTIF", "BATCH", "SOWIE", "PATER", + "DEANE", "DOETH", "OBJET", "GAIUS", "LAMPE", "DOZED", "GULLY", "SEAMS", "BOYCE", "MAILS", "USEIN", "HENDE", + "PSHAW", "WHINE", "PRINZ", "WILLY", "HALFE", "MILKY", "ANVIL", "SHRED", "CHIDE", "EFFIE", "MOANS", "LEITH", + "COPSE", "DRAGS", "FARRE", "BANDE", "BHIMA", "YATES", "NIVER", "DEVEZ", "LAGEN", "OMBRE", "JONGE", "WATTS", + "ISTUI", "TUSKS", "AMACR", "JAREN", "VARIT", "REDDY", "PRIAM", "PRIMO", "COWED", "TOWED", "AVERY", "AXIOM", + "HOVEL", "BRICE", "CONES", "KUNNA", "LEISE", "HOBBY", "MINUT", "POOLE", "SINGH", "BOILS", "SATTE", "CRABS", + "MERLE", "WARNS", "FOURE", "ACHES", "YEARN", "SAHEN", "AILES", "LORRY", "LOVEL", "LUEUR", "SLANT", "METER", + "PHONE", "TAGES", "SUPER", "FUSED", "GLOSS", "NULLA", "ADEPT", "COREY", "LEGER", "SUAVE", "GAZES", "NEMEN", + "NOGET", "PANDU", "CAREW", "XXXII", "SCAMP", "HEATS", "TIPSY", "HOWLS", "MEMES", "ROOMY", "SANOA", "HAYDN", + "JETTE", "AMORY", "CONTE", "VERRA", "TILLY", "WELCH", "PEALS", "SULLY", "BRINE", "MOINE", "FACTO", "BORDS", + "FIRMS", "FARES", "TUNSI", "WAXEN", "BULBS", "BLIVA", "MESSE", "BRISE", "BORIS", "KAUAN", "RAKED", "IBSEN", + "PINTS", "DENKT", "POPES", "WHIFF", "SYBIL", "QUITS", "ARGOS", "AGGIE", "ANDES", "DALLA", "ABOOT", "EINDE", + "GERNE", "TYROL", "FADER", "SORTI", "MEDES", "AMIGO", "CELTS", "LEONE", "LILLE", "AGLOW", "INJUN", "KEEPE", + "RUINE", "LIEUX", "GEENE", "AUDIO", "SEETH", "STREW", "GRAZE", "KHAKI", "ABRAM", "APTLY", "HEERE", "ETANT", + "GOOSE", "POSED", "CHOPS", "ALTRO", "TITHE", "ANGST", "WORIN", "HIGHT", "CREAK", "NULLE", "DOWER", "SENTI", + "QUELL", "CHINK", "DESIR", "SETZT", "JAUNE", "LYNCH", "SPOUT", "PORES", "GOMEZ", "GABLE", "ACRID", "COZEN", + "BROCK", "DOYLE", "ZWECK", "ACTON", "RARES", "PIPED", "ZONES", "LIARS", "BOYLE", "KAMPF", "ALBUM", "SNIFF", + "MARES", "SHACK", "OMITS", "CITES", "AROMA", "WITTE", "HILFE", "HEERD", "ARGUS", "PINED", "USURY", "EARLS", + "YKSIN", "CULTE", "NOBIS", "SPILL", "FLOTS", "PROIE", "LURCH", "ADOLF", "ARDEN", "CARNE", "INTET", "WREAK", + "VOLLE", "SLIME", "TONGS", "GUERE", "VALES", "MANNA", "ISLET", "MANER", "DUNNO", "EASEL", "ECRIT", "NOOKS", + "GLENN", "HOSEA", "BUSTS", "ENACT", "FETES", "CHAUD", "GUILD", "SIZED", "ENOCH", "SPIEL", "OILED", "NATAL", + "ROCHE", "BASER", "PODER", "QUOTA", "FINIR", "SELMA", "NADIE", "DRAME", "LARKS", "DOWNY", "GULLS", "ARBOR", + "CERES", "LANDE", "WYATT", "IDIOM", "QUILT", "TUTTA", "HEINZ", "MAMMY", "MONDO", "TACHE", "QUACK", "TALON", + "ENSIN", "VEUVE", "SODOM", "DUELS", "RANCH", "MINNA", "CRAPE", "WAVER", "HOOPS", "MEBBE", "VERSA", "KUTEN", + "AVIEZ", "WIJZE", "PLAIT", "BIGOT", "URSUS", "VOLTA", "DRONA", "PADDY", "AHMED", "GIBBS", "OGSAA", "PABLO", + "TOILE", "TERME", "GRAIL", "JUICY", "CINCO", "VOORT", "RERUM", "MUNRO", "SAMMA", "REMIS", "AVOND", "ALLOY", + "RIZAL", "SITTE", "NEWES", "ONZEN", "OFFEN", "SCOFF", "XXXIV", "NOUSI", "GLUED", "BEARE", "STARB", "FUITE", + "BRETT", "NOTCH", "AARDE", "BRUNT", "DEWEY", "GAMLE", "POBRE", "LAITY", "RAVED", "TROTZ", "SPRIG", "JEMMY", + "MEUSE", "FUEGO", "BUELL", "WARMS", "BIXBY", "TWIST", "ROLLO", "RISKY", "GROPE", "MEGET", "DANNY", "LONGE", + "LOOKE", "AYRES", "SUPRA", "CHANG", "FOLGE", "SWAIN", "ARRAS", "CHRON", "SEIEN", "OTTAA", "OBEYS", "HALTE", + "MOLTO", "HORUS", "HALLE", "DEVON", "HARPS", "PHIAL", "SELIM", "TIDAL", "TAPIS", "PRIER", "DENKE", "FONDO", + "OGDEN", "ENDOW", "ASTIR", "FAIRS", "BAKED", "ENGEL", "HANNA", "SZENE", "PIVOT", "WIRED", "RAFTS", "COTES", + "PLATS", "TIMON", "ANNAN", "MENTE", "RONDE", "STINT", "RAGES", "KENNY", "SADIE", "ZIJDE", "CALLE", "DIVIN", + "DUPED", "VOUCH", "FOOTE", "LEWES", "LIISA", "DARCY", "SIGER", "LUCAN", "IEDER", "ARMAS", "DONAL", "BRADY", + "WERKE", "ANNEX", "STATO", "HUNTS", "KAMER", "ALWAY", "PHILO", "SYNOD", "PROBE", "HORNY", "LEEDS", "FOUNT", + "NABOB", "ESTAR", "AVRIL", "DEFOE", "CAGED", "LOOPS", "NEVEU", "CLYDE", "GOEST", "MANES", "LEAUE", "TUNED", + "RISHI", "POSER", "STOIC", "NUEVO", "SAMOA", "SALIR", "SNORT", "ADIEU", "BOCHE", "LOGIS", "JAKOB", "LESSE", + "FILMY", "FEINT", "HOOGE", "LESEN", "TUTTE", "VEDIC", "BATON", "SCION", "SUBIR", "MASSY", "CLOVE", "WHOOP", + "TEILE", "DEBUT", "STOWE", "HELPE", "PAILS", "ADELE", "HERUM", "DESKS", "LIIAN", "HINZU", "SIDED", "TRITE", + "HORAS", "JINNY", "POPPY", "EQUIP", "NEERE", "URINE", "VIENE", "BERNE", "DUROC", "PERTH", "ARIEL", "GREGG", + "WOLLT", "MOREL", "DEEMS", "VIEIL", "MEZZO", "POETE", "HERON", "SHIRK", "SIETE", "HEMOS", "SORES", "EATON", + "ASHEN", "VERSO", "ALTON", "STORA", "SWOOP", "CZECH", "JOVEN", "SOFAS", "TIMED", "XXXVI", "KREIS", "WEILE", + "SCOUR", "JACKY", "ROLFE", "PIENI", "STIRN", "DREAR", "PIERO", "TRAYS", "SYLLA", "ERANO", "LURKS", "GOURD", + "MAAKT", "WANED", "LIBYA", "ALSOF", "FELON", "BREST", "ELENA", "WERDE", "JUNTO", "TENTE", "VERTE", "SILLE", + "VOLER", "ALBAN", "GALEN", "REDAN", "WALSH", "ARBRE", "ASTOR", "RAYON", "REEVE", "MUCHA", "NUORI", "LABAN", + "SIKSI", "LUCCA", "ALLUS", "PURTY", "BUDGE", "USURP", "MONTY", "YEMEN", "JOKED", "QUAYS", "LECOQ", "WYLIE", + "MACON", "UPTON", "NEALE", "SEENE", "OFFRE", "OTTER", "POING", "LEHRE", "ADOBE", "ABYSS", "SPURN", "PLATT", + "HUUSI", "PLUTO", "REMUS", "MOGUL", "DULLY", "BLOED", "SPICY", "GEEST", "FEIND", "KETCH", "LASSO", "ESTIS", + "BRYCE", "MENER", "PACHA", "ANTOI", "LEPER", "BAUER", "JOHNS", "FEREZ", "SWEDE", "HERAB", "VOTER", "SIREN", + "ELDON", "ROOKS", "EBERS", "NESTA", "YEERE", "ROLLE", "SLADE", "GLAND", "JOSEF", "BORES", "GALOP", "NELLE", + "HEGEL", "MEANE", "WIDEN", "DAWES", "PLUSH", "ERHOB", "FORSE", "SOLLE", "ZIEHT", "SUCHE", "BERGE", "HASAN", + "MANDY", "OEVER", "BAIRN", "ELGIN", "ZULKE", "AKBAR", "BUREN", "CRUMB", "BACKE", "LINGE", "WOHIN", "HACEN", + "BEENE", "LARVA", "REGEL", "BEVEL", "DUVAL", "KNAPP", "GEEFT", "REELS", "JUANA", "ESSER", "PLATA", "STIEG", + "RACEY", "KNELL", "LLAMA", "VOISI", "HINDI", "PASAR", "VENTS", "HOLDE", "PITIE", "PAGET", "SLUMS", "GIRTH", + "DROSS", "MAIRE", "LAGER", "FEHLT", "MAZES", "UNDID", "THYME", "WHORE", "WOGAN", "BLUES", "RIAIT", "PANZA", + "SALVE", "ZOUDE", "HERRE", "MIDAS", "PREIS", "SIRES", "GLUCK", "ROAST", "FABER", "ASIAN", "DEREK", "DIGBY", + "YARNS", "NUAGE", "SENCE", "HEWER", "SAWED", "SAVOR", "SWIMS", "CABOT", "TAFEL", "ANSON", "CAROL", "SNART", + "HUSKS", "SERRA", "LYMAN", "MARNE", "EDDIE", "GENII", "DAMEN", "RAMON", "FAUNA", "ARMEE", "CHUSE", "DEMIE", + "COMBS", "VENAL", "BLINK", "CYNIC", "JOLLA", "FUESE", "DUPES", "LOSER", "TERSE", "DUNES", "KENNE", "WINDE", + "GAYER", "CONAN", "MISSY", "BALLE", "RANGS", "REBUS", "FUSSY", "DRILY", "JUGES", "HABET", "IMACR", "CUZCO", + "FELLA", "HEWED", "AIENT", "SOULE", "CAPUA", "ULTRA", "EBBED", "VIPER", "CAFES", "KENNT", "CRAZE", "ELMER", + "DRONE", "SERAS", "SIENA", "WOLFF", "GILLS", "CRYPT", "GRAFT", "SANTE", "HALTS", "GAPED", "GELUK", "YOKED", + "FONDS", "FERNE", "NAOMI", "SECHS", "BUXOM", "TENEZ", "QUAKE", "TENUE", "ROUVA", "LOURD", "LYNDE", "REACT", + "SOUPS", "ODEUR", "KAZAN", "BOXED", "GRUEL", "ECLAT", "GLENS", "JOHON", "SOPRA", "COCKE", "RENEE", "SWISH", + "PFERD", "SCHIP", "KERRY", "ALDEN", "DANSE", "DELTA", "UNIDO", "SOORT", "EXULT", "ANDEN", "BLIVE", "HORNE", + "AMORE", "EORUM", "KINDE", "JEERS", "NAMUR", "ULICK", "SNAIL", "ALECK", "MEEST", "SUEUR", "DUMMY", "MARSA", + "CURRY", "ELTON", "NEDDA", "DOLOR", "GEVAL", "DEUIL", "THONG", "MULTA", "RESIN", "DIRGE", "LARGO", "INDUS", + "LEMMY", "BONUS", "PLEAS", "ALINE", "ABOUE", "CRAMP", "CORSE", "VERTY", "FALTA", "WOMIT", "EARNS", "PRATE", + "WEARE", "PHEBE", "OVERT", "BRACH", "CAMPS", "TRAUM", "TALTE", "MERGE", "SAUVE", "RIUER", "COALS", "FLOYD", + "ASSAI", "EMPOR", "NOTTE", "VINDT", "ENKEL", "SUCHT", "ZAGEN", "EPIST", "LYELL", "RILLS", "WOFUL", "MEKKA", + "ZULUS", "HUSSY", "GASPS", "JOTTA", "SOOTY", "ANNUL", "CAPES", "SHERE", "NENNT", "SORGE", "DUANE", "CALMS", + "DARBY", "TAHOE", "LIEST", "SAUTA", "SPURT", "VINCI", "ANNAT", "BOORD", "DROPT", "ZELVE", "DWARF", "ATTER", + "BLEUS", "DEANS", "JEWEL", "HEMEL", "AFIRE", "FLEAS", "ADAIR", "INSEL", "MUITA", "TALLY", "CABAL", "SAMME", + "MELON", "TEPID", "MIELI", "FOLGT", "POLLS", "ROSIE", "WALDO", "LEBTE", "HINGE", "SNOUT", "STURM", "BEALE", + "HAGAR", "SEWER", "SONIA", "TEXAN", "BEETS", "CHERS", "BURMA", "HOWEL", "EYING", "SAIDE", "PUNIC", "ANTIQ", + "UNTIE", "DILKE", "SOUPE", "WYNNE", "SKATE", "AMYAS", "AGUAS", "HESSE", "ADAGE", "ROOST", "WOLDE", "LEIPZ", + "TILED", "EAMES", "ASTUI", "SCOOP", "KNABE", "CLINK", "ELEVE", "RAZON", "VIEJO", "REYES", "ENDNU", "RIVEN", + "ORDEN", "PARRY", "GULFS", "EXTOL", "AUNTY", "TWEED", "RENDS", "STERK", "SIDON", "XXXIX", "SUDAN", "THANS", + "COMYN", "MATTI", "MONCK", "OHREN", "HADDE", "VREDE", "WAIVE", "LEJOS", "MITRE", "BEFEL", "PASTY", "WORKE", + "BLOTT", "LIEBT", "BARRE", "SOARS", "FISKE", "CERTO", "WRITS", "LENTE", "VAINE", "FALLE", "VERDE", "MOGEN", + "GRETA", "MOCKS", "TRANS", "CROAK", "DITTY", "VRAIS", "MINOS", "SALUA", "CHAOS", "PEKIN", "APPEL", "MAGIS", + "FINCH", "ELVES", "ISANG", "KOLME", "POESY", "DECOY", "STOPT", "LUCIE", "NETTA", "SOULT", "ALGUN", "IDIOT", + "DISKS", "CRISE", "EVOKE", "LIEVE", "KLEUR", "FERMA", "EENIG", "CIRCE", "SUELO", "MATER", "BATTU", "LINIE", + "MORNE", "SEGUN", "LARRY", "TOMAR", "GAGNE", "SCALY", "STAVE", "GLEAN", "KOVIN", "DORIC", "GORSE", "INDES", + "BEPPO", "GLANZ", "KUNTI", "OPFER", "DRIES", "TOTTA", "SHIRE", "SILVA", "OBRAS", "NAAST", "DETTO", "OMAHA", + "SHEBA", "TITAN", "BEAKS", "COWAN", "MARKT", "ADOWN", "GOUTY", "HAREN", "TOADS", "QUALI", "RENAN", "MANNE", + "SNORE", "COMMA", "ZYNDE", "LYGIA", "RUDER", "SCRIP", "WIERD", "ELEGY", "TWANG", "ANTTI", "SNIPE", "TABOO", + "MOHUN", "BRANT", "CAIRE", "DEMAS", "ARDAN", "ALTID", "COLLA", "BRAWL", "LEASH", "CHAFE", "PARED", "VOMIT", + "MYERS", "SOURD", "SANOO", "TAIRE", "ABDUL", "GNADE", "PALSY", "MIKSI", "JOHAN", "MELEE", "DRAWL", "HULOT", + "SOUCI", "WILLE", "AGATE", "NOMEN", "SEEKE", "GRIPS", "LEGEN", "SINEW", "JAMBE", "VRAAG", "EMACR", "FETID", + "LEWIN", "EDERS", "VIVES", "KALLE", "FILMS", "GUTES", "MARYA", "MYSIE", "JERKS", "PAARD", "NANNY", "EENER", + "ROVED", "CHICK", "BITCH", "HINAB", "IDEEN", "ESSET", "HOURE", "RHYME", "JULIO", "BELLO", "HAVIA", "DEARE", + "IDLER", "WOBEI", "JABEZ", "MIAMI", "ESTOY", "AULUS", "ANCOR", "MOVER", "WARRE", "IMPEL", "KRONE", "RACHE", + "FASTS", "NEPAL", "BANJO", "REINO", "DAWNS", "HETTA", "STAEL", "CHIRP", "PENTE", "SOLLT", "VOCES", "DEZER", + "LAINE", "MINDE", "ANENT", "ASSET", "PUITS", "SWAYS", "MOCHT", "WAHRE", "DEMUR", "BETTE", "LUZON", "TIESI", + "TONED", "FREDA", "TAINE", "DALLE", "SPITS", "DYKES", "TOLLS", "SOONE", "EURER", "ANDAR", "KITES", "KENBY", + "PROPS", "PUNKT", "TIEFE", "FLAPS", "NIMES", "ROHAN", "GUIDA", "GOODY", "LEROY", "BUCKS", "ONKEL", "ADLER", + "ALAIN", "SWIRL", "ZOORT", "GARRY", "AMAIN", "TEINT", "EERIE", "GUSTY", "AIDES", "ANIMO", "DOEST", "DREST", + "LAPSI", "FARBE", "RACKS", "VOEUX", "BELIE", "LEMON", "INCAS", "NECKS", "LOGIK", "RIVET", "DOTTY", "EWING", + "LENOX", "ZORZI", "TUNNE", "COLDS", "INNES", "CARON", "TULLY", "KULKI", "NIEUW", "RIPER", "HATER", "ROSEN", + "WAIFE", "FATTY", "VOGEL", "YPRES", "CASAS", "SINUT", "WINKS", "NOLAN", "BOARS", "BIRON", "MINAE", "KRAAL", + "STUHL", "PODIA", "MARLY", "MOROK", "DINES", "VENTE", "WHACK", "AMINE", "PALMA", "DANDO", "TAVIA", "QUILS", + "PATIO", "TRICE", "MARIN", "FOUET", "WAILS", "HAKON", "WALLY", "WINCE", "VOLGA", "TEKEE", "TURNE", "REFUS", + "SERRE", "LEAKY", "PROXY", "CAPPY", "BLARE", "BUSEN", "MAORI", "ALOES", "SLAVS", "BANCS", "VESTA", "METED", + "NEUES", "ALWYN", "GURTH", "JOSUE", "STOLZ", "JOZEF", "JOSIE", "MAYER", "STUBE", "ZADIG", "SLOEG", "STRUT", + "REARS", "MARTA", "BROIL", "CARTA", "DEGEN", "HURST", "MUNDE", "EPICS", "VILJA", "PESAR", "SPAWN", "MINUN", + "TALKE", "CROFT", "TESTA", "SEARS", "MYRRH", "CASED", "LISTE", "CHURL", "DICEN", "KURUS", "CORAL", "LIVIA", + "COLTS", "DRAYS", "ESTAN", "POETA", "CABUL", "JUDEN", "LATHE", "LITLE", "LEPIC", "EATER", "DIJON", "PAVIA", + "CLODS", "BYRNE", "JULEY", "GENTS", "VERNE", "ASSAY", "CHINS", "LIZZY", "PUHUI", "CANBY", "KNOBS", "KRING", + "BEZIG", "STINK", "STUND", "BUTTS", "DONOR", "SLICK", "WASPS", "HAMAN", "LIMES", "MEURT", "HAMEL", "CITIE", + "DIVIL", "SLAYS", "CHUMP", "DIKES", "PEEPS", "LISLE", "MOTTE", "FORET", "MACHE", "BREVE", "STROM", "BUENO", + "JEHAN", "BONTE", "GETAL", "HOBBS", "TAHDO", "VOLTE", "GIUEN", "TEXTE", "CLANK", "VITAE", "MAUVE", "LIDIA", + "SMEAR", "CIRCA", "JOUIR", "PRETE", "ARTEN", "PARKE", "CAVIL", "XLIII", "STUDS", "VALLE", "BOUTS", "KOMMO", + "NEITI", "GRIPE", "JOKES", "SOFIA", "ASPEN", "HEROS", "LOSSE", "BRAUT", "HOODS", "KRANK", "LINER", "TIDEN", + "FLAWS", "HAUTS", "UMHER", "MAATA", "HELAS", "SULLE", "JERKY", "OUTDO", "RABID", "CINNA", "MANIE", "TUSEN", + "BATED", "BELLY", "BORST", "MOLES", "YUKON", "STORK", "RAZED", "OMACR", "SLITS", "SERIE", "HAITI", "MORAN", + "RESTO", "WOVON", "BISON", "TULIP", "DARYA", "REEDY", "AILED", "DIESS", "RIRES", "SELBY", "ELITE", "FIXER", + "MIHIN", "NORMA", "MEADS", "TIERE", "OOZED", "POSES", "MIENE", "GEHAD", "DIENT", "RECIT", "AZTEC", "EILTE", + "GNATS", "JETTY", "JOIES", "KOHTI", "BARTO", "MCKEE", "SAUDI", "STAGS", "PARME", "SINGE", "ALVAN", "CLAMS", + "CRONE", "CORSO", "PUHUA", "SATIS", "DENRY", "FURRY", "FURZE", "PUEDO", "COUDE", "DIDNA", "SOIRS", "ACORN", + "KNOWE", "KREEG", "TOOTH", "SERBS", "DUNCE", "THEIL", "SANFT", "ASHER", "NOGEN", "TETES", "AFFIX", "CREER", + "PITHY", "MARIO", "WORDY", "SIMLA", "ROTEN", "VISOR", "ZAKEN", "BOGEN", "HERAN", "ILLUD", "JAHRH", "CUBIT", + "PRISM", "BAIRD", "DUPRE", "TORRE", "REGNE", "SUCRE", "WERRY", "MORIR", "SLASH", "MENEE", "SERUE", "WIENS", + "BUNKS", "SEERS", "ZOGEN", "CORKS", "HOGAN", "GARDA", "TARTS", "FROME", "POCOS", "SPRAY", "CAIRN", "BAITH", + "DAGAR", "DEJAH", "ICFTU", "ZUNGE", "CHARY", "CHURN", "FLOES", "SIENS", "VAPID", "GAVIN", "GRIER", "BLOTS", + "GRAPH", "CUBES", "BIGHT", "ARLES", "MICAH", "NEBEL", "DIVER", "STABS", "BESSY", "KUULI", "SITZT", "SENAT", + "PINKS", "SPANS", "VINER", "ALKAA", "HAELT", "BAYLE", "CREUX", "ABBAS", "CROCE", "JAFFA", "ABLER", "GULCH", + "KANSA", "LIENS", "TITEL", "MATED", "MEGAN", "ALACK", "COHEN", "HELGI", "SAMOS", "ALDER", "FORAN", "LANKY", + "GRISE", "OATES", "LAVER", "IONIC", "ALIBI", "OUVRE", "GERTY", "PONER", "KEENE", "FESTE", "SANAT", "ESSEN", + "ADDER", "KURZE", "REALS", "CRAYE", "SANON", "TULEN", "VIDES", "POATE", "WIPES", "ANITA", "GAMLA", "DELFT", + "CESSA", "FORDS", "SERIA", "ASAPH", "BUDDY", "GRAAF", "BENNY", "KREBS", "CLAPS", "AMIES", "DUPIN", "VEXES", + "COMUS", "ROUES", "IRATE", "MANSE", "TACKS", "BRACY", "TABOR", "STORT", "JINKS", "SHEIK", "PARVA", "SKUNK", + "ALPES", "VERUS", "BOGUS", "RIVES", "NINON", "FILLY", "JENKS", "DOLCE", "ZETTE", "EPSOM", "DENYS", "LOBES", + "SLUSH", "BONUM", "CANOT", "MYLES", "OTWAY", "TREUE", "DANKE", "FREIE", "FITCH", "SUMME", "CAPER", "BETTS", + "SPREE", "TEENS", "IRWIN", "STAHL", "MIXES", "TIARA", "MOTTO", "NOGHT", "STURT", "BLEUE", "DALES", "STING", + "SMOCK", "BLOCH", "FUROR", "SATED", "VENNE", "JAPON", "RIGBY", "DULCE", "HEIRS", "WISPS", "PROWL", "REGEN", + "PENIS", "SICUT", "TOSTO", "HULDA", "HOFFE", "SEEDY", "DRIER", "LILLA", "ZEVEN", "LADLE", "SAURA", "VESTE", + "AINOA", "ALICK", "RUGBY", "CORPO", "BLYTH", "JANUS", "DICTA", "VAUNT", "LAVAL", "DIGHT", "PHOTO", "ZUVOR", + "ELSJE", "LUNGE", "TOMBS", "STOFF", "SURAT", "BEVOR", "OUDEN", "TRESS", "WIRES", "TIMOR", "RUSSE", "TABAC", + "WIRFT", "BERYL", "CACAO", "HEALS", "ROODE", "SWARE", "LESER", "LIDDY", "ANIMA", "ILLIS", "LUONA", "PECHE", + "ROPER", "TORAH", "GOTTA", "SOMIT", "KOMIK", "ANNEE", "ERDEN", "BANGS", "PREST", "HOUDT", "ONZER", "RAKES", + "PAIRE", "SOLVE", "TYSON", "UTICA", "TROUS", "GAMUT", "PEPIN", "CHUNK", "MAMIE", "GUYON", "ORIEL", "MIEDO", + "BUTTE", "OMNIS", "QUISO", "GERDA", "DIETH", "SIRUP", "WHISK", "FAERY", "REGIS", "INANE", "MULLE", "SHINS", + "TAMAR", "ETTEN", "CIBOT", "BOGGY", "CAPUT", "ETEEN", "GRETE", "WADNA", "CRUST", "IPSUM", "BLIGH", "LOUER", + "ONKIN", "BOGGS", "HOLDT", "MEINT", "SKEIN", "TOCHT", "VIALS", "AIDER", "RECUT", "GRECS", "LOUGH", "SPOOR", + "SEEME", "KNOPF", "STACY", "FIEFS", "WEEDY", "BENIN", "CHINE", "BREDA", "BEKAM", "SPINS", "WARST", "BOXER", + "TROST", "STUNT", "ANIMI", "AURAS", "SUMMA", "YHDEN", "YOUSE", "GROOM", "PETTO", "POMPS", "MIRZA", "HALLA", + "DENNY", "JAILS", "PUFFY", "DELLS", "MUREN", "UNCUT", "LEACH", "LIKEN", "MURAL", "OSMAN", "TARDA", "DESEO", + "MODER", "MODUS", "MANON", "COLIC", "PRUNE", "VIVIR", "CLINT", "RIGGS", "CANNE", "SALVO", "AIRES", "PIKKU", + "SAMEN", "BASTE", "ARCIS", "EASIE", "SHAMS", "MATTY", "CANNY", "NEIGH", "HYMEN", "DAUNT", "BRUNE", "FORZA", + "YAZOO", "VENDU", "NICKY", "WINNA", "PESTS", "WARME", "BENET", "TOSIN", "VASCO", "BETES", "LIEUE", "PETAL", + "ANNOS", "COLLE", "TRIEB", "KLAUS", "PUNIR", "VODKA", "THRON", "DECIA", "BRAUN", "SHUNS", "UUDEN", "ALPHA", + "FLOUT", "SELLE", "STOOL", "NEUEM", "BASLE", "HERNE", "INFRA", "ERASE", "NESTS", "BERGS", "MEDAN", "NOITA", + "SUCKS", "IESUS", "CITER", "FRAGT", "VEDER", "DRUID", "SCALD", "ALIDA", "QUITO", "HOERT", "JUNOT", "PERDU", + "GEARY", "BOOKE", "KENDE", "CHICO", "MEATH", "ROGUE", "SMYTH", "DIVES", "FILET", "FLAKE", "PUTTY", "SIDAN", + "DANAE", "ILAND", "BORDE", "HERBS", "LIMBO", "PEINT", "ROLES", "BRINY", "ETRES", "FLANC", "BLATT", "DERRY", + "MUEHE", "RODDY", "DAMES", "INGER", "XLVII", "AIRED", "CHATS", "PAULO", "EJECT", "FUNGI", "SIKES", "CADRE", + "COOLS", "SNAPS", "BLENT", "BOOBY", "VIIME", "DALLY", "HIVES", "LEDDY", "BALDY", "PRANK", "NONCE", "OVENS", + "SCHAL", "AUGUR", "CALYX", "MOWED", "TOITS", "CIMON", "SWEYN", "EDLEN", "PALES", "SPERM", "THANE", "BEZIT", + "EILEN", "UUTTA", "NAMAN", "MACKO", "SHANG", "LITEN", "BAZIN", "THORD", "ARMES", "YEARE", "CALLY", "TRYST", + "CAPRI", "ETHAN", "HOEHE", "AOUDA", "HALEY", "BERTA", "KAREL", "CLUES", "CURST", "FORAY", "ITHER", "LASST", + "RICHT", "YAWNS", "WILKS", "GRIFF", "JOKER", "MOCHE", "ATLEE", "BAIZE", "SITAE", "TEMOR", "TENET", "HAYTI", + "MANGY", "LEVIS", "SHAKS", "CURIO", "ERICA", "FRETS", "TAKAA", "KLAAS", "FROCK", "SHIED", "TRUSS", "WACHT", + "LAMME", "SAMAA", "ARMER", "SLINK", "BLEEK", "BIBBS", "ARMIE", "BANDY", "HARDI", "MINNE", "MORIN", "COURU", + "CLUNY", "MOLTI", "PLOMB", "AHMAD", "ROCCO", "SLAKE", "HAARE", "ARIAN", "WOCHE", "ZILAH", "LURES", "KLEID", + "NUOVO", "TUMOR", "CLEON", "CORTE", "SUNNE", "ELFIN", "GLAZE", "HEDEN", "VREES", "BODED", "HVIDE", "TRIBU", + "ZACHT", "YUSUF", "FRUEH", "LEDEN", "SITIO", "LITET", "MAURY", "PRYOR", "ROBUR", "GRIST", "RUFEN", "VAERE", + "VILKA", "JORAM", "FONTE", "NOGLE", "BASEL", "FIRMA", "HUNCH", "STAVA", "ALLIE", "FIERI", "HOLEN", "TROTS", + "KNEAD", "MANGO", "OEUFS", "PONCE", "QUEED", "KLANG", "AMENI", "PONTE", "EIGNE", "BRILL", "LOCUM", "NERFS", + "CLEVE", "COPIE", "EHREN", "BOTHE", "SIKHS", "SHREW", "HODGE", "WILEY", "ALTRE", "SIGLO", "CORFU", "HAIRS", + "MALER", "OZONE", "IGUAL", "VORST", "ANGES", "INNAN", "NOMAD", "DOMIN", "FUTUR", "ROPED", "HALED", "NAVEL", + "PEASE", "SAMBO", "HABLA", "ALTYD", "BREIT", "RENTE", "BAGGY", "LANGT", "NOCES", "SHEAR", "BULGE", "SLEDS", + "TRURO", "ZWARE", "BANNS", "NAPPE", "PLASH", "TARES", "WHIRR", "SCOTS", "WANDA", "CREDO", "FUMED", "GABEN", + "POJAT", "SISTA", "VIVIE", "HALVE", "SICKE", "SIMUL", "WHELP", "BREEN", "EBENE", "HAWKE", "STAUB", "MOVIE", + "PECHO", "BUONA", "CULPA", "GAGNA", "HEELE", "FLITS", "HIESS", "SOLUM", "STEHN", "FIRME", "ESTER", "MACAO", + "LIBER", "MCKAY", "PETRA", "TOKYO", "SOLCH", "BAYOU", "HELGA", "LOCUS", "ZWART", "FABIO", "ROSNY", "CORNE", + "MESMO", "BASTA", "DISCS", "GRINS", "FLEES", "PIGMY", "MAUER", "FRISK", "HUNDE", "LOGOS", "TROMP", "HOHER", + "SOGEN", "BOEUF", "CUORE", "FACIE", "RANDY", "ALTRA", "ANKER", "DROLE", "LARGA", "POJAN", "ORTON", "DEJAR", + "GIBES", "MARIS", "SETTE", "SUBIT", "VARMA", "WISHT", "GUPPY", "THOTH", "MANUS", "TRILL", "FIRTH", "BANAL", + "METTE", "SNAKY", "ESIIN", "LUNGA", "BOOMS", "GRUBS", "NEEMT", "TASSE", "OHEIM", "ENCOR", "FELLS", "VERTS", + "GUNST", "SYKES", "VESTS", "COLON", "AGONE", "AIMEZ", "BAHIA", "LEDGE", "LICET", "BEGAB", "FOAMY", "HULKS", + "NEGER", "KEYED", "MAIST", "FABEL", "LORNA", "DICHA", "SACRE", "SITEN", "GODEN", "DARKY", "DRAVE", "NIXON", + "LAULU", "FIGUR", "GANGE", "KLAMM", "LACEY", "KORTE", "RUDES", "TUERE", "LUNGO", "VERRY", "VAURA", "CUANT", + "DRAUF", "OFFAL", "GORGO", "MENUS", "MINED", "OPTIC", "DRANG", "GRIME", "CORNY", "KETTE", "HEADY", "IRAIT", + "PAPEL", "BLITZ", "HUGHS", "DROEG", "TENDU", "WIERP", "DOANE", "ELATE", "LACHE", "PIEUX", "TULLE", "TONGA", + "NAEHE", "CLASP", "FANNS", "WAFER", "DRUCK", "SAUTE", "NEVIS", "PITTI", "FALDT", "USQUE", "EWELL", "GUNGA", + "MORAY", "FISHY", "FLAIL", "MEBBY", "DAGNY", "CLOGS", "ELEND", "MEAUX", "GOLFE", "HACHE", "SAILE", "GIRTY", + "NADIA", "PAVEL", "PAHAA", "SAEPE", "KIRKE", "WALDE", "EXITS", "DOBBS", "HAMAR", "MARCO", "NOYES", "HAILS", + "MALEN", "SHALE", "TAGER", "VELDT", "GREIF", "MASHA", "LOUIE", "DEDEN", "LEZEN", "NETTE", "CROWE", "MOYNE", + "GILDS", "CREON", "GOTHA", "KAREN", "BRUME", "VERIE", "BOSCH", "MEURS", "HYENA", "LLENO", "HOCHE", "CONDY", + "PYOTR", "CALMA", "SAMAN", "STEHE", "BERIC", "LIBBY", "MONTS", "TRONE", "JUSSI", "ALCUN", "BEGGE", "COVES", + "NYODA", "ALLZU", "DAIES", "MULTE", "JUNKS", "MUSST", "DRAPS", "HAAST", "JACKS", "FAYRE", "RUNDT", "FREUD", + "COBRA", "FUMEE", "SAGST", "PURDY", "BEELD", "TORTS", "VERVE", "BRAZO", "JOYED", "WAGTE", "JOURN", "MUNGO", + "PAPUA", "FUELS", "HALBE", "PALMY", "REGLE", "ELISA", "JUSTO", "TWIRL", "CONST", "PASSO", "AMANG", "LICKS", + "NEDER", "DEGRE", "DIREZ", "FRILL", "KORAK", "KREUZ", "TYCHO", "POVAS", "ASTON", "GOSSE", "ASILE", "BOOZE", + "BUMPS", "SOMOS", "WASNA", "LORCS", "CRATE", "GROEN", "WOOER", "HEBER", "JUNTA", "TURCS", "VOLTO", "WHILK", + "DEEPE", "NEBST", "CODEX", "JUIFS", "ASIAA", "FULCO", "DOMED", "OUTEN", "STEPT", "GALBA", "CHEAT", "COULE", + "MEETE", "PAWED", "BUNCE", "SANIN", "JOLIS", "PORED", "PYYSI", "HOWAT", "LAURE", "ONNEN", "PRIED", "SCHAR", + "BAZAR", "MITAD", "MOUND", "FUERE", "TRYON", "BAISA", "CROUP", "KUNNE", "DOONE", "FOLEY", "NEHME", "DUOMO", + "SONNA", "FACIT", "KENYA", "GIVIT", "BLEAT", "HURLS", "EIFER", "AILIE", "BASIL", "HARMS", "SUTRA", "LARME", + "BRITT", "GRADY", "MOROS", "TRIPE", "ADMIS", "HERTE", "VOIES", "LANKA", "FUSSE", "GLOAT", "GROAT", "CLEEK", + "MESES", "MOOIE", "HAMIL", "FORDI", "MENOR", "SATYR", "TODDY", "TANNO", "GAMIN", "SOLOS", "YOURN", "AHMET", + "FATTA", "ERWIN", "POWIS", "SEGEN", "AMENE", "BASAL", "MOSTE", "TOYED", "MAGDA", "DIEPE", "GENOU", "OCHRE", + "WEITE", "VOLKE", "POORT", "AIKEN", "JOPPA", "SLUYS", "FIERE", "JOUST", "LACHT", "SAYDE", "DELOS", "LETHE", + "TAGUS", "ALIIS", "HIVER", "TENGA", "ECOLE", "FUENF", "ROTHA", "VETCH", "GLANS", "LAIDE", "FLYNN", "KHASI", + "KELLS", "BOONS", "SLUGS", "ULCER", "VENTO", "BRAWN", "ENTRO", "VAIMO", "AGNEW", "CREES", "MEIGS", "GOUGH", + "PUSSY", "VERBA", "RICKS", "ROWAN", "FERRO", "LAGDE", "PRESO", "RUPEE", "FRANS", "ALDUS", "CLARO", "HANNO", + "VIEJA", "MARFA", "OSTEN", "GAUZY", "BOULE", "DRAFT", "GREVE", "SETON", "HEEDS", "SONNY", "HANSE", "LUISE", + "ALIUD", "AMASS", "EARES", "ROWDY", "STEWS", "KLARA", "ROSSI", "HENKI", "NOSTI", "ZOUDT", "GROTE", "DEALE", + "FREMD", "KLONK", "LIBRI", "CAPET", "FLOSI", "SONST", "CULTS", "DIKKE", "ALGER", "DIXIE", "NUNEZ", "QUAFF", + "QUALM", "TAFFY", "QUEDA", "SEDGE", "GUERT", "ETIEZ", "ICING", "PHIPS", "PICTS", "ABBIA", "THEER", "COREA", + "KABUL", "COMEN", "GOLDE", "VIXEN", "ZWAAR", "EADEM", "FREES", "MARGE", "MEREN", "SANAN", "EDLER", "LUOGO", + "PEDAL", "VAMOS", "OSTIA", "ALTES", "COUTE", "MANDO", "AESOP", "SAPOR", "PIEDI", "GIRLE", "EMORY", "MADDY", + "HULLS", "REVES", "BAITS", "MARTS", "PROUE", "RIFTS", "IFRCS", "SANER", "EISEN", "MORGA", "WISSE", "DANCY", + "HAZEN", "LASKI", "ESTAS", "AVOUE", "CACHA", "EWIGE", "LOOPT", "WAMBA", "ACASO", "MOLLE", "JUXON", "BOCCA", + "COLUI", "TRONC", "DAUER", "PARET", "CIEUX", "DOERS", "MICHT", "NADAT", "ROULE", "DURER", "GRADO", "SUFFI", + "WAYES", "HAWES", "VOREN", "MILCH", "HVERT", "COTTA", "ZAIRE", "ALLOT", "PANSY", "FOILS", "LEAKS", "SEKIN", + "SEUEN", "WHOME", "BERRI", "BIGGS", "PARAS", "PROSY", "RACER", "BOHUN", "OOTAH", "NIOBE", "COPRA", "PLAGE", + "SPEKE", "AUDIT", "FINIS", "ROMPU", "BANDA", "DAMPS", "QUARE", "TOQUE", "BRIER", "DOREN", "NANON", "BELLI", + "TYRAN", "HILFT", "PUEDA", "MOLDS", "OELIG", "ATHEN", "LARCH", "PUNTA", "CAMEO", "MALIN", "SONYA", "JAUNT", + "LAPIS", "FINIE", "POMPE", "SIMMS", "BEADY", "GUANO", "KEHRT", "RUIDO", "WRANG", "FAHRT", "URIAH", "BLEVO", + "DUQUE", "HACKS", "TASTY", "MAINZ", "COWER", "FUORI", "TEHTY", "ARRAN", "TOOTS", "UTHER", "CROCK", "HELLE", + "QUUNE", "LINGO", "CLLIA", "ELIHU", "APPUI", "BLUER", "DIRIS", "BYLES", "FEDER", "INJIN", "STENO", "AGORA", + "COMUN", "GILET", "GRAUE", "MISES", "WOHER", "MENKO", "AMBOS", "MUTES", "MOREA", "YONGE", "LAKOU", "TECLE", + "BRATS", "LAMED", "PENDU", "TOPAZ", "DRUSE", "LAPEL", "WANDS", "AINDA", "FOOLE", "FUSES", "DEBBY", "EUGEN", + "FABRE", "MALTE", "FEINE", "GANTS", "HETKI", "SENTE", "AMICI", "LOUPS", "OPDAT", "LAUNE", "TOULD", "DURAS", + "VERDI", "STUMM", "CHENG", "FLUSS", "SACRA", "SPADA", "CRONY", "LEGDE", "OIKEA", "ALVAR", "ABAJO", "CURDS", + "PRINS", "THEYR", "BOWIE", "REDET", "WRACK", "FLORE", "ABAFT", "ANDET", "CENCI", "CLAES", "GONGS", "OWENS", + "CUIUS", "EXIGE", "IGNIS", "SLAAP", "OWAIN", "MALLE", "SACHS", "SIGNE", "BOESE", "MUSTA", "TORNO", "GORKA", + "ERANT", "REGNO", "SWART", "DORMI", "MASAI", "FIDES", "KARMA", "RAIDE", "ILIUM", "MARTY", "ROLLA", "DEERE", + "TOMES", "KNOOP", "DOLCH", "PAIGE", "ISTUU", "WAIFS", "COLES", "ASKEW", "EUREN", "HELST", "CLAUD", "QUENU", + "ROJAS", "VYASA", "KULTA", "LEYES", "BOYER", "FUCHS", "NAWAB", "BIDED", "BUSCA", "LORNE", "AIKOI", "BEFAL", + "DUCAT", "ELOPE", "FERAS", "LLEVA", "BEINE", "EMERY", "BRIAR", "DIXIT", "MURAD", "TODES", "OLELO", "SALUE", + "TOTEM", "DILLY", "HAFEN", "NOEMI", "UNCAS", "JUGEA", "QUIVI", "ALIXE", "CASSE", "GAVEN", "GRAMS", "TORSO", + "DAMIS", "NILES", "NORTE", "OXLEY", "PASOS", "AGIAS", "ESSAI", "REFIT", "FAGIN", "HARUN", "AENNU", "DECRY", + "HOOSE", "PONTS", "RENDE", "UARDA", "DRUMS", "PLIES", "PRIUS", "USKOA", "GAHAN", "MIRAR", "MELAS", "ECHTE", + "DARIO", "RHEIN", "ROSAS", "FRUTO", "SLAPS", "BRAHE", "OGIER", "ROQUE", "BEFIT", "VIAJE", "ETATS", "FINOT", + "LINDE", "ZISKA", "LAHAT", "MITAE", "AUTOR", "FERRE", "LAMES", "LLENA", "HOLME", "MAHON", "MUDGE", "SAVVA", + "SEDER", "THUGS", "CERTE", "EILIG", "RAMPE", "GHANA", "TALER", "LEERE", "BURGH", "CLASE", "DIRAS", "MITES", + "PULPY", "CUFFE", "PLADS", "HALEN", "PAWNS", "FAKIR", "MANET", "BRUNG", "TEPEE", "CERTA", "LULLS", "BUTCH", + "ARSON", "DUMPS", "EMITS", "FORTO", "PESTE", "SHANK", "HARPE", "KAMES", "MEERE", "ENGEN", "SUING", "VITAM", + "LLOYD", "THEOL", "WITWE", "DOTED", "DUETS", "MONET", "AVERS", "CADDY", "SINFI", "CALOR", "CORNS", "GEBET", + "HAYNE", "TOTEN", "GUTEM", "TILLS", "JULES", "ROSSE", "LECKY", "STAMM", "WAUGH", "ALLEE", "COMED", "COVEY", + "LONGA", "ZATEN", "BEVIS", "MATTH", "AVERE", "VERUM", "MIRAH", "SOREL", "BOURG", "CREPE", "PRUDE", "MAGUS", + "TAPIO", "ZOCHT", "TOMAS", "TURAN", "BLOUD", "BOORS", "DRAGA", "HELLS", "DECKE", "GEMMA", "CONGE", "ETAIS", + "SANAA", "SLONE", "AXLES", "FAVRE", "ORTEN", "CAPON", "STODO", "POSTO", "DANBY", "SLIGO", "VACHE", "BOTHA", + "CANTY", "CONEY", "LAVES", "NEUVE", "TALAR", "CAKED", "POCHI", "CAULD", "DAAGS", "GNRAL", "RAVES", "OLSEN", + "AMICO", "CINCH", "TIGRE", "MIKKO", "SABIN", "FECIT", "GARNI", "ILLUM", "OOREN", "RUBAN", "BEGAF", "QUIRE", + "TALUS", "FINNA", "HARER", "MORTO", "BHAER", "KOPFE", "LEMME", "BOITE", "UUSIA", "VENIA", "ASAKO", "BOTTE", + "CLEFS", "FAWNS", "MUNDI", "NUBES", "SEGUR", "ANTIC", "JURER", "CRAIK", "BANGE", "KOUDE", "PARER", "VOBIS", + "YOKES", "SAKRA", "SAULT", "BODEM", "PASSI", "SMIRK", "WHAUR", "GYGES", "MONNY", "REIMS", "KOPJE", "POSSA", + "CHIOS", "VARUS", "CLOUT", "LOUSE", "PLAIE", "PROWS", "HOLTE", "DANEY", "WONNE", "FESTA", "SPOOL", "TOREN", + "MATEO", "NINNY", "JUPES", "PLEBE", "TRAHI", "DACRE", "LEIBE", "LUBIN", "AGAPE", "HIDES", "OPERE", "LAWES", + "GALLS", "ASHBY", "ASSAM", "SHIEL", "FREYA", "RODEN", "TYLOR", "CUIRE", "RAKAS", "SPAET", "THUIS", "MINGO", + "NILUS", "FIERS", "MOLTE", "SCAPE", "SKIPS", "ZAMEN", "FINNS", "JUMBO", "PAPST", "ZIELE", "ABASE", "GREEP", + "HONRA", "TESTY", "ZYNEN", "BRUIN", "FLUCH", "PRUDY", "DEPIT", "LUNDI", "TUULI", "UEBEL", "DAGON", "COUPA", + "LUULI", "MORES", "STATA", "KEBLE", "PEKKA", "KLAAR", "MOURN", "DRAXY", "CLAVE", "JOLLE", "VENGA", "OSAGE", + "POLEN", "DIGNO", "HELFT", "GABON", "KHOJA", "SIDEN", "DARKE", "LUMPY", "POPPA", "HECLA", "AGING", "FREUT", + "HALES", "TANIS", "DRAMS", "NEGEN", "STEER", "EULER", "ETOIT", "MAAND", "PHAON", "CREME", "FULLE", "SALUD", + "CHUNG", "FELDE", "RUBEN", "FALLA", "PELEG", "CUYAS", "FACTA", "KNITS", "NOEMT", "TUPIA", "URREA", "SYNES", + "TROUW", "JEWRY", "KAMAR", "PETRI", "STEEN", "HECHA", "WEAKE", "AVILA", "HAFIZ", "HARDE", "MOQUE", "THEIM", + "FARIA", "GRYCE", "PASSY", "OPHIR", "ROSEY", "WEIBE", "GANGA", "PALUS", "AGENS", "COOED", "TRINA", "BETEL", + "UNDAN", "BALLY", "DELLO", "HAIRE", "PEONS", "RAVIN", "WAXES", "VIDAL", "ELKEN", "MAGNO", "RINSE", "VERBO", + "RIPON", "ADORE", "FAGOT", "ROAMS", "BOTEN", "NOBLY", "ROULA", "THAIS", "LOGER", "VOLGT", "MOSCA", "SABES", + "DAMER", "SCALA", "BASSO", "GORED", "NOEUD", "PISTI", "SLATS", "SIXTE", "BUOYS", "PAYED", "REAPS", "RUSES", + "HEALY", "AILLE", "BLASE", "BRULE", "OASES", "PLUGS", "HEARN", "IYONG", "PRIMI", "GRAYS", "PALOS", "TICKS", + "ESTRE", "POUCE", "DAMME", "FLECK", "GENET", "HOUGH", "JEBEL", "CLAPP", "HYDRA", "TAMER", "BLUME", "AVIDE", + "DOWDY", "GIMME", "ESCAP", "POSEY", "CRASS", "EGARD", "NUDGE", "TURBA", "WARTS", "KLAGE", "LENNY", "ELLEI", + "GONNA", "LETTO", "IONIA", "SCHAM", "EPHOD", "MIENS", "TAGIT", "DOUAY", "NICOL", "STOKE", "FLICK", "HUOLI", + "REEKS", "FAROE", "FRUEN", "FICHU", "KERTA", "JARED", "WUNDE", "AYONS", "PAESE", "PATTE", "SINGT", "SCAND", + "ADHUC", "LYMPH", "PARVE", "SCHOB", "STRAX", "PERON", "BUSTE", "DARLE", "EODEM", "MOTES", "ORSON", "RAUME", + "BRINK", "GNOME", "LASTA", "CRUMP", "SKENE", "GRILL", "NIEVE", "BUBEN", "JOSUA", "CEDER", "INNEN", "MANGA", + "OLEVA", "SIXES", "ERICK", "GERRY", "STUFE", "CLAYS", "FACHE", "GELYK", "SALTY", "TEEMS", "AISNE", "RANCE", + "KAUAS", "VYING", "DOUAI", "CASUS", "CORES", "GENIO", "JEUDI", "RADIC", "RUNES", "CLARY", "BRISA", "MUCUS", + "NATUR", "COGIA", "MOXON", "LUTES", "MARKE", "POOTY", "SLAAN", "HAVAS", "MALOS", "SOWER", "DOMUS", "SEGNO", + "DONDE", "ETHIE", "CONCH", "MERES", "CHING", "KITTS", "MARCY", "REPAY", "COMBE", "PAOLI", "ARDEA", "ENCYC", + "KONGE", "MCCOY", "VESEY", "BURRO", "FURIE", "GEZAG", "JIFFY", "QUIPS", "COLLO", "MAGIE", "MYNEN", "COWES", + "OMEGA", "SKEAT", "BELGE", "PAEAN", "EMLYN", "CALDO", "KINDA", "AFTRE", "CITTA", "FETUS", "ONHAN", "TULTA", + "WINCH", "ADIGE", "EMMET", "EUREM", "NATTY", "TALMA", "WILNA", "FORBI", "BARTH", "MONRO", "ZADOK", "BARBA", + "BARCO", "GAZON", "VAART", "ANNAS", "MAHDI", "WERTH", "RAHAA", "SMAAK", "WIRKT", "YEELD", "YERBA", "FINGO", + "HEBEN", "VEINE", "LLEGA", "USKON", "SEMEN", "STOEL", "ZZZZZ", "HOWDY", "REGAN", "CLACK", "SELVE", "GLEGG", + "RAUCH", "DESTA", "FUMER", "KOMST", "BLOCS", "TRIAD", "VENIT", "BALIN", "UPANI", "VISST", "LOMAX", "RYMER", + "SOTTE", "ZOETE", "FOKER", "MILDE", "TATEN", "AKING", "DAVOR", "NITRE", "ERROL", "CLERC", "CONTA", "DEBAR", + "KREET", "MODEM", "CULCH", "FLERE", "OLIKO", "PREYS", "SLOPS", "WHIPT", "ADDIE", "BEULE", "BIJOU", "ECHAR", + "FUOCO", "HATHE", "QUAIS", "SOLDE", "FUZZY", "PISTE", "SAINE", "WEREN", "ZEBRA", "MASKE", "ANIME", "SAYST", + "LVIII", "MOIRA", "RAINA", "WOTAN", "ARMIS", "ASIAT", "BAGUE", "PRESE", "VOLSE", "NUOVA", "PARIA", "ALTOS", + "ENSAM", "TROLL", "ABBIE", "WILTS", "CESTE", "CUYOS", "VIGNE", "BEEBE", "GLIED", "RASHI", "DEVRA", "HOERE", + "LEZER", "NAHEN", "BERUF", "HIGGS", "LEEKS", "POLKA", "UMACR", "DOOMS", "FASTE", "GLEBE", "NOSED", "LOWTH", + "DOORE", "HIRES", "SKULK", "BILDE", "CHEBE", "WAITE", "ACEST", "DAMAS", "HOJAS", "MOMMA", "PERLE", "BALAK", + "LADIE", "MARCK", "YAQUI", "YEATS", "RECAL", "SINAE", "RUDRA", "AMENA", "CUJUS", "SIGNA", "SOLUS", "KISSE", + "NULLO", "PALJO", "SIEHE", "TOLDE", "VISCH", "HOVEY", "ROOME", "ETZEL", "SHURE", "FIFES", "BALTY", "FRITH", + "MICHU", "MOCHA", "TURKE", "FATTI", "GULES", "GALLO", "HANNE", "IZAAK", "MINTO", "ROYCE", "SARUM", "DEBIT", + "TENNE", "MANDI", "WELNU", "GOLLY", "JEUGD", "KELLO", "FULKE", "GUEUX", "VALSE", "WOEDE", "BORGO", "GALLE", + "HEMAN", "LODER", "SHANE", "FUGUE", "PESER", "CYRUS", "HOVED", "MACES", "NADER", "BUCKY", "HOFER", "KARIN", + "MELUN", "WAITZ", "ACTIF", "THEWS", "BOSIO", "MARYS", "CEOUS", "WOHNT", "OISIN", "ARTES", "ILLAM", "MASTE", + "LAMAR", "MULTO", "SABIA", "SAGAS", "SCANS", "VITRE", "ZUMAL", "KRIPA", "MORUS", "PINTO", "COCHE", "DEDIT", + "TALAT", "LEBEL", "SLOOT", "WUCHS", "BABET", "BEUTE", "FESCH", "NUBIA", "DELVE", "DEVAS", "NOHOW", "BAKOM", + "CIOUS", "MANDA", "OVALS", "PURES", "DYAKS", "ECTOR", "HERBE", "HUVUD", "LAIRS", "LIUES", "PLENA", "DOLED", + "MINST", "VELEN", "COROT", "OLNEY", "TELLO", "CARLE", "GNAWS", "HAAND", "ISSUS", "VINCY", "ARAGO", "BORAX", + "CENTO", "NAGOT", "SAUVA", "SOUSE", "VOUTE", "GRECE", "HEYST", "MILNE", "NANDA", "PRETS", "SLIPT", "STATU", + "WEICH", "ACTUS", "LOTYS", "HOORT", "PERES", "SOGGY", "BUSCH", "PUSEY", "MENEN", "SACAR", "GESTO", "TOTUM", + "AUBRY", "BEARN", "KAUAI", "BRAHM", "LAING", "BODIE", "LACKE", "OVARY", "SAYES", "SNAGS", "THATS", "ALPEN", + "MAYNE", "SELAH", "AUGER", "BETEN", "CORRE", "RUNNE", "TRANK", "TYPED", "MULEY", "SPOTT", "IGLOO", "KEELS", + "CASSY", "FAGON", "ACCES", "CXIUJ", "INTRA", "PITTY", "AHURA", "BAMBI", "BAYNE", "HIERO", "MAGOG", "MOONE", + "TODAS", "WENNA", "CAPAZ", "FONDE", "NAGON", "SLAGS", "RILLA", "GIVET", "POMME", "ANNAL", "BARTY", "CURIA", + "LEGGE", "PERSE", "SAALE", "THORA", "EDELE", "PERDS", "POULE", "BOGAN", "KAABA", "EGALE", "VEDEN", "PINKY", + "GERME", "MOULT", "VARIE", "DEWAN", "TEKLA", "FUDGE", "OPINE", "OSIER", "LAUFE", "LERAT", "PINUS", "THULE", + "EUERN", "PERRO", "SENDE", "WRENN", "DATUM", "AILSA", "GHITA", "WACHE", "ATOLL", "PELTS", "PLOWS", "POCAS", + "SHOON", "BEBER", "KATSO", "SOLEN", "HORRY", "PLASE", "EDIFY", "LADER", "TENUS", "JIMSY", "MAIJA", "SOLIS", + "AIMEE", "KUOLI", "ORTER", "OLAVI", "ORMUZ", "COSEY", "JAMAS", "PUOLI", "HEDDA", "TAMPA", "GRABS", "GRATA", + "LOUSY", "TRATO", "UDDER", "BEZUG", "BINET", "AMBLE", "HELMS", "LEVAI", "PAPPI", "COPIA", "SIRVE", "LIGUE", + "ORMUS", "GOUTS", "SINKT", "VRIJE", "LOTTA", "GENOT", "GRUPO", "MOEGE", "SLILY", "ARIES", "FRIST", "KYNGE", + "LLEGO", "MESSO", "NUEVE", "ALAMO", "OJEDA", "ROCCA", "THIER", "ALIOS", "BIDES", "DIEDE", "RUHEN", "TESTE", + "OZIAS", "QATAR", "PILES", "ALMAS", "BARBS", "BODDE", "BRIGS", "GWYNE", "SUELE", "GEOFF", "OAKUM", "ROCAS", + "GESTA", "HUBER", "VILNA", "CLAMP", "KUKIN", "NUQUE", "PELOS", "VIDEO", "WEKEN", "CRECY", "GRABE", "WESEL", + "ABORD", "AIMES", "HOHEM", "TULIN", "AUTUN", "BASHA", "JADIN", "CORAM", "LOOKT", "NEARS", "CLYST", "INIGO", + "NEPOS", "THERM", "FLUES", "FOCAL", "MUETS", "NOYER", "OCKSA", "SHOLD", "FRITS", "HITZE", "LAMAS", "MAHAL", + "MOMUS", "POWYS", "MAGST", "MENNE", "GOLPE", "WAFTS", "DORFE", "IRVIN", "RYDAL", "WYMAN", "CAVED", "CUEUR", + "FLIER", "SIGUE", "STRAF", "TRAJE", "DIRCK", "KANAG", "WESER", "WHYTE", "NKARA", "PECKS", "SAFER", "TRONO", + "ASURA", "MENTZ", "SASHA", "APPAL", "ETAGE", "LASTE", "USETH", "VOIMA", "VORAN", "LEMAN", "SHOOP", "AGITE", + "DABAN", "OMAAN", "BEATS", "TUBBS", "COLPA", "MATAR", "JORGE", "MCGEE", "AVOWS", "HUSET", "JOASH", "KARTE", + "AVISO", "MEATE", "MKHYA", "MUCHE", "ASCOT", "GALLA", "ISORA", "ROMER", "EPOUX", "FALSO", "BALBI", "SUNDA", + "LECON", "SPIJT", "SPUNK", "TRAMS", "VARAS", "LOTTE", "MAZDA", "MOXOS", "AMADO", "BEURT", "BOOST", "CATER", + "NEBER", "NEGRA", "BREDE", "MYRON", "ARTIG", "CIEGO", "FILER", "GAYON", "HABLO", "HINDS", "KUULU", "LIGGA", + "MARDI", "PRIVE", "VLOOG", "CRABB", "SOANE", "DALEN", "DIGNA", "IDLED", "INNIG", "LATHS", "SANZA", "TRAGE", + "WELKS", "BERKS", "BRECK", "ELIAB", "HAUCH", "HERAT", "AVARE", "HAFVA", "PRIJS", "GROIN", "NUIRE", "OBESE", + "OLTRE", "RIJKE", "WEEPE", "SNELL", "SYRIE", "CONTO", "FURIA", "ILLOS", "MEALY", "QUEDO", "ROSIN", "SNOBS", + "JUNIA", "LUKIN", "MESTY", "HELIX", "WALKE", "BUSSI", "TIBBY", "KURTZ", "DOTES", "FAUVE", "GAINE", "LEEFT", + "TAELS", "ACHAB", "EMIRS", "JEPPE", "KNIPP", "PILAR", "REUSS", "WEGES", "DROOM", "EYRIE", "JAGEN", "SLUMP", + "MAREN", "MODUM", "TREIN", "BEBEL", "KRUPP", "LAURI", "LYNNE", "VINET", "FANNO", "HADNA", "MENDS", "TIOUS", + "AMREI", "ARNOT", "FLOSS", "HYLDA", "DEBEN", "OUBLI", "TYLKO", "AVUTO", "BOATE", "LUKEA", "TRATA", "ECLAC", + "WIESE", "ZWEIG", "LINKE", "PRESA", "VIRTU", "BINES", "HERSE", "LUPUS", "MATTA", "FIVES", "INSTR", "KLEED", + "POLVO", "GWILT", "UNHCR", "DOSED", "JUROR", "RUNGS", "SILKE", "ISHAM", "KURDS", "SILLA", "SPINK", "ACTOS", + "DORST", "RIMES", "VIISI", "ZUCHT", "ATTEN", "BUCHE", "BYERS", "CISSY", "FIONN", "KATRI", "ASSIM", "HUIUS", + "MENTI", "PARSE", "VIDER", "KAYAN", "KRANZ", "NIELS", "FOIRE", "KIOSK", "OBERN", "RECTE", "LIPPO", "CABBY", + "IMBUE", "DOLPH", "RADHA", "FELIZ", "HOMEM", "TONTY", "YAKOV", "AEGIS", "PEDIR", "ROUTS", "UNICO", "VELUT", + "CROLL", "ALZOO", "FALSA", "KLARE", "PONEN", "AMEER", "BOSSU", "ORAGE", "POSEE", "PUTEA", "SELIG", "CHAKA", + "DISKO", "FALCO", "TEXEL", "ASCHE", "DONAU", "DUFFY", "LERMA", "QUINN", "SISSY", "TORCY", "ALTAS", "BEEST", + "BOUGE", "MONEN", "REGNA", "SNACK", "LADKO", "PIPPA", "WEALD", "ILLAN", "KALLA", "KOTIA", "BARCA", "CENIS", + "KATZE", "KORAH", "PRADO", "SANNA", "BURNE", "IDYLL", "SITTA", "VIRUM", "BEATA", "GUION", "RASSI", "SHAND", + "GAGES", "PUHUU", "RAHAT", "CERRO", "FARAO", "PAZZI", "RIZZO", "SCENA", "KALTE", "PATES", "TEUER", "CASOS", + "COLPO", "FLUME", "MAIOR", "SABIO", "VULGO", "ARIUS", "FLAMM", "MARET", "COTON", "DUCTS", "FORGO", "GRITO", + "POSSO", "ZULKS", "ETAIN", "POSEN", "TAMIL", "ZUEGE", "BIPED", "BLURT", "THEOS", "FLUKE", "MUNCH", "YOURE", + "BANTU", "BAREE", "BEGUM", "CHILO", "DINKS", "FADEN", "MAHAN", "PEAUX", "REGNI", "EINAR", "ISLAS", "RAYNE", + "TAAVI", "ARABE", "SCEAU", "DUCIE", "LORDY", "CHOUX", "GAMMA", "HAIES", "HORTE", "REMUE", "SCULL", "ANTAR", + "ENGLE", "NOVUM", "TONYA", "BAUEN", "IPSIS", "LOIKE", "LOUES", "SINGW", "STRID", "VILER", "SHAWN", "AURAI", + "GUISA", "KUULE", "SALEN", "WONEN", "CARBO", "MELKY", "DUMPY", "SPICK", "SPORE", "BARAK", "ONGAR", "VOLKS", + "DIETS", "LAVAS", "MUUAN", "ANMUT", "BEMIS", "BOLES", "GOOCH", "KAFIR", "LAGOS", "LUNDY", "BESOM", "JURIS", + "MESAS", "TYMES", "WORAN", "FOUAN", "HOOKE", "PUGET", "SIGEL", "DEARY", "LUMEN", "MEURE", "SERUM", "GREIS", + "PFEIL", "BONIS", "EIKAE", "ZWANG", "ASIEN", "CREAN", "LENIN", "CYTEE", "HALVT", "LOCIS", "NENNE", "SPINY", + "TOXIC", "FERDY", "MAROT", "WOVEN", "AEONS", "DEBIA", "DRAPE", "REAMS", "RISER", "RONDS", "TAGET", "AMASA", + "JANIE", "NIZZA", "ICILY", "TENTA", "FUNDY", "FANES", "GOERA", "VALDE", "PINTA", "PITTS", "SEIDE", "BILGE", + "DOMUM", "GEHST", "GNASH", "HETTE", "DORAN", "FABRY", "HEIGH", "SARTO", "SCHAU", "DIENS", "DOGME", "FAUNS", + "FROMM", "TAPES", "DREUX", "FAGAN", "HELLA", "SKYLD", "ARMUT", "JUBAL", "LUISA", "NOYON", "PIOTR", "PYLOS", + "BRAGT", "MEENT", "VAREN", "ILION", "NARES", "STUBB", "ASTER", "DANNO", "MOPED", "ONNEA", "PALAA", "WORDE", + "DONAT", "MACAN", "POVEY", "BODES", "DRIPS", "EGGED", "FEELE", "KLUGE", "MUROS", "ORBIS", "TABBY", "WICKS", + "JUMNA", "SYLVI", "SUNNI", "SUYOS", "JINGO", "MONAT", "PIPPO", "APING", "CHACE", "FLUFF", "POLIS", "AUBER", + "RAMEE", "SEOUL", "LYDER", "RICOS", "UNSAY", "ELSON", "SNOOP", "ANNIS", "FANGE", "FIJNE", "HUMUS", "LENTS", + "PUREE", "STOUP", "YERES", "JONDO", "LIGNY", "BLOKE", "PROPE", "ELLIE", "ERNIE", "OAKES", "DURES", "NEEDE", + "PLIER", "GONDI", "HAMET", "DUNKT", "FOAMS", "LOGES", "SERES", "STELT", "ARRAH", "GRATZ", "VEDIA", "DICIT", + "HARTS", "REINA", "SIELU", "KAPPA", "LIPPE", "OLINE", "PROUT", "INGOT", "LENTO", "NEGLI", "PARIE", "VEZES", + "RUSTY", "SUKEY", "SURRY", "ANIGH", "CIMES", "CLOUS", "FARSI", "GRAAG", "TINHA", "ARLEE", "DONGO", "HAGAN", + "LXIII", "RAMAH", "CLEPT", "MULTI", "NIAIS", "VAINS", "LEONI", "LECHE", "RAINE", "HYDER", "LARES", "HAVET", + "MINHA", "BATTY", "ERICH", "KUGEL", "BRAGS", "CRIBS", "FAINE", "IHANA", "KAHTA", "SAATU", "SAYLE", "SEINS", + "SLATY", "SQUIB", "VIZIR", "YELPS", "ENDEN", "HINDE", "CORTA", "CRICK", "FLERA", "SVARA", "HIPPO", "DRONG", + "FETED", "LEHRT", "TULIT", "ADERN", "DIDON", "QUILP", "TREAS", "CARRE", "MALUM", "SULKS", "LEARY", "LYDDY", + "PETYA", "SALIM", "TOPSY", "BOWEL", "CRESS", "DESTE", "ENROL", "FLAKY", "BARBY", "BEERS", "SOMAL", "TIMMY", + "YEGOR", "JONNE", "POILS", "SOLDI", "ZARTE", "KROOL", "MARKO", "DERER", "TIETO", "VOSSA", "BULOW", "ALENE", + "DIMES", "DOIVE", "HOPEN", "MOLDY", "TOIVO", "AVICE", "HERMY", "BAINS", "DAGUE", "FLINK", "FRASE", "HANGT", + "LEGUA", "TEMPI", "VAITI", "CUTTS", "VANEL", "ALTHO", "EINIG", "FIORD", "HARME", "MOWER", "PAGAR", "ROEPT", + "FULDA", "MOSUL", "VIRGO", "DINAR", "KATSE", "KURJA", "LINEA", "NEARE", "RUINA", "TUVAN", "ASSUR", "LANDA", + "EVERE", "LIVRA", "PENSO", "TILTS", "VSQUE", "MILER", "NERVA", "SHINT", "STICH", "BEDST", "BLAUW", "COLDE", + "GLAUB", "MOATS", "PRISA", "SIGMA", "MAHEU", "CHENE", "MUITO", "POEME", "PUDGY", "RILED", "SILLS", "VERTA", + "AMRAM", "BONAR", "SVAVA", "CURSO", "FIDEM", "CAPEL", "JAINS", "PRAED", "GAVEL", "KIVEN", "PEDIS", "WAARD", + "FIUME", "GLYNN", "PIGOT", "BICHE", "DOOTY", "LADYE", "VENGE", "JUKES", "PETRO", "BONIE", "FOWLE", "INURE", + "KONDE", "POKES", "SIMBA", "LITRE", "PAREA", "HOREB", "TIMUR", "HEATE", "NAGRA", "NOYSE", "PAURA", "PEECE", + "AGNUS", "HYNDS", "MOSER", "GAWKY", "HYDRO", "ISTUA", "JACHT", "SCUDO", "UUREN", "MOSCO", "BLAUE", "LIVET", + "SECHE", "MAYDE", "PIEDE", "RUFFS", "KAPOR", "MARTE", "QUADE", "GLATT", "INDIO", "OMNEM", "SALIO", "SADOC", + "VOBAN", "ZICCI", "BRAES", "COELO", "CORTO", "IRKED", "SABEN", "DWYER", "JOUIT", "OFFRA", "SCIRE", "SELVA", + "TULET", "FLAGG", "ETUDE", "GOUGE", "HUJUS", "OLHOS", "ASOKA", "HURRA", "KEVIN", "NELLO", "FICHE", "JETAI", + "LECHO", "LIENE", "MUMPS", "ORICE", "VIZOR", "HARAN", "LUCRE", "MAGEN", "MOORD", "WEEKE", "GOREE", "KIMON", + "AVASI", "FINNY", "POSTA", "REGTE", "REMET", "SILEX", "TELCO", "TERVE", "SOHNE", "TOBIN", "GEZET", "GRIDO", + "IVIED", "ODDER", "FLACK", "JESSY", "DIECI", "EPAIS", "THRUE", "DICES", "GREYS", "MORTI", "PERCE", "BASSA", + "FASTI", "MCTEE", "SOUSA", "VIGNY", "WOLKE", "DUROS", "GRAVI", "PROVA", "SWILL", "VEELE", "WANES", "BASAN", + "CUFFY", "XVIIE", "HUZZA", "VIDAS", "ALLIS", "BEUST", "BRESL", "SWASH", "PEPLE", "GOWDY", "HADJI", "NAHUM", + "THESS", "FROND", "HAINT", "AMOOR", "LARNE", "NADAB", "NEILL", "SAGET", "CREDE", "HUMPS", "MOSSE", "MUTED", + "OLLEN", "VERBE", "ALVIN", "CLERY", "JOVIS", "SEYNS", "SONEY", "SYLPH", "VITRY", "WIGAN", "HILTS", "LINTU", + "SINDS", "UMBRA", "VISER", "CASCA", "MERAN", "MONNA", "PINEY", "BRAUE", "LAPIN", "MUEDE", "POWRE", "STAAR", + "BONEY", "MAGUA", "MAULE", "COYLY", "GUMMY", "HAULS", "RUEGO", "ARCHY", "BAUME", "BEVAN", "DEGAS", "KAISA", + "LESTE", "MOULE", "REDLY", "VETUS", "AFRIC", "BINGO", "LXVII", "OLLIE", "SURYA", "ABRIR", "DEDOS", "EXCES", + "LAITA", "MINTS", "POTER", "VARDA", "BACHE", "SALIC", "HURLA", "MEWED", "NECKE", "NEITO", "PARKA", "VAPAA", + "WAPEN", "BIJAH", "GUETE", "ZANTE", "SCUDI", "IDUNA", "KONTO", "LUXOR", "DIEST", "REGEM", "SPANK", "TIEDE", + "URSEL", "GENTI", "NAVES", "ONDAS", "PRZED", "FOLCO", "NABAL", "NOSEY", "PAULI", "TOKIO", "ATING", "DICTE", + "GUDAR", "RHEUM", "CUTTY", "OSMIA", "MAGER", "SANOT", "TIBIA", "TRAER", "LIANE", "MATHO", "MUNDY", "PATTI", + "REESE", "SEELY", "HARPY", "HUTTE", "LEGES", "LIETO", "LOBEN", "RAVIE", "SOAPY", "SOUGH", "VUOLE", "BOGES", + "LUTHA", "MAPPO", "BONED", "FLYER", "VISTE", "ENDOR", "FAYAL", "HALSE", "KEANE", "SIHON", "ANCHO", "ATTAR", + "AVETE", "KROPP", "MIMES", "OPALS", "PLUMA", "UNLIT", "AKLIS", "ELSLI", "HERDE", "SKALE", "ANDAN", "JEANS", + "PREVU", "BAIAE", "GOETZ", "HYLAS", "JANIN", "CHYLE", "DAUBS", "HANDE", "KNAAP", "NATUS", "ZONEN", "MONTI", + "SUDRA", "LIKER", "LYCKA", "UKASE", "VIMOS", "VOYCE", "AMIEL", "PANGO", "RANDE", "SHIVA", "SPION", "TIECK", + "DECEM", "MOLAR", "WENTE", "IMLAC", "PONTO", "WASTL", "BORTA", "FANDT", "JOKIN", "LYFTE", "RESET", "RIENS", + "SEMEL", "TWIXT", "WISST", "BEORN", "CEUTA", "NABAB", "EVIGT", "LEGNO", "LERNT", "LILAS", "MYLEN", "PAPPA", + "PREUX", "SPOOK", "FOGER", "TILDA", "CIVET", "ERRER", "GEARS", "JUGEZ", "MAGNI", "SEZEE", "SPISE", "AYEAR", + "BYEAR", "DUNNE", "JEZUS", "LUCIO", "MATEY", "SKALA", "TIBUR", "BAADE", "HOOTS", "LOONS", "TUNGA", "BIORN", + "DAGLI", "OUTRO", "GAETA", "JANEY", "MENIN", "SITKA", "FOIST", "JEHER", "LAVED", "LINUS", "PAWLE", "VICKY", + "RIDES", "TREED", "ARIST", "AWEEL", "CELIE", "BURRS", "CLIPS", "DICEA", "MOLTA", "NAVVY", "OLIKA", "RAYOS", + "SEYDE", "VANES", "ALLYN", "GEBOT", "GROSE", "BAHAY", "DUREE", "FAZER", "GOADS", "GULPH", "HAVER", "NIGRA", + "NOCTE", "PARUM", "SAATA", "SKIMS", "WAART", "WRAAK", "MUZIO", "OLSON", "RABAT", "REGIN", "RUNIC", "MANDE", + "RAUHA", "AYMER", "NAURU", "DREEF", "GAAET", "IRREN", "LESKI", "MECUM", "SAVON", "ARIAS", "HANKS", "ROZEL", + "ZEUGE", "DEUGD", "ETHIC", "GOODE", "TROVA", "AETNA", "BENJY", "ORMES", "TUBAL", "BRIMS", "BULKS", "FILCH", + "MONAD", "OOZES", "REGIA", "SARVE", "VENTI", "VOERT", "ARION", "COOTE", "SNECK", "TESSA", "BASON", "GIENG", + "INTIL", "NABIJ", "PRZEZ", "RAMAS", "SITUE", "CATHY", "GREER", "LINUX", "TOBIT", "DIDNT", "DROGO", "NEHMT", + "TIGES", "EDRIC", "TROUP", "AYUDA", "BEVAT", "DAPAT", "OVATE", "BALCH", "COGAN", "AIRTH", "GOWNE", "JUEGO", + "REBUT", "SALSA", "WIJDE", "JUBEL", "NEDDY", "RONNY", "FLAIR", "PASEO", "BRONX", "EWALD", "STURZ", "WODEN", + "KUMMA", "LETTE", "VANTE", "CAERE", "MISHA", "AMBAS", "FAAET", "RUNDE", "EYLAU", "MENGS", "FEEST", "LOWED", + "VENDE", "CUMAE", "GASSE", "HILMA", "IZUMO", "PISAN", "SOFIE", "CARRO", "CONIC", "CRIMP", "MUSKY", "SAYTH", + "STANK", "BOWES", "FAHNE", "THOME", "VIION", "ASTUU", "DICKE", "FASST", "PAROI", "LAWRY", "PRATO", "TENCH", + "CERTI", "ISCHT", "NOONE", "SENTO", "TAULD", "VLOOT", "VOIRE", "VOLTS", "BELUS", "CAVOR", "JORIS", "SEPPI", + "ERGAB", "BABIE", "CATON", "ABAHT", "BEDRE", "OPPIA", "POUCO", "SCHEU", "SEALE", "SUMUS", "VASTY", "FRIDA", + "LIPPI", "LOUVE", "BANCO", "BUONO", "CLOMB", "EDELN", "HOULD", "PELAS", "BODIN", "FAUGH", "JUDAR", "SEITZ", + "TOTTY", "WERTE", "STANE", "WINNE", "COCOS", "RIATT", "WERFF", "FIERA", "HINAN", "MATKA", "OATEN", "PLACA", + "CONOR", "DARRY", "HERTZ", "LAMON", "BEYNG", "CONTR", "INEPT", "SAKEN", "SIAMO", "STEDE", "MORNA", "AGERE", + "LANCA", "OSASI", "SADAN", "WOONT", "EVERS", "TROVE", "FACIL", "GRENE", "KUUSI", "PAPAS", "RECUS", "UUDET", + "VENTA", "ALOIS", "GILLY", "GONDY", "NAIRN", "BAILE", "CLAPT", "CLUCK", "CULTO", "EETEN", "GAPES", "NIVEL", + "TREKT", "WARLD", "ABIES", "AUTOS", "BARKE", "BRDER", "EASES", "GIRDS", "HAILL", "HIJAS", "RETRO", "SOMMA", + "STEEG", "BAYER", "CAMUS", "DEVIN", "HAMID", "HUANG", "IMOLA", "DICED", "HOCHA", "KASTA", "MULGA", "MYNDE", + "PIENO", "YAONG", "FURST", "GATTE", "SPASS", "SVEIN", "THEMA", "ANSAH", "CHAUX", "HEVIG", "PRIES", "ZOMER", + "BAGGS", "DOURO", "HIPPY", "SAONE", "VAVEL", "WETTE", "ALNAR", "EUILL", "PEATY", "VENIS", "CRITO", "LYNDA", + "OGRAM", "TEMPE", "ABOON", "FACEA", "FINEM", "KNOUT", "LLAMO", "LOAMY", "MENAR", "TISSU", "VERAS", "VIRES", + "COLBY", "JAINA", "TEUSE", "TYNAN", "YANKS", "BEZAT", "KLOOF", "SEAMY", "SIDDE", "STEIL", "TYYNI", "CARIA", + "HENTY", "LOWRY", "SOUZA", "ALIUS", "FEETE", "GRANO", "HALKI", "KELPO", "PESKY", "PLEBS", "POVIS", "TOPER", + "VLOER", "VOULD", "EDMEE", "GAZEN", "GITON", "HOLLO", "KAROL", "REIZE", "ROACH", "ULLOA", "VERBY", "CLEWS", + "EVANG", "FERAL", "GLICH", "THROE", "ZIJNS", "EURIE", "FERIA", "JEEMS", "PAAVO", "SAUTI", "DOODE", "PIENA", + "RADII", "UNICA", "CROLY", "HADAD", "KEYES", "NIKKY", "SANDE", "SEGEL", "SOFYA", "TEXAR", "GAZER", "LEDIG", + "NOTAR", "OBEIR", "VIMES", "GENUA", "KANAL", "LIMON", "RAHAB", "SUOMI", "VEJEN", "BATEN", "FJORD", "LEVES", + "PHARE", "RECTO", "AAGOT", "GIZUR", "NADJA", "RUGGE", "SNEYD", "DECKT", "FAILE", "GAOLS", "MELER", "PACTO", + "PAHAN", "CALIF", "MENON", "SEPOY", "WADDY", "ZELLE", "AENDA", "ASTUA", "KROON", "LETRA", "MINIT", "NEEWA", + "PATNA", "URIEL", "HITTE", "HOMOJ", "JOUET", "KOSKI", "LYSTE", "MINAS", "RUHTE", "SETZE", ] alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] -def most_used_letters(): +def most_used_letters(words=legal_list): ''' Outputs how many times each letter is used in the words array. ''' @@ -337,7 +963,7 @@ def most_used_letters(): print(f"{k.upper()} | {dicto[k]}") -def list_of_valid_words(letters): +def list_of_valid_words(letters, words=legal_list): ''' Outputs the array of valid words that can be made with the combination of letters. ''' @@ -356,33 +982,67 @@ def list_of_valid_words(letters): return legal_words -def print_valid_words(letters): +def rearrange_words_by_uniqueness(words): + unique = [word for word in words if len(word) == len(set(word))] + duplicates = [word for word in words if len(word) != len(set(word))] + return unique + duplicates, len(unique) + +def capitalize_all_and_remove_duplicates(arr): + for i,word in enumerate(arr): + arr[i] = word.upper() + result = [] + for word in arr: + if word not in result: + result.append(word) + return arr + + +def print_valid_words(letters=alphabet): ''' Prints the array of valid words that the wordle_face.c can use ''' - legal_words = list_of_valid_words(letters) - for i,word in enumerate(legal_words): - legal_words[i] = word.upper().replace("D","d") + items_per_row = 9 + legal_words = list_of_valid_words(letters, legal_list) + legal_words = capitalize_all_and_remove_duplicates(legal_words) random.shuffle(legal_words) # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter - + legal_words, num_uniq = rearrange_words_by_uniqueness(legal_words) + print("static const char _valid_letters[] = {", end='') for letter in letters[:-1]: print(f"'{letter}', ", end='') print(f"'{letters[-1]}'" + "};") print("") - + print("// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff") print(f"// Number of words found: {len(legal_words)}") - items_per_row = 9 i = 0 print("static const char _legal_words[][WORDLE_LENGTH + 1] = {") while i < len(legal_words): print(" ", end='') - for j in range(min(items_per_row, len(legal_words)-i)): + for _ in range(min(items_per_row, len(legal_words)-i)): print(f'"{legal_words[i]}", ', end='') i+=1 print('') + print("};\n") + + expanded_words = list_of_valid_words(letters, expanded_list) + expanded_words = [word for word in expanded_words if word not in legal_list] + expanded_words = capitalize_all_and_remove_duplicates(expanded_words) + + print("// These are words that'll never be used, but still need to be in the dictionary for guesses.") + print("// Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236") + print(f"// Number of words found: {len(expanded_words)}") + i = 0 + print("static const char _expanded_words[][WORDLE_LENGTH + 1] = {") + while i < len(expanded_words): + print(" ", end='') + for j in range(min(items_per_row, len(expanded_words)-i)): + print(f'"{expanded_words[i]}", ', end='') + i+=1 + print('') print("};") + + print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _legal_words array begins with this many words where each letter is different.") def get_sec_val_and_units(seconds): @@ -449,6 +1109,6 @@ def txt_of_all_letter_combos(num_letters_in_set): if __name__ == "__main__": - most_used_letters() + #most_used_letters() print_valid_words(['A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S']) #txt_of_all_letter_combos(10) \ No newline at end of file From 6dd46b46b1037cbfcd64b8594acb39dc5eeb85c0 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 06:22:24 -0400 Subject: [PATCH 106/220] Able to turn the expanded dict on and off with a variable --- movement/watch_faces/complication/wordle_face.c | 8 ++++++++ movement/watch_faces/complication/wordle_face.h | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 9b1db1b..2b991e3 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -67,11 +67,14 @@ static const char _legal_words[][WORDLE_LENGTH + 1] = { "SCOOP", "EASEL", "LOONS", "CONIC", "SPANS", "SPIES", "PRIOR", "SALSA", "SELLS", "PILLS", "RISES", "RARES", "SNEER", "SOILS", "ARENA", "CASES", "CANAL", "SAILS", "LASSO", "COCOA", "ERROR", "CALLS", +#if (USE_EXPANDED_DICT != 1) }; // These are words that'll never be used, but still need to be in the dictionary for guesses. // Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236 // Number of words found: 469 static const char _expanded_words[][WORDLE_LENGTH + 1] = { +#endif +#if (USE_EXPANDED_DICT != 0) "PARIS", "APRIL", "SPAIN", "EINEN", "ASCII", "EINER", "SEINE", "AINSI", "ALICE", "ALLES", "ALORS", "EINES", "ALLER", "PEINE", "PARCE", "CELLE", "CLARA", "ELLES", "ELLEN", "OLISI", "ALLEN", "ISAAC", "APRES", "CROIS", "SANOI", "PASSE", "ELSIE", @@ -125,8 +128,13 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", "CONOR", "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", "CARIA", "PIENA", +#endif }; +#if (USE_EXPANDED_DICT == 1) +static const char _expanded_words[][WORDLE_LENGTH + 1] = {}; +#endif + static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index b137512..1860984 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -60,6 +60,13 @@ #define WORDLE_MAX_ATTEMPTS 6 #define USE_DAILY_STREAK false +/* USE_EXPANDED_DICT + * 0 = don't use it at all (saves 2.8KB of ROM) + * 1 = Include the expanded dict in answers + * 2 = Only include it in the dict for guessing, but it's never an answer +*/ +#define USE_EXPANDED_DICT 2 + typedef enum { WORDLE_LETTER_WRONG = 0, WORDLE_LETTER_WRONG_LOC, From 1675af64496535b76d5c4e7e46a3e1453159e353 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 09:49:48 -0400 Subject: [PATCH 107/220] bug fix on max score --- .../watch_faces/complication/wordle_face.c | 165 +++++++++--------- .../watch_faces/complication/wordle_face.h | 3 +- utils/wordle_face/wordle_list.py | 10 +- 3 files changed, 84 insertions(+), 94 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 2b991e3..c6f28bb 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -33,45 +33,45 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; // From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff -// Number of words found: 283 +// Number of words found: 282 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "PROSE", "SLOPE", "CLAPS", "CAIRN", "PLANE", "SPACE", "ARISE", "NEARS", "OPERA", - "EPICS", "LAIRS", "AISLE", "APRON", "SCRIP", "CARES", "PERIL", "PILES", "CLEAN", - "CLANS", "CANOE", "COALS", "RELIC", "CRANE", "SLICE", "RESIN", "NAILS", "LIARS", - "RISEN", "COILS", "RINSE", "PRIES", "PLEAS", "CAPON", "PARSE", "ROLES", "SPICE", - "CORAL", "LEARN", "SNIPE", "CAPER", "SIREN", "CORES", "CRAPE", "SPOIL", "EARNS", - "LANES", "PEALS", "RAINS", "ACRES", "SINCE", "LACES", "ROPES", "CLEAR", "PANIC", - "SCALE", "SANER", "CLOSE", "LINER", "CORNS", "SPINE", "SCARE", "ACORN", "PLANS", - "POSER", "SCORN", "AROSE", "LINES", "LIONS", "OCEAN", "SNAIL", "OSIER", "SCOPE", - "OPINE", "REINS", "COINS", "LOSER", "PANES", "RAISE", "RAILS", "ALIEN", "PLAIN", - "SNARE", "LOANS", "IRONS", "COPRA", "PLIES", "COPSE", "PEARS", "PALER", "SLAIN", - "LAPSE", "CONES", "ARSON", "POISE", "OPENS", "SPORE", "CAROL", "PACES", "LEAPS", - "ALONE", "ASPEN", "SNORE", "CRIES", "OPALS", "PENAL", "PAINS", "ENROL", "POLAR", - "SPEAR", "SCION", "POLES", "SCORE", "LEANS", "PEONS", "ROSIN", "REAPS", "SOLAR", - "EARLS", "PENIS", "PRICE", "REALS", "SCALP", "CLASP", "NOISE", "PANEL", "NICER", - "CRISP", "SPARE", "PEARL", "CORPS", "SALON", "RACES", "LOINS", "RIPEN", "PLACE", - "CRONE", "CANES", "LANCE", "LIENS", "ALOES", "PIANO", "PRONE", "PIERS", "SNARL", - "AEONS", "PINES", "SPIRE", "PAILS", "CAPES", "CLIPS", "PALES", "CROPS", "PAIRS", - "SCRAP", "PORES", "SALES", "COOLS", "SLOPS", "APACE", "CRIER", "ROLLS", "PIPES", - "SLIPS", "EASES", "PEACE", "CELLS", "POLLS", "POPPA", "SPREE", "RILLS", "SPILL", - "RIPER", "ONION", "ISLES", "CROSS", "PAPAS", "NOSES", "PAPAL", "SLEEP", "EERIE", - "SEALS", "PAPER", "COLOR", "SORES", "SPELL", "SNAPS", "SEERS", "SPARS", "PIPER", - "POSES", "LOOPS", "APPAL", "LOSES", "LINEN", "ROARS", "RARER", "NIECE", "LOCAL", - "NONCE", "CREEP", "SCARS", "SPOOR", "PEASE", "SIRES", "PRESS", "CLASS", "PAEAN", - "SPOOL", "LOOSE", "CRASS", "LILAC", "COLIC", "RACER", "CREPE", "NASAL", "SOLOS", - "ROSES", "COLON", "REPEL", "ASSES", "INANE", "SCENE", "SLAPS", "PEEPS", "LAPEL", - "PIECE", "LEPER", "REELS", "INNER", "PROPS", "SILLS", "LEASE", "SOLES", "CIRCA", - "CRESS", "SCANS", "SPOON", "REARS", "CACAO", "ERASE", "CANON", "SOARS", "SLOOP", - "AREAS", "SPINS", "OASIS", "OASES", "POPES", "ELOPE", "CEASE", "CINCO", "APPLE", - "NOOSE", "PEERS", "SENSE", "POOLS", "RISER", "PENCE", "POSSE", "ALIAS", "PESOS", - "SCOOP", "EASEL", "LOONS", "CONIC", "SPANS", "SPIES", "PRIOR", "SALSA", "SELLS", - "PILLS", "RISES", "RARES", "SNEER", "SOILS", "ARENA", "CASES", "CANAL", "SAILS", - "LASSO", "COCOA", "ERROR", "CALLS", + "CRISP", "SALSA", "PRESS", "LIONS", "SPIRE", "CAPES", "ROLLS", "LOOSE", "ALOES", + "COPSE", "ENROL", "SLOPE", "CAPER", "SCORE", "SLAPS", "PLEAS", "CANOE", "REAPS", + "PEASE", "SLEEP", "LEAPS", "CORAL", "PILLS", "LOCAL", "ARENA", "PROSE", "SALES", + "OPENS", "REPEL", "REALS", "COLIC", "APRON", "LOINS", "COINS", "LASSO", "SIREN", + "SCARS", "RISER", "CRIES", "CRESS", "POSES", "NEARS", "CAIRN", "PARSE", "SCENE", + "SCOOP", "SPINS", "CORNS", "NOSES", "CLEAR", "LANES", "LOSES", "PIERS", "SLAIN", + "ROPES", "ALIEN", "LINER", "PRIES", "PROPS", "CRANE", "SCARE", "PEONS", "POLLS", + "LINEN", "SLIPS", "CAROL", "PEEPS", "SPANS", "ARISE", "POLES", "SCRAP", "OASIS", + "PAPAS", "PAINS", "SPOOL", "RELIC", "ALONE", "SLOPS", "PIANO", "PERIL", "SPICE", + "SPIES", "SPORE", "CLEAN", "SOLOS", "CREEP", "NONCE", "POISE", "COALS", "LEASE", + "SEALS", "COILS", "PILES", "RARES", "APPAL", "OASES", "RINSE", "POPES", "CONIC", + "SLICE", "SPACE", "ACRES", "ACORN", "ROLES", "CASES", "RESIN", "CREPE", "SOILS", + "PANEL", "SNEER", "INANE", "SCANS", "APACE", "EASEL", "CORES", "SOLAR", "PALES", + "SCOPE", "SCRIP", "LOANS", "ASSES", "EARNS", "CANON", "PLAIN", "POPPA", "SPOIL", + "APPLE", "ROSIN", "PANIC", "RISES", "AISLE", "CAPON", "COLON", "CLANS", "IRONS", + "RISEN", "PAILS", "LEANS", "PRICE", "AREAS", "SPARE", "LEARN", "PANES", "PRIOR", + "CRAPE", "LINES", "LEPER", "SNAPS", "POOLS", "SIRES", "SNARE", "COCOA", "PALER", + "CLOSE", "CRIER", "SANER", "PEARL", "CIRCA", "PAEAN", "RAISE", "SELLS", "OPINE", + "CEASE", "CANES", "ONION", "REELS", "RIPER", "SPARS", "RIPEN", "EPICS", "PLIES", + "CELLS", "SCALP", "ELOPE", "CANAL", "ROARS", "EASES", "OPERA", "SLOOP", "RARER", + "LIENS", "CROPS", "LACES", "LAIRS", "AEONS", "SOLES", "SNIPE", "PIECE", "NOOSE", + "NICER", "PENAL", "SILLS", "LANCE", "LOOPS", "SNORE", "PACES", "PLACE", "SPILL", + "PAIRS", "ARSON", "LAPSE", "CLASS", "EERIE", "PEERS", "PLANS", "LOONS", "SPOON", + "POSER", "SEERS", "REARS", "ROSES", "INNER", "NASAL", "OCEAN", "OPALS", "ALIAS", + "RACES", "ERASE", "SPINE", "SAILS", "CACAO", "CLASP", "REINS", "PAPER", "PIPER", + "EARLS", "PINES", "POLAR", "SNARL", "SCALE", "SPEAR", "SCION", "CRONE", "PRONE", + "SCORN", "RACER", "LILAC", "AROSE", "ISLES", "LAPEL", "PLANE", "PEARS", "POSSE", + "SORES", "PENCE", "CLAPS", "PESOS", "PENIS", "CALLS", "ASPEN", "COOLS", "CRASS", + "OSIER", "CARES", "PAPAL", "SOARS", "RILLS", "ERROR", "NAILS", "COPRA", "LOSER", + "SPELL", "CONES", "PORES", "SNAIL", "CROSS", "NIECE", "COLOR", "SALON", "SINCE", + "CORPS", "LIARS", "PIPES", "RAINS", "SPREE", "CLIPS", "NOISE", "PEALS", "SPOOR", + "SENSE", "PEACE", "RAILS", #if (USE_EXPANDED_DICT != 1) }; // These are words that'll never be used, but still need to be in the dictionary for guesses. // Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236 -// Number of words found: 469 +// Number of words found: 470 static const char _expanded_words[][WORDLE_LENGTH + 1] = { #endif #if (USE_EXPANDED_DICT != 0) @@ -86,48 +86,48 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { "NELLA", "PAOLO", "SOLON", "REPAS", "NANCE", "PAINE", "SAISI", "ELISE", "CESAR", "CANNA", "SALLE", "SINON", "SINAI", "LOIRE", "PENSA", "LEILA", "REISE", "ELLAS", "POORE", "EARLE", "CERCA", "LEISE", "POOLE", "AILES", "SANOA", "LEONE", "LILLE", - "PROIE", "CARNE", "SPIEL", "CERES", "ENSIN", "ROLLO", "ARRAS", "SEIEN", "PRIER", - "ANNAN", "CALLE", "LIISA", "SALIR", "LESSE", "LESEN", "LIIAN", "NEERE", "ARIEL", - "PIENI", "PIERO", "ERANO", "ELENA", "SILLE", "NEALE", "SEENE", "ROLLE", "NELLE", - "SOLLE", "ESSER", "PASAR", "PREIS", "ASIAN", "SENCE", "ANSON", "SERRA", "CONAN", - "SERAS", "SIENA", "SOPRA", "RENEE", "ALINE", "CORSE", "ASSAI", "INSEL", "ROSIE", - "SONIA", "APPEL", "CRISE", "CIRCE", "LINIE", "RENAN", "CAIRE", "COLLA", "SANOO", - "EENER", "ANCOR", "NEPAL", "REINO", "LAINE", "SOONE", "ALAIN", "LAPSI", "INCAS", - "INNES", "CARON", "ROSEN", "CASAS", "NOLAN", "SERRE", "PESAR", "SEARS", "LEPIC", - "LISLE", "LOSSE", "CINNA", "SERIE", "RIRES", "CORSO", "SOIRS", "CREER", "POCOS", - "SIENS", "ARLES", "CROCE", "IONIC", "PONER", "ESSEN", "SANON", "CESSA", "SERIA", - "ALPES", "NINON", "LILLA", "AINOA", "CORPO", "LESER", "ILLIS", "ROPER", "ANNEE", - "PAIRE", "PEPIN", "ORIEL", "CANNE", "AIRES", "ARCIS", "EASIE", "ANNOS", "COLLE", - "SELLE", "EILEN", "CAPRI", "ERICA", "ROCCO", "ARIAN", "CLEON", "ALLIE", "PONCE", - "COPIE", "INNAN", "NOCES", "NAPPE", "CORNE", "ESIIN", "ENCOR", "LORNA", "SACRE", - "PAPEL", "SAILE", "SAEPE", "CREON", "LLENO", "ELISA", "PASSO", "ASILE", "LORCS", - "ASIAA", "SANIN", "ONNEN", "SONNA", "AILIE", "ALIIS", "ECOLE", "CREES", "PRESO", - "CLARO", "EARES", "ROSSI", "COREA", "SANAN", "AESOP", "SAPOR", "EISEN", "ACASO", - "PARAS", "NANON", "LAPIS", "ARRAN", "CLLIA", "SACRA", "PRINS", "CENCI", "CLAES", - "SLAAP", "ROLLA", "COLES", "LORNE", "OLELO", "CASSE", "NILES", "PASOS", "ESSAI", - "ROSAS", "LLENA", "LEERE", "CLASE", "CALOR", "ROSSE", "ALLEE", "SOREL", "SANAA", - "SLONE", "OLSEN", "OOREN", "PARER", "PASSI", "POSSA", "PLAIE", "OPERE", "SCAPE", - "POLEN", "RIPON", "SCALA", "AILLE", "PALOS", "CLAPP", "ESCAP", "ELLEI", "IONIA", - "NICOL", "PAESE", "PERON", "ORSON", "INNEN", "AISNE", "RANCE", "SLAAN", "PAOLI", - "COLLO", "ANNAS", "ERROL", "CLERC", "SAINE", "RAINA", "PRESE", "PARIA", "PERLE", - "RECAL", "SINAE", "PESER", "OISIN", "PLENA", "CARLE", "PERES", "SACAR", "ALPEN", - "CORRE", "ACCES", "RILLA", "ANNAL", "PERSE", "SAALE", "PERRO", "AILSA", "POCAS", - "SOLEN", "PLASE", "SOLIS", "PAPPI", "COPIA", "ARIES", "ROCCA", "ALIOS", "ROCAS", - "PELOS", "NEPOS", "COLPA", "ISORA", "LECON", "SOANE", "SNELL", "ILLOS", "PILAR", - "ECLAC", "PRESA", "SILLA", "NIELS", "LIPPO", "CROLL", "PONEN", "POSEE", "PIPPA", - "ILLAN", "CENIS", "SANNA", "RASSI", "CERRO", "SCENA", "CASOS", "COLPO", "POSSO", - "POSEN", "EINAR", "ISLAS", "IPSIS", "SALEN", "ASIEN", "CREAN", "LENIN", "LOCIS", - "NENNE", "ILION", "NARES", "ONNEA", "PALAA", "PIPPO", "POLIS", "RICOS", "ELSON", - "SNOOP", "ANNIS", "PROPE", "ELLIE", "ERNIE", "PLIER", "SERES", "REINA", "LIPPE", - "OLINE", "PARIE", "ARLEE", "NIAIS", "LEONI", "RAINE", "LARES", "SEINS", "CARRE", - "POILS", "ALENE", "LINEA", "NEARE", "PENSO", "PRISA", "CAPEL", "PAREA", "PEECE", - "SALIO", "COELO", "SCIRE", "NELLO", "LIENE", "ORICE", "EPAIS", "PERCE", "ALLIS", - "PEPLE", "LARNE", "NEILL", "OLLEN", "CASCA", "LAPIN", "OLLIE", "SALIC", "LIANE", - "REESE", "ELSLI", "SPION", "RIENS", "LILAS", "PAPPA", "ERRER", "SPISE", "CELIE", - "OLSON", "IRREN", "ARIAS", "ARION", "PASEO", "CAERE", "PISAN", "CARRO", "PAROI", - "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", "CONOR", - "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", "CARIA", - "PIENA", + "PROIE", "CARNE", "SPIEL", "CERES", "ENSIN", "CINCO", "ROLLO", "ARRAS", "SEIEN", + "PRIER", "ANNAN", "CALLE", "LIISA", "SALIR", "LESSE", "LESEN", "LIIAN", "NEERE", + "ARIEL", "PIENI", "PIERO", "ERANO", "ELENA", "SILLE", "NEALE", "SEENE", "ROLLE", + "NELLE", "SOLLE", "ESSER", "PASAR", "PREIS", "ASIAN", "SENCE", "ANSON", "SERRA", + "CONAN", "SERAS", "SIENA", "SOPRA", "RENEE", "ALINE", "CORSE", "ASSAI", "INSEL", + "ROSIE", "SONIA", "APPEL", "CRISE", "CIRCE", "LINIE", "RENAN", "CAIRE", "COLLA", + "SANOO", "EENER", "ANCOR", "NEPAL", "REINO", "LAINE", "SOONE", "ALAIN", "LAPSI", + "INCAS", "INNES", "CARON", "ROSEN", "CASAS", "NOLAN", "SERRE", "PESAR", "SEARS", + "LEPIC", "LISLE", "LOSSE", "CINNA", "SERIE", "RIRES", "CORSO", "SOIRS", "CREER", + "POCOS", "SIENS", "ARLES", "CROCE", "IONIC", "PONER", "ESSEN", "SANON", "CESSA", + "SERIA", "ALPES", "NINON", "LILLA", "AINOA", "CORPO", "LESER", "ILLIS", "ROPER", + "ANNEE", "PAIRE", "PEPIN", "ORIEL", "CANNE", "AIRES", "ARCIS", "EASIE", "ANNOS", + "COLLE", "SELLE", "EILEN", "CAPRI", "ERICA", "ROCCO", "ARIAN", "CLEON", "ALLIE", + "PONCE", "COPIE", "INNAN", "NOCES", "NAPPE", "CORNE", "ESIIN", "ENCOR", "LORNA", + "SACRE", "PAPEL", "SAILE", "SAEPE", "CREON", "LLENO", "ELISA", "PASSO", "ASILE", + "LORCS", "ASIAA", "SANIN", "ONNEN", "SONNA", "AILIE", "ALIIS", "ECOLE", "CREES", + "PRESO", "CLARO", "EARES", "ROSSI", "COREA", "SANAN", "AESOP", "SAPOR", "EISEN", + "ACASO", "PARAS", "NANON", "LAPIS", "ARRAN", "CLLIA", "SACRA", "PRINS", "CENCI", + "CLAES", "SLAAP", "ROLLA", "COLES", "LORNE", "OLELO", "CASSE", "NILES", "PASOS", + "ESSAI", "ROSAS", "LLENA", "LEERE", "CLASE", "CALOR", "ROSSE", "ALLEE", "SOREL", + "SANAA", "SLONE", "OLSEN", "OOREN", "PARER", "PASSI", "POSSA", "PLAIE", "OPERE", + "SCAPE", "POLEN", "RIPON", "SCALA", "AILLE", "PALOS", "CLAPP", "ESCAP", "ELLEI", + "IONIA", "NICOL", "PAESE", "PERON", "ORSON", "INNEN", "AISNE", "RANCE", "SLAAN", + "PAOLI", "COLLO", "ANNAS", "ERROL", "CLERC", "SAINE", "RAINA", "PRESE", "PARIA", + "PERLE", "RECAL", "SINAE", "PESER", "OISIN", "PLENA", "CARLE", "PERES", "SACAR", + "ALPEN", "CORRE", "ACCES", "RILLA", "ANNAL", "PERSE", "SAALE", "PERRO", "AILSA", + "POCAS", "SOLEN", "PLASE", "SOLIS", "PAPPI", "COPIA", "ARIES", "ROCCA", "ALIOS", + "ROCAS", "PELOS", "NEPOS", "COLPA", "ISORA", "LECON", "SOANE", "SNELL", "ILLOS", + "PILAR", "ECLAC", "PRESA", "SILLA", "NIELS", "LIPPO", "CROLL", "PONEN", "POSEE", + "PIPPA", "ILLAN", "CENIS", "SANNA", "RASSI", "CERRO", "SCENA", "CASOS", "COLPO", + "POSSO", "POSEN", "EINAR", "ISLAS", "IPSIS", "SALEN", "ASIEN", "CREAN", "LENIN", + "LOCIS", "NENNE", "ILION", "NARES", "ONNEA", "PALAA", "PIPPO", "POLIS", "RICOS", + "ELSON", "SNOOP", "ANNIS", "PROPE", "ELLIE", "ERNIE", "PLIER", "SERES", "REINA", + "LIPPE", "OLINE", "PARIE", "ARLEE", "NIAIS", "LEONI", "RAINE", "LARES", "SEINS", + "CARRE", "POILS", "ALENE", "LINEA", "NEARE", "PENSO", "PRISA", "CAPEL", "PAREA", + "PEECE", "SALIO", "COELO", "SCIRE", "NELLO", "LIENE", "ORICE", "EPAIS", "PERCE", + "ALLIS", "PEPLE", "LARNE", "NEILL", "OLLEN", "CASCA", "LAPIN", "OLLIE", "SALIC", + "LIANE", "REESE", "ELSLI", "SPION", "RIENS", "LILAS", "PAPPA", "ERRER", "SPISE", + "CELIE", "OLSON", "IRREN", "ARIAS", "ARION", "PASEO", "CAERE", "PISAN", "CARRO", + "PAROI", "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", + "CONOR", "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", + "CARIA", "PIENA", #endif }; @@ -135,7 +135,6 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { static const char _expanded_words[][WORDLE_LENGTH + 1] = {}; #endif -static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); @@ -157,8 +156,8 @@ static uint8_t get_first_pos(WordleLetterResult *word_elements_result) { } static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { - for (size_t pos = curr_pos+1; pos < WORDLE_LENGTH; pos++) { - if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + for (size_t pos = curr_pos; pos < WORDLE_LENGTH;) { + if (word_elements_result[++pos] != WORDLE_LETTER_CORRECT) return pos; } return WORDLE_LENGTH; @@ -166,8 +165,8 @@ static uint8_t get_next_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_result) { if (curr_pos == 0) return 0; - for (int8_t pos = curr_pos-1; pos >= 0; pos--) { - if (word_elements_result[pos] != WORDLE_LETTER_CORRECT) + for (int8_t pos = curr_pos; pos >= 0;) { + if (word_elements_result[--pos] != WORDLE_LETTER_CORRECT) return pos; } return curr_pos; @@ -262,7 +261,7 @@ static bool check_word(wordle_state_t *state) { } static void display_attempt(uint8_t attempt) { - char buf[2]; + char buf[3]; sprintf(buf, "%d", attempt+1); watch_display_string(buf, 3); } @@ -455,7 +454,7 @@ static void get_result(wordle_state_t *state) { #endif return; } - if (state->attempt++ > WORDLE_MAX_ATTEMPTS) { + if (++state->attempt >= WORDLE_MAX_ATTEMPTS) { state->playing = false; state->curr_screen = SCREEN_LOSE; state->streak = 0; diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 1860984..aee40f4 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -94,10 +94,9 @@ typedef struct { uint8_t word_elements[WORDLE_LENGTH]; WordleLetterResult word_elements_result[WORDLE_LENGTH]; uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; - uint8_t attempt : 3; + uint8_t attempt : 4; uint8_t position : 3; bool playing : 1; - bool unused : 1; uint16_t curr_answer; uint8_t streak; WordleScreen curr_screen; diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index d508a8b..87f2f82 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -260,7 +260,7 @@ legal_list = [ "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", "WOUND", "WRACK", "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", "WRITE", "WRITS", "WRONG", "WROTE", "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", - "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", "CINCO", + "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", ] expanded_list = [ @@ -982,11 +982,6 @@ def list_of_valid_words(letters, words=legal_list): return legal_words -def rearrange_words_by_uniqueness(words): - unique = [word for word in words if len(word) == len(set(word))] - duplicates = [word for word in words if len(word) != len(set(word))] - return unique + duplicates, len(unique) - def capitalize_all_and_remove_duplicates(arr): for i,word in enumerate(arr): arr[i] = word.upper() @@ -1006,7 +1001,6 @@ def print_valid_words(letters=alphabet): legal_words = capitalize_all_and_remove_duplicates(legal_words) random.shuffle(legal_words) # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter - legal_words, num_uniq = rearrange_words_by_uniqueness(legal_words) print("static const char _valid_letters[] = {", end='') for letter in letters[:-1]: @@ -1041,8 +1035,6 @@ def print_valid_words(letters=alphabet): i+=1 print('') print("};") - - print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _legal_words array begins with this many words where each letter is different.") def get_sec_val_and_units(seconds): From 1e76022146fa7f8e40e308c1e4395c1b064d2a9a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 11:36:35 -0400 Subject: [PATCH 108/220] Gave the wordle face the ability to give random guesses on the first word --- movement/movement_config.h | 1 + .../watch_faces/complication/wordle_face.c | 98 ++++++++++++------- .../watch_faces/complication/wordle_face.h | 9 +- utils/wordle_face/wordle_list.py | 8 ++ 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/movement/movement_config.h b/movement/movement_config.h index abceacf..f8a1958 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -29,6 +29,7 @@ const watch_face_t watch_faces[] = { simple_clock_face, + wordle_face, world_clock_face, sunrise_sunset_face, moon_phase_face, diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index c6f28bb..32c396b 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -35,38 +35,38 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R // From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff // Number of words found: 282 static const char _legal_words[][WORDLE_LENGTH + 1] = { - "CRISP", "SALSA", "PRESS", "LIONS", "SPIRE", "CAPES", "ROLLS", "LOOSE", "ALOES", - "COPSE", "ENROL", "SLOPE", "CAPER", "SCORE", "SLAPS", "PLEAS", "CANOE", "REAPS", - "PEASE", "SLEEP", "LEAPS", "CORAL", "PILLS", "LOCAL", "ARENA", "PROSE", "SALES", - "OPENS", "REPEL", "REALS", "COLIC", "APRON", "LOINS", "COINS", "LASSO", "SIREN", - "SCARS", "RISER", "CRIES", "CRESS", "POSES", "NEARS", "CAIRN", "PARSE", "SCENE", - "SCOOP", "SPINS", "CORNS", "NOSES", "CLEAR", "LANES", "LOSES", "PIERS", "SLAIN", - "ROPES", "ALIEN", "LINER", "PRIES", "PROPS", "CRANE", "SCARE", "PEONS", "POLLS", - "LINEN", "SLIPS", "CAROL", "PEEPS", "SPANS", "ARISE", "POLES", "SCRAP", "OASIS", - "PAPAS", "PAINS", "SPOOL", "RELIC", "ALONE", "SLOPS", "PIANO", "PERIL", "SPICE", - "SPIES", "SPORE", "CLEAN", "SOLOS", "CREEP", "NONCE", "POISE", "COALS", "LEASE", - "SEALS", "COILS", "PILES", "RARES", "APPAL", "OASES", "RINSE", "POPES", "CONIC", - "SLICE", "SPACE", "ACRES", "ACORN", "ROLES", "CASES", "RESIN", "CREPE", "SOILS", - "PANEL", "SNEER", "INANE", "SCANS", "APACE", "EASEL", "CORES", "SOLAR", "PALES", - "SCOPE", "SCRIP", "LOANS", "ASSES", "EARNS", "CANON", "PLAIN", "POPPA", "SPOIL", - "APPLE", "ROSIN", "PANIC", "RISES", "AISLE", "CAPON", "COLON", "CLANS", "IRONS", - "RISEN", "PAILS", "LEANS", "PRICE", "AREAS", "SPARE", "LEARN", "PANES", "PRIOR", - "CRAPE", "LINES", "LEPER", "SNAPS", "POOLS", "SIRES", "SNARE", "COCOA", "PALER", - "CLOSE", "CRIER", "SANER", "PEARL", "CIRCA", "PAEAN", "RAISE", "SELLS", "OPINE", - "CEASE", "CANES", "ONION", "REELS", "RIPER", "SPARS", "RIPEN", "EPICS", "PLIES", - "CELLS", "SCALP", "ELOPE", "CANAL", "ROARS", "EASES", "OPERA", "SLOOP", "RARER", - "LIENS", "CROPS", "LACES", "LAIRS", "AEONS", "SOLES", "SNIPE", "PIECE", "NOOSE", - "NICER", "PENAL", "SILLS", "LANCE", "LOOPS", "SNORE", "PACES", "PLACE", "SPILL", - "PAIRS", "ARSON", "LAPSE", "CLASS", "EERIE", "PEERS", "PLANS", "LOONS", "SPOON", - "POSER", "SEERS", "REARS", "ROSES", "INNER", "NASAL", "OCEAN", "OPALS", "ALIAS", - "RACES", "ERASE", "SPINE", "SAILS", "CACAO", "CLASP", "REINS", "PAPER", "PIPER", - "EARLS", "PINES", "POLAR", "SNARL", "SCALE", "SPEAR", "SCION", "CRONE", "PRONE", - "SCORN", "RACER", "LILAC", "AROSE", "ISLES", "LAPEL", "PLANE", "PEARS", "POSSE", - "SORES", "PENCE", "CLAPS", "PESOS", "PENIS", "CALLS", "ASPEN", "COOLS", "CRASS", - "OSIER", "CARES", "PAPAL", "SOARS", "RILLS", "ERROR", "NAILS", "COPRA", "LOSER", - "SPELL", "CONES", "PORES", "SNAIL", "CROSS", "NIECE", "COLOR", "SALON", "SINCE", - "CORPS", "LIARS", "PIPES", "RAINS", "SPREE", "CLIPS", "NOISE", "PEALS", "SPOOR", - "SENSE", "PEACE", "RAILS", + "ROPES", "RESIN", "PACES", "RIPEN", "ALIEN", "SPINE", "ROSIN", "PIERS", "CAPER", + "SNORE", "SANER", "RAILS", "SCORN", "PENIS", "NEARS", "ENROL", "PROSE", "CANES", + "POSER", "ACORN", "PAILS", "SLAIN", "REALS", "CLAPS", "PLIES", "PALES", "LIENS", + "PLAIN", "SLOPE", "REAPS", "CRAPE", "ASPEN", "COINS", "ARISE", "ALOES", "PANES", + "SCION", "SNARL", "COPRA", "PALER", "CLIPS", "PANIC", "PARSE", "PENAL", "SPARE", + "LIONS", "LINES", "SNARE", "PEONS", "CLEAN", "SPACE", "SCALE", "COILS", "SCRAP", + "OPINE", "NICER", "LOANS", "RACES", "RELIC", "NOISE", "PIANO", "CRANE", "SNAIL", + "SCORE", "CLEAR", "CROPS", "CORES", "COPSE", "PINES", "PANEL", "RINSE", "LOINS", + "PRONE", "ALONE", "RAISE", "OSIER", "LEARN", "SPICE", "SPOIL", "EARLS", "NAILS", + "PLANE", "CARES", "CRIES", "CORNS", "CORPS", "CLASP", "LACES", "ARSON", "LANES", + "OPENS", "SALON", "SINCE", "PLANS", "SCARE", "SPORE", "OCEAN", "AEONS", "PRICE", + "IRONS", "SCALP", "EPICS", "LIARS", "SPIRE", "LINER", "PILES", "SLICE", "LEANS", + "RAINS", "PLEAS", "SOLAR", "CAPES", "APRON", "RISEN", "POISE", "CONES", "PEARS", + "PERIL", "COALS", "OPALS", "ROLES", "CLOSE", "CAPON", "POLES", "EARNS", "CRISP", + "AROSE", "SCOPE", "AISLE", "CLANS", "CORAL", "SCRIP", "PAINS", "OPERA", "PAIRS", + "PEARL", "SIREN", "ACRES", "CAROL", "LAIRS", "PORES", "PRIES", "CRONE", "CANOE", + "LAPSE", "LEAPS", "SNIPE", "REINS", "PEALS", "SPEAR", "LOSER", "POLAR", "LANCE", + "CAIRN", "PLACE", "SILLS", "CELLS", "EERIE", "PIECE", "ISLES", "NOOSE", "SNEER", + "SOLOS", "ELOPE", "INNER", "SLOOP", "SOARS", "SPREE", "SPANS", "PAPAL", "RIPER", + "COLON", "SCANS", "RARES", "PILLS", "CANON", "POLLS", "POPPA", "ERROR", "REARS", + "PESOS", "CRESS", "PENCE", "SPOOL", "COLOR", "NONCE", "CLASS", "SELLS", "NASAL", + "ERASE", "RILLS", "LAPEL", "COOLS", "EASEL", "COLIC", "SPELL", "SPOOR", "LASSO", + "APPAL", "PEACE", "SALSA", "SCENE", "NIECE", "CONIC", "APPLE", "SNAPS", "PEERS", + "ROARS", "SPARS", "SAILS", "SLOPS", "APACE", "POSES", "SENSE", "PEEPS", "CASES", + "CANAL", "CIRCA", "SLAPS", "SCOOP", "ROLLS", "PIPES", "SCARS", "LOOSE", "ROSES", + "LILAC", "OASES", "SOLES", "PAEAN", "PAPAS", "CRASS", "PROPS", "SEALS", "CACAO", + "LINEN", "SORES", "EASES", "POPES", "OASIS", "LOSES", "NOSES", "SIRES", "SPILL", + "CREPE", "ALIAS", "CROSS", "ARENA", "SPINS", "REPEL", "SPIES", "PRIOR", "POOLS", + "PRESS", "RISER", "AREAS", "SPOON", "SALES", "CREEP", "CEASE", "LOOPS", "ASSES", + "CALLS", "CRIER", "COCOA", "SEERS", "LOONS", "SLIPS", "PAPER", "REELS", "RISES", + "POSSE", "RARER", "SOILS", "PIPER", "INANE", "LOCAL", "PEASE", "ONION", "SLEEP", + "LEASE", "RACER", "LEPER", #if (USE_EXPANDED_DICT != 1) }; // These are words that'll never be used, but still need to be in the dictionary for guesses. @@ -127,7 +127,7 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { "CELIE", "OLSON", "IRREN", "ARIAS", "ARION", "PASEO", "CAERE", "PISAN", "CARRO", "PAROI", "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", "CONOR", "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", - "CARIA", "PIENA", + "CARIA", "PIENA", #endif }; @@ -135,6 +135,9 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { static const char _expanded_words[][WORDLE_LENGTH + 1] = {}; #endif +static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. + + static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); @@ -286,6 +289,7 @@ static void reset_board(wordle_state_t *state) { state->guessed_words[i] = _num_words + _num_expanded_words; } state->curr_answer = get_random(_num_words); + state->using_random_guess = false; state->attempt = 0; watch_clear_colon(); watch_display_string(" ", 4); @@ -464,6 +468,23 @@ static void get_result(wordle_state_t *state) { return; } +static void insert_random_guess(wordle_state_t *state) { + uint16_t random_guess; + do { // Don't allow the guess to be the same as the answer + random_guess = get_random(_num_unique_words); + } while (random_guess == state->curr_answer); + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + for (size_t j = 0; j < _num_valid_letters; j++) + { + if (_legal_words[random_guess][i] == _valid_letters[j]) + state->word_elements[i] = j; + } + } + state->position = WORDLE_LENGTH - 1; + display_all_letters(state); + state->using_random_guess = true; +} + void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; @@ -484,6 +505,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; if (state->curr_day != now) state->playing = false; #endif + state->using_random_guess = false; movement_request_tick_frequency(2); display_title(state); } @@ -530,9 +552,17 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; state->playing = true; + + if (watch_get_pin_level(BTN_LIGHT) && + (state->using_random_guess || (state->attempt == 0 && state->position == 0))) { + insert_random_guess(state); + break; + } state->position = get_next_pos(state->position, state->word_elements_result); - if (state->position >= WORDLE_LENGTH) + if (state->position >= WORDLE_LENGTH) { get_result(state); + state->using_random_guess = false; + } break; case EVENT_ALARM_LONG_PRESS: if (state->curr_screen != SCREEN_PLAYING) break; diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index aee40f4..64a1665 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -49,7 +49,11 @@ * Else: None * * Alarm Press - * If Playing: Next position + * If Playing: If Light btn held and + * (on first letter or already used a random guess) + * and first attempt: Use a random 5 letter word with all letters that are different. + * Else: Next position + * Next position * Else: Next screen * Alarm Hold * If Playing: Previous position @@ -97,7 +101,8 @@ typedef struct { uint8_t attempt : 4; uint8_t position : 3; bool playing : 1; - uint16_t curr_answer; + uint16_t curr_answer : 15; + bool using_random_guess : 1; uint8_t streak; WordleScreen curr_screen; #if USE_DAILY_STREAK diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index 87f2f82..38e9510 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -982,6 +982,11 @@ def list_of_valid_words(letters, words=legal_list): return legal_words +def rearrange_words_by_uniqueness(words): + unique = [word for word in words if len(word) == len(set(word))] + duplicates = [word for word in words if len(word) != len(set(word))] + return unique + duplicates, len(unique) + def capitalize_all_and_remove_duplicates(arr): for i,word in enumerate(arr): arr[i] = word.upper() @@ -1001,6 +1006,7 @@ def print_valid_words(letters=alphabet): legal_words = capitalize_all_and_remove_duplicates(legal_words) random.shuffle(legal_words) # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter + legal_words, num_uniq = rearrange_words_by_uniqueness(legal_words) print("static const char _valid_letters[] = {", end='') for letter in letters[:-1]: @@ -1035,6 +1041,8 @@ def print_valid_words(letters=alphabet): i+=1 print('') print("};") + + print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _legal_words array begins with this many words where each letter is different.") def get_sec_val_and_units(seconds): From 4bb4bc85faca225e11910c2c4b7cadfb7e1d164b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 11:50:07 -0400 Subject: [PATCH 109/220] USE_RANDOM_GUESS variable added --- movement/watch_faces/complication/wordle_face.c | 10 ++++++---- movement/watch_faces/complication/wordle_face.h | 9 ++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 32c396b..be466cd 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -135,8 +135,6 @@ static const char _expanded_words[][WORDLE_LENGTH + 1] = { static const char _expanded_words[][WORDLE_LENGTH + 1] = {}; #endif -static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. - static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); @@ -468,10 +466,12 @@ static void get_result(wordle_state_t *state) { return; } +#if (USE_RANDOM_GUESS != 0) +static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer - random_guess = get_random(_num_unique_words); + random_guess = get_random(USE_RANDOM_GUESS == 2 ? _num_unique_words : _num_words); } while (random_guess == state->curr_answer); for (size_t i = 0; i < WORDLE_LENGTH; i++) { for (size_t j = 0; j < _num_valid_letters; j++) @@ -484,6 +484,7 @@ static void insert_random_guess(wordle_state_t *state) { display_all_letters(state); state->using_random_guess = true; } +#endif void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; @@ -552,12 +553,13 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; state->playing = true; - +#if (USE_RANDOM_GUESS != 0) if (watch_get_pin_level(BTN_LIGHT) && (state->using_random_guess || (state->attempt == 0 && state->position == 0))) { insert_random_guess(state); break; } +#endif state->position = get_next_pos(state->position, state->word_elements_result); if (state->position >= WORDLE_LENGTH) { get_result(state); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 64a1665..97c3261 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -49,7 +49,7 @@ * Else: None * * Alarm Press - * If Playing: If Light btn held and + * If Playing: If USE_RANDOM_GUESS is set and Light btn held and * (on first letter or already used a random guess) * and first attempt: Use a random 5 letter word with all letters that are different. * Else: Next position @@ -64,6 +64,13 @@ #define WORDLE_MAX_ATTEMPTS 6 #define USE_DAILY_STREAK false +/* USE_RANDOM_GUESS + * 0 = Don't allow quickly choosing a random quess + * 1 = Allow using a random guess of any value that can be an answer + * 2 = Allow using a random guess of any value that can be an answer where all of its letters are unique +*/ +#define USE_RANDOM_GUESS 2 + /* USE_EXPANDED_DICT * 0 = don't use it at all (saves 2.8KB of ROM) * 1 = Include the expanded dict in answers From 2a10402d19166b81e0294fd96144bb8daf06b896 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 11:52:42 -0400 Subject: [PATCH 110/220] Removed wordle from movmeent face after testing --- movement/movement_config.h | 1 - 1 file changed, 1 deletion(-) diff --git a/movement/movement_config.h b/movement/movement_config.h index f8a1958..abceacf 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -29,7 +29,6 @@ const watch_face_t watch_faces[] = { simple_clock_face, - wordle_face, world_clock_face, sunrise_sunset_face, moon_phase_face, From a0111fbe24fdf0085ca9ee89d9f56e1d1603af63 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 12:35:48 -0400 Subject: [PATCH 111/220] Swapped the Nice and Job so the Nice text is more likely to appear first. --- movement/watch_faces/complication/wordle_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index be466cd..2838b7f 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -357,7 +357,7 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { 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 ? "JOb " : "NICE"); watch_display_string(buf, 0); } From 67c1089fb2c2b7133f3e93803b4f911c8ba87379 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 13:22:03 -0400 Subject: [PATCH 112/220] Don't delete the submitted characters if already guessed or not in dict --- movement/watch_faces/complication/wordle_face.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 2838b7f..94b28e5 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -415,7 +415,9 @@ static bool act_on_btn(wordle_state_t *state) { return true; case SCREEN_NO_DICT: case SCREEN_ALREADY_GUESSED: - show_start_of_attempt(state); + state->position= state->position = get_first_pos(state->word_elements_result); + display_all_letters(state); + state->curr_screen = SCREEN_PLAYING; return true; #if USE_DAILY_STREAK case SCREEN_WAIT: From 4257b71562084fbe0e81ccd915a9557ef5e45ba4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 17 Aug 2024 19:36:19 -0400 Subject: [PATCH 113/220] combined two areas of code into one function --- .../watch_faces/complication/wordle_face.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 94b28e5..6802a22 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -267,10 +267,12 @@ static void display_attempt(uint8_t attempt) { watch_display_string(buf, 3); } -static void show_start_of_attempt(wordle_state_t *state) { - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) - state->word_elements[i] = _num_valid_letters; +static void show_start_of_attempt(wordle_state_t *state, bool reset_all_letters) { + if (reset_all_letters) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) + state->word_elements[i] = _num_valid_letters; + } } display_attempt(state->attempt); display_all_letters(state); @@ -291,7 +293,7 @@ static void reset_board(wordle_state_t *state) { state->attempt = 0; watch_clear_colon(); watch_display_string(" ", 4); - show_start_of_attempt(state); + show_start_of_attempt(state, true); watch_display_string("-", 5); #if __EMSCRIPTEN__ printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); @@ -389,7 +391,7 @@ static bool act_on_btn(wordle_state_t *state) { switch (state->curr_screen) { case SCREEN_RESULT: - show_start_of_attempt(state); + show_start_of_attempt(state, true); return true; case SCREEN_TITLE: #if USE_DAILY_STREAK @@ -399,7 +401,7 @@ static bool act_on_btn(wordle_state_t *state) { } #endif if (state->playing) - show_start_of_attempt(state); + show_start_of_attempt(state, true); else display_streak(state); return true; @@ -415,9 +417,7 @@ static bool act_on_btn(wordle_state_t *state) { return true; case SCREEN_NO_DICT: case SCREEN_ALREADY_GUESSED: - state->position= state->position = get_first_pos(state->word_elements_result); - display_all_letters(state); - state->curr_screen = SCREEN_PLAYING; + show_start_of_attempt(state, false); return true; #if USE_DAILY_STREAK case SCREEN_WAIT: From 935ede9fda862b94ba5ec300232bca8d1f92ad63 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 10:35:36 -0400 Subject: [PATCH 114/220] Fixed leaving the screen and coming back --- .../watch_faces/complication/wordle_face.c | 35 ++++++++++++------- .../watch_faces/complication/wordle_face.h | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 6802a22..361faf5 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -267,19 +267,19 @@ static void display_attempt(uint8_t attempt) { watch_display_string(buf, 3); } -static void show_start_of_attempt(wordle_state_t *state, bool reset_all_letters) { - if (reset_all_letters) { - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) - state->word_elements[i] = _num_valid_letters; - } - } +static void display_playing(wordle_state_t *state) { display_attempt(state->attempt); display_all_letters(state); - state->position = get_first_pos(state->word_elements_result); state->curr_screen = SCREEN_PLAYING; } +static void reset_incorrect_elements(wordle_state_t *state) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) + state->word_elements[i] = _num_valid_letters; + } +} + static void reset_board(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { state->word_elements[i] = _num_valid_letters; @@ -293,7 +293,9 @@ static void reset_board(wordle_state_t *state) { state->attempt = 0; watch_clear_colon(); watch_display_string(" ", 4); - show_start_of_attempt(state, true); + reset_incorrect_elements(state); + state->position = get_first_pos(state->word_elements_result); + display_playing(state); watch_display_string("-", 5); #if __EMSCRIPTEN__ printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); @@ -391,7 +393,9 @@ static bool act_on_btn(wordle_state_t *state) { switch (state->curr_screen) { case SCREEN_RESULT: - show_start_of_attempt(state, true); + reset_incorrect_elements(state); + state->position = get_first_pos(state->word_elements_result); + display_playing(state); return true; case SCREEN_TITLE: #if USE_DAILY_STREAK @@ -401,7 +405,7 @@ static bool act_on_btn(wordle_state_t *state) { } #endif if (state->playing) - show_start_of_attempt(state, true); + display_playing(state); else display_streak(state); return true; @@ -417,7 +421,8 @@ static bool act_on_btn(wordle_state_t *state) { return true; case SCREEN_NO_DICT: case SCREEN_ALREADY_GUESSED: - show_start_of_attempt(state, false); + state->position = get_first_pos(state->word_elements_result); + display_playing(state); return true; #if USE_DAILY_STREAK case SCREEN_WAIT: @@ -509,6 +514,10 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { if (state->curr_day != now) state->playing = false; #endif state->using_random_guess = false; + if (state->playing && state->curr_screen >= SCREEN_WIN) { + reset_incorrect_elements(state); + state->position = get_first_pos(state->word_elements_result); + } movement_request_tick_frequency(2); display_title(state); } @@ -581,7 +590,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: - if (state->curr_screen == SCREEN_TITLE) + if (state->curr_screen != SCREEN_TITLE) display_title(state); break; default: diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 97c3261..8ad9155 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -87,7 +87,6 @@ typedef enum { typedef enum { SCREEN_PLAYING = 0, - SCREEN_RESULT, SCREEN_TITLE, SCREEN_STREAK, #if USE_DAILY_STREAK @@ -95,6 +94,7 @@ typedef enum { #endif SCREEN_WIN, SCREEN_LOSE, + SCREEN_RESULT, SCREEN_NO_DICT, SCREEN_ALREADY_GUESSED, SCREEN_COUNT From 57ca74b25338c10a8d6cb58410b69d6bf79ba010 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 10:46:33 -0400 Subject: [PATCH 115/220] Added SONIC and LASER to the allowed words --- movement/watch_faces/complication/wordle_face.c | 8 ++++---- utils/wordle_face/wordle_list.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 361faf5..58ad55a 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -33,7 +33,7 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; // From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff -// Number of words found: 282 +// Number of words found: 284 static const char _legal_words[][WORDLE_LENGTH + 1] = { "ROPES", "RESIN", "PACES", "RIPEN", "ALIEN", "SPINE", "ROSIN", "PIERS", "CAPER", "SNORE", "SANER", "RAILS", "SCORN", "PENIS", "NEARS", "ENROL", "PROSE", "CANES", @@ -52,7 +52,7 @@ static const char _legal_words[][WORDLE_LENGTH + 1] = { "AROSE", "SCOPE", "AISLE", "CLANS", "CORAL", "SCRIP", "PAINS", "OPERA", "PAIRS", "PEARL", "SIREN", "ACRES", "CAROL", "LAIRS", "PORES", "PRIES", "CRONE", "CANOE", "LAPSE", "LEAPS", "SNIPE", "REINS", "PEALS", "SPEAR", "LOSER", "POLAR", "LANCE", - "CAIRN", "PLACE", "SILLS", "CELLS", "EERIE", "PIECE", "ISLES", "NOOSE", "SNEER", + "CAIRN", "PLACE", "LASER", "SONIC", "EERIE", "PIECE", "ISLES", "NOOSE", "SNEER", "SOLOS", "ELOPE", "INNER", "SLOOP", "SOARS", "SPREE", "SPANS", "PAPAL", "RIPER", "COLON", "SCANS", "RARES", "PILLS", "CANON", "POLLS", "POPPA", "ERROR", "REARS", "PESOS", "CRESS", "PENCE", "SPOOL", "COLOR", "NONCE", "CLASS", "SELLS", "NASAL", @@ -66,7 +66,7 @@ static const char _legal_words[][WORDLE_LENGTH + 1] = { "PRESS", "RISER", "AREAS", "SPOON", "SALES", "CREEP", "CEASE", "LOOPS", "ASSES", "CALLS", "CRIER", "COCOA", "SEERS", "LOONS", "SLIPS", "PAPER", "REELS", "RISES", "POSSE", "RARER", "SOILS", "PIPER", "INANE", "LOCAL", "PEASE", "ONION", "SLEEP", - "LEASE", "RACER", "LEPER", + "LEASE", "RACER", "LEPER", "SILLS", "CELLS", #if (USE_EXPANDED_DICT != 1) }; // These are words that'll never be used, but still need to be in the dictionary for guesses. @@ -474,7 +474,7 @@ static void get_result(wordle_state_t *state) { } #if (USE_RANDOM_GUESS != 0) -static const uint16_t _num_unique_words = 155; // The _legal_words array begins with this many words where each letter is different. +static const uint16_t _num_unique_words = 157; // The _legal_words array begins with this many words where each letter is different. static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index 38e9510..da370b7 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -260,7 +260,7 @@ legal_list = [ "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", "WOUND", "WRACK", "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", "WRITE", "WRITS", "WRONG", "WROTE", "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", - "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", + "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", "LASER", "SONIC", ] expanded_list = [ @@ -938,7 +938,7 @@ expanded_list = [ "NOTAR", "OBEIR", "VIMES", "GENUA", "KANAL", "LIMON", "RAHAB", "SUOMI", "VEJEN", "BATEN", "FJORD", "LEVES", "PHARE", "RECTO", "AAGOT", "GIZUR", "NADJA", "RUGGE", "SNEYD", "DECKT", "FAILE", "GAOLS", "MELER", "PACTO", "PAHAN", "CALIF", "MENON", "SEPOY", "WADDY", "ZELLE", "AENDA", "ASTUA", "KROON", "LETRA", "MINIT", "NEEWA", - "PATNA", "URIEL", "HITTE", "HOMOJ", "JOUET", "KOSKI", "LYSTE", "MINAS", "RUHTE", "SETZE", + "PATNA", "URIEL", "HITTE", "HOMOJ", "JOUET", "KOSKI", "LYSTE", "MINAS", "RUHTE", "SETZE", "LASER", "SONIC", ] alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] From 1868f8446ae6f2a99fc36d2a0f5574e606ae10a9 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 18:33:38 -0400 Subject: [PATCH 116/220] Added continue screen --- .../watch_faces/complication/wordle_face.c | 44 +++++++++++++++---- .../watch_faces/complication/wordle_face.h | 6 ++- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 58ad55a..3638255 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -307,6 +307,13 @@ static void display_title(wordle_state_t *state) { watch_display_string("WO WordLE", 0); } +static void display_continue(wordle_state_t *state) { + char buf[7]; + state->curr_screen = SCREEN_CONTINUE; + sprintf(buf, "Cont %c", state->continuing ? 'y' : 'n'); + watch_display_string(buf, 4); +} + static void display_streak(wordle_state_t *state) { char buf[12]; state->curr_screen = SCREEN_STREAK; @@ -389,7 +396,7 @@ 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) { +static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { switch (state->curr_screen) { case SCREEN_RESULT: @@ -401,13 +408,15 @@ static bool act_on_btn(wordle_state_t *state) { #if USE_DAILY_STREAK if (state->prev_day == get_day_unix_time()) { display_wait(state); - return true; } -#endif - if (state->playing) - display_playing(state); +#else + if (state->playing) { + state->continuing = true; + display_continue(state); + } else display_streak(state); +#endif return true; case SCREEN_STREAK: #if USE_DAILY_STREAK @@ -424,6 +433,23 @@ static bool act_on_btn(wordle_state_t *state) { state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; + case SCREEN_CONTINUE: + switch (pin) + { + case BTN_ALARM: + if (state->continuing) + display_playing(state); + else { + reset_board(state); + display_streak(state); + } + break; + case BTN_LIGHT: + state->continuing = !state->continuing; + display_continue(state); + break; + } + return true; #if USE_DAILY_STREAK case SCREEN_WAIT: display_title(state); @@ -514,7 +540,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { if (state->curr_day != now) state->playing = false; #endif state->using_random_guess = false; - if (state->playing && state->curr_screen >= SCREEN_WIN) { + if (state->playing && state->curr_screen >= SCREEN_RESULT) { reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); } @@ -550,7 +576,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi } break; case EVENT_LIGHT_BUTTON_UP: - if (act_on_btn(state)) break; + if (act_on_btn(state, BTN_LIGHT)) break; get_next_letter(state->position, state->word_elements); display_letter(state, true); break; @@ -560,7 +586,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); break; case EVENT_ALARM_BUTTON_UP: - if (act_on_btn(state)) break; + if (act_on_btn(state, BTN_ALARM)) break; display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; state->playing = true; @@ -586,7 +612,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case EVENT_ACTIVATE: break; case EVENT_TIMEOUT: - if (state->curr_screen >= SCREEN_WIN) + if (state->curr_screen >= SCREEN_RESULT) display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 8ad9155..fe92492 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -89,12 +89,13 @@ typedef enum { SCREEN_PLAYING = 0, SCREEN_TITLE, SCREEN_STREAK, + SCREEN_CONTINUE, #if USE_DAILY_STREAK SCREEN_WAIT, #endif + SCREEN_RESULT, SCREEN_WIN, SCREEN_LOSE, - SCREEN_RESULT, SCREEN_NO_DICT, SCREEN_ALREADY_GUESSED, SCREEN_COUNT @@ -108,8 +109,9 @@ typedef struct { uint8_t attempt : 4; uint8_t position : 3; bool playing : 1; - uint16_t curr_answer : 15; + uint16_t curr_answer : 14; bool using_random_guess : 1; + bool continuing : 1; uint8_t streak; WordleScreen curr_screen; #if USE_DAILY_STREAK From ce31db3712faf22957cabf26d5c4a4217cd8056b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 22:30:41 -0400 Subject: [PATCH 117/220] Continue test now blinks --- movement/watch_faces/complication/wordle_face.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 3638255..1a3c2f8 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -307,11 +307,17 @@ static void display_title(wordle_state_t *state) { watch_display_string("WO WordLE", 0); } +static void display_continue_result(bool continuing, uint8_t subsecond) { + char buf[2]; + char result = subsecond % 2 ? ' ' : (continuing ? 'y' : 'n'); + sprintf(buf,"%c", result); + watch_display_string(buf, 9); +} + static void display_continue(wordle_state_t *state) { - char buf[7]; state->curr_screen = SCREEN_CONTINUE; - sprintf(buf, "Cont %c", state->continuing ? 'y' : 'n'); - watch_display_string(buf, 4); + watch_display_string("Cont ", 4); + display_continue_result(state->continuing, 0); } static void display_streak(wordle_state_t *state) { @@ -446,7 +452,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { break; case BTN_LIGHT: state->continuing = !state->continuing; - display_continue(state); + display_continue_result(state->continuing, 0); break; } return true; @@ -571,6 +577,9 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case SCREEN_WIN: display_win(state, event.subsecond); break; + case SCREEN_CONTINUE: + display_continue_result(state->continuing, event.subsecond); + break; default: break; } From cb57ef237d9b9518b3b9d804affe9b52f7cf9226 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 22:33:03 -0400 Subject: [PATCH 118/220] Got rid of continue's blinking --- movement/watch_faces/complication/wordle_face.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 1a3c2f8..f232786 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -307,17 +307,14 @@ static void display_title(wordle_state_t *state) { watch_display_string("WO WordLE", 0); } -static void display_continue_result(bool continuing, uint8_t subsecond) { - char buf[2]; - char result = subsecond % 2 ? ' ' : (continuing ? 'y' : 'n'); - sprintf(buf,"%c", result); - watch_display_string(buf, 9); +static void display_continue_result(bool continuing) { + watch_display_string(continuing ? "y" : "n", 9); } static void display_continue(wordle_state_t *state) { state->curr_screen = SCREEN_CONTINUE; watch_display_string("Cont ", 4); - display_continue_result(state->continuing, 0); + display_continue_result(state->continuing); } static void display_streak(wordle_state_t *state) { @@ -452,7 +449,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { break; case BTN_LIGHT: state->continuing = !state->continuing; - display_continue_result(state->continuing, 0); + display_continue_result(state->continuing); break; } return true; @@ -577,9 +574,6 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case SCREEN_WIN: display_win(state, event.subsecond); break; - case SCREEN_CONTINUE: - display_continue_result(state->continuing, event.subsecond); - break; default: break; } From 7ceb68267538e0565fc8b73e010e8d201caa5b2f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 22:52:10 -0400 Subject: [PATCH 119/220] Better handling of seeing if currently playing --- .../watch_faces/complication/wordle_face.c | 49 ++++++++++++------- .../watch_faces/complication/wordle_face.h | 3 +- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f232786..bd82fba 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -273,6 +273,18 @@ static void display_playing(wordle_state_t *state) { state->curr_screen = SCREEN_PLAYING; } +static void reset_all_elements(wordle_state_t *state) { + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + state->word_elements[i] = _num_valid_letters; + state->word_elements_result[i] = WORDLE_LETTER_WRONG; + } + for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { + state->guessed_words[i] = _num_words + _num_expanded_words; + } + state->using_random_guess = false; + state->attempt = 0; +} + static void reset_incorrect_elements(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) @@ -281,22 +293,12 @@ static void reset_incorrect_elements(wordle_state_t *state) { } static void reset_board(wordle_state_t *state) { - for (size_t i = 0; i < WORDLE_LENGTH; i++) { - state->word_elements[i] = _num_valid_letters; - state->word_elements_result[i] = WORDLE_LETTER_WRONG; - } - for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { - state->guessed_words[i] = _num_words + _num_expanded_words; - } + reset_all_elements(state); state->curr_answer = get_random(_num_words); - state->using_random_guess = false; - state->attempt = 0; watch_clear_colon(); - watch_display_string(" ", 4); - reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); display_playing(state); - watch_display_string("-", 5); + watch_display_string(" -", 4); #if __EMSCRIPTEN__ printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); #endif @@ -371,10 +373,19 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; - sprintf(buf," W %s ", subsecond % 2 ? "JOb " : "NICE"); + sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOB "); watch_display_string(buf, 0); } +static bool is_playing(const wordle_state_t *state) { + if (state->attempt > 0) return true; + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements[i] != _num_valid_letters) + return true; + } + return false; +} + static void display_result(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 1]; for (size_t i = 0; i < WORDLE_LENGTH; i++) @@ -413,7 +424,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { display_wait(state); } #else - if (state->playing) { + if (is_playing(state)) { state->continuing = true; display_continue(state); } @@ -483,7 +494,7 @@ static void get_result(wordle_state_t *state) { state->guessed_words[state->attempt] = in_dict; bool exact_match = check_word(state); if (exact_match) { - state->playing = false; + state->attempt = 0; state->curr_screen = SCREEN_WIN; if (state->streak < 0x7F) state->streak++; @@ -493,7 +504,7 @@ static void get_result(wordle_state_t *state) { return; } if (++state->attempt >= WORDLE_MAX_ATTEMPTS) { - state->playing = false; + state->attempt = 0; state->curr_screen = SCREEN_LOSE; state->streak = 0; return; @@ -530,6 +541,7 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, memset(*context_ptr, 0, sizeof(wordle_state_t)); wordle_state_t *state = (wordle_state_t *)*context_ptr; state->curr_screen = SCREEN_TITLE; + reset_all_elements(state); } // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. } @@ -540,10 +552,10 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { #if USE_DAILY_STREAK uint32_t now = get_day_unix_time() ; if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; - if (state->curr_day != now) state->playing = false; + if (state->curr_day != now) state->attempt = 0; #endif state->using_random_guess = false; - if (state->playing && state->curr_screen >= SCREEN_RESULT) { + if (is_playing(state) && state->curr_screen >= SCREEN_RESULT) { reset_incorrect_elements(state); state->position = get_first_pos(state->word_elements_result); } @@ -592,7 +604,6 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi if (act_on_btn(state, BTN_ALARM)) break; display_letter(state, true); if (state->word_elements[state->position] == _num_valid_letters) break; - state->playing = true; #if (USE_RANDOM_GUESS != 0) if (watch_get_pin_level(BTN_LIGHT) && (state->using_random_guess || (state->attempt == 0 && state->position == 0))) { diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index fe92492..00c97b3 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -108,9 +108,8 @@ typedef struct { uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; uint8_t attempt : 4; uint8_t position : 3; - bool playing : 1; - uint16_t curr_answer : 14; bool using_random_guess : 1; + uint16_t curr_answer : 15; bool continuing : 1; uint8_t streak; WordleScreen curr_screen; From a0ffd0ca7f78a628439738d63cbdd67d04bea121 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 23:10:17 -0400 Subject: [PATCH 120/220] Fix on playing with daily streak --- movement/watch_faces/complication/wordle_face.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index bd82fba..59edb9d 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -309,6 +309,7 @@ static void display_title(wordle_state_t *state) { watch_display_string("WO WordLE", 0); } +#if !USE_DAILY_STREAK static void display_continue_result(bool continuing) { watch_display_string(continuing ? "y" : "n", 9); } @@ -318,6 +319,7 @@ static void display_continue(wordle_state_t *state) { watch_display_string("Cont ", 4); display_continue_result(state->continuing); } +#endif static void display_streak(wordle_state_t *state) { char buf[12]; @@ -423,6 +425,10 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { if (state->prev_day == get_day_unix_time()) { display_wait(state); } + else if (is_playing(state)) + display_playing(state); + else + display_streak(state); #else if (is_playing(state)) { state->continuing = true; @@ -447,6 +453,12 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; +#if USE_DAILY_STREAK + case SCREEN_WAIT: + (void) pin; + display_title(state); + return true; +#else case SCREEN_CONTINUE: switch (pin) { @@ -464,10 +476,6 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { break; } return true; -#if USE_DAILY_STREAK - case SCREEN_WAIT: - display_title(state); - return true; #endif default: return false; From af6f6002ba2f490bc061f8d4b15d9ec5015f23e6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 23:55:54 -0400 Subject: [PATCH 121/220] Fixed the bug of the text not resetting after a timeout --- movement/watch_faces/complication/wordle_face.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 59edb9d..90cec69 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -634,8 +634,11 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case EVENT_ACTIVATE: break; case EVENT_TIMEOUT: - if (state->curr_screen >= SCREEN_RESULT) + if (state->curr_screen >= 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) From 0d58f0d77dd79596201b647be87654ebbbf3ce9d Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 18 Aug 2024 23:58:39 -0400 Subject: [PATCH 122/220] JOB to JOb --- movement/watch_faces/complication/wordle_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 90cec69..f5b69a6 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -375,7 +375,7 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { 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); } From 1a1560b59d614db26847ce464b113276946d1e76 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 19 Aug 2024 00:13:18 -0400 Subject: [PATCH 123/220] Bugfix on elements not resetting when winning or losing --- movement/watch_faces/complication/wordle_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f5b69a6..35953bb 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -502,7 +502,7 @@ static void get_result(wordle_state_t *state) { state->guessed_words[state->attempt] = in_dict; bool exact_match = check_word(state); if (exact_match) { - state->attempt = 0; + reset_all_elements(state); state->curr_screen = SCREEN_WIN; if (state->streak < 0x7F) state->streak++; @@ -512,7 +512,7 @@ static void get_result(wordle_state_t *state) { return; } if (++state->attempt >= WORDLE_MAX_ATTEMPTS) { - state->attempt = 0; + reset_all_elements(state); state->curr_screen = SCREEN_LOSE; state->streak = 0; return; @@ -560,7 +560,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { #if USE_DAILY_STREAK uint32_t now = get_day_unix_time() ; if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; - if (state->curr_day != now) state->attempt = 0; + if (state->curr_day != now) reset_all_elements(state); #endif state->using_random_guess = false; if (is_playing(state) && state->curr_screen >= SCREEN_RESULT) { From 7f38f8e4169194ac3241eadbc30f2da902b31d7c Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 23 Aug 2024 16:39:53 -0400 Subject: [PATCH 124/220] modified Wordle list to use Wordle's own list and add 2 more letters --- .../watch_faces/complication/wordle_face.c | 336 ++- .../watch_faces/complication/wordle_face.h | 7 - utils/wordle_face/wordle_list.py | 2174 +++++++++-------- 3 files changed, 1406 insertions(+), 1111 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 35953bb..f27ae4a 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -30,114 +30,220 @@ #endif -static const char _valid_letters[] = {'A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; +static const char _valid_letters[] = {'A', 'C', 'E', 'G', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; -// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff -// Number of words found: 284 -static const char _legal_words[][WORDLE_LENGTH + 1] = { - "ROPES", "RESIN", "PACES", "RIPEN", "ALIEN", "SPINE", "ROSIN", "PIERS", "CAPER", - "SNORE", "SANER", "RAILS", "SCORN", "PENIS", "NEARS", "ENROL", "PROSE", "CANES", - "POSER", "ACORN", "PAILS", "SLAIN", "REALS", "CLAPS", "PLIES", "PALES", "LIENS", - "PLAIN", "SLOPE", "REAPS", "CRAPE", "ASPEN", "COINS", "ARISE", "ALOES", "PANES", - "SCION", "SNARL", "COPRA", "PALER", "CLIPS", "PANIC", "PARSE", "PENAL", "SPARE", - "LIONS", "LINES", "SNARE", "PEONS", "CLEAN", "SPACE", "SCALE", "COILS", "SCRAP", - "OPINE", "NICER", "LOANS", "RACES", "RELIC", "NOISE", "PIANO", "CRANE", "SNAIL", - "SCORE", "CLEAR", "CROPS", "CORES", "COPSE", "PINES", "PANEL", "RINSE", "LOINS", - "PRONE", "ALONE", "RAISE", "OSIER", "LEARN", "SPICE", "SPOIL", "EARLS", "NAILS", - "PLANE", "CARES", "CRIES", "CORNS", "CORPS", "CLASP", "LACES", "ARSON", "LANES", - "OPENS", "SALON", "SINCE", "PLANS", "SCARE", "SPORE", "OCEAN", "AEONS", "PRICE", - "IRONS", "SCALP", "EPICS", "LIARS", "SPIRE", "LINER", "PILES", "SLICE", "LEANS", - "RAINS", "PLEAS", "SOLAR", "CAPES", "APRON", "RISEN", "POISE", "CONES", "PEARS", - "PERIL", "COALS", "OPALS", "ROLES", "CLOSE", "CAPON", "POLES", "EARNS", "CRISP", - "AROSE", "SCOPE", "AISLE", "CLANS", "CORAL", "SCRIP", "PAINS", "OPERA", "PAIRS", - "PEARL", "SIREN", "ACRES", "CAROL", "LAIRS", "PORES", "PRIES", "CRONE", "CANOE", - "LAPSE", "LEAPS", "SNIPE", "REINS", "PEALS", "SPEAR", "LOSER", "POLAR", "LANCE", - "CAIRN", "PLACE", "LASER", "SONIC", "EERIE", "PIECE", "ISLES", "NOOSE", "SNEER", - "SOLOS", "ELOPE", "INNER", "SLOOP", "SOARS", "SPREE", "SPANS", "PAPAL", "RIPER", - "COLON", "SCANS", "RARES", "PILLS", "CANON", "POLLS", "POPPA", "ERROR", "REARS", - "PESOS", "CRESS", "PENCE", "SPOOL", "COLOR", "NONCE", "CLASS", "SELLS", "NASAL", - "ERASE", "RILLS", "LAPEL", "COOLS", "EASEL", "COLIC", "SPELL", "SPOOR", "LASSO", - "APPAL", "PEACE", "SALSA", "SCENE", "NIECE", "CONIC", "APPLE", "SNAPS", "PEERS", - "ROARS", "SPARS", "SAILS", "SLOPS", "APACE", "POSES", "SENSE", "PEEPS", "CASES", - "CANAL", "CIRCA", "SLAPS", "SCOOP", "ROLLS", "PIPES", "SCARS", "LOOSE", "ROSES", - "LILAC", "OASES", "SOLES", "PAEAN", "PAPAS", "CRASS", "PROPS", "SEALS", "CACAO", - "LINEN", "SORES", "EASES", "POPES", "OASIS", "LOSES", "NOSES", "SIRES", "SPILL", - "CREPE", "ALIAS", "CROSS", "ARENA", "SPINS", "REPEL", "SPIES", "PRIOR", "POOLS", - "PRESS", "RISER", "AREAS", "SPOON", "SALES", "CREEP", "CEASE", "LOOPS", "ASSES", - "CALLS", "CRIER", "COCOA", "SEERS", "LOONS", "SLIPS", "PAPER", "REELS", "RISES", - "POSSE", "RARER", "SOILS", "PIPER", "INANE", "LOCAL", "PEASE", "ONION", "SLEEP", - "LEASE", "RACER", "LEPER", "SILLS", "CELLS", -#if (USE_EXPANDED_DICT != 1) +// From: https://matthewminer.name/projects/calculators/wordle-words-left/ +// Number of words found: 298 +static const char _valid_words[][WORDLE_LENGTH + 1] = { + "PLACE", "SHONE", "POSER", "CHAIN", "CAPER", "POLAR", "LEARN", "SHORN", "PORCH", + "GRAPE", "GNASH", "CHAIR", "SCORE", "CIGAR", "GRASP", "SINCE", "SPIRE", "NEIGH", + "SHORE", "CHASE", "RAISE", "CAIRN", "PLIER", "LOSER", "GRACE", "LEASH", "PENAL", + "SLING", "RISEN", "LOGIC", "PRICE", "POISE", "SCALE", "SINGE", "SNARL", "LINER", + "ANGEL", "SNAIL", "PALER", "SCION", "ALONE", "AGILE", "APRON", "PERIL", "GRIPE", + "SPICE", "LOGIN", "REGAL", "CAROL", "SLICE", "CRONE", "LEACH", "COPSE", "SHEAR", + "ALIGN", "LARGE", "LAPSE", "AISLE", "NICER", "OCEAN", "OPERA", "ALIEN", "ACORN", + "ASHEN", "SHINE", "PANEL", "SPORE", "SCOPE", "SPACE", "PHASE", "AROSE", "CHOIR", + "SNIPE", "CHAOS", "RALPH", "EPOCH", "GRAIN", "SANER", "GROIN", "SLANG", "SLAIN", + "CRASH", "CLASP", "SHIRE", "SCONE", "ALONG", "APING", "NICHE", "CHEAP", "CHIRP", + "LAGER", "CHORE", "SNORE", "SHAPE", "RESIN", "PERCH", "PECAN", "GLARE", "GROAN", + "RHINO", "RENAL", "SALON", "GRAIL", "SEPIA", "LANCE", "PRONG", "RECAP", "CLONE", + "CLASH", "HORSE", "SOLAR", "HERON", "PEACH", "ARSON", "HINGE", "CLEAN", "CLING", + "PHONE", "CRANE", "CLANG", "SCORN", "SPEAR", "PLAIN", "PROSE", "SPOIL", "GONER", + "SHOAL", "REIGN", "CLEAR", "ANGER", "CHINA", "GRAPH", "PEARL", "CARGO", "CHOSE", + "SCALP", "CANOE", "RINSE", "RANGE", "LINGO", "RANCH", "PLANE", "SPINE", "REACH", + "CRISP", "PARSE", "RIPEN", "SNARE", "CLOSE", "SHARE", "CORAL", "NOISE", "SHARP", + "SPARE", "SONIC", "SCRAP", "SPIEL", "RELIC", "OPINE", "SCARE", "SPRIG", "SHALE", + "PANIC", "SONAR", "GROPE", "SLOPE", "ANGLE", "ORGAN", "PIANO", "PINCH", "GLEAN", + "PRONE", "ARISE", "ROACH", "SIREN", "CLASS", "POSSE", "INANE", "HENCE", "SNEER", + "PAGAN", "PREEN", "ROGER", "SPELL", "SHEEP", "SENSE", "INNER", "ALPHA", "SHEEN", + "SCREE", "CIRCA", "PRIOR", "RARER", "PEACE", "GENRE", "HELLO", "CACAO", "GORGE", + "GLOSS", "CRIER", "CROSS", "CREPE", "COLON", "CHILL", "ONION", "LINEN", "PIPER", + "SLOOP", "LEGAL", "SNOOP", "PAPER", "ALGAE", "LAPEL", "CHEER", "HIPPO", "PIECE", + "LILAC", "HONOR", "PAPAL", "ARENA", "APNEA", "RIPER", "SCENE", "SHALL", "NASAL", + "SPREE", "RIGOR", "EAGER", "LIEGE", "LEPER", "LEASE", "CORER", "SPOON", "GROSS", + "COACH", "CEASE", "GENIE", "HARSH", "PENCE", "CHILI", "SHELL", "CREEP", "RISER", + "ERASE", "CINCH", "SIEGE", "GOING", "SCOOP", "SPILL", "NOOSE", "EAGLE", "AGING", + "NIECE", "SPOOL", "APPLE", "SALSA", "LEECH", "GREEN", "IONIC", "LASSO", "CONCH", + "PENNE", "SLASH", "CANAL", "CRASS", "REPEL", "COCOA", "CRESS", "AGAPE", "EASEL", + "CELLO", "CONIC", "IGLOO", "RACER", "GOOSE", "ICING", "POOCH", "ILIAC", "GRASS", + "SHEER", "CANON", "ELOPE", "LOCAL", "EERIE", "COLOR", "AGREE", "PRESS", "GEESE", + "SLOSH", "SLEEP", "GRILL", "AGAIN", "GLASS", "PARER", "CHESS", "CACHE", "ERROR", + "LOOSE", }; + // These are words that'll never be used, but still need to be in the dictionary for guesses. -// Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236 -// Number of words found: 470 -static const char _expanded_words[][WORDLE_LENGTH + 1] = { -#endif -#if (USE_EXPANDED_DICT != 0) - "PARIS", "APRIL", "SPAIN", "EINEN", "ASCII", "EINER", "SEINE", "AINSI", "ALICE", - "ALLES", "ALORS", "EINES", "ALLER", "PEINE", "PARCE", "CELLE", "CLARA", "ELLES", - "ELLEN", "OLISI", "ALLEN", "ISAAC", "APRES", "CROIS", "SANOI", "PASSE", "ELSIE", - "REINE", "ELLER", "AARON", "CLARE", "IRENE", "ANNIE", "ELLOS", "PARLE", "ALLAN", - "PELLE", "CAIRO", "SENOR", "PENSE", "CECIL", "SEELE", "ORION", "SELON", "COSAS", - "PASSA", "ELLIS", "CARLO", "ENNEN", "SILAS", "EENEN", "OSCAR", "ONCLE", "CESSE", - "SONNE", "ASSIS", "PRISE", "SERAI", "CELIA", "NOIRE", "NORSE", "SINNE", "LIESS", - "ELIAS", "REPOS", "COLIN", "NOIRS", "CLAIR", "CIELO", "PARLA", "SOINS", "LASSE", - "NELLA", "PAOLO", "SOLON", "REPAS", "NANCE", "PAINE", "SAISI", "ELISE", "CESAR", - "CANNA", "SALLE", "SINON", "SINAI", "LOIRE", "PENSA", "LEILA", "REISE", "ELLAS", - "POORE", "EARLE", "CERCA", "LEISE", "POOLE", "AILES", "SANOA", "LEONE", "LILLE", - "PROIE", "CARNE", "SPIEL", "CERES", "ENSIN", "CINCO", "ROLLO", "ARRAS", "SEIEN", - "PRIER", "ANNAN", "CALLE", "LIISA", "SALIR", "LESSE", "LESEN", "LIIAN", "NEERE", - "ARIEL", "PIENI", "PIERO", "ERANO", "ELENA", "SILLE", "NEALE", "SEENE", "ROLLE", - "NELLE", "SOLLE", "ESSER", "PASAR", "PREIS", "ASIAN", "SENCE", "ANSON", "SERRA", - "CONAN", "SERAS", "SIENA", "SOPRA", "RENEE", "ALINE", "CORSE", "ASSAI", "INSEL", - "ROSIE", "SONIA", "APPEL", "CRISE", "CIRCE", "LINIE", "RENAN", "CAIRE", "COLLA", - "SANOO", "EENER", "ANCOR", "NEPAL", "REINO", "LAINE", "SOONE", "ALAIN", "LAPSI", - "INCAS", "INNES", "CARON", "ROSEN", "CASAS", "NOLAN", "SERRE", "PESAR", "SEARS", - "LEPIC", "LISLE", "LOSSE", "CINNA", "SERIE", "RIRES", "CORSO", "SOIRS", "CREER", - "POCOS", "SIENS", "ARLES", "CROCE", "IONIC", "PONER", "ESSEN", "SANON", "CESSA", - "SERIA", "ALPES", "NINON", "LILLA", "AINOA", "CORPO", "LESER", "ILLIS", "ROPER", - "ANNEE", "PAIRE", "PEPIN", "ORIEL", "CANNE", "AIRES", "ARCIS", "EASIE", "ANNOS", - "COLLE", "SELLE", "EILEN", "CAPRI", "ERICA", "ROCCO", "ARIAN", "CLEON", "ALLIE", - "PONCE", "COPIE", "INNAN", "NOCES", "NAPPE", "CORNE", "ESIIN", "ENCOR", "LORNA", - "SACRE", "PAPEL", "SAILE", "SAEPE", "CREON", "LLENO", "ELISA", "PASSO", "ASILE", - "LORCS", "ASIAA", "SANIN", "ONNEN", "SONNA", "AILIE", "ALIIS", "ECOLE", "CREES", - "PRESO", "CLARO", "EARES", "ROSSI", "COREA", "SANAN", "AESOP", "SAPOR", "EISEN", - "ACASO", "PARAS", "NANON", "LAPIS", "ARRAN", "CLLIA", "SACRA", "PRINS", "CENCI", - "CLAES", "SLAAP", "ROLLA", "COLES", "LORNE", "OLELO", "CASSE", "NILES", "PASOS", - "ESSAI", "ROSAS", "LLENA", "LEERE", "CLASE", "CALOR", "ROSSE", "ALLEE", "SOREL", - "SANAA", "SLONE", "OLSEN", "OOREN", "PARER", "PASSI", "POSSA", "PLAIE", "OPERE", - "SCAPE", "POLEN", "RIPON", "SCALA", "AILLE", "PALOS", "CLAPP", "ESCAP", "ELLEI", - "IONIA", "NICOL", "PAESE", "PERON", "ORSON", "INNEN", "AISNE", "RANCE", "SLAAN", - "PAOLI", "COLLO", "ANNAS", "ERROL", "CLERC", "SAINE", "RAINA", "PRESE", "PARIA", - "PERLE", "RECAL", "SINAE", "PESER", "OISIN", "PLENA", "CARLE", "PERES", "SACAR", - "ALPEN", "CORRE", "ACCES", "RILLA", "ANNAL", "PERSE", "SAALE", "PERRO", "AILSA", - "POCAS", "SOLEN", "PLASE", "SOLIS", "PAPPI", "COPIA", "ARIES", "ROCCA", "ALIOS", - "ROCAS", "PELOS", "NEPOS", "COLPA", "ISORA", "LECON", "SOANE", "SNELL", "ILLOS", - "PILAR", "ECLAC", "PRESA", "SILLA", "NIELS", "LIPPO", "CROLL", "PONEN", "POSEE", - "PIPPA", "ILLAN", "CENIS", "SANNA", "RASSI", "CERRO", "SCENA", "CASOS", "COLPO", - "POSSO", "POSEN", "EINAR", "ISLAS", "IPSIS", "SALEN", "ASIEN", "CREAN", "LENIN", - "LOCIS", "NENNE", "ILION", "NARES", "ONNEA", "PALAA", "PIPPO", "POLIS", "RICOS", - "ELSON", "SNOOP", "ANNIS", "PROPE", "ELLIE", "ERNIE", "PLIER", "SERES", "REINA", - "LIPPE", "OLINE", "PARIE", "ARLEE", "NIAIS", "LEONI", "RAINE", "LARES", "SEINS", - "CARRE", "POILS", "ALENE", "LINEA", "NEARE", "PENSO", "PRISA", "CAPEL", "PAREA", - "PEECE", "SALIO", "COELO", "SCIRE", "NELLO", "LIENE", "ORICE", "EPAIS", "PERCE", - "ALLIS", "PEPLE", "LARNE", "NEILL", "OLLEN", "CASCA", "LAPIN", "OLLIE", "SALIC", - "LIANE", "REESE", "ELSLI", "SPION", "RIENS", "LILAS", "PAPPA", "ERRER", "SPISE", - "CELIE", "OLSON", "IRREN", "ARIAS", "ARION", "PASEO", "CAERE", "PISAN", "CARRO", - "PAROI", "NOONE", "SEPPI", "OPPIA", "SEALE", "LIPPI", "PELAS", "COCOS", "PLACA", - "CONOR", "LANCA", "OSASI", "ALOIS", "NAIRN", "PIENO", "SPASS", "SAONE", "ALNAR", - "CARIA", "PIENA", -#endif +static const char _possible_words[][WORDLE_LENGTH + 1] = { + "AALII", "AARGH", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACHAR", "ACHES", "ACHOO", + "ACING", "ACINI", "ACNES", "ACRES", "ACROS", "AECIA", "AEGIS", "AEONS", "AERIE", + "AEROS", "AESIR", "AGARS", "AGENE", "AGERS", "AGGER", "AGGIE", "AGGRI", "AGGRO", + "AGHAS", "AGILA", "AGIOS", "AGLEE", "AGLOO", "AGOGE", "AGONE", "AGONS", "AGORA", + "AGRIA", "AGRIN", "AGROS", "AHEAP", "AHIGH", "AHING", "AIGAS", "AINEE", "AINGA", + "AIOLI", "AIRER", "AIRNS", "ALAAP", "ALANE", "ALANG", "ALANS", "ALAPA", "ALAPS", + "ALCOS", "ALECS", "ALEPH", "ALGAL", "ALGAS", "ALGIN", "ALGOR", "ALIAS", "ALINE", + "ALLEE", "ALLEL", "ALLIS", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ANANA", "ANCHO", + "ANCLE", "ANCON", "ANEAR", "ANELE", "ANGAS", "ANGLO", "ANIGH", "ANILE", "ANILS", + "ANION", "ANISE", "ANLAS", "ANNAL", "ANNAS", "ANOAS", "ANOLE", "ANSAE", "APACE", + "APAGE", "APERS", "APGAR", "APHIS", "APIAN", "APIOL", "APISH", "APOOP", "APPAL", + "APPEL", "APPRO", "APRES", "APSES", "APSIS", "APSOS", "ARARS", "ARCHI", "ARCOS", + "AREAE", "AREAL", "AREAR", "AREAS", "ARECA", "AREIC", "ARENE", "AREPA", "ARERE", + "ARGAL", "ARGAN", "ARGIL", "ARGLE", "ARGOL", "ARGON", "ARIAS", "ARIEL", "ARILS", + "ARISH", "ARLES", "ARNAS", "AROHA", "ARPAS", "ARPEN", "ARRAH", "ARRAS", "ARRIS", + "ARSES", "ARSIS", "ASANA", "ASCON", "ASHES", "ASPEN", "ASPER", "ASPIC", "ASPIE", + "ASPIS", "ASPRO", "ASSAI", "ASSES", "CACAS", "CAECA", "CAESE", "CAGER", "CAGES", + "CAINS", "CALLA", "CALLS", "CALOS", "CALPA", "CALPS", "CANEH", "CANER", "CANES", + "CANGS", "CANNA", "CANNS", "CANSO", "CAPAS", "CAPES", "CAPHS", "CAPLE", "CAPON", + "CAPOS", "CAPRI", "CARAP", "CARER", "CARES", "CARLE", "CARLS", "CARNS", "CARON", + "CARPI", "CARPS", "CARRS", "CARSE", "CASAS", "CASCO", "CASES", "CECAL", "CEILI", + "CEILS", "CELLA", "CELLI", "CELLS", "CENSE", "CEORL", "CEPES", "CERCI", "CERES", + "CERGE", "CERIA", "CERIC", "CERNE", "CEROC", "CEROS", "CESSE", "CHACE", "CHACO", + "CHAIS", "CHALS", "CHANA", "CHANG", "CHAPE", "CHAPS", "CHARA", "CHARE", "CHARR", + "CHARS", "CHEEP", "CHELA", "CHELP", "CHERE", "CHIAO", "CHIAS", "CHICA", "CHICH", + "CHICO", "CHICS", "CHIEL", "CHILE", "CHINE", "CHING", "CHINO", "CHINS", "CHIPS", + "CHIRL", "CHIRO", "CHIRR", "CHOCO", "CHOCS", "CHOGS", "CHOIL", "CHOLA", "CHOLI", + "CHOLO", "CHONS", "CHOON", "CHOPS", "CIELS", "CILIA", "CILLS", "CINES", "CIONS", + "CIPPI", "CIRCS", "CIRES", "CIRLS", "CIRRI", "CISCO", "CLACH", "CLAES", "CLAGS", + "CLANS", "CLAPS", "CLARO", "CLEEP", "CLEGS", "CLEPE", "CLIES", "CLINE", "CLIPE", + "CLIPS", "CLOGS", "CLONS", "CLOOP", "CLOPS", "COALA", "COALS", "COCAS", "COCCI", + "COCCO", "COCOS", "COGIE", "COGON", "COHEN", "COHOE", "COHOG", "COHOS", "COIGN", + "COILS", "COINS", "COIRS", "COLAS", "COLES", "COLIC", "COLIN", "COLLS", "COLOG", + "CONES", "CONGA", "CONGE", "CONGO", "CONIA", "CONIN", "CONNE", "CONNS", "COOCH", + "COOEE", "COOER", "COOLS", "COONS", "COOPS", "COPAL", "COPEN", "COPER", "COPES", + "COPRA", "CORES", "CORGI", "CORIA", "CORNI", "CORNO", "CORNS", "CORPS", "CORSE", + "CORSO", "COSEC", "COSES", "COSIE", "CRAAL", "CRAGS", "CRAIC", "CRAIG", "CRANS", + "CRAPE", "CRAPS", "CRARE", "CREEL", "CREES", "CRENA", "CREPS", "CRIAS", "CRIES", + "CRINE", "CRIOS", "CRIPE", "CRIPS", "CRISE", "CROCI", "CROCS", "CROGS", "CRONS", + "CROOL", "CROON", "CROPS", "CRORE", "EAGRE", "EALES", "EARLS", "EARNS", "EASER", + "EASES", "EASLE", "ECHES", "ECHOS", "EGERS", "EGGAR", "EGGER", "EHING", "EIGNE", + "EISEL", "ELAIN", "ELANS", "ELCHI", "ELOGE", "ELOIN", "ELOPS", "ELPEE", "ELSIN", + "ENIAC", "ENNOG", "ENOLS", "ENROL", "EORLS", "EOSIN", "EPEES", "EPHAH", "EPHAS", + "EPHOR", "EPICS", "EPRIS", "ERGON", "ERGOS", "ERICA", "ERICS", "ERING", "ERNES", + "EROSE", "ERSES", "ESCAR", "ESILE", "ESNES", "ESSES", "GAGER", "GAGES", "GAINS", + "GAIRS", "GALAH", "GALAS", "GALEA", "GALES", "GALLS", "GALOP", "GANCH", "GANGS", + "GAOLS", "GAPER", "GAPES", "GAPOS", "GARES", "GARIS", "GARNI", "GARRE", "GASES", + "GASPS", "GEALS", "GEANS", "GEARE", "GEARS", "GEEPS", "GELEE", "GENAL", "GENAS", + "GENES", "GENIC", "GENII", "GENIP", "GENOA", "GENRO", "GERAH", "GERES", "GERLE", + "GERNE", "GESSE", "GESSO", "GHEES", "GIGAS", "GIGHE", "GILAS", "GILLS", "GINCH", + "GINGE", "GINGS", "GIPON", "GIPPO", "GIRLS", "GIRNS", "GIRON", "GIROS", "GIRRS", + "GIRSH", "GLACE", "GLAIR", "GLANS", "GLEES", "GLEIS", "GLENS", "GLIAL", "GLIAS", + "GLOGG", "GLOOP", "GLOPS", "GNARL", "GNARR", "GNARS", "GOALS", "GOELS", "GOERS", + "GOGGA", "GOGOS", "GOIER", "GOLES", "GOLPE", "GOLPS", "GONCH", "GONGS", "GONIA", + "GONNA", "GOOGS", "GOOLS", "GOONS", "GOOPS", "GOORS", "GORAL", "GORAS", "GORES", + "GORIS", "GORPS", "GORSE", "GOSSE", "GRAAL", "GRAIP", "GRANA", "GRANS", "GRECE", + "GREES", "GREGE", "GREGO", "GREIN", "GRENS", "GRESE", "GRICE", "GRIGS", "GRINS", + "GRIPS", "GRISE", "GROGS", "GRONE", "GRRLS", "GRRRL", "HAARS", "HAGGS", "HAHAS", + "HAILS", "HAINS", "HAIRS", "HALAL", "HALER", "HALES", "HALLO", "HALLS", "HALON", + "HALOS", "HALSE", "HANAP", "HANCE", "HANCH", "HANGI", "HANGS", "HANSA", "HANSE", + "HAOLE", "HAPPI", "HARES", "HARLS", "HARNS", "HAROS", "HARPS", "HASPS", "HEALS", + "HEAPS", "HEARE", "HEARS", "HEELS", "HEIGH", "HEILS", "HEIRS", "HELES", "HELIO", + "HELLS", "HELOS", "HELPS", "HENCH", "HENGE", "HENNA", "HEPAR", "HERES", "HERLS", + "HERNS", "HEROS", "HERSE", "HESPS", "HIGHS", "HILAR", "HILCH", "HILLO", "HILLS", + "HINGS", "HIOIS", "HIREE", "HIRER", "HIRES", "HOARS", "HOERS", "HOGAN", "HOGEN", + "HOGGS", "HOGHS", "HOING", "HOISE", "HOLES", "HOLLA", "HOLLO", "HOLON", "HOLOS", + "HONAN", "HONER", "HONES", "HONGI", "HONGS", "HOOCH", "HOONS", "HOOPS", "HOORS", + "HOOSH", "HOPER", "HOPES", "HORAH", "HORAL", "HORAS", "HORIS", "HORNS", "HOSEL", + "HOSEN", "HOSER", "HOSES", "ICERS", "ICHES", "ICHOR", "ICIER", "ICONS", "IGAPO", + "ILEAC", "ILEAL", "ILIAL", "ILLER", "INCEL", "INCLE", "INCOG", "INGAN", "INGLE", + "INION", "INSPO", "IPPON", "IRING", "IRONE", "IRONS", "ISHES", "ISLES", "ISNAE", + "ISSEI", "LAARI", "LACER", "LACES", "LAERS", "LAGAN", "LAHAL", "LAHAR", "LAICH", + "LAICS", "LAIGH", "LAIRS", "LALLS", "LANAI", "LANAS", "LANCH", "LANES", "LAPIN", + "LAPIS", "LARCH", "LAREE", "LARES", "LARGO", "LARIS", "LARNS", "LASER", "LASES", + "LASSI", "LEANS", "LEAPS", "LEARE", "LEARS", "LEEAR", "LEEPS", "LEERS", "LEESE", + "LEGER", "LEGES", "LEGGE", "LEGGO", "LEHRS", "LEIRS", "LEISH", "LENES", "LENGS", + "LENIS", "LENOS", "LENSE", "LEONE", "LEPRA", "LERES", "LERPS", "LESES", "LIANA", + "LIANE", "LIANG", "LIARS", "LICHI", "LIENS", "LIERS", "LIGAN", "LIGER", "LIGGE", + "LIGNE", "LILLS", "LILOS", "LINAC", "LINCH", "LINES", "LINGA", "LINGS", "LININ", + "LINNS", "LINOS", "LIONS", "LIPAS", "LIPES", "LIPIN", "LIPOS", "LIRAS", "LISLE", + "LISPS", "LLANO", "LOACH", "LOANS", "LOCHE", "LOCHS", "LOCIE", "LOCIS", "LOCOS", + "LOESS", "LOGAN", "LOGES", "LOGIA", "LOGIE", "LOGOI", "LOGON", "LOGOS", "LOHAN", + "LOINS", "LOIPE", "LOIRS", "LOLLS", "LOLOG", "LONER", "LONGA", "LONGE", "LONGS", + "LOOIE", "LOONS", "LOOPS", "LOPER", "LOPES", "LORAL", "LORAN", "LOREL", "LORES", + "LORIC", "LORIS", "LOSEL", "LOSEN", "LOSES", "NAANS", "NACHE", "NACHO", "NACRE", + "NAGAS", "NAGOR", "NAHAL", "NAILS", "NAIRA", "NALAS", "NALLA", "NANAS", "NANCE", + "NANNA", "NANOS", "NAPAS", "NAPES", "NAPOO", "NAPPA", "NAPPE", "NARAS", "NARCO", + "NARCS", "NARES", "NARIC", "NARIS", "NARRE", "NASHI", "NEALS", "NEAPS", "NEARS", + "NEELE", "NEEPS", "NEESE", "NEGRO", "NELIS", "NENES", "NEONS", "NEPER", "NERAL", + "NEROL", "NGAIO", "NGANA", "NICOL", "NIGER", "NIGHS", "NIHIL", "NILLS", "NINER", + "NINES", "NINON", "NIPAS", "NIRLS", "NISEI", "NISSE", "NOAHS", "NOELS", "NOGGS", + "NOILS", "NOIRS", "NOLES", "NOLLS", "NOLOS", "NONAS", "NONCE", "NONES", "NONGS", + "NONIS", "NOONS", "NOOPS", "NOPAL", "NORIA", "NORIS", "NOSER", "NOSES", "OASES", + "OASIS", "OCHER", "OCHES", "OCHRE", "OCREA", "OGEES", "OGGIN", "OGLER", "OGLES", + "OGRES", "OHIAS", "OHING", "OHONE", "OILER", "OLEIC", "OLEIN", "OLEOS", "OLIOS", + "OLLAS", "OLLER", "OLLIE", "OLPAE", "OLPES", "ONCER", "ONCES", "ONERS", "OORIE", + "OOSES", "OPAHS", "OPALS", "OPENS", "OPEPE", "OPING", "OPPOS", "OPSIN", "ORACH", + "ORALS", "ORANG", "ORCAS", "ORCIN", "ORGIA", "ORGIC", "ORIEL", "ORLES", "ORLON", + "ORLOP", "ORNIS", "ORPIN", "ORRIS", "OSCAR", "OSHAC", "OSIER", "OSSIA", "PAALS", + "PAANS", "PACAS", "PACER", "PACES", "PACHA", "PACOS", "PAEAN", "PAEON", "PAGER", + "PAGES", "PAGLE", "PAGRI", "PAILS", "PAINS", "PAIRE", "PAIRS", "PAISA", "PAISE", + "PALAS", "PALEA", "PALES", "PALIS", "PALLA", "PALLS", "PALPI", "PALPS", "PALSA", + "PANCE", "PANES", "PANGA", "PANGS", "PANNE", "PANNI", "PAOLI", "PAOLO", "PAPAS", + "PAPES", "PAPPI", "PARAE", "PARAS", "PARCH", "PAREN", "PAREO", "PARES", "PARGE", + "PARGO", "PARIS", "PARLE", "PAROL", "PARPS", "PARRA", "PARRS", "PASEO", "PASES", + "PASHA", "PASSE", "PEAGE", "PEAGS", "PEALS", "PEANS", "PEARE", "PEARS", "PEASE", + "PECHS", "PEECE", "PEELS", "PEENS", "PEEPE", "PEEPS", "PEERS", "PEGHS", "PEINS", + "PEISE", "PELAS", "PELES", "PELLS", "PELON", "PENES", "PENGO", "PENIE", "PENIS", + "PENNA", "PENNI", "PEONS", "PEPLA", "PEPOS", "PEPSI", "PERAI", "PERCE", "PERCS", + "PEREA", "PERES", "PERIS", "PERNS", "PEROG", "PERPS", "PERSE", "PESOS", "PHAGE", + "PHANG", "PHARE", "PHEER", "PHENE", "PHEON", "PHESE", "PHIAL", "PHISH", "PHOCA", + "PHONO", "PHONS", "PIANI", "PIANS", "PICAL", "PICAS", "PICRA", "PIERS", "PIING", + "PILAE", "PILAO", "PILAR", "PILCH", "PILEA", "PILEI", "PILER", "PILES", "PILIS", + "PILLS", "PINAS", "PINES", "PINGO", "PINGS", "PINNA", "PINON", "PIONS", "PIPAL", + "PIPAS", "PIPES", "PIPIS", "PIRAI", "PIRLS", "PIRNS", "PIROG", "PISCO", "PISES", + "PISOS", "PLAAS", "PLAGE", "PLANS", "PLAPS", "PLASH", "PLEAS", "PLENA", "PLEON", + "PLESH", "PLICA", "PLIES", "PLING", "PLONG", "PLOPS", "POACH", "POEPS", "POGGE", + "POGOS", "POLER", "POLES", "POLIO", "POLIS", "POLLS", "POLOS", "PONCE", "PONES", + "PONGA", "PONGO", "PONGS", "POOHS", "POOLS", "POONS", "POOPS", "POORI", "POPES", + "POPPA", "PORAE", "PORAL", "PORER", "PORES", "PORGE", "PORIN", "PORNO", "PORNS", + "POSES", "POSHO", "PRANA", "PRANG", "PRAOS", "PRASE", "PREES", "PREON", "PREOP", + "PREPS", "PRESA", "PRESE", "PRIAL", "PRIER", "PRIES", "PRIGS", "PRILL", "PRION", + "PRISE", "PRISS", "PROAS", "PROGS", "PROIN", "PROLE", "PROLL", "PROPS", "PRORE", + "PROSO", "PROSS", "PSION", "PSOAE", "PSOAI", "PSOAS", "PSORA", "RACES", "RACHE", + "RACON", "RAGAS", "RAGEE", "RAGER", "RAGES", "RAGGA", "RAGGS", "RAGIS", "RAIAS", + "RAILE", "RAILS", "RAINE", "RAINS", "RALES", "RANAS", "RANCE", "RANEE", "RANGA", + "RANGI", "RANGS", "RANIS", "RAPER", "RAPES", "RAPHE", "RAPPE", "RAREE", "RARES", + "RASER", "RASES", "RASPS", "RASSE", "REAIS", "REALO", "REALS", "REANS", "REAPS", + "REARS", "RECAL", "RECCE", "RECCO", "RECON", "REECH", "REELS", "REENS", "REGAR", + "REGES", "REGGO", "REGIE", "REGNA", "REGOS", "REINS", "RELIE", "RELLO", "RENGA", + "RENIG", "RENIN", "RENNE", "RENOS", "REOIL", "REORG", "REPEG", "REPIN", "REPLA", + "REPOS", "REPPS", "REPRO", "RERAN", "RERIG", "RESEE", "RESES", "RHEAS", "RHIES", + "RHINE", "RHONE", "RIALS", "RICER", "RICES", "RICIN", "RIELS", "RIGGS", "RIGOL", + "RILES", "RILLE", "RILLS", "RINES", "RINGS", "RIPES", "RIPPS", "RISES", "RISHI", + "RISPS", "ROANS", "ROARS", "ROHES", "ROILS", "ROINS", "ROLAG", "ROLES", "ROLLS", + "RONEO", "RONES", "RONIN", "RONNE", "ROONS", "ROOPS", "ROOSA", "ROOSE", "ROPER", + "ROPES", "RORAL", "RORES", "RORIC", "RORIE", "ROSES", "ROSHI", "ROSIN", "SAAGS", + "SACRA", "SAGAS", "SAGER", "SAGES", "SAGOS", "SAICE", "SAICS", "SAIGA", "SAILS", + "SAINE", "SAINS", "SAIRS", "SALAL", "SALEP", "SALES", "SALIC", "SALLE", "SALOL", + "SALOP", "SALPA", "SALPS", "SALSE", "SANES", "SANGA", "SANGH", "SANGO", "SANGS", + "SANSA", "SAOLA", "SAPAN", "SAPOR", "SARAN", "SAREE", "SARGE", "SARGO", "SARIN", + "SARIS", "SAROS", "SASER", "SASIN", "SASSE", "SCAGS", "SCAIL", "SCALA", "SCALL", + "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", "SCARS", "SCENA", "SCOGS", "SCOOG", + "SCOPA", "SCOPS", "SCRAE", "SCRAG", "SCRAN", "SCRIP", "SCROG", "SEALS", "SEANS", + "SEARE", "SEARS", "SEASE", "SECCO", "SECHS", "SEELS", "SEEPS", "SEERS", "SEGAR", + "SEGNI", "SEGNO", "SEGOL", "SEGOS", "SEHRI", "SEILS", "SEINE", "SEIRS", "SEISE", + "SELAH", "SELES", "SELLA", "SELLE", "SELLS", "SENAS", "SENES", "SENGI", "SENNA", + "SENOR", "SENSA", "SENSI", "SEPAL", "SEPIC", "SERAC", "SERAI", "SERAL", "SERER", + "SERES", "SERGE", "SERIC", "SERIN", "SERON", "SERRA", "SERRE", "SERRS", "SESSA", + "SHAGS", "SHAHS", "SHANS", "SHAPS", "SHARN", "SHASH", "SHCHI", "SHEAL", "SHEAS", + "SHEEL", "SHEOL", "SHERE", "SHERO", "SHIAI", "SHIEL", "SHIER", "SHIES", "SHILL", + "SHINS", "SHIPS", "SHIRR", "SHIRS", "SHISH", "SHISO", "SHLEP", "SHOER", "SHOES", + "SHOGI", "SHOGS", "SHOLA", "SHOOL", "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", + "SHRIS", "SIALS", "SICES", "SIENS", "SIGHS", "SIGIL", "SIGLA", "SIGNA", "SIGNS", + "SILEN", "SILER", "SILES", "SILLS", "SILOS", "SINES", "SINGS", "SINHS", "SIPES", + "SIREE", "SIRES", "SIRIH", "SIRIS", "SIROC", "SIRRA", "SISAL", "SISES", "SLAES", + "SLAGS", "SLANE", "SLAPS", "SLEER", "SLIER", "SLIPE", "SLIPS", "SLISH", "SLOAN", + "SLOES", "SLOGS", "SLOPS", "SNAGS", "SNAPS", "SNARS", "SNASH", "SNEAP", "SNEES", + "SNELL", "SNIES", "SNIGS", "SNIPS", "SNOEP", "SNOGS", "SNOOL", "SOAPS", "SOARE", + "SOARS", "SOCAS", "SOCES", "SOCLE", "SOGER", "SOILS", "SOLAH", "SOLAN", "SOLAS", + "SOLEI", "SOLER", "SOLES", "SOLON", "SOLOS", "SONCE", "SONES", "SONGS", "SONNE", + "SONSE", "SOOLE", "SOOLS", "SOOPS", "SOPHS", "SOPOR", "SOPRA", "SORAL", "SORAS", + "SOREE", "SOREL", "SORER", "SORES", "SORGO", "SORNS", "SORRA", "SPAER", "SPAES", + "SPAGS", "SPAHI", "SPAIL", "SPAIN", "SPALE", "SPALL", "SPANE", "SPANG", "SPANS", + "SPARS", "SPEAL", "SPEAN", "SPECS", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEOS", + "SPIAL", "SPICA", "SPICS", "SPIER", "SPIES", "SPILE", "SPINA", "SPINS", "SPLOG", + "SPOOR", "SPOSH", "SPRAG", "SPROG", }; -#if (USE_EXPANDED_DICT == 1) -static const char _expanded_words[][WORDLE_LENGTH + 1] = {}; -#endif - - -static const uint16_t _num_words = (sizeof(_legal_words) / sizeof(_legal_words[0])); -static const uint16_t _num_expanded_words = (sizeof(_expanded_words) / sizeof(_expanded_words[0])); +static const uint16_t _num_words = (sizeof(_valid_words) / sizeof(_valid_words[0])); +static const uint16_t _num_possible_words = (sizeof(_possible_words) / sizeof(_possible_words[0])); static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); static uint32_t get_random(uint32_t max) { @@ -211,24 +317,24 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { for (uint16_t i = 0; i < _num_words; i++) { is_exact_match = true; for (size_t j = 0; j < WORDLE_LENGTH; j++) { - if (_valid_letters[word_elements[j]] != _legal_words[i][j]) { + if (_valid_letters[word_elements[j]] != _valid_words[i][j]) { is_exact_match = false; break; } } if (is_exact_match) return i; } - for (uint16_t i = 0; i < _num_expanded_words; i++) { + for (uint16_t i = 0; i < _num_possible_words; i++) { is_exact_match = true; for (size_t j = 0; j < WORDLE_LENGTH; j++) { - if (_valid_letters[word_elements[j]] != _expanded_words[i][j]) { + if (_valid_letters[word_elements[j]] != _possible_words[i][j]) { is_exact_match = false; break; } } if (is_exact_match) return _num_words + i; } - return _num_words + _num_expanded_words; + return _num_words + _num_possible_words; } static bool check_word(wordle_state_t *state) { @@ -236,7 +342,7 @@ static bool check_word(wordle_state_t *state) { bool is_exact_match = true; bool answer_already_accounted[WORDLE_LENGTH] = { false }; for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][i]) { + if (_valid_letters[state->word_elements[i]] == _valid_words[state->curr_answer][i]) { state->word_elements_result[i] = WORDLE_LETTER_CORRECT; answer_already_accounted[i] = true; } @@ -251,7 +357,7 @@ static bool check_word(wordle_state_t *state) { if (state->word_elements_result[i] != WORDLE_LETTER_WRONG) continue; for (size_t j = 0; j < WORDLE_LENGTH; j++) { if (answer_already_accounted[j]) continue; - if (_valid_letters[state->word_elements[i]] == _legal_words[state->curr_answer][j]) { + if (_valid_letters[state->word_elements[i]] == _valid_words[state->curr_answer][j]) { state->word_elements_result[i] = WORDLE_LETTER_WRONG_LOC; answer_already_accounted[j] = true; break; @@ -279,7 +385,7 @@ static void reset_all_elements(wordle_state_t *state) { state->word_elements_result[i] = WORDLE_LETTER_WRONG; } for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { - state->guessed_words[i] = _num_words + _num_expanded_words; + state->guessed_words[i] = _num_words + _num_possible_words; } state->using_random_guess = false; state->attempt = 0; @@ -300,7 +406,7 @@ static void reset_board(wordle_state_t *state) { display_playing(state); watch_display_string(" -", 4); #if __EMSCRIPTEN__ - printf("ANSWER: %s\r\n", _legal_words[state->curr_answer]); + printf("ANSWER: %s\r\n", _valid_words[state->curr_answer]); #endif } @@ -368,7 +474,7 @@ static void display_already_guessed(wordle_state_t *state) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; - sprintf(buf," L %s", subsecond % 2 ? _legal_words[state->curr_answer] : " "); + sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } @@ -486,7 +592,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { static void get_result(wordle_state_t *state) { // Check if it's in the dict uint16_t in_dict = check_word_in_dict(state->word_elements); - if (in_dict == _num_words + _num_expanded_words) { + if (in_dict == _num_words + _num_possible_words) { display_not_in_dict(state); return; } @@ -522,7 +628,7 @@ static void get_result(wordle_state_t *state) { } #if (USE_RANDOM_GUESS != 0) -static const uint16_t _num_unique_words = 157; // The _legal_words array begins with this many words where each letter is different. +static const uint16_t _num_unique_words = 157; // The _valid_words array begins with this many words where each letter is different. static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer @@ -531,7 +637,7 @@ static void insert_random_guess(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { for (size_t j = 0; j < _num_valid_letters; j++) { - if (_legal_words[random_guess][i] == _valid_letters[j]) + if (_valid_words[random_guess][i] == _valid_letters[j]) state->word_elements[i] = j; } } diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 00c97b3..62a4571 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -71,13 +71,6 @@ */ #define USE_RANDOM_GUESS 2 -/* USE_EXPANDED_DICT - * 0 = don't use it at all (saves 2.8KB of ROM) - * 1 = Include the expanded dict in answers - * 2 = Only include it in the dict for guessing, but it's never an answer -*/ -#define USE_EXPANDED_DICT 2 - typedef enum { WORDLE_LETTER_WRONG = 0, WORDLE_LETTER_WRONG_LOC, diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index da370b7..ed90988 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1,953 +1,1101 @@ import random, itertools, time, ast -# From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff -legal_list = [ - "ABACK", "ABAFT", "ABASE", "ABATE", "ABBEY", "ABBOT", "ABHOR", "ABIDE", "ABLER", "ABODE", "ABOUT", "ABOVE", - "ABUSE", "ABYSS", "ACHED", "ACHES", "ACIDS", "ACORN", "ACRES", "ACRID", "ACTED", "ACTOR", "ACUTE", "ADAGE", - "ADAPT", "ADDED", "ADDER", "ADEPT", "ADIEU", "ADMIT", "ADOBE", "ADOPT", "ADORE", "ADORN", "ADULT", "AEGIS", - "AEONS", "AFFIX", "AFIRE", "AFOOT", "AFTER", "AGAIN", "AGAPE", "AGATE", "AGENT", "AGILE", "AGING", "AGLOW", - "AGONY", "AGREE", "AHEAD", "AIDED", "AIDES", "AILED", "AIMED", "AIRED", "AISLE", "ALARM", "ALBUM", "ALDER", - "ALERT", "ALIAS", "ALIBI", "ALIEN", "ALIKE", "ALIVE", "ALLAY", "ALLEY", "ALLOT", "ALLOW", "ALLOY", "ALOES", - "ALOFT", "ALONE", "ALONG", "ALOOF", "ALOUD", "ALPHA", "ALTAR", "ALTER", "ALTOS", "AMASS", "AMAZE", "AMBER", - "AMBLE", "AMEND", "AMIGO", "AMISS", "AMITY", "AMONG", "AMOUR", "AMPLE", "AMPLY", "AMUSE", "ANGEL", "ANGER", - "ANGLE", "ANGRY", "ANGST", "ANIME", "ANKLE", "ANNEX", "ANNOY", "ANNUL", "ANTES", "ANTIC", "ANVIL", "APACE", - "APART", "APING", "APPAL", "APPLE", "APPLY", "APRON", "APTLY", "AREAS", "ARENA", "ARGUE", "ARISE", "ARMED", - "AROMA", "AROSE", "ARRAY", "ARROW", "ARSON", "ASHEN", "ASHES", "ASIDE", "ASKED", "ASKEW", "ASPEN", "ASSAY", - "ASSES", "ASSET", "ASTER", "ASTIR", "ATLAS", "ATOLL", "ATOMS", "ATONE", "ATTAR", "ATTIC", "AUDIO", "AUDIT", - "AUGER", "AUGHT", "AUGUR", "AUNTS", "AURAS", "AUTOS", "AVAIL", "AVERS", "AVERT", "AVOID", "AVOWS", "AWAIT", - "AWAKE", "AWARD", "AWARE", "AWFUL", "AWOKE", "AXIOM", "AXLES", "AZURE", "BABEL", "BABES", "BACKS", "BACON", - "BADGE", "BADLY", "BAGGY", "BAITS", "BAIZE", "BAKED", "BAKER", "BALES", "BALLS", "BALMY", "BANAL", "BANDS", - "BANDY", "BANGS", "BANJO", "BANKS", "BANNS", "BARBS", "BARDS", "BARED", "BARGE", "BARKS", "BARNS", "BARON", - "BASAL", "BASED", "BASER", "BASES", "BASIC", "BASIL", "BASIN", "BASIS", "BASSO", "BASTE", "BATCH", "BATED", - "BATHE", "BATHS", "BATON", "BAYOU", "BEACH", "BEADS", "BEADY", "BEAKS", "BEAMS", "BEANS", "BEARD", "BEARS", - "BEAST", "BEAUX", "BEECH", "BEETS", "BEFIT", "BEGAN", "BEGAT", "BEGET", "BEGIN", "BEGOT", "BEGUN", "BEING", - "BELIE", "BELLE", "BELLS", "BELLY", "BELOW", "BELTS", "BENCH", "BENDS", "BERGS", "BERRY", "BERTH", "BERYL", - "BESET", "BESOM", "BEVEL", "BIBLE", "BIDED", "BIDES", "BIGHT", "BIGOT", "BILGE", "BILLS", "BILLY", "BINDS", - "BIPED", "BIRCH", "BIRDS", "BIRTH", "BISON", "BITCH", "BITES", "BLACK", "BLADE", "BLAME", "BLAND", "BLANK", - "BLARE", "BLAST", "BLAZE", "BLEAK", "BLEAT", "BLEED", "BLEND", "BLENT", "BLESS", "BLEST", "BLIND", "BLINK", - "BLISS", "BLOCK", "BLOCS", "BLOND", "BLOOD", "BLOOM", "BLOTS", "BLOWN", "BLOWS", "BLUER", "BLUES", "BLUFF", - "BLUNT", "BLURT", "BLUSH", "BOARD", "BOARS", "BOAST", "BOATS", "BODED", "BODES", "BOGGY", "BOGUS", "BOILS", - "BOLES", "BOLTS", "BOMBS", "BONDS", "BONED", "BONES", "BONNY", "BONUS", "BOOBY", "BOOKS", "BOOMS", "BOONS", - "BOORS", "BOOST", "BOOTH", "BOOTS", "BOOTY", "BOOZE", "BORAX", "BORED", "BORES", "BORNE", "BOSOM", "BOUGH", - "BOUND", "BOUTS", "BOWED", "BOWEL", "BOWER", "BOWLS", "BOXED", "BOXER", "BOXES", "BRACE", "BRAGS", "BRAID", - "BRAIN", "BRAKE", "BRAND", "BRASS", "BRATS", "BRAVE", "BRAVO", "BRAWL", "BRAWN", "BREAD", "BREAK", "BREED", - "BRIAR", "BRIBE", "BRICK", "BRIDE", "BRIEF", "BRIER", "BRIGS", "BRIMS", "BRINE", "BRING", "BRINK", "BRINY", - "BRISK", "BROAD", "BROIL", "BROKE", "BROOD", "BROOK", "BROOM", "BROTH", "BROWN", "BROWS", "BRUIN", "BRUNT", - "BRUSH", "BRUTE", "BUCKS", "BUDGE", "BUGGY", "BUGLE", "BUILD", "BUILT", "BULBS", "BULGE", "BULKS", "BULKY", - "BULLS", "BULLY", "BUMPS", "BUNCH", "BUNKS", "BUOYS", "BURLY", "BURNS", "BURNT", "BURRO", "BURRS", "BURST", - "BUSHY", "BUSTS", "BUTTE", "BUTTS", "BUXOM", "BUYER", "CABAL", "CABBY", "CABIN", "CABLE", "CACAO", "CACHE", - "CADET", "CADRE", "CAGED", "CAGES", "CAIRN", "CAKED", "CAKES", "CALLS", "CALMS", "CALYX", "CAMEL", "CAMEO", - "CAMPS", "CANAL", "CANDY", "CANES", "CANNY", "CANOE", "CANON", "CANTO", "CAPER", "CAPES", "CAPON", "CARDS", - "CARED", "CARES", "CARGO", "CAROL", "CARRY", "CARTS", "CARVE", "CASED", "CASES", "CASKS", "CASTE", "CASTS", - "CATCH", "CATER", "CAUSE", "CAVED", "CAVES", "CAVIL", "CEASE", "CEDAR", "CEDED", "CELLS", "CENTS", "CHAFE", - "CHAFF", "CHAIN", "CHAIR", "CHALK", "CHAMP", "CHANT", "CHAOS", "CHAPS", "CHARM", "CHART", "CHARY", "CHASE", - "CHASM", "CHATS", "CHEAP", "CHEAT", "CHECK", "CHEEK", "CHEER", "CHEFS", "CHESS", "CHEST", "CHICK", "CHIDE", - "CHIEF", "CHILD", "CHILL", "CHIME", "CHINA", "CHINK", "CHINS", "CHIPS", "CHIRP", "CHOIR", "CHOKE", "CHOPS", - "CHORD", "CHOSE", "CHUCK", "CHUMP", "CHUMS", "CHUNK", "CHURL", "CHURN", "CHUTE", "CIDER", "CIGAR", "CINCH", - "CIRCA", "CITED", "CITES", "CIVET", "CIVIC", "CIVIL", "CLACK", "CLAIM", "CLAMP", "CLAMS", "CLANG", "CLANK", - "CLANS", "CLAPS", "CLASH", "CLASP", "CLASS", "CLAWS", "CLEAN", "CLEAR", "CLEFS", "CLEFT", "CLERK", "CLEWS", - "CLICK", "CLIFF", "CLIMB", "CLIME", "CLING", "CLINK", "CLIPS", "CLOAK", "CLOCK", "CLODS", "CLOGS", "CLOSE", - "CLOTH", "CLOUD", "CLOUT", "CLOVE", "CLOWN", "CLUBS", "CLUCK", "CLUES", "CLUMP", "CLUNG", "COACH", "COALS", - "COAST", "COATS", "COBRA", "COCKS", "COCOA", "CODES", "COILS", "COINS", "COLDS", "COLIC", "COLON", "COLTS", - "COMBS", "COMER", "COMES", "COMET", "COMIC", "COMMA", "CONCH", "CONES", "CONIC", "COOED", "COOKS", "COOLS", - "COPRA", "COPSE", "CORAL", "CORDS", "CORES", "CORKS", "CORNS", "CORPS", "COSTS", "COTES", "COUCH", "COUGH", - "COULD", "COUNT", "COUPE", "COUPS", "COURT", "COVER", "COVES", "COVET", "COVEY", "COWED", "COWER", "COYLY", - "COZEN", "CRABS", "CRACK", "CRAFT", "CRAGS", "CRAMP", "CRANE", "CRANK", "CRAPE", "CRASH", "CRASS", "CRATE", - "CRAVE", "CRAWL", "CRAZE", "CRAZY", "CREAK", "CREAM", "CREDO", "CREED", "CREEK", "CREEP", "CREPE", "CREPT", - "CRESS", "CREST", "CREWS", "CRIBS", "CRICK", "CRIED", "CRIER", "CRIES", "CRIME", "CRIMP", "CRISP", "CROAK", - "CROCK", "CRONE", "CRONY", "CROOK", "CROPS", "CROSS", "CROUP", "CROWD", "CROWN", "CROWS", "CRUDE", "CRUEL", - "CRUMB", "CRUSH", "CRUST", "CRYPT", "CUBES", "CUBIC", "CUBIT", "CUFFS", "CULTS", "CURDS", "CURED", "CURES", - "CURLS", "CURLY", "CURRY", "CURSE", "CURST", "CURVE", "CYCLE", "CYNIC", "DADDY", "DAILY", "DAIRY", "DAISY", - "DALES", "DALLY", "DAMES", "DAMPS", "DANCE", "DANDY", "DARED", "DARES", "DARTS", "DATED", "DATES", "DATUM", - "DAUBS", "DAUNT", "DAWNS", "DAZED", "DEALS", "DEALT", "DEANS", "DEARS", "DEATH", "DEBAR", "DEBIT", "DEBTS", - "DEBUT", "DECAY", "DECKS", "DECOY", "DECRY", "DEEDS", "DEEMS", "DEEPS", "DEFER", "DEIGN", "DEITY", "DELAY", - "DELLS", "DELTA", "DELVE", "DEMON", "DEMUR", "DENSE", "DENTS", "DEPOT", "DEPTH", "DERBY", "DESKS", "DETER", - "DEUCE", "DEVIL", "DIARY", "DICED", "DICES", "DICTA", "DIETS", "DIGIT", "DIKES", "DIMES", "DIMLY", "DINED", - "DINER", "DINES", "DINGY", "DIRGE", "DIRTY", "DISCS", "DISKS", "DITCH", "DITTO", "DITTY", "DIVAN", "DIVED", - "DIVER", "DIVES", "DIZZY", "DOCKS", "DODGE", "DOERS", "DOGMA", "DOING", "DOLED", "DOLLS", "DOMED", "DOMES", - "DONOR", "DOOMS", "DOORS", "DOSED", "DOSES", "DOTED", "DOTES", "DOUBT", "DOUGH", "DOVES", "DOWDY", "DOWNS", - "DOWNY", "DOWRY", "DOZED", "DOZEN", "DRAFT", "DRAGS", "DRAIN", "DRAKE", "DRAMA", "DRAMS", "DRANK", "DRAPE", - "DRAWL", "DRAWN", "DRAWS", "DRAYS", "DREAD", "DREAM", "DREGS", "DRESS", "DRIED", "DRIER", "DRIES", "DRIFT", - "DRILL", "DRILY", "DRINK", "DRIPS", "DRIVE", "DROLL", "DRONE", "DROOP", "DROPS", "DROSS", "DROVE", "DROWN", - "DRUGS", "DRUMS", "DRUNK", "DRYLY", "DUCAL", "DUCAT", "DUCHY", "DUCKS", "DUCTS", "DUELS", "DUETS", "DUKES", - "DULLY", "DUMMY", "DUMPS", "DUMPY", "DUNCE", "DUNES", "DUNNO", "DUPED", "DUPES", "DUSKY", "DUSTY", "DWARF", - "DWELL", "DWELT", "DYING", "DYKES", "EAGER", "EAGLE", "EARLS", "EARLY", "EARNS", "EARTH", "EASED", "EASEL", - "EASES", "EATEN", "EATER", "EAVES", "EBBED", "EBONY", "EDGED", "EDGES", "EDICT", "EDIFY", "EERIE", "EGGED", - "EIGHT", "EJECT", "ELATE", "ELBOW", "ELDER", "ELECT", "ELEGY", "ELFIN", "ELITE", "ELOPE", "ELUDE", "ELVES", - "EMAIL", "EMITS", "EMPTY", "ENACT", "ENDED", "ENDOW", "ENEMY", "ENJOY", "ENNUI", "ENROL", "ENSUE", "ENTER", - "ENTRY", "ENVOY", "EPICS", "EPOCH", "EQUAL", "EQUIP", "ERASE", "ERECT", "ERRED", "ERROR", "ESSAY", "ETHER", - "ETHIC", "EVADE", "EVENT", "EVERY", "EVILS", "EVOKE", "EXACT", "EXALT", "EXCEL", "EXERT", "EXILE", "EXIST", - "EXITS", "EXPEL", "EXTOL", "EXTRA", "EXULT", "EYING", "EYRIE", "FABLE", "FACED", "FACES", "FACTS", "FADED", - "FADES", "FAILS", "FAINT", "FAIRS", "FAIRY", "FAITH", "FAKIR", "FALLS", "FALSE", "FAMED", "FANCY", "FANGS", - "FARCE", "FARED", "FARES", "FARMS", "FASTS", "FATAL", "FATED", "FATES", "FATTY", "FAULT", "FAUNA", "FAUNS", - "FAWNS", "FEARS", "FEAST", "FEATS", "FEEDS", "FEELS", "FEIGN", "FEINT", "FELLS", "FELON", "FENCE", "FERAL", - "FERNS", "FERRY", "FETCH", "FETED", "FETID", "FETUS", "FEUDS", "FEVER", "FEWER", "FICHE", "FIEFS", "FIELD", - "FIEND", "FIERY", "FIFES", "FIFTH", "FIFTY", "FIGHT", "FILCH", "FILED", "FILES", "FILET", "FILLS", "FILLY", - "FILMS", "FILMY", "FILTH", "FINAL", "FINCH", "FINDS", "FINED", "FINER", "FINES", "FINIS", "FINNY", "FIORD", - "FIRED", "FIRES", "FIRMS", "FIRST", "FISHY", "FISTS", "FITLY", "FIVES", "FIXED", "FIXER", "FIXES", "FJORD", - "FLAGS", "FLAIL", "FLAIR", "FLAKE", "FLAKY", "FLAME", "FLANK", "FLAPS", "FLARE", "FLASH", "FLASK", "FLATS", - "FLAWS", "FLEAS", "FLECK", "FLEES", "FLEET", "FLESH", "FLICK", "FLIER", "FLIES", "FLING", "FLINT", "FLIRT", - "FLITS", "FLOAT", "FLOCK", "FLOES", "FLOOD", "FLOOR", "FLORA", "FLOSS", "FLOUR", "FLOUT", "FLOWN", "FLOWS", - "FLUES", "FLUFF", "FLUID", "FLUKE", "FLUME", "FLUNG", "FLUSH", "FLUTE", "FLYER", "FOAMS", "FOAMY", "FOCAL", - "FOCUS", "FOGGY", "FOILS", "FOIST", "FOLDS", "FOLIO", "FOLKS", "FOLLY", "FOODS", "FOOLS", "FORAY", "FORCE", - "FORDS", "FORGE", "FORGO", "FORKS", "FORMS", "FORTE", "FORTH", "FORTS", "FORTY", "FORUM", "FOUND", "FOUNT", - "FOURS", "FOWLS", "FOXES", "FOYER", "FRAIL", "FRAME", "FRANC", "FRANK", "FRAUD", "FREAK", "FREED", "FREER", - "FREES", "FRESH", "FRETS", "FRIAR", "FRIED", "FRILL", "FRISK", "FROCK", "FROGS", "FROND", "FRONT", "FROST", - "FROTH", "FROWN", "FROZE", "FRUIT", "FUDGE", "FUELS", "FUGUE", "FULLY", "FUMED", "FUMES", "FUNDS", "FUNGI", - "FUNNY", "FURRY", "FURZE", "FUSED", "FUSES", "FUSSY", "FUZZY", "GABLE", "GAILY", "GAINS", "GALES", "GALLS", - "GAMES", "GAMIN", "GAMMA", "GAMUT", "GANGS", "GAPED", "GAPES", "GASES", "GASPS", "GATES", "GAUDY", "GAUGE", - "GAUNT", "GAUZE", "GAUZY", "GAVEL", "GAWKY", "GAYER", "GAYLY", "GAZED", "GAZER", "GAZES", "GEARS", "GEESE", - "GENIE", "GENII", "GENRE", "GENTS", "GENUS", "GERMS", "GHOST", "GIANT", "GIBES", "GIDDY", "GIFTS", "GILDS", - "GILLS", "GIMME", "GIPSY", "GIRDS", "GIRLS", "GIRTH", "GIVEN", "GIVES", "GLADE", "GLAND", "GLARE", "GLASS", - "GLAZE", "GLEAM", "GLEAN", "GLENS", "GLIDE", "GLINT", "GLOAT", "GLOBE", "GLOOM", "GLORY", "GLOSS", "GLOVE", - "GLOWS", "GLUED", "GNASH", "GNATS", "GNAWS", "GNOME", "GOADS", "GOALS", "GOATS", "GODLY", "GOING", "GOLLY", - "GONGS", "GONNA", "GOODS", "GOODY", "GOOSE", "GORED", "GORGE", "GORSE", "GOTTA", "GOUGE", "GOURD", "GOUTY", - "GOWNS", "GRABS", "GRACE", "GRADE", "GRAFT", "GRAIN", "GRAMS", "GRAND", "GRANT", "GRAPE", "GRAPH", "GRASP", - "GRASS", "GRATE", "GRAVE", "GRAVY", "GRAZE", "GREAT", "GREED", "GREEN", "GREET", "GREYS", "GRIEF", "GRILL", - "GRIME", "GRIMY", "GRIND", "GRINS", "GRIPE", "GRIPS", "GRIST", "GROAN", "GROIN", "GROOM", "GROPE", "GROSS", - "GROUP", "GROVE", "GROWL", "GROWN", "GROWS", "GRUBS", "GRUEL", "GRUFF", "GRUNT", "GUANO", "GUARD", "GUESS", - "GUEST", "GUIDE", "GUILD", "GUILE", "GUILT", "GUISE", "GULCH", "GULFS", "GULLS", "GULLY", "GUMMY", "GUSTO", - "GUSTS", "GUSTY", "GYPSY", "HABIT", "HACKS", "HAILS", "HAIRS", "HAIRY", "HALED", "HALLS", "HALTS", "HALVE", - "HANDS", "HANDY", "HANGS", "HAPPY", "HARDY", "HAREM", "HARES", "HARMS", "HARPS", "HARPY", "HARRY", "HARSH", - "HARTS", "HASTE", "HASTY", "HATCH", "HATED", "HATER", "HAULS", "HAVEN", "HAVOC", "HAWKS", "HAZEL", "HEADS", - "HEADY", "HEALS", "HEAPS", "HEARD", "HEARS", "HEART", "HEATH", "HEATS", "HEAVE", "HEAVY", "HEDGE", "HEEDS", - "HEELS", "HEIRS", "HELIX", "HELLO", "HELMS", "HELPS", "HENCE", "HERBS", "HERDS", "HERON", "HEROS", "HEWED", - "HIDES", "HILLS", "HILLY", "HILTS", "HINDS", "HINGE", "HINTS", "HIRED", "HIRES", "HITCH", "HIVES", "HOARD", - "HOARY", "HOBBY", "HOIST", "HOLDS", "HOLES", "HOLLY", "HOMES", "HONEY", "HOODS", "HOOFS", "HOOKS", "HOOPS", - "HOOTS", "HOPED", "HOPES", "HORDE", "HORNS", "HORNY", "HORSE", "HOSTS", "HOTEL", "HOTLY", "HOUND", "HOURS", - "HOUSE", "HOVEL", "HOVER", "HOWLS", "HULKS", "HULLS", "HUMAN", "HUMID", "HUMPS", "HUMUS", "HUNCH", "HUNTS", - "HURLS", "HURRY", "HURTS", "HUSKS", "HUSKY", "HUSSY", "HYDRA", "HYENA", "HYMNS", "ICILY", "ICING", "IDEAL", - "IDEAS", "IDIOM", "IDIOT", "IDLED", "IDLER", "IDOLS", "IDYLL", "IGLOO", "IMAGE", "IMBUE", "IMPEL", "IMPLY", - "INANE", "INCUR", "INDEX", "INEPT", "INERT", "INFER", "INGOT", "INLET", "INNER", "INTER", "INURE", "IRATE", - "IRKED", "IRONS", "IRONY", "ISLES", "ISLET", "ISSUE", "ITEMS", "IVORY", "JACKS", "JADED", "JAILS", "JAUNT", - "JEANS", "JEERS", "JELLY", "JERKS", "JERKY", "JESTS", "JETTY", "JEWEL", "JIFFY", "JOINS", "JOINT", "JOKED", - "JOKER", "JOKES", "JOLLY", "JOUST", "JOYED", "JUDGE", "JUICE", "JUICY", "JUMPS", "JUNKS", "JUNTA", "JUROR", - "KARMA", "KEELS", "KEEPS", "KETCH", "KEYED", "KHAKI", "KICKS", "KILLS", "KINDA", "KINDS", "KINGS", "KIOSK", - "KITES", "KNACK", "KNAVE", "KNEAD", "KNEEL", "KNEES", "KNELL", "KNELT", "KNIFE", "KNITS", "KNOBS", "KNOCK", - "KNOLL", "KNOTS", "KNOWN", "KNOWS", "LABEL", "LACED", "LACES", "LACKS", "LADEN", "LADLE", "LAGER", "LAIRS", - "LAITY", "LAKES", "LAMBS", "LAMED", "LAMES", "LAMPS", "LANCE", "LANDS", "LANES", "LANKY", "LAPEL", "LAPSE", - "LARCH", "LARGE", "LARGO", "LARKS", "LARVA", "LASSO", "LASTS", "LATCH", "LATER", "LATHE", "LATHS", "LAUGH", - "LAWNS", "LAYER", "LEADS", "LEAFY", "LEAKS", "LEAKY", "LEANS", "LEAPS", "LEAPT", "LEARN", "LEASE", "LEASH", - "LEAST", "LEAVE", "LEDGE", "LEECH", "LEEKS", "LEGAL", "LEMME", "LEMON", "LENDS", "LEPER", "LEVEE", "LEVEL", - "LEVER", "LIARS", "LIBEL", "LICKS", "LIEGE", "LIENS", "LIFTS", "LIGHT", "LIKED", "LIKEN", "LIKER", "LIKES", - "LILAC", "LIMBO", "LIMBS", "LIMES", "LIMIT", "LINED", "LINEN", "LINER", "LINES", "LINGO", "LINKS", "LIONS", - "LISTS", "LITHE", "LIVED", "LIVER", "LIVES", "LIVID", "LLAMA", "LOADS", "LOAMY", "LOANS", "LOATH", "LOBBY", - "LOBES", "LOCAL", "LOCKS", "LOCUS", "LODGE", "LOFTY", "LOGES", "LOGIC", "LOGIN", "LOINS", "LONGS", "LOOKS", - "LOOMS", "LOONS", "LOOPS", "LOOSE", "LORDS", "LOSER", "LOSES", "LOTUS", "LOUSE", "LOUSY", "LOVED", "LOVER", - "LOVES", "LOWED", "LOWER", "LOWLY", "LOYAL", "LUCID", "LUCKY", "LULLS", "LUMPS", "LUMPY", "LUNAR", "LUNCH", - "LUNGE", "LUNGS", "LURCH", "LURED", "LURES", "LURID", "LURKS", "LUSTS", "LUSTY", "LUTES", "LYING", "LYMPH", - "LYNCH", "LYRIC", "MACES", "MADAM", "MADLY", "MAGIC", "MAIDS", "MAILS", "MAINS", "MAIZE", "MAJOR", "MAKER", - "MAKES", "MALES", "MAMMA", "MANES", "MANGA", "MANGE", "MANGO", "MANGY", "MANIA", "MANLY", "MANNA", "MANOR", - "MANSE", "MAPLE", "MARCH", "MARES", "MARKS", "MARRY", "MARSH", "MARTS", "MASKS", "MASON", "MASTS", "MATCH", - "MATED", "MATES", "MAUVE", "MAXIM", "MAYBE", "MAYOR", "MAZES", "MEALS", "MEALY", "MEANS", "MEANT", "MEATS", - "MEDAL", "MEDIA", "MEETS", "MELON", "MELTS", "MEMES", "MENDS", "MENUS", "MERCY", "MERES", "MERGE", "MERIT", - "MERRY", "MESAS", "METAL", "METED", "METER", "MEWED", "MIDST", "MIENS", "MIGHT", "MILCH", "MILES", "MILKY", - "MILLS", "MIMES", "MIMIC", "MINCE", "MINDS", "MINED", "MINER", "MINES", "MINOR", "MINTS", "MINUS", "MIRTH", - "MISER", "MISTS", "MITES", "MIXED", "MIXES", "MOANS", "MOATS", "MOCKS", "MODEL", "MODEM", "MODES", "MOIST", - "MOLAR", "MOLES", "MOMMA", "MONEY", "MONKS", "MONTH", "MOODS", "MOODY", "MOONS", "MOORS", "MOOSE", "MOPED", - "MORAL", "MORES", "MOSSY", "MOTES", "MOTHS", "MOTIF", "MOTOR", "MOTTO", "MOUND", "MOUNT", "MOURN", "MOUSE", - "MOUTH", "MOVED", "MOVER", "MOVES", "MOVIE", "MOWED", "MOWER", "MUCUS", "MUDDY", "MULES", "MULTI", "MUMMY", - "MUMPS", "MUNCH", "MURAL", "MURKY", "MUSED", "MUSES", "MUSIC", "MUSKY", "MUSTY", "MUTED", "MUTES", "MYRRH", - "MYTHS", "NABOB", "NAILS", "NAIVE", "NAKED", "NAMED", "NAMES", "NASAL", "NASTY", "NATAL", "NATTY", "NAVAL", - "NAVEL", "NAVES", "NEARS", "NECKS", "NEEDS", "NEEDY", "NEIGH", "NERVE", "NESTS", "NEVER", "NEWER", "NEWLY", - "NICER", "NICHE", "NIECE", "NIGHT", "NINNY", "NOBLE", "NOBLY", "NOISE", "NOISY", "NOMAD", "NONCE", "NOOKS", - "NOOSE", "NORTH", "NOSED", "NOSES", "NOTCH", "NOTED", "NOTES", "NOUNS", "NOVEL", "NUDGE", "NURSE", "NYMPH", - "OAKEN", "OAKUM", "OASES", "OASIS", "OATEN", "OATHS", "OBESE", "OBEYS", "OCCUR", "OCEAN", "OCHRE", "ODDER", - "ODDLY", "ODIUM", "OFFAL", "OFFER", "OFTEN", "OILED", "OLDEN", "OLDER", "OMENS", "OMITS", "ONION", "ONSET", - "OOZED", "OOZES", "OPALS", "OPENS", "OPERA", "OPINE", "OPIUM", "OPTIC", "ORBIT", "ORDER", "ORGAN", "OSIER", - "OTHER", "OTTER", "OUGHT", "OUNCE", "OUTDO", "OUTER", "OVALS", "OVARY", "OVENS", "OVERT", "OWING", "OWNED", - "OWNER", "OXIDE", "OZONE", "PACES", "PACKS", "PADDY", "PADRE", "PAEAN", "PAGAN", "PAGES", "PAILS", "PAINS", - "PAINT", "PAIRS", "PALED", "PALER", "PALES", "PALMS", "PALMY", "PALSY", "PANEL", "PANES", "PANGS", "PANIC", - "PANSY", "PANTS", "PAPAL", "PAPAS", "PAPER", "PARED", "PARKA", "PARKS", "PARRY", "PARSE", "PARTS", "PARTY", - "PASHA", "PASTE", "PASTY", "PATCH", "PATES", "PATHS", "PATIO", "PAUSE", "PAVED", "PAWED", "PAWNS", "PAYED", - "PAYER", "PEACE", "PEACH", "PEAKS", "PEALS", "PEARL", "PEARS", "PEASE", "PECKS", "PEDAL", "PEEPS", "PEERS", - "PELTS", "PENAL", "PENCE", "PENIS", "PENNY", "PEONS", "PERCH", "PERIL", "PESKY", "PESOS", "PESTS", "PETAL", - "PETTY", "PHASE", "PHIAL", "PHONE", "PHOTO", "PIANO", "PICKS", "PIECE", "PIERS", "PIETY", "PIGMY", "PIKES", - "PILED", "PILES", "PILLS", "PILOT", "PINCH", "PINED", "PINES", "PINKS", "PINTO", "PINTS", "PIOUS", "PIPED", - "PIPER", "PIPES", "PIQUE", "PITCH", "PITHY", "PIVOT", "PLACE", "PLAID", "PLAIN", "PLAIT", "PLANE", "PLANK", - "PLANS", "PLANT", "PLATE", "PLAYS", "PLAZA", "PLEAD", "PLEAS", "PLIED", "PLIES", "PLOTS", "PLUCK", "PLUGS", - "PLUMB", "PLUME", "PLUMS", "PLUSH", "PODIA", "POEMS", "POESY", "POETS", "POINT", "POISE", "POKED", "POKER", - "POKES", "POLAR", "POLES", "POLKA", "POLLS", "PONDS", "POOLS", "POPES", "POPPA", "POPPY", "PORCH", "PORED", - "PORES", "PORTS", "POSED", "POSER", "POSES", "POSSE", "POSTS", "POUCH", "POUND", "POURS", "POWER", "PRANK", - "PRATE", "PRAYS", "PRESS", "PREYS", "PRICE", "PRICK", "PRIDE", "PRIED", "PRIES", "PRIME", "PRINT", "PRIOR", - "PRISM", "PRIVY", "PRIZE", "PROBE", "PRONE", "PROOF", "PROPS", "PROSE", "PROSY", "PROUD", "PROVE", "PROWL", - "PROWS", "PROXY", "PRUDE", "PRUNE", "PSALM", "PSHAW", "PUDGY", "PUFFS", "PUFFY", "PULLS", "PULPY", "PULSE", - "PUMPS", "PUNCH", "PUPIL", "PUPPY", "PUREE", "PURER", "PURGE", "PURSE", "PUSSY", "PUTTY", "QUACK", "QUAFF", - "QUAIL", "QUAKE", "QUALM", "QUART", "QUASI", "QUAYS", "QUEEN", "QUEER", "QUELL", "QUERY", "QUEST", "QUEUE", - "QUICK", "QUIET", "QUILL", "QUILT", "QUIPS", "QUIRE", "QUITE", "QUITS", "QUOTA", "QUOTE", "QUOTH", "RABBI", - "RABID", "RACED", "RACER", "RACES", "RACKS", "RADII", "RADIO", "RAFTS", "RAGED", "RAGES", "RAIDS", "RAILS", - "RAINS", "RAINY", "RAISE", "RAJAH", "RAKED", "RAKES", "RALLY", "RANCH", "RANGE", "RANKS", "RAPID", "RARER", - "RARES", "RATED", "RATES", "RATIO", "RAVED", "RAVEN", "RAVES", "RAYON", "RAZED", "RAZOR", "REACH", "REACT", - "READS", "READY", "REALM", "REALS", "REAMS", "REAPS", "REARS", "REBEL", "REBUS", "REBUT", "RECUR", "REEDS", - "REEDY", "REEFS", "REEKS", "REELS", "REEVE", "REFER", "REFIT", "REGAL", "REIGN", "REINS", "RELAX", "RELAY", - "RELIC", "REMIT", "RENDS", "RENEW", "RENTS", "REPAY", "REPEL", "REPLY", "RESET", "RESIN", "RESTS", "REVEL", - "REVUE", "RHEUM", "RHYME", "RICKS", "RIDER", "RIDES", "RIDGE", "RIFLE", "RIFTS", "RIGHT", "RIGID", "RILED", - "RILLS", "RIMES", "RINGS", "RINSE", "RIOTS", "RIPEN", "RIPER", "RISEN", "RISER", "RISES", "RISKS", "RISKY", - "RITES", "RIVAL", "RIVEN", "RIVER", "RIVET", "ROADS", "ROAMS", "ROARS", "ROAST", "ROBED", "ROBES", "ROBIN", - "ROCKS", "ROCKY", "ROGUE", "ROLES", "ROLLS", "ROMAN", "ROOFS", "ROOKS", "ROOMS", "ROOMY", "ROOST", "ROOTS", - "ROPED", "ROPES", "ROSES", "ROSIN", "ROUGE", "ROUGH", "ROUND", "ROUSE", "ROUTE", "ROUTS", "ROVED", "ROVER", - "ROWDY", "ROWED", "ROYAL", "RUDER", "RUFFS", "RUINS", "RULED", "RULER", "RULES", "RUNES", "RUNGS", "RUPEE", - "RURAL", "RUSES", "SABLE", "SABRE", "SACKS", "SADLY", "SAFER", "SAGAS", "SAGES", "SAHIB", "SAILS", "SAINT", - "SAITH", "SALAD", "SALES", "SALLY", "SALON", "SALSA", "SALTS", "SALTY", "SALVE", "SALVO", "SANDS", "SANDY", - "SANER", "SATED", "SATIN", "SATYR", "SAUCE", "SAUCY", "SAVED", "SAVES", "SAWED", "SCALD", "SCALE", "SCALP", - "SCALY", "SCAMP", "SCANS", "SCANT", "SCARE", "SCARF", "SCARS", "SCENE", "SCENT", "SCION", "SCOFF", "SCOLD", - "SCOOP", "SCOPE", "SCORE", "SCORN", "SCOUR", "SCOUT", "SCOWL", "SCRAP", "SCREW", "SCRIP", "SCRUB", "SCULL", - "SEALS", "SEAMS", "SEAMY", "SEATS", "SECTS", "SEDAN", "SEDGE", "SEEDS", "SEEDY", "SEEKS", "SEEMS", "SEERS", - "SEIZE", "SELLS", "SEMEN", "SENDS", "SENSE", "SERFS", "SERGE", "SERUM", "SERVE", "SEVEN", "SEVER", "SEWED", - "SEWER", "SEXES", "SHACK", "SHADE", "SHADY", "SHAFT", "SHAKE", "SHAKY", "SHALE", "SHALL", "SHALT", "SHAME", - "SHAMS", "SHANK", "SHAPE", "SHARE", "SHARK", "SHARP", "SHAVE", "SHAWL", "SHEAF", "SHEAR", "SHEDS", "SHEEN", - "SHEEP", "SHEER", "SHEET", "SHEIK", "SHELF", "SHELL", "SHIED", "SHIFT", "SHINE", "SHINS", "SHINY", "SHIPS", - "SHIRE", "SHIRK", "SHIRT", "SHOAL", "SHOCK", "SHOES", "SHONE", "SHOOK", "SHOON", "SHOOT", "SHOPS", "SHORE", - "SHORN", "SHORT", "SHOTS", "SHOUT", "SHOVE", "SHOWN", "SHOWS", "SHOWY", "SHRED", "SHREW", "SHRUB", "SHRUG", - "SHUNS", "SHUTS", "SHYLY", "SIBYL", "SIDED", "SIDES", "SIEGE", "SIEVE", "SIGHS", "SIGHT", "SIGMA", "SIGNS", - "SILKS", "SILKY", "SILLS", "SILLY", "SINCE", "SINEW", "SINGE", "SINGS", "SINKS", "SIREN", "SIRES", "SITES", - "SIXES", "SIXTH", "SIXTY", "SIZED", "SIZES", "SKATE", "SKEIN", "SKIES", "SKIFF", "SKILL", "SKIMS", "SKINS", - "SKIPS", "SKIRT", "SKULK", "SKULL", "SKUNK", "SLABS", "SLACK", "SLAGS", "SLAIN", "SLAKE", "SLANG", "SLANT", - "SLAPS", "SLASH", "SLATE", "SLATS", "SLAVE", "SLAYS", "SLEDS", "SLEEK", "SLEEP", "SLEET", "SLEPT", "SLICE", - "SLICK", "SLIDE", "SLILY", "SLIME", "SLIMY", "SLING", "SLINK", "SLIPS", "SLITS", "SLOOP", "SLOPE", "SLOPS", - "SLOTH", "SLUGS", "SLUMP", "SLUMS", "SLUNG", "SLUNK", "SLUSH", "SLYLY", "SMACK", "SMALL", "SMART", "SMASH", - "SMEAR", "SMELL", "SMELT", "SMILE", "SMIRK", "SMITE", "SMITH", "SMOCK", "SMOKE", "SMOKY", "SMOTE", "SNACK", - "SNAGS", "SNAIL", "SNAKE", "SNAKY", "SNAPS", "SNARE", "SNARL", "SNEAK", "SNEER", "SNIFF", "SNIPE", "SNOBS", - "SNORE", "SNORT", "SNOUT", "SNOWS", "SNOWY", "SNUFF", "SOAPY", "SOARS", "SOBER", "SOCKS", "SOFAS", "SOGGY", - "SOILS", "SOLAR", "SOLES", "SOLID", "SOLOS", "SOLVE", "SONGS", "SONNY", "SOOTH", "SOOTY", "SORES", "SORRY", - "SORTS", "SOUGH", "SOULS", "SOUND", "SOUPS", "SOUSE", "SOUTH", "SOWED", "SOWER", "SPACE", "SPADE", "SPAKE", - "SPANK", "SPANS", "SPARE", "SPARK", "SPARS", "SPASM", "SPAWN", "SPEAK", "SPEAR", "SPECK", "SPEED", "SPELL", - "SPELT", "SPEND", "SPENT", "SPERM", "SPICE", "SPICY", "SPIED", "SPIES", "SPIKE", "SPILL", "SPILT", "SPINE", - "SPINS", "SPINY", "SPIRE", "SPITE", "SPITS", "SPLIT", "SPOIL", "SPOKE", "SPOOK", "SPOOL", "SPOON", "SPOOR", - "SPORE", "SPORT", "SPOTS", "SPOUT", "SPRAY", "SPREE", "SPRIG", "SPUNK", "SPURN", "SPURS", "SPURT", "SQUAD", - "SQUAT", "SQUAW", "STABS", "STACK", "STAFF", "STAGE", "STAGS", "STAID", "STAIN", "STAIR", "STAKE", "STALE", - "STALK", "STALL", "STAMP", "STAND", "STANK", "STARE", "STARK", "STARS", "START", "STATE", "STAVE", "STAYS", - "STEAD", "STEAK", "STEAL", "STEAM", "STEED", "STEEL", "STEEP", "STEER", "STEMS", "STEPS", "STERN", "STEWS", - "STICK", "STIFF", "STILE", "STILL", "STING", "STINK", "STINT", "STIRS", "STOCK", "STOIC", "STOLE", "STONE", - "STONY", "STOOD", "STOOL", "STOOP", "STOPS", "STORE", "STORK", "STORM", "STORY", "STOUT", "STOVE", "STRAP", - "STRAW", "STRAY", "STREW", "STRIP", "STRUT", "STUCK", "STUDS", "STUDY", "STUFF", "STUMP", "STUNG", "STUNT", - "STYLE", "SUAVE", "SUCKS", "SUGAR", "SUING", "SUITE", "SUITS", "SULKS", "SULKY", "SULLY", "SUNNY", "SUPER", - "SURER", "SURGE", "SURLY", "SWAIN", "SWAMP", "SWANS", "SWARD", "SWARM", "SWAYS", "SWEAR", "SWEAT", "SWEEP", - "SWEET", "SWELL", "SWEPT", "SWIFT", "SWILL", "SWIMS", "SWINE", "SWING", "SWIRL", "SWISH", "SWOON", "SWOOP", - "SWORD", "SWORE", "SWORN", "SWUNG", "SYNOD", "SYRUP", "TABBY", "TABLE", "TABOO", "TACIT", "TACKS", "TAILS", - "TAINT", "TAKEN", "TAKES", "TALES", "TALKS", "TALLY", "TALON", "TAMED", "TAMER", "TANKS", "TAPER", "TAPES", - "TARDY", "TARES", "TARRY", "TARTS", "TASKS", "TASTE", "TASTY", "TAUNT", "TAWNY", "TAXED", "TAXES", "TEACH", - "TEAMS", "TEARS", "TEASE", "TEEMS", "TEENS", "TEETH", "TELLS", "TEMPI", "TEMPO", "TEMPS", "TENDS", "TENET", - "TENOR", "TENSE", "TENTH", "TENTS", "TEPEE", "TEPID", "TERMS", "TERSE", "TESTS", "TESTY", "TEXTS", "THANK", - "THEFT", "THEIR", "THEME", "THERE", "THESE", "THICK", "THIEF", "THIGH", "THINE", "THING", "THINK", "THIRD", - "THONG", "THORN", "THOSE", "THREE", "THREW", "THROB", "THROE", "THROW", "THUMB", "THUMP", "THYME", "TIARA", - "TIBIA", "TICKS", "TIDAL", "TIDES", "TIERS", "TIGER", "TIGHT", "TILDE", "TILED", "TILES", "TILLS", "TILTS", - "TIMED", "TIMES", "TIMID", "TINGE", "TINTS", "TIPSY", "TIRED", "TIRES", "TITHE", "TITLE", "TOADS", "TOAST", - "TODAY", "TODDY", "TOILS", "TOKEN", "TOLLS", "TOMBS", "TOMES", "TONED", "TONES", "TONGS", "TONIC", "TOOLS", - "TOOTH", "TOPAZ", "TOPIC", "TOQUE", "TORCH", "TORSO", "TORTS", "TOTAL", "TOTEM", "TOUCH", "TOUGH", "TOURS", - "TOWED", "TOWEL", "TOWER", "TOWNS", "TOXIC", "TOYED", "TRACE", "TRACK", "TRACT", "TRADE", "TRAIL", "TRAIN", - "TRAIT", "TRAMP", "TRAMS", "TRAPS", "TRASH", "TRAYS", "TREAD", "TREAT", "TREED", "TREES", "TREND", "TRESS", - "TRIAD", "TRIAL", "TRIBE", "TRICE", "TRICK", "TRIED", "TRIES", "TRILL", "TRIPE", "TRIPS", "TRITE", "TROLL", - "TROOP", "TROTH", "TROTS", "TROUT", "TRUCE", "TRUCK", "TRUER", "TRULY", "TRUMP", "TRUNK", "TRUSS", "TRUST", - "TRUTH", "TRYST", "TUBES", "TUFTS", "TULIP", "TULLE", "TUNED", "TUNES", "TUNIC", "TURNS", "TUSKS", "TUTOR", - "TWAIN", "TWANG", "TWEED", "TWICE", "TWIGS", "TWINE", "TWINS", "TWIRL", "TWIST", "TYING", "TYPED", "TYPES", - "UDDER", "ULCER", "ULTRA", "UNCLE", "UNCUT", "UNDER", "UNDID", "UNDUE", "UNFIT", "UNION", "UNITE", "UNITS", - "UNITY", "UNSAY", "UNTIE", "UNTIL", "UPPER", "UPSET", "URBAN", "URGED", "URGES", "URINE", "USAGE", "USERS", - "USHER", "USING", "USUAL", "USURP", "USURY", "UTTER", "VAGUE", "VALES", "VALET", "VALID", "VALUE", "VALVE", - "VANES", "VAPID", "VASES", "VAULT", "VAUNT", "VEILS", "VEINS", "VELDT", "VENAL", "VENOM", "VENTS", "VENUE", - "VERBS", "VERGE", "VERSE", "VERVE", "VESTS", "VEXED", "VEXES", "VIALS", "VICAR", "VICES", "VIDEO", "VIEWS", - "VIGIL", "VILER", "VILLA", "VINES", "VIOLA", "VIPER", "VIRUS", "VISIT", "VISOR", "VISTA", "VITAL", "VIVID", - "VIXEN", "VIZOR", "VOCAL", "VODKA", "VOGUE", "VOICE", "VOILE", "VOLTS", "VOMIT", "VOTED", "VOTER", "VOTES", - "VOUCH", "VOWED", "VOWEL", "VYING", "WADED", "WAFER", "WAFTS", "WAGED", "WAGER", "WAGES", "WAGON", "WAIFS", - "WAILS", "WAIST", "WAITS", "WAIVE", "WAKED", "WAKEN", "WAKES", "WALKS", "WALLS", "WALTZ", "WANDS", "WANED", - "WANES", "WANTS", "WARDS", "WARES", "WARMS", "WARNS", "WARTS", "WASPS", "WASTE", "WATCH", "WATER", "WAVED", - "WAVER", "WAVES", "WAXED", "WAXEN", "WAXES", "WEARS", "WEARY", "WEAVE", "WEDGE", "WEEDS", "WEEDY", "WEEKS", - "WEEPS", "WEIGH", "WEIRD", "WELCH", "WELLS", "WENCH", "WHACK", "WHALE", "WHARF", "WHEAT", "WHEEL", "WHELP", - "WHERE", "WHICH", "WHIFF", "WHILE", "WHIMS", "WHINE", "WHIPS", "WHIRL", "WHIRR", "WHISK", "WHIST", "WHITE", - "WHOLE", "WHOOP", "WHORE", "WHOSE", "WICKS", "WIDEN", "WIDER", "WIDOW", "WIDTH", "WIELD", "WIGHT", "WILDS", - "WILES", "WILLS", "WINCE", "WINCH", "WINDS", "WINDY", "WINES", "WINGS", "WINKS", "WIPED", "WIPES", "WIRED", - "WIRES", "WISER", "WISPS", "WITCH", "WITTY", "WIVES", "WOMAN", "WOMEN", "WOODS", "WOODY", "WOOED", "WOOER", - "WORDS", "WORDY", "WORKS", "WORLD", "WORMS", "WORRY", "WORSE", "WORST", "WORTH", "WOULD", "WOUND", "WRACK", - "WRAPS", "WRAPT", "WRATH", "WREAK", "WRECK", "WREST", "WRING", "WRIST", "WRITE", "WRITS", "WRONG", "WROTE", - "WROTH", "YACHT", "YARDS", "YARNS", "YAWNS", "YEARN", "YEARS", "YEAST", "YELLS", "YELPS", "YIELD", "YOKED", - "YOKES", "YOLKS", "YOUNG", "YOURS", "YOUTH", "ZEBRA", "ZONES", "COLOR", "LASER", "SONIC", +source_link = "https://matthewminer.name/projects/calculators/wordle-words-left/" +valid_list = [ + "CIGAR", "REBUT", "SISSY", "HUMPH", "AWAKE", "BLUSH", "FOCAL", "EVADE", "NAVAL", "SERVE", "HEATH", "DWARF", + "MODEL", "KARMA", "STINK", "GRADE", "QUIET", "BENCH", "ABATE", "FEIGN", "MAJOR", "DEATH", "FRESH", "CRUST", + "STOOL", "COLON", "ABASE", "MARRY", "REACT", "BATTY", "PRIDE", "FLOSS", "HELIX", "CROAK", "STAFF", "PAPER", + "UNFED", "WHELP", "TRAWL", "OUTDO", "ADOBE", "CRAZY", "SOWER", "REPAY", "DIGIT", "CRATE", "CLUCK", "SPIKE", + "MIMIC", "POUND", "MAXIM", "LINEN", "UNMET", "FLESH", "BOOBY", "FORTH", "FIRST", "STAND", "BELLY", "IVORY", + "SEEDY", "PRINT", "YEARN", "DRAIN", "BRIBE", "STOUT", "PANEL", "CRASS", "FLUME", "OFFAL", "AGREE", "ERROR", + "SWIRL", "ARGUE", "BLEED", "DELTA", "FLICK", "TOTEM", "WOOER", "FRONT", "SHRUB", "PARRY", "BIOME", "LAPEL", + "START", "GREET", "GONER", "GOLEM", "LUSTY", "LOOPY", "ROUND", "AUDIT", "LYING", "GAMMA", "LABOR", "ISLET", + "CIVIC", "FORGE", "CORNY", "MOULT", "BASIC", "SALAD", "AGATE", "SPICY", "SPRAY", "ESSAY", "FJORD", "SPEND", + "KEBAB", "GUILD", "ABACK", "MOTOR", "ALONE", "HATCH", "HYPER", "THUMB", "DOWRY", "OUGHT", "BELCH", "DUTCH", + "PILOT", "TWEED", "COMET", "JAUNT", "ENEMA", "STEED", "ABYSS", "GROWL", "FLING", "DOZEN", "BOOZY", "ERODE", + "WORLD", "GOUGE", "CLICK", "BRIAR", "GREAT", "ALTAR", "PULPY", "BLURT", "COAST", "DUCHY", "GROIN", "FIXER", + "GROUP", "ROGUE", "BADLY", "SMART", "PITHY", "GAUDY", "CHILL", "HERON", "VODKA", "FINER", "SURER", "RADIO", + "ROUGE", "PERCH", "RETCH", "WROTE", "CLOCK", "TILDE", "STORE", "PROVE", "BRING", "SOLVE", "CHEAT", "GRIME", + "EXULT", "USHER", "EPOCH", "TRIAD", "BREAK", "RHINO", "VIRAL", "CONIC", "MASSE", "SONIC", "VITAL", "TRACE", + "USING", "PEACH", "CHAMP", "BATON", "BRAKE", "PLUCK", "CRAZE", "GRIPE", "WEARY", "PICKY", "ACUTE", "FERRY", + "ASIDE", "TAPIR", "TROLL", "UNIFY", "REBUS", "BOOST", "TRUSS", "SIEGE", "TIGER", "BANAL", "SLUMP", "CRANK", + "GORGE", "QUERY", "DRINK", "FAVOR", "ABBEY", "TANGY", "PANIC", "SOLAR", "SHIRE", "PROXY", "POINT", "ROBOT", + "PRICK", "WINCE", "CRIMP", "KNOLL", "SUGAR", "WHACK", "MOUNT", "PERKY", "COULD", "WRUNG", "LIGHT", "THOSE", + "MOIST", "SHARD", "PLEAT", "ALOFT", "SKILL", "ELDER", "FRAME", "HUMOR", "PAUSE", "ULCER", "ULTRA", "ROBIN", + "CYNIC", "AROMA", "CAULK", "SHAKE", "DODGE", "SWILL", "TACIT", "OTHER", "THORN", "TROVE", "BLOKE", "VIVID", + "SPILL", "CHANT", "CHOKE", "RUPEE", "NASTY", "MOURN", "AHEAD", "BRINE", "CLOTH", "HOARD", "SWEET", "MONTH", + "LAPSE", "WATCH", "TODAY", "FOCUS", "SMELT", "TEASE", "CATER", "MOVIE", "SAUTE", "ALLOW", "RENEW", "THEIR", + "SLOSH", "PURGE", "CHEST", "DEPOT", "EPOXY", "NYMPH", "FOUND", "SHALL", "STOVE", "LOWLY", "SNOUT", "TROPE", + "FEWER", "SHAWL", "NATAL", "COMMA", "FORAY", "SCARE", "STAIR", "BLACK", "SQUAD", "ROYAL", "CHUNK", "MINCE", + "SHAME", "CHEEK", "AMPLE", "FLAIR", "FOYER", "CARGO", "OXIDE", "PLANT", "OLIVE", "INERT", "ASKEW", "HEIST", + "SHOWN", "ZESTY", "TRASH", "LARVA", "FORGO", "STORY", "HAIRY", "TRAIN", "HOMER", "BADGE", "MIDST", "CANNY", + "SHINE", "GECKO", "FARCE", "SLUNG", "TIPSY", "METAL", "YIELD", "DELVE", "BEING", "SCOUR", "GLASS", "GAMER", + "SCRAP", "MONEY", "HINGE", "ALBUM", "VOUCH", "ASSET", "TIARA", "CREPT", "BAYOU", "ATOLL", "MANOR", "CREAK", + "SHOWY", "PHASE", "FROTH", "DEPTH", "GLOOM", "FLOOD", "TRAIT", "GIRTH", "PIETY", "GOOSE", "FLOAT", "DONOR", + "ATONE", "PRIMO", "APRON", "BLOWN", "CACAO", "LOSER", "INPUT", "GLOAT", "AWFUL", "BRINK", "SMITE", "BEADY", + "RUSTY", "RETRO", "DROLL", "GAWKY", "HUTCH", "PINTO", "EGRET", "LILAC", "SEVER", "FIELD", "FLUFF", "AGAPE", + "VOICE", "STEAD", "BERTH", "MADAM", "NIGHT", "BLAND", "LIVER", "WEDGE", "ROOMY", "WACKY", "FLOCK", "ANGRY", + "TRITE", "APHID", "TRYST", "MIDGE", "POWER", "ELOPE", "CINCH", "MOTTO", "STOMP", "UPSET", "BLUFF", "CRAMP", + "QUART", "COYLY", "YOUTH", "RHYME", "BUGGY", "ALIEN", "SMEAR", "UNFIT", "PATTY", "CLING", "GLEAN", "LABEL", + "HUNKY", "KHAKI", "POKER", "GRUEL", "TWICE", "TWANG", "SHRUG", "TREAT", "WASTE", "MERIT", "WOVEN", "NEEDY", + "CLOWN", "IRONY", "RUDER", "GAUZE", "CHIEF", "ONSET", "PRIZE", "FUNGI", "CHARM", "GULLY", "INTER", "WHOOP", + "TAUNT", "LEERY", "CLASS", "THEME", "LOFTY", "TIBIA", "BOOZE", "ALPHA", "THYME", "DOUBT", "PARER", "CHUTE", + "STICK", "TRICE", "ALIKE", "RECAP", "SAINT", "GLORY", "GRATE", "ADMIT", "BRISK", "SOGGY", "USURP", "SCALD", + "SCORN", "LEAVE", "TWINE", "STING", "BOUGH", "MARSH", "SLOTH", "DANDY", "VIGOR", "HOWDY", "ENJOY", "VALID", + "IONIC", "EQUAL", "FLOOR", "CATCH", "SPADE", "STEIN", "EXIST", "QUIRK", "DENIM", "GROVE", "SPIEL", "MUMMY", + "FAULT", "FOGGY", "FLOUT", "CARRY", "SNEAK", "LIBEL", "WALTZ", "APTLY", "PINEY", "INEPT", "ALOUD", "PHOTO", + "DREAM", "STALE", "UNITE", "SNARL", "BAKER", "THERE", "GLYPH", "POOCH", "HIPPY", "SPELL", "FOLLY", "LOUSE", + "GULCH", "VAULT", "GODLY", "THREW", "FLEET", "GRAVE", "INANE", "SHOCK", "CRAVE", "SPITE", "VALVE", "SKIMP", + "CLAIM", "RAINY", "MUSTY", "PIQUE", "DADDY", "QUASI", "ARISE", "AGING", "VALET", "OPIUM", "AVERT", "STUCK", + "RECUT", "MULCH", "GENRE", "PLUME", "RIFLE", "COUNT", "INCUR", "TOTAL", "WREST", "MOCHA", "DETER", "STUDY", + "LOVER", "SAFER", "RIVET", "FUNNY", "SMOKE", "MOUND", "UNDUE", "SEDAN", "PAGAN", "SWINE", "GUILE", "GUSTY", + "EQUIP", "TOUGH", "CANOE", "CHAOS", "COVET", "HUMAN", "UDDER", "LUNCH", "BLAST", "STRAY", "MANGA", "MELEE", + "LEFTY", "QUICK", "PASTE", "GIVEN", "OCTET", "RISEN", "GROAN", "LEAKY", "GRIND", "CARVE", "LOOSE", "SADLY", + "SPILT", "APPLE", "SLACK", "HONEY", "FINAL", "SHEEN", "EERIE", "MINTY", "SLICK", "DERBY", "WHARF", "SPELT", + "COACH", "ERUPT", "SINGE", "PRICE", "SPAWN", "FAIRY", "JIFFY", "FILMY", "STACK", "CHOSE", "SLEEP", "ARDOR", + "NANNY", "NIECE", "WOOZY", "HANDY", "GRACE", "DITTO", "STANK", "CREAM", "USUAL", "DIODE", "VALOR", "ANGLE", + "NINJA", "MUDDY", "CHASE", "REPLY", "PRONE", "SPOIL", "HEART", "SHADE", "DINER", "ARSON", "ONION", "SLEET", + "DOWEL", "COUCH", "PALSY", "BOWEL", "SMILE", "EVOKE", "CREEK", "LANCE", "EAGLE", "IDIOT", "SIREN", "BUILT", + "EMBED", "AWARD", "DROSS", "ANNUL", "GOODY", "FROWN", "PATIO", "LADEN", "HUMID", "ELITE", "LYMPH", "EDIFY", + "MIGHT", "RESET", "VISIT", "GUSTO", "PURSE", "VAPOR", "CROCK", "WRITE", "SUNNY", "LOATH", "CHAFF", "SLIDE", + "QUEER", "VENOM", "STAMP", "SORRY", "STILL", "ACORN", "APING", "PUSHY", "TAMER", "HATER", "MANIA", "AWOKE", + "BRAWN", "SWIFT", "EXILE", "BIRCH", "LUCKY", "FREER", "RISKY", "GHOST", "PLIER", "LUNAR", "WINCH", "SNARE", + "NURSE", "HOUSE", "BORAX", "NICER", "LURCH", "EXALT", "ABOUT", "SAVVY", "TOXIN", "TUNIC", "PRIED", "INLAY", + "CHUMP", "LANKY", "CRESS", "EATER", "ELUDE", "CYCLE", "KITTY", "BOULE", "MORON", "TENET", "PLACE", "LOBBY", + "PLUSH", "VIGIL", "INDEX", "BLINK", "CLUNG", "QUALM", "CROUP", "CLINK", "JUICY", "STAGE", "DECAY", "NERVE", + "FLIER", "SHAFT", "CROOK", "CLEAN", "CHINA", "RIDGE", "VOWEL", "GNOME", "SNUCK", "ICING", "SPINY", "RIGOR", + "SNAIL", "FLOWN", "RABID", "PROSE", "THANK", "POPPY", "BUDGE", "FIBER", "MOLDY", "DOWDY", "KNEEL", "TRACK", + "CADDY", "QUELL", "DUMPY", "PALER", "SWORE", "REBAR", "SCUBA", "SPLAT", "FLYER", "HORNY", "MASON", "DOING", + "OZONE", "AMPLY", "MOLAR", "OVARY", "BESET", "QUEUE", "CLIFF", "MAGIC", "TRUCE", "SPORT", "FRITZ", "EDICT", + "TWIRL", "VERSE", "LLAMA", "EATEN", "RANGE", "WHISK", "HOVEL", "REHAB", "MACAW", "SIGMA", "SPOUT", "VERVE", + "SUSHI", "DYING", "FETID", "BRAIN", "BUDDY", "THUMP", "SCION", "CANDY", "CHORD", "BASIN", "MARCH", "CROWD", + "ARBOR", "GAYLY", "MUSKY", "STAIN", "DALLY", "BLESS", "BRAVO", "STUNG", "TITLE", "RULER", "KIOSK", "BLOND", + "ENNUI", "LAYER", "FLUID", "TATTY", "SCORE", "CUTIE", "ZEBRA", "BARGE", "MATEY", "BLUER", "AIDER", "SHOOK", + "RIVER", "PRIVY", "BETEL", "FRISK", "BONGO", "BEGUN", "AZURE", "WEAVE", "GENIE", "SOUND", "GLOVE", "BRAID", + "SCOPE", "WRYLY", "ROVER", "ASSAY", "OCEAN", "BLOOM", "IRATE", "LATER", "WOKEN", "SILKY", "WRECK", "DWELT", + "SLATE", "SMACK", "SOLID", "AMAZE", "HAZEL", "WRIST", "JOLLY", "GLOBE", "FLINT", "ROUSE", "CIVIL", "VISTA", + "RELAX", "COVER", "ALIVE", "BEECH", "JETTY", "BLISS", "VOCAL", "OFTEN", "DOLLY", "EIGHT", "JOKER", "SINCE", + "EVENT", "ENSUE", "SHUNT", "DIVER", "POSER", "WORST", "SWEEP", "ALLEY", "CREED", "ANIME", "LEAFY", "BOSOM", + "DUNCE", "STARE", "PUDGY", "WAIVE", "CHOIR", "STOOD", "SPOKE", "OUTGO", "DELAY", "BILGE", "IDEAL", "CLASP", + "SEIZE", "HOTLY", "LAUGH", "SIEVE", "BLOCK", "MEANT", "GRAPE", "NOOSE", "HARDY", "SHIED", "DRAWL", "DAISY", + "PUTTY", "STRUT", "BURNT", "TULIP", "CRICK", "IDYLL", "VIXEN", "FUROR", "GEEKY", "COUGH", "NAIVE", "SHOAL", + "STORK", "BATHE", "AUNTY", "CHECK", "PRIME", "BRASS", "OUTER", "FURRY", "RAZOR", "ELECT", "EVICT", "IMPLY", + "DEMUR", "QUOTA", "HAVEN", "CAVIL", "SWEAR", "CRUMP", "DOUGH", "GAVEL", "WAGON", "SALON", "NUDGE", "HAREM", + "PITCH", "SWORN", "PUPIL", "EXCEL", "STONY", "CABIN", "UNZIP", "QUEEN", "TROUT", "POLYP", "EARTH", "STORM", + "UNTIL", "TAPER", "ENTER", "CHILD", "ADOPT", "MINOR", "FATTY", "HUSKY", "BRAVE", "FILET", "SLIME", "GLINT", + "TREAD", "STEAL", "REGAL", "GUEST", "EVERY", "MURKY", "SHARE", "SPORE", "HOIST", "BUXOM", "INNER", "OTTER", + "DIMLY", "LEVEL", "SUMAC", "DONUT", "STILT", "ARENA", "SHEET", "SCRUB", "FANCY", "SLIMY", "PEARL", "SILLY", + "PORCH", "DINGO", "SEPIA", "AMBLE", "SHADY", "BREAD", "FRIAR", "REIGN", "DAIRY", "QUILL", "CROSS", "BROOD", + "TUBER", "SHEAR", "POSIT", "BLANK", "VILLA", "SHANK", "PIGGY", "FREAK", "WHICH", "AMONG", "FECAL", "SHELL", + "WOULD", "ALGAE", "LARGE", "RABBI", "AGONY", "AMUSE", "BUSHY", "COPSE", "SWOON", "KNIFE", "POUCH", "ASCOT", + "PLANE", "CROWN", "URBAN", "SNIDE", "RELAY", "ABIDE", "VIOLA", "RAJAH", "STRAW", "DILLY", "CRASH", "AMASS", + "THIRD", "TRICK", "TUTOR", "WOODY", "BLURB", "GRIEF", "DISCO", "WHERE", "SASSY", "BEACH", "SAUNA", "COMIC", + "CLUED", "CREEP", "CASTE", "GRAZE", "SNUFF", "FROCK", "GONAD", "DRUNK", "PRONG", "LURID", "STEEL", "HALVE", + "BUYER", "VINYL", "UTILE", "SMELL", "ADAGE", "WORRY", "TASTY", "LOCAL", "TRADE", "FINCH", "ASHEN", "MODAL", + "GAUNT", "CLOVE", "ENACT", "ADORN", "ROAST", "SPECK", "SHEIK", "MISSY", "GRUNT", "SNOOP", "PARTY", "TOUCH", + "MAFIA", "EMCEE", "ARRAY", "SOUTH", "VAPID", "JELLY", "SKULK", "ANGST", "TUBAL", "LOWER", "CREST", "SWEAT", + "CYBER", "ADORE", "TARDY", "SWAMI", "NOTCH", "GROOM", "ROACH", "HITCH", "YOUNG", "ALIGN", "READY", "FROND", + "STRAP", "PUREE", "REALM", "VENUE", "SWARM", "OFFER", "SEVEN", "DRYER", "DIARY", "DRYLY", "DRANK", "ACRID", + "HEADY", "THETA", "JUNTO", "PIXIE", "QUOTH", "BONUS", "SHALT", "PENNE", "AMEND", "DATUM", "BUILD", "PIANO", + "SHELF", "LODGE", "SUING", "REARM", "CORAL", "RAMEN", "WORTH", "PSALM", "INFER", "OVERT", "MAYOR", "OVOID", + "GLIDE", "USAGE", "POISE", "RANDY", "CHUCK", "PRANK", "FISHY", "TOOTH", "ETHER", "DROVE", "IDLER", "SWATH", + "STINT", "WHILE", "BEGAT", "APPLY", "SLANG", "TAROT", "RADAR", "CREDO", "AWARE", "CANON", "SHIFT", "TIMER", + "BYLAW", "SERUM", "THREE", "STEAK", "ILIAC", "SHIRK", "BLUNT", "PUPPY", "PENAL", "JOIST", "BUNNY", "SHAPE", + "BEGET", "WHEEL", "ADEPT", "STUNT", "STOLE", "TOPAZ", "CHORE", "FLUKE", "AFOOT", "BLOAT", "BULLY", "DENSE", + "CAPER", "SNEER", "BOXER", "JUMBO", "LUNGE", "SPACE", "AVAIL", "SHORT", "SLURP", "LOYAL", "FLIRT", "PIZZA", + "CONCH", "TEMPO", "DROOP", "PLATE", "BIBLE", "PLUNK", "AFOUL", "SAVOY", "STEEP", "AGILE", "STAKE", "DWELL", + "KNAVE", "BEARD", "AROSE", "MOTIF", "SMASH", "BROIL", "GLARE", "SHOVE", "BAGGY", "MAMMY", "SWAMP", "ALONG", + "RUGBY", "WAGER", "QUACK", "SQUAT", "SNAKY", "DEBIT", "MANGE", "SKATE", "NINTH", "JOUST", "TRAMP", "SPURN", + "MEDAL", "MICRO", "REBEL", "FLANK", "LEARN", "NADIR", "MAPLE", "COMFY", "REMIT", "GRUFF", "ESTER", "LEAST", + "MOGUL", "FETCH", "CAUSE", "OAKEN", "AGLOW", "MEATY", "GAFFE", "SHYLY", "RACER", "PROWL", "THIEF", "STERN", + "POESY", "ROCKY", "TWEET", "WAIST", "SPIRE", "GROPE", "HAVOC", "PATSY", "TRULY", "FORTY", "DEITY", "UNCLE", + "SWISH", "GIVER", "PREEN", "BEVEL", "LEMUR", "DRAFT", "SLOPE", "ANNOY", "LINGO", "BLEAK", "DITTY", "CURLY", + "CEDAR", "DIRGE", "GROWN", "HORDE", "DROOL", "SHUCK", "CRYPT", "CUMIN", "STOCK", "GRAVY", "LOCUS", "WIDER", + "BREED", "QUITE", "CHAFE", "CACHE", "BLIMP", "DEIGN", "FIEND", "LOGIC", "CHEAP", "ELIDE", "RIGID", "FALSE", + "RENAL", "PENCE", "ROWDY", "SHOOT", "BLAZE", "ENVOY", "POSSE", "BRIEF", "NEVER", "ABORT", "MOUSE", "MUCKY", + "SULKY", "FIERY", "MEDIA", "TRUNK", "YEAST", "CLEAR", "SKUNK", "SCALP", "BITTY", "CIDER", "KOALA", "DUVET", + "SEGUE", "CREME", "SUPER", "GRILL", "AFTER", "OWNER", "EMBER", "REACH", "NOBLY", "EMPTY", "SPEED", "GIPSY", + "RECUR", "SMOCK", "DREAD", "MERGE", "BURST", "KAPPA", "AMITY", "SHAKY", "HOVER", "CAROL", "SNORT", "SYNOD", + "FAINT", "HAUNT", "FLOUR", "CHAIR", "DETOX", "SHREW", "TENSE", "PLIED", "QUARK", "BURLY", "NOVEL", "WAXEN", + "STOIC", "JERKY", "BLITZ", "BEEFY", "LYRIC", "HUSSY", "TOWEL", "QUILT", "BELOW", "BINGO", "WISPY", "BRASH", + "SCONE", "TOAST", "EASEL", "SAUCY", "VALUE", "SPICE", "HONOR", "ROUTE", "SHARP", "BAWDY", "RADII", "SKULL", + "PHONY", "ISSUE", "LAGER", "SWELL", "URINE", "GASSY", "TRIAL", "FLORA", "UPPER", "LATCH", "WIGHT", "BRICK", + "RETRY", "HOLLY", "DECAL", "GRASS", "SHACK", "DOGMA", "MOVER", "DEFER", "SOBER", "OPTIC", "CRIER", "VYING", + "NOMAD", "FLUTE", "HIPPO", "SHARK", "DRIER", "OBESE", "BUGLE", "TAWNY", "CHALK", "FEAST", "RUDDY", "PEDAL", + "SCARF", "CRUEL", "BLEAT", "TIDAL", "SLUSH", "SEMEN", "WINDY", "DUSTY", "SALLY", "IGLOO", "NERDY", "JEWEL", + "SHONE", "WHALE", "HYMEN", "ABUSE", "FUGUE", "ELBOW", "CRUMB", "PANSY", "WELSH", "SYRUP", "TERSE", "SUAVE", + "GAMUT", "SWUNG", "DRAKE", "FREED", "AFIRE", "SHIRT", "GROUT", "ODDLY", "TITHE", "PLAID", "DUMMY", "BROOM", + "BLIND", "TORCH", "ENEMY", "AGAIN", "TYING", "PESKY", "ALTER", "GAZER", "NOBLE", "ETHOS", "BRIDE", "EXTOL", + "DECOR", "HOBBY", "BEAST", "IDIOM", "UTTER", "THESE", "SIXTH", "ALARM", "ERASE", "ELEGY", "SPUNK", "PIPER", + "SCALY", "SCOLD", "HEFTY", "CHICK", "SOOTY", "CANAL", "WHINY", "SLASH", "QUAKE", "JOINT", "SWEPT", "PRUDE", + "HEAVY", "WIELD", "FEMME", "LASSO", "MAIZE", "SHALE", "SCREW", "SPREE", "SMOKY", "WHIFF", "SCENT", "GLADE", + "SPENT", "PRISM", "STOKE", "RIPER", "ORBIT", "COCOA", "GUILT", "HUMUS", "SHUSH", "TABLE", "SMIRK", "WRONG", + "NOISY", "ALERT", "SHINY", "ELATE", "RESIN", "WHOLE", "HUNCH", "PIXEL", "POLAR", "HOTEL", "SWORD", "CLEAT", + "MANGO", "RUMBA", "PUFFY", "FILLY", "BILLY", "LEASH", "CLOUT", "DANCE", "OVATE", "FACET", "CHILI", "PAINT", + "LINER", "CURIO", "SALTY", "AUDIO", "SNAKE", "FABLE", "CLOAK", "NAVEL", "SPURT", "PESTO", "BALMY", "FLASH", + "UNWED", "EARLY", "CHURN", "WEEDY", "STUMP", "LEASE", "WITTY", "WIMPY", "SPOOF", "SANER", "BLEND", "SALSA", + "THICK", "WARTY", "MANIC", "BLARE", "SQUIB", "SPOON", "PROBE", "CREPE", "KNACK", "FORCE", "DEBUT", "ORDER", + "HASTE", "TEETH", "AGENT", "WIDEN", "ICILY", "SLICE", "INGOT", "CLASH", "JUROR", "BLOOD", "ABODE", "THROW", + "UNITY", "PIVOT", "SLEPT", "TROOP", "SPARE", "SEWER", "PARSE", "MORPH", "CACTI", "TACKY", "SPOOL", "DEMON", + "MOODY", "ANNEX", "BEGIN", "FUZZY", "PATCH", "WATER", "LUMPY", "ADMIN", "OMEGA", "LIMIT", "TABBY", "MACHO", + "AISLE", "SKIFF", "BASIS", "PLANK", "VERGE", "BOTCH", "CRAWL", "LOUSY", "SLAIN", "CUBIC", "RAISE", "WRACK", + "GUIDE", "FOIST", "CAMEO", "UNDER", "ACTOR", "REVUE", "FRAUD", "HARPY", "SCOOP", "CLIMB", "REFER", "OLDEN", + "CLERK", "DEBAR", "TALLY", "ETHIC", "CAIRN", "TULLE", "GHOUL", "HILLY", "CRUDE", "APART", "SCALE", "OLDER", + "PLAIN", "SPERM", "BRINY", "ABBOT", "RERUN", "QUEST", "CRISP", "BOUND", "BEFIT", "DRAWN", "SUITE", "ITCHY", + "CHEER", "BAGEL", "GUESS", "BROAD", "AXIOM", "CHARD", "CAPUT", "LEANT", "HARSH", "CURSE", "PROUD", "SWING", + "OPINE", "TASTE", "LUPUS", "GUMBO", "MINER", "GREEN", "CHASM", "LIPID", "TOPIC", "ARMOR", "BRUSH", "CRANE", + "MURAL", "ABLED", "HABIT", "BOSSY", "MAKER", "DUSKY", "DIZZY", "LITHE", "BROOK", "JAZZY", "FIFTY", "SENSE", + "GIANT", "SURLY", "LEGAL", "FATAL", "FLUNK", "BEGAN", "PRUNE", "SMALL", "SLANT", "SCOFF", "TORUS", "NINNY", + "COVEY", "VIPER", "TAKEN", "MORAL", "VOGUE", "OWING", "TOKEN", "ENTRY", "BOOTH", "VOTER", "CHIDE", "ELFIN", + "EBONY", "NEIGH", "MINIM", "MELON", "KNEED", "DECOY", "VOILA", "ANKLE", "ARROW", "MUSHY", "TRIBE", "CEASE", + "EAGER", "BIRTH", "GRAPH", "ODDER", "TERRA", "WEIRD", "TRIED", "CLACK", "COLOR", "ROUGH", "WEIGH", "UNCUT", + "LADLE", "STRIP", "CRAFT", "MINUS", "DICEY", "TITAN", "LUCID", "VICAR", "DRESS", "DITCH", "GYPSY", "PASTA", + "TAFFY", "FLAME", "SWOOP", "ALOOF", "SIGHT", "BROKE", "TEARY", "CHART", "SIXTY", "WORDY", "SHEER", "LEPER", + "NOSEY", "BULGE", "SAVOR", "CLAMP", "FUNKY", "FOAMY", "TOXIC", "BRAND", "PLUMB", "DINGY", "BUTTE", "DRILL", + "TRIPE", "BICEP", "TENOR", "KRILL", "WORSE", "DRAMA", "HYENA", "THINK", "RATIO", "COBRA", "BASIL", "SCRUM", + "BUSED", "PHONE", "COURT", "CAMEL", "PROOF", "HEARD", "ANGEL", "PETAL", "POUTY", "THROB", "MAYBE", "FETAL", + "SPRIG", "SPINE", "SHOUT", "CADET", "MACRO", "DODGY", "SATYR", "RARER", "BINGE", "TREND", "NUTTY", "LEAPT", + "AMISS", "SPLIT", "MYRRH", "WIDTH", "SONAR", "TOWER", "BARON", "FEVER", "WAVER", "SPARK", "BELIE", "SLOOP", + "EXPEL", "SMOTE", "BALER", "ABOVE", "NORTH", "WAFER", "SCANT", "FRILL", "AWASH", "SNACK", "SCOWL", "FRAIL", + "DRIFT", "LIMBO", "FENCE", "MOTEL", "OUNCE", "WREAK", "REVEL", "TALON", "PRIOR", "KNELT", "CELLO", "FLAKE", + "DEBUG", "ANODE", "CRIME", "SALVE", "SCOUT", "IMBUE", "PINKY", "STAVE", "VAGUE", "CHOCK", "FIGHT", "VIDEO", + "STONE", "TEACH", "CLEFT", "FROST", "PRAWN", "BOOTY", "TWIST", "APNEA", "STIFF", "PLAZA", "LEDGE", "TWEAK", + "BOARD", "GRANT", "MEDIC", "BACON", "CABLE", "BRAWL", "SLUNK", "RASPY", "FORUM", "DRONE", "WOMEN", "MUCUS", + "BOAST", "TODDY", "COVEN", "TUMOR", "TRUER", "WRATH", "STALL", "STEAM", "AXIAL", "PURER", "DAILY", "TRAIL", + "NICHE", "MEALY", "JUICE", "NYLON", "PLUMP", "MERRY", "FLAIL", "PAPAL", "WHEAT", "BERRY", "COWER", "ERECT", + "BRUTE", "LEGGY", "SNIPE", "SINEW", "SKIER", "PENNY", "JUMPY", "RALLY", "UMBRA", "SCARY", "MODEM", "GROSS", + "AVIAN", "GREED", "SATIN", "TONIC", "PARKA", "SNIFF", "LIVID", "STARK", "TRUMP", "GIDDY", "REUSE", "TABOO", + "AVOID", "QUOTE", "DEVIL", "LIKEN", "GLOSS", "GAYER", "BERET", "NOISE", "GLAND", "DEALT", "SLING", "RUMOR", + "OPERA", "THIGH", "TONGA", "FLARE", "WOUND", "WHITE", "BULKY", "ETUDE", "HORSE", "CIRCA", "PADDY", "INBOX", + "FIZZY", "GRAIN", "EXERT", "SURGE", "GLEAM", "BELLE", "SALVO", "CRUSH", "FRUIT", "SAPPY", "TAKER", "TRACT", + "OVINE", "SPIKY", "FRANK", "REEDY", "FILTH", "SPASM", "HEAVE", "MAMBO", "RIGHT", "CLANK", "TRUST", "LUMEN", + "BORNE", "SPOOK", "SAUCE", "AMBER", "LATHE", "CARAT", "CORER", "DIRTY", "SLYLY", "AFFIX", "ALLOY", "TAINT", + "SHEEP", "KINKY", "WOOLY", "MAUVE", "FLUNG", "YACHT", "FRIED", "QUAIL", "BRUNT", "GRIMY", "CURVY", "CAGEY", + "RINSE", "DEUCE", "STATE", "GRASP", "MILKY", "BISON", "GRAFT", "SANDY", "BASTE", "FLASK", "HEDGE", "GIRLY", + "SWASH", "BONEY", "COUPE", "ENDOW", "ABHOR", "WELCH", "BLADE", "TIGHT", "GEESE", "MISER", "MIRTH", "CLOUD", + "CABAL", "LEECH", "CLOSE", "TENTH", "PECAN", "DROIT", "GRAIL", "CLONE", "GUISE", "RALPH", "TANGO", "BIDDY", + "SMITH", "MOWER", "PAYEE", "SERIF", "DRAPE", "FIFTH", "SPANK", "GLAZE", "ALLOT", "TRUCK", "KAYAK", "VIRUS", + "TESTY", "TEPEE", "FULLY", "ZONAL", "METRO", "CURRY", "GRAND", "BANJO", "AXION", "BEZEL", "OCCUR", "CHAIN", + "NASAL", "GOOEY", "FILER", "BRACE", "ALLAY", "PUBIC", "RAVEN", "PLEAD", "GNASH", "FLAKY", "MUNCH", "DULLY", + "EKING", "THING", "SLINK", "HURRY", "THEFT", "SHORN", "PYGMY", "RANCH", "WRING", "LEMON", "SHORE", "MAMMA", + "FROZE", "NEWER", "STYLE", "MOOSE", "ANTIC", "DROWN", "VEGAN", "CHESS", "GUPPY", "UNION", "LEVER", "LORRY", + "IMAGE", "CABBY", "DRUID", "EXACT", "TRUTH", "DOPEY", "SPEAR", "CRIED", "CHIME", "CRONY", "STUNK", "TIMID", + "BATCH", "GAUGE", "ROTOR", "CRACK", "CURVE", "LATTE", "WITCH", "BUNCH", "REPEL", "ANVIL", "SOAPY", "METER", + "BROTH", "MADLY", "DRIED", "SCENE", "KNOWN", "MAGMA", "ROOST", "WOMAN", "THONG", "PUNCH", "PASTY", "DOWNY", + "KNEAD", "WHIRL", "RAPID", "CLANG", "ANGER", "DRIVE", "GOOFY", "EMAIL", "MUSIC", "STUFF", "BLEEP", "RIDER", + "MECCA", "FOLIO", "SETUP", "VERSO", "QUASH", "FAUNA", "GUMMY", "HAPPY", "NEWLY", "FUSSY", "RELIC", "GUAVA", + "RATTY", "FUDGE", "FEMUR", "CHIRP", "FORTE", "ALIBI", "WHINE", "PETTY", "GOLLY", "PLAIT", "FLECK", "FELON", + "GOURD", "BROWN", "THRUM", "FICUS", "STASH", "DECRY", "WISER", "JUNTA", "VISOR", "DAUNT", "SCREE", "IMPEL", + "AWAIT", "PRESS", "WHOSE", "TURBO", "STOOP", "SPEAK", "MANGY", "EYING", "INLET", "CRONE", "PULSE", "MOSSY", + "STAID", "HENCE", "PINCH", "TEDDY", "SULLY", "SNORE", "RIPEN", "SNOWY", "ATTIC", "GOING", "LEACH", "MOUTH", + "HOUND", "CLUMP", "TONAL", "BIGOT", "PERIL", "PIECE", "BLAME", "HAUTE", "SPIED", "UNDID", "INTRO", "BASAL", + "RODEO", "GUARD", "STEER", "LOAMY", "SCAMP", "SCRAM", "MANLY", "HELLO", "VAUNT", "ORGAN", "FERAL", "KNOCK", + "EXTRA", "CONDO", "ADAPT", "WILLY", "POLKA", "RAYON", "SKIRT", "FAITH", "TORSO", "MATCH", "MERCY", "TEPID", + "SLEEK", "RISER", "TWIXT", "PEACE", "FLUSH", "CATTY", "LOGIN", "EJECT", "ROGER", "RIVAL", "UNTIE", "REFIT", + "AORTA", "ADULT", "JUDGE", "ROWER", "ARTSY", "RURAL", "SHAVE", "BOBBY", "ECLAT", "FELLA", "GAILY", "HARRY", + "HASTY", "HYDRO", "LIEGE", "OCTAL", "OMBRE", "PAYER", "SOOTH", "UNSET", "UNLIT", "VOMIT", "FANNY", "FETUS", + "BUTCH", "STALK", "FLACK", "WIDOW", "AUGUR", "LITER", ] -expanded_list = [ - "WHICH", "THEIR", "WOULD", "THERE", "COULD", "OTHER", "ABOUT", "GREAT", "THESE", "AFTER", "FIRST", "NEVER", - "WHERE", "THOSE", "SHALL", "BEING", "MIGHT", "EVERY", "THINK", "UNDER", "FOUND", "STILL", "WHILE", "AGAIN", - "PLACE", "YOUNG", "YEARS", "THREE", "RIGHT", "HOUSE", "WHOLE", "WORLD", "THING", "NIGHT", "GOING", "HEARD", - "HEART", "AMONG", "ASKED", "SMALL", "WOMAN", "WHOSE", "QUITE", "WORDS", "GIVEN", "TAKEN", "HANDS", "UNTIL", - "SINCE", "LIGHT", "BEGAN", "LARGE", "WATER", "WORKS", "OFTEN", "STOOD", "POWER", "MONEY", "ORDER", "MEANS", - "ROUND", "VOICE", "WHITE", "POINT", "STATE", "ABOVE", "DEATH", "LEAST", "KNOWN", "ALONG", "LEAVE", "ALONE", - "WOMEN", "TIMES", "SPEAK", "FORTH", "TERMS", "CRIED", "CHILD", "HUMAN", "SHORT", "CAUSE", "SEEMS", "BRING", - "DOUBT", "BLACK", "SENSE", "CLOSE", "TRUTH", "OUGHT", "PARTY", "READY", "FORCE", "EARLY", "EARTH", "EBOOK", - "SIGHT", "SPOKE", "STORY", "LATER", "ADDED", "STAND", "NICHT", "MILES", "COMES", "TABLE", "HOURS", "RIVER", - "HAPPY", "CLEAR", "SOUND", "MAKES", "BLOOD", "COMME", "DOING", "AVAIT", "TRIED", "FRONT", "QUILL", "PEACE", - "LIVED", "HORSE", "WROTE", "PAPER", "CETTE", "CHIEF", "PARIS", "BOOKS", "VISIT", "HEAVY", "KNOWS", "LOVED", - "CARRY", "PLAIN", "SWEET", "WRITE", "TREES", "BELOW", "WRONG", "REACH", "NOBLE", "PARTS", "AGREE", "MOVED", - "ENEMY", "WORTH", "GREEN", "THIRD", "MOUTH", "SLEEP", "FRESH", "FAITH", "SMILE", "USUAL", "BOUND", "QUIET", - "ETEXT", "COURT", "YOUTH", "PIECE", "SOUTH", "MEANT", "SEVEN", "TEARS", "VALUE", "BROKE", "FIGHT", "STONE", - "BEGIN", "HENRY", "LEARN", "LINES", "GRAND", "TAKES", "MONTH", "GIRLS", "GIVES", "EIGHT", "SCENE", "LIVES", - "DRAWN", "FIFTY", "FIELD", "CHAIR", "NAMED", "ALLOW", "MUSIC", "FIXED", "STUDY", "SPENT", "ROMAN", "TRUST", - "BREAK", "EQUAL", "NORTH", "THREW", "WATCH", "LOOKS", "BUILT", "USING", "SPITE", "MORAL", "WALLS", "TOUCH", - "JAMES", "STEPS", "OFFER", "DRESS", "LYING", "GRAVE", "LEGAL", "QUEEN", "LOWER", "CASES", "SHOWN", "NAMES", - "GREEK", "BOARD", "FAIRE", "GLASS", "SHARE", "FORMS", "CLASS", "START", "SHOOK", "TRAIN", "ENTER", "PROVE", - "FLOOR", "XPAGE", "WORSE", "SORRY", "PRIDE", "MARCH", "MARRY", "CROWD", "SHORE", "DRINK", "JUDGE", "SERVE", - "LAUGH", "TRADE", "BROAD", "GRACE", "PETER", "JESUS", "ROYAL", "LOUIS", "HEADS", "PROUD", "SPACE", "FULLY", - "QUICK", "IDEAS", "FANCY", "TASTE", "SWORD", "SHIPS", "DAILY", "GLORY", "BRAVE", "HONOR", "DREAM", "WEEKS", - "THICK", "CLAIM", "CHECK", "ASIDE", "REPLY", "FALSE", "SIDES", "CROSS", "SHARP", "FACTS", "HILLS", "BREAD", - "COAST", "DAVID", "AWARE", "GROUP", "FACES", "GROWN", "BIRDS", "MIDST", "TELLS", "LIKED", "THROW", "HABIT", - "STAGE", "ANGRY", "BROWN", "OWNER", "TIRED", "TRULY", "RULES", "TOTAL", "GRASS", "STYLE", "SAVED", "DRIVE", - "TWICE", "GUARD", "BURST", "PRICE", "WANTS", "THANK", "BASED", "APRIL", "GUESS", "CHOSE", "HOPES", "UNCLE", - "WOODS", "FINAL", "FORTY", "DROVE", "SPAIN", "TITLE", "UPPER", "MINDS", "NOISE", "HOPED", "BEGUN", "ALIVE", - "ITALY", "CRUEL", "SHAPE", "SLAVE", "BIRTH", "YOURS", "STORM", "CATCH", "LOOSE", "EMPTY", "CIVIL", "DOZEN", - "SHOWS", "ADMIT", "SMOKE", "DYING", "BRIEF", "APPLY", "PROOF", "FLESH", "FRUIT", "ENJOY", "WORST", "SHAME", - "ROUGH", "COVER", "ROCKS", "COUNT", "CRIME", "GRIEF", "IRISH", "NOTES", "NEEDS", "LANDS", "BLIND", "BRAIN", - "PRINT", "CLEAN", "DURCH", "ETAIT", "LEVEL", "RAISE", "EAGER", "STARS", "FAINT", "TEETH", "LABOR", "ROOMS", - "OLDER", "EINEN", "KNEES", "MERCY", "AWFUL", "AVOID", "DOORS", "INDIA", "ENDED", "DEVIL", "WEARY", "AROSE", - "FAULT", "CROWN", "COLOR", "AUSSI", "EVENT", "GOODS", "QUAND", "YARDS", "UNION", "ASCII", "TEMPS", "FAVOR", - "VOTRE", "SOULS", "CALLS", "AHEAD", "HARRY", "SMITH", "ANGER", "PLANS", "LOCAL", "LOVER", "PAGES", "LEURS", - "VIEWS", "SIGNS", "TEACH", "STOCK", "KINDS", "APART", "GUIDE", "ARMED", "EXACT", "HOMME", "TOUTE", "LATIN", - "TRIAL", "HOTEL", "SPEND", "SKILL", "KINGS", "SHALT", "LINKS", "DANCE", "SWEPT", "FATAL", "EINER", "WOUND", - "STORE", "SLEPT", "RANGE", "HATTE", "HENCE", "IMAGE", "ARISE", "FILES", "EINEM", "BANKS", "RAPID", "WASTE", - "ENTRE", "DARED", "PLANT", "SHADE", "ACTED", "CLOUD", "PRESS", "LOVES", "UTTER", "WINGS", "FLUNG", "BOWED", - "EGYPT", "GAZED", "THINE", "STICK", "FRANK", "REIGN", "MAJOR", "URGED", "FLEET", "TURNS", "SUGAR", "SPARE", - "SOLID", "BLAME", "BORNE", "WAVES", "TOWNS", "BOSOM", "CORPS", "SPEED", "GRANT", "FINDS", "MOINS", "BUILD", - "COSTS", "ERROR", "CEASE", "BOATS", "MIXED", "DELAY", "AGENT", "CLOTH", "ISSUE", "CHARM", "TREAT", "EMAIL", - "FRAME", "SHEEP", "ALIKE", "DUTCH", "PAUSE", "SEINE", "MERRY", "FEMME", "CRIES", "VAGUE", "TRACE", "VERSE", - "NOTRE", "FLAME", "HELEN", "IDEAL", "AVOIR", "HASTE", "ROUTE", "DREAD", "BIBLE", "EXIST", "OWING", "STERN", - "SAINT", "SORTS", "FALLS", "YIELD", "OCCUR", "GATES", "BONES", "OCEAN", "JEUNE", "SHAKE", "CARED", "STAFF", - "HURRY", "BEAST", "LOFTY", "BLESS", "TROIS", "INNER", "SHONE", "DRANK", "NOTED", "WINDS", "CHEEK", "CHAIN", - "KNIFE", "FEARS", "SWIFT", "WIVES", "WIDOW", "HATED", "HOLDS", "SIXTY", "MERIT", "GROSS", "DIESE", "ANGEL", - "MARIE", "FEVER", "FIRED", "AINSI", "ROADS", "CHINA", "QUEER", "GIFTS", "SWEAR", "NURSE", "CABIN", "MARKS", - "TRIBE", "ALOUD", "PAINS", "ALARM", "SCOTT", "OUTER", "WALKS", "NAKED", "FOLKS", "ELDER", "POETS", "MATCH", - "FOLLY", "WRATH", "DWELL", "SHOES", "SLAIN", "WAREN", "COACH", "ALICE", "TOWER", "DEEDS", "HABEN", "STEEL", - "TRAIL", "DEPTH", "BEARS", "PORTE", "SHOCK", "MOSES", "GUEST", "SCHON", "PLATE", "SAITH", "FEELS", "CLERK", - "STUFF", "TRACK", "POEMS", "PLAYS", "MAGIC", "KEEPS", "MONDE", "SONGS", "LEADS", "DRUNK", "AWAKE", "COEUR", - "SHOOT", "SMELL", "PETIT", "ALTAR", "AUTRE", "LIMBS", "EATEN", "TONES", "BASIS", "STEEP", "FEAST", "NOVEL", - "SILLY", "GROWS", "BADLY", "STOUT", "ALLES", "MARIA", "RISEN", "CREPT", "SCALE", "RANKS", "FLASH", "BURNT", - "RIVAL", "BRIDE", "TALES", "BOOTS", "CHEST", "SEATS", "GRAIN", "PRIZE", "FETCH", "DIRTY", "MOUNT", "CURSE", - "STIFF", "BEARD", "BARON", "NEGRO", "ASSEZ", "UNTER", "MODEL", "GRASP", "FIRES", "FLOOD", "ALORS", "JOURS", - "FLOUR", "STOLE", "POUND", "SWUNG", "ALTER", "SHEET", "CENTS", "DWELT", "CLOAK", "ROSES", "ROOTS", "CLOCK", - "SCORE", "GLOOM", "AGONY", "SEIZE", "PIOUS", "WAGES", "BLOWS", "WHEEL", "SAGTE", "CARES", "VITAL", "RISES", - "BILLY", "AVANT", "OWNED", "SCORN", "METAL", "ABIDE", "IMMER", "JONES", "STUCK", "SHOUT", "PAINT", "DRIED", - "AMPLE", "RUINS", "MAYBE", "FILLE", "SIEGE", "STEAM", "BETTY", "MILLE", "TRICK", "TYPES", "SHELL", "REFER", - "GIANT", "LORDS", "ROGER", "FIFTH", "FACED", "DATES", "MEINE", "HOMES", "LINEN", "VIVID", "STRAW", "BRASS", - "NAVAL", "MADAM", "BONNE", "FADED", "SPORT", "DROPS", "DINED", "BEACH", "LOYAL", "CELUI", "LIKES", "CARDS", - "RACES", "CHEAP", "TRUNK", "RALPH", "CHEER", "JOINT", "EINES", "SPELL", "ABODE", "WAVED", "DRAMA", "ARRAY", - "CRAFT", "CANOE", "GEGEN", "RIFLE", "CODES", "FIERY", "MIEUX", "TAXES", "AWOKE", "LIMIT", "ASHES", "JACOB", - "GHOST", "BELLS", "STEAL", "ALLER", "CLIMB", "HEELS", "TEXAS", "TERRE", "MUTTA", "SOBER", "HARSH", "SHINE", - "KNOCK", "PEINE", "SPAKE", "VIRUS", "DEALT", "MINOR", "LAURA", "SLOPE", "HUMOR", "BENCH", "WHEAT", "MAMMA", - "PIANO", "FUNNY", "SIMON", "ROCKY", "SWEEP", "SADLY", "ERECT", "BELLE", "PURSE", "PARCE", "CLUNG", "SWORE", - "FENCE", "WORRY", "SUITE", "JAPAN", "WAGON", "POLLY", "EVILS", "CELLE", "HENRI", "JETZT", "PETTY", "BANDS", - "CHASE", "GLEAM", "UNITY", "HASTY", "ACRES", "MAINS", "LUCKY", "PERIL", "ADAMS", "HIRED", "FILED", "EXTRA", - "FLIES", "DOUTE", "TIGHT", "BLANK", "JULIA", "ENFIN", "SEEDS", "IHRER", "FAIRY", "SITES", "SMART", "ETWAS", - "WURDE", "SHIRT", "ELECT", "SPOIL", "GUILT", "ADOPT", "BILLS", "CLARA", "HOLES", "ELLES", "SAILS", "RINGS", - "EDITH", "FANNY", "BLOWN", "RIDGE", "DENSE", "TODAY", "SHEER", "ABUSE", "RESTE", "QUOTH", "LUNCH", "NIECE", - "TIMID", "MANLY", "SWORN", "PRIME", "PROSE", "BOXES", "VILLE", "LEWIS", "CREEK", "ORGAN", "BRUSH", "OPERA", - "FLOCK", "SUSAN", "BOAST", "PITCH", "ANGLE", "ELLEN", "SUNNY", "AUGHT", "FOOLS", "SWAMP", "VEINS", "NEWLY", - "COUCH", "VIEUX", "JOLLY", "OPENS", "SPEAR", "BRICK", "CREAM", "THIEF", "WAIST", "RULED", "KNELT", "SARAH", - "BRUTE", "SENDS", "MINES", "DATED", "PATHS", "MOVES", "SHOPS", "PORCH", "IHREN", "TENTS", "BACKS", "KITTY", - "SANDY", "PUPIL", "LODGE", "BLOOM", "BONDS", "BLOCK", "SATAN", "SWELL", "RIGID", "OLLUT", "GAMES", "SANTA", - "DEBTS", "FELIX", "SPOTS", "SCENT", "HEEFT", "CIGAR", "ALLAH", "ENTRY", "SAXON", "AVAIL", "MAINE", "EFFET", - "TREAD", "WIDER", "OLISI", "CHILL", "BROOK", "AMUSE", "ALLEN", "COMTE", "ROBIN", "SKALL", "VEXED", "BALLS", - "KEINE", "CROPS", "UEBER", "REALM", "EMILY", "MONKS", "REBEL", "WALES", "LAMPS", "EXILE", "HEARS", "UNITE", - "ROBES", "SKIES", "SIXTH", "SEULE", "CRAZY", "ISAAC", "IHNEN", "RULER", "WRECK", "DRAWS", "DIDST", "ARROW", - "STARE", "DROIT", "BLUSH", "DONNE", "SOBRE", "PRIOR", "VOTES", "MATIN", "THEME", "FINER", "PSEUD", "NERVE", - "APPLE", "RESTS", "PENNY", "STAKE", "BROWS", "READS", "BUNCH", "TWAIN", "TOKEN", "TALKS", "WISER", "HORNS", - "APRES", "BLAZE", "SORTE", "CLIFF", "STRIP", "TEXTS", "AWAIT", "ELBOW", "GLOBE", "FLUSH", "UPSET", "AVONS", - "TURKS", "JANET", "GENUS", "LOCKS", "HONEY", "CARGO", "LEBEN", "STOVE", "CANAL", "JERRY", "CRACK", "POSTS", - "PIETY", "LADEN", "BLADE", "LINED", "HONOM", "NOISY", "DRIFT", "DECAY", "FUNDS", "MIRTH", "TOOLS", "ACUTE", - "AUGEN", "PACES", "SPLIT", "BREED", "VENUS", "JUICE", "PLATO", "FREED", "FAILS", "PORTS", "BLISS", "USAGE", - "HEURE", "PILED", "RATES", "AIDED", "BYRON", "WIPED", "HOMER", "STAMP", "VICES", "VATER", "TOMMY", "PIEDS", - "TROOP", "SAGDE", "BLAST", "MOTOR", "RURAL", "WEARS", "CREED", "VOTED", "DELLA", "GLARE", "SKINS", "TODOS", - "SHAFT", "VENIR", "NANCY", "CROIS", "DAVIS", "QUOTE", "PLEAD", "ITEMS", "SWING", "SEEKS", "CREST", "BASIN", - "LAKES", "SNAKE", "SALES", "DUSTY", "CRUSH", "MEETS", "TENTH", "SALLY", "MEALS", "GROVE", "SUITS", "SMOTE", - "LANGE", "ACTOR", "ARGUE", "FLOWS", "DIMLY", "REEDS", "PLANE", "GREET", "SWEAT", "DIANA", "BRUIT", "ROOFS", - "SANOI", "BEAMS", "JIMMY", "PILOT", "ARABS", "FROST", "CELLS", "MOORE", "AYANT", "PERCY", "EDGAR", "PIPES", - "MOLLY", "QUEST", "FOLDS", "CURED", "PASSE", "RHINE", "MAIDS", "MULES", "REINS", "SALON", "CANST", "AUCUN", - "ALERT", "RENEW", "EDGES", "IHREM", "SWISS", "PATCH", "ALOFT", "CRUDE", "AIMED", "NAILS", "RECHT", "SAGEN", - "VIGOR", "TRIES", "FLANK", "TRACT", "VERGE", "ELSIE", "TRAMP", "IVORY", "CRASH", "CURVE", "CAKES", "EAGLE", - "HEDGE", "VOULU", "RITES", "LOGIC", "CLARK", "SPARK", "SINGS", "EPOCH", "MAYOR", "WEIGH", "ONDER", "ABBEY", - "KEITH", "AGNES", "ESSAY", "VALET", "HOSTS", "HANGS", "COMIC", "PANIC", "PEDRO", "COATS", "SCOPE", "FRAIL", - "BURKE", "GOALS", "MODES", "SKULL", "FLOAT", "RHODE", "PULSE", "LOSES", "DANTE", "TOPIC", "TIGER", "FEWER", - "PINES", "SHIFT", "HEAPS", "VIENT", "FILLS", "REINE", "FORTS", "LIONS", "MILAN", "ZIJNE", "LUNGS", "SANDS", - "PHASE", "HALLS", "ROUSE", "DAISY", "PARTE", "FARMS", "SKIRT", "SATIN", "RAINS", "HINTS", "CABLE", "GARDE", - "THUMB", "QUART", "HARDY", "ELLER", "POLES", "AARON", "SYRIA", "GROAN", "ROPES", "MOULD", "JENNY", "ROLLS", - "BACON", "MOIST", "FLING", "LANCE", "PARTI", "SHELF", "ETHEL", "MALES", "PLUCK", "SHOTS", "STOPS", "ALIEN", - "FRAUD", "CLUBS", "NEWBY", "CREEP", "TILDE", "SHAWL", "FORME", "AFORE", "RAOUL", "FROWN", "SNOWY", "LISTS", - "STONY", "FLAGS", "BORED", "MELAN", "PGLAF", "CURLS", "EFTER", "STAID", "TOUGH", "CHOIR", "HASTA", "DITCH", - "CLARE", "WITCH", "RIDER", "SEHEN", "MASON", "WEEDS", "TENDS", "PALMS", "FLUID", "VILLA", "STADT", "IRENE", - "SAUCE", "PAVED", "ANNIE", "MUDDY", "SEXES", "USERS", "STRAY", "DAMIT", "OATHS", "WIDTH", "STAIN", "STEED", - "UNFIT", "BEADS", "HERDS", "INDEM", "WENIG", "VINES", "BLAKE", "GORGE", "DEMON", "CANON", "BRISK", "GANZE", - "TUTOR", "SIGHS", "RAINY", "TANTO", "MOYEN", "HELPS", "GRADE", "PEAKS", "ELLOS", "PARMI", "PARLE", "ALLAN", - "FLORA", "PELLE", "APRON", "COINS", "CITED", "LIEBE", "DEREN", "WELSH", "HOOFS", "PAIRS", "DONNA", "VOWED", - "WEIRD", "IRONY", "BRUCE", "BEANS", "DOLLY", "CLING", "LAPSE", "GAILY", "DUSKY", "TEGEN", "WITTY", "WRIST", - "TORCH", "MONTE", "SHADY", "AMOUR", "BESET", "ATHOS", "COUPS", "TRUCE", "GOATS", "VAULT", "SPURS", "GAINS", - "TELLE", "TOAST", "VINGT", "LAYER", "ARMOR", "JUDAH", "KOMMT", "MOODS", "IMPLY", "LEVER", "JOTKA", "EXERT", - "FOULE", "SPIES", "YACHT", "ALTEN", "PAGAN", "TENSE", "ALOOF", "VALOR", "HYMNS", "ENVOY", "PINCH", "PRONE", - "HELLO", "BROOD", "MABEL", "FOWLS", "HERRN", "DROWN", "ROUGE", "BURNS", "MISMO", "CALME", "LIVRE", "SLIDE", - "LYONS", "CAIRO", "PATTY", "CASTE", "STOOP", "BAKER", "MILLS", "BLEST", "TAILS", "VOICI", "LOGIN", "FARED", - "CHANT", "SEITE", "FABLE", "MISTS", "DIEGO", "ROWED", "WORDT", "DUCKS", "MAXIM", "BOERS", "LEVEN", "SWARM", - "QUANT", "MUSED", "WAITS", "CHALK", "BEIDE", "FORTE", "CAVES", "JEDER", "SENOR", "INFER", "TRAIT", "TASKS", - "DIARY", "HOUND", "SAVEZ", "BLUFF", "PENSE", "CECIL", "JUDAS", "OUNCE", "STARK", "DRAIN", "LEAPT", "WEISE", - "CLAWS", "TWINS", "IDOLS", "SUJET", "ALTSP", "WILLS", "RAGED", "HAVDE", "BEAUX", "FRITZ", "DARES", "DEPOT", - "DAZED", "RODIN", "LOWLY", "STUNG", "NASTY", "DEITY", "BOBBY", "ELIZA", "SIZES", "GAUNT", "LYDIA", "KNEEL", - "RISKS", "DURST", "TENOR", "STEMS", "FAITE", "LIVER", "STEAD", "EVANS", "TESTS", "GILES", "HUNNE", "SEALS", - "PLANK", "SINKS", "WHIRL", "SCREW", "SPOON", "GEHEN", "HABIA", "JAHRE", "BARRY", "AMPLY", "FRANZ", "TENIR", - "SLICE", "CARTS", "EDWIN", "DEALS", "JUSTE", "WHALE", "VIVRE", "PARUT", "HAUSE", "WESEN", "GEESE", "NAMEN", - "PASTE", "NIETS", "SCRAP", "LIBRE", "ADORN", "NOSES", "MADLY", "USTED", "WELKE", "ARDOR", "TROUT", "WELLS", - "ZEIDE", "DINGY", "SOEUR", "KNAVE", "KNOTS", "TIENE", "SEELE", "ESSEX", "CRAWL", "THEFT", "WEDER", "AREAS", - "STAIR", "ORION", "HEUTE", "DADDY", "COUGH", "GUISE", "PARMA", "FISTS", "GENUG", "FAUST", "AQUEL", "SNEER", - "PLOTS", "DAVON", "JENER", "PLUME", "GEBEN", "HAUTE", "PADRE", "ABBOT", "VOILA", "SELON", "ODOUR", "MEDAL", - "OPIUM", "DRAKE", "AMISS", "ETTEI", "GLOVE", "BLEAK", "SACHE", "GENRE", "RATIO", "DRUGS", "WINES", "HINDU", - "POOLS", "VISTA", "SCANT", "CORDS", "RAILS", "STAYS", "GENOA", "WHARF", "COSAS", "STEHT", "PRIMA", "DEUCE", - "KILLS", "MADGE", "BLUNT", "PASSA", "WAKED", "PUNCH", "SABLE", "FLOWN", "DRILL", "LIFTS", "ILLUS", "CRISP", - "WORMS", "FLINT", "HANDY", "ALLEY", "MASTS", "FIEND", "STUMP", "ADULT", "LEUTE", "SCRUB", "DECKS", "PANGS", - "CAMEL", "DEINE", "TITUS", "INDEX", "HAVEN", "HILDA", "GEIST", "ORDRE", "THORN", "TOILS", "CEDAR", "LOADS", - "WAXED", "SOPHY", "DAHER", "GAUGE", "WAGER", "JULIE", "WILDE", "PEERS", "AISLE", "BLANC", "LONGS", "ELLIS", - "MINUA", "CARLO", "SCARE", "LEVIN", "KUNDE", "DINGE", "SNARE", "WEISS", "STALE", "FAITS", "AVERT", "HADST", - "BATHS", "XVIII", "TIDES", "TINTS", "BULLY", "AZURE", "WHIGS", "VIELE", "ATOMS", "PERRY", "BLICK", "GIDDY", - "SOLAR", "FARCE", "BOWER", "WARES", "FROID", "SLIPS", "RENTS", "FEATS", "LEASE", "BLAIR", "BLAND", "CONDE", - "MARSH", "RUMOR", "BERTH", "BATHE", "BOOTY", "GLIDE", "ENNEN", "BERRY", "PAULA", "AIVAN", "FRIED", "DANES", - "WORTE", "BOOTH", "OTROS", "TAXED", "TWIGS", "ERSTE", "UNDUE", "CLUMP", "RICHE", "LIEGT", "LYRIC", "IHRES", - "LASTS", "ANNOY", "WEGEN", "OOGEN", "SELFE", "DABEI", "CASTS", "BOUGH", "ROUEN", "DIGNE", "SILAS", "LOCKE", - "CHART", "MAKER", "PEGGY", "SNOWS", "DETTA", "CHRIS", "ARENA", "QUIEN", "RELIC", "SECTS", "DOUCE", "BRIBE", - "FRIAR", "TINGE", "COURS", "VIENS", "HACER", "PEPYS", "LANES", "ETHER", "VICAR", "STEVE", "JASON", "VOCAL", - "JOINS", "BASES", "UNSER", "LIGNE", "DINAH", "VASES", "TEDDY", "NEUEN", "CURLY", "VALID", "CLEFT", "CHAPS", - "BARGE", "HAGUE", "BOLTS", "DARIN", "SMELT", "TULEE", "VOYEZ", "PURER", "ENTRA", "ODDLY", "VAPOR", "EENEN", - "SPIED", "SLATE", "CREWE", "BRACE", "CYRIL", "OSCAR", "IRONS", "DESDE", "LAMBS", "VROEG", "VROUW", "DICKY", - "KRAFT", "RALLY", "RESTA", "CRAVE", "GAYLY", "LEAPS", "FLUTE", "ONCLE", "SCOUT", "GOWNS", "SWINE", "GREED", - "ONION", "SAGES", "AMBER", "TARRY", "GRATE", "GESTE", "ANDRE", "MOEST", "JUURI", "CESSE", "SHEWN", "THIGH", - "LEAFY", "BENDS", "FERME", "BRAKE", "HURTS", "GRIND", "SILKS", "CLASH", "JELLY", "SOOTH", "DIZZY", "NEBEN", - "INCUR", "DOVER", "VERRE", "HAIRY", "STALK", "TOMBE", "HEAVE", "MUCHO", "INTER", "SPADE", "CREWS", "DENIS", - "HIELT", "FOSSE", "BULLS", "ALLEZ", "SOMME", "SNUFF", "BLIEB", "HAVOC", "ALLEM", "SMITE", "LACKS", "THROB", - "GUTEN", "PUEDE", "CLICK", "TIRER", "KORAN", "FLASK", "GRUND", "TERRA", "CAUSA", "GAUDY", "HIRAM", "JETER", - "FIBRE", "VERBS", "FORGE", "WAERE", "SPECK", "OLDEN", "SHRUB", "FLEDA", "GODLY", "HOOFD", "SONNE", "EDICT", - "DAGEN", "ISLES", "CHASM", "EDGED", "TRAPS", "BIRCH", "TRUER", "PAPAL", "ASSIS", "MASSE", "MORTE", "HETTY", - "REPEL", "INGEN", "SHRUG", "KOMEN", "PRISE", "HENNE", "LIVID", "BRIAN", "SALAD", "MATES", "BOVEN", "DOUZE", - "MEATS", "FEEDS", "TANTE", "WENCH", "DARTS", "FLATS", "TUBES", "UNITS", "HECHO", "PERCH", "POURS", "TRIPS", - "CIVIC", "MUNDO", "SPRAK", "ANTES", "SCARF", "DELLE", "HYVIN", "VASTE", "FOLIO", "NEEDY", "YELLS", "FAUTE", - "REGAL", "HOOKS", "ELIOT", "PALER", "TRENT", "MALTA", "MAKEN", "LUCAS", "NOUNS", "ATTIC", "WINDY", "SPIRE", - "SIEHT", "DERBY", "SAVOY", "MAUDE", "ILIAD", "DITTO", "PLINY", "FERNS", "BASIC", "GRAPE", "SOGAR", "TYING", - "DOUGH", "DERAS", "LEANT", "BELLA", "LICHT", "FILTH", "PEARL", "LUSTY", "OLIVE", "DROLL", "BADGE", "WAGED", - "SEEST", "ANKLE", "FERRY", "PENAL", "NEUER", "ESTOS", "FUMES", "SHEDS", "SHOWY", "SALEM", "REDEN", "STALL", - "JOYCE", "SANTO", "ZELFS", "HOTLY", "SCOLD", "CANDY", "LUCIA", "WEAVE", "MONTA", "SLACK", "VOGUE", "RHODA", - "SLANG", "FOODS", "PRIVY", "SERAI", "RENDU", "VALVE", "KUNST", "CYCLE", "EXCEL", "QUASI", "CRAIG", "VERTU", - "AVEVA", "MAYST", "HEATH", "MEDIO", "CELIA", "DEEZE", "BATES", "ATONE", "VENUE", "LURID", "SKOLA", "MDLLE", - "CHOKE", "PREND", "DARAN", "SAVES", "MANOR", "FORKS", "GENTE", "DITES", "TEAMS", "GOETH", "STOND", "COOKS", - "CHIEN", "PANEL", "GROWL", "MURAT", "BODEN", "DERES", "CAIUS", "FRERE", "HEIDI", "BROTH", "TEMPO", "JONAS", - "BROOM", "RACED", "SACKS", "TERRY", "NOOIT", "GENOM", "RAVEN", "FROGS", "ANNUM", "ACHED", "HEROD", "NOIRE", - "SURLY", "EIGEN", "EVADE", "BUGGY", "CONNU", "ATQUE", "WAKES", "REMIT", "REEFS", "ASSES", "MORSE", "RUFUS", - "ROMEO", "CHORD", "SLOOP", "LOANS", "TAWNY", "ETIAM", "NOCHE", "CONGO", "GHENT", "BINDS", "SLUNG", "CUBIC", - "GROOT", "DARUM", "MUSES", "GERMS", "NYMPH", "CUPID", "LUTTE", "FINES", "LUEGO", "PSALM", "HOARY", "XXIII", - "MUJER", "CROWS", "DECIR", "SULLA", "TITRE", "MAPLE", "SALTS", "POSTE", "DODGE", "ARMEN", "DISSE", "MACHT", - "FOCUS", "LITHE", "PASHA", "HIJOS", "BUSSY", "FLEUR", "JOLIE", "SMASH", "NORAH", "DOGMA", "TUTTO", "BOWLS", - "JEDEN", "LOATH", "JESTS", "MALAY", "DIGIT", "PENCE", "JOUER", "NORSE", "SLEEK", "DOWRY", "DEFER", "SELLS", - "CALEB", "BLEED", "DOWNS", "BABES", "DENTS", "ALKOI", "JEDEM", "PANES", "SINNE", "LEIGH", "MENOS", "WILDS", - "WOLFE", "ANDRA", "IDAHO", "DOWNE", "BETSY", "ADAPT", "MIJNE", "LIESS", "DICHO", "NELLY", "TUDOR", "BLOND", - "GRAVY", "FRAGE", "AELIG", "TIENT", "STEIN", "PLIED", "SABRE", "SQUAD", "STAAT", "REVEL", "YOLKS", "SEWED", - "CRANE", "SYRUP", "GIVER", "SEULS", "HAINE", "CASEY", "HICKS", "KATIE", "LOINS", "ABEND", "EXALT", "DENNA", - "STIRS", "SMOKY", "EUERY", "NICHE", "ONELY", "SINUN", "RABBI", "BRUST", "ELIAS", "JOSSA", "PAYER", "VEILS", - "CHAFF", "SULKY", "JUNGE", "PEACH", "MAMAN", "TUNES", "KAMEN", "DIANE", "LUMPS", "SHAVE", "CLOWN", "SHINY", - "LATEN", "HUSKY", "TOWEL", "REPOS", "GLADE", "GEORG", "NUEVA", "MANIA", "SAKES", "KEATS", "PULLS", "SIOUX", - "TAINT", "SUIVI", "CROIT", "MUUTA", "TILES", "BUSHY", "BLEND", "HAPLY", "ANJOU", "TRITT", "TURIN", "PLATZ", - "FAMED", "BARNS", "VECES", "DAMON", "LANGS", "HUMPH", "RELAX", "AUNTS", "SALUT", "CRAGS", "SHOAL", "COLIN", - "MOSSY", "GAULS", "CASKS", "WRING", "AHORA", "SPICE", "BONNY", "CURES", "BRUNO", "BRAVO", "ANDER", "CHESS", - "ALIAS", "SAUCY", "NOIRS", "TUNIC", "TOMBA", "LURED", "POKER", "RADIO", "CHAMP", "FINDE", "HULLO", "MECCA", - "SCALP", "FORMA", "SCARS", "MORTS", "BASSE", "SPINE", "PEREZ", "DIVAN", "HONTE", "LINDA", "ENVIE", "TEASE", - "BARED", "MADRE", "TUTTI", "OUTRE", "OMDAT", "LUCID", "SURGE", "CLAIR", "ONSET", "PONDS", "AUTEM", "MOODY", - "BALES", "SEDAN", "GALES", "LATCH", "LENDS", "LUIGI", "CAREY", "HOIST", "OTRAS", "GWINE", "WARUM", "DIVED", - "STRAP", "KELLY", "BEECH", "TROTH", "CHLOE", "PUNTO", "CIELO", "FROZE", "BUGLE", "POISE", "SIEUR", "TRUCK", - "ZEKER", "FOXES", "BALMY", "PICKS", "ISLAM", "AMEND", "DROOP", "LOPEZ", "DAIRY", "WALTZ", "BLOSS", "KYSYI", - "NAIVE", "SHORN", "URGES", "MAIZE", "WIELD", "HOLLY", "ARTER", "YORKE", "FRAIS", "NIGEL", "PAYNE", "COMET", - "SHUTS", "SOCKS", "HORDE", "SKIFF", "HACIA", "BESTE", "PARKS", "INERT", "PARLA", "INLET", "ODORS", "PRICK", - "KOREA", "FORUM", "LIEGE", "JEDES", "LIETH", "GASES", "SENZA", "TISCH", "PRATT", "ABNER", "BURLY", "BRAND", - "MINER", "TRACY", "LUGAR", "LAWNS", "GOTHS", "TARDY", "WIGHT", "DRURY", "VADER", "COOKE", "LAIRD", "TUFTS", - "FIXES", "SOLES", "VOWEL", "CLIVE", "KOSKA", "TAPER", "GUSTS", "RIANT", "FERAI", "COILS", "GROND", "SOINS", - "ABATE", "AFOOT", "EMILE", "POCHE", "LASSE", "ADELA", "PORTA", "BULKY", "MASKS", "DORIS", "KOMMA", "TAMEN", - "CRIER", "LOBBY", "ERNST", "EXPEL", "MANGE", "PACKS", "FATED", "DOVES", "DOLLS", "METRE", "AVAIS", "NEWER", - "LOGAN", "FREER", "DENNE", "MILLY", "GENIE", "STILE", "WARDS", "GEVEN", "WROTH", "BOONE", "COMER", "ANGUS", - "USHER", "CEDED", "PUFFS", "EBONY", "MIMIC", "SIEVE", "MEDIA", "ATLAS", "DESSA", "DUKES", "TERUG", "GRIMM", - "HAYES", "INDRA", "SHEWS", "DUMAS", "MYTHS", "FACON", "LILAC", "MISER", "TACIT", "SABER", "TUNIS", "NELLA", - "PORTO", "EERST", "ENNUI", "CIDER", "POUCH", "JUIST", "SHYLY", "NEVIL", "FROTH", "GIPSY", "EINST", "HERRA", - "ALLAY", "DANDY", "TOWNE", "HARTE", "HOHEN", "PLUIE", "NEIGE", "FASSE", "WAKEN", "YEAST", "CADIZ", "MINCE", - "WOODY", "AVOIT", "BOIRE", "PIERS", "FANGS", "KRIEG", "SAHIB", "DRYLY", "BELTS", "LACED", "WAYNE", "KOMME", - "AWARD", "FRANC", "FUERA", "IDEES", "COUPE", "VIOLA", "QUERY", "VENOM", "WAZIR", "SLABS", "FATES", "DIXON", - "POKED", "TOURS", "CHEFS", "ABACK", "PAOLO", "BECKY", "OAKEN", "KLEIN", "MOONS", "POLAR", "PRAYS", "DEIGN", - "CROOK", "SOLON", "LEANS", "PUPPY", "FINIT", "XXVII", "FOLIE", "POTTS", "LABEL", "SUSIE", "STACK", "POIDS", - "JESSE", "BLEEF", "HILLY", "BITTE", "WADED", "AIKAA", "EAVES", "MINUS", "REPAS", "NANCE", "DELHI", "HAZEL", - "ORBIT", "SURER", "TYLER", "DELIA", "RAJAH", "JUDEA", "STATT", "MENGE", "WHIMS", "BOYNE", "WHIPS", "PAINE", - "LIBEL", "MANOS", "TAMED", "CHIPS", "REIHE", "SWANN", "QUALE", "SPASM", "VRAIE", "TWINE", "CLIME", "ABHOR", - "DOMES", "KNOLL", "VISTO", "WEDGE", "GRIMY", "ROVER", "WRAPS", "LEVEE", "LEGTE", "AMITY", "NIHIL", "HALLO", - "CARVE", "TREND", "DAYES", "HEARE", "DEZEN", "SAISI", "COCOA", "QUEUE", "SWOON", "WAGEN", "PUMPS", "TRASH", - "SHARK", "MEADE", "RECUR", "DREGS", "VOILE", "CANTO", "HAVRE", "FATTO", "SNEAK", "MISMA", "SHOVE", "SOYEZ", - "CRANK", "FINED", "JUMPS", "BRYAN", "VENEZ", "ENSUE", "WILES", "ROARS", "FREAK", "POSSE", "SINUA", "FUSIL", - "DAHIN", "BEGAT", "ELUDE", "CROIX", "CLANG", "HOARD", "HATCH", "SOILS", "SAMMY", "STARR", "POIKA", "ELISE", - "LISZT", "JENES", "WIRST", "RUHIG", "MEDEA", "RAIDS", "NOOSE", "LETTY", "CHERE", "EASED", "SERVI", "MEJOR", - "FEARE", "SMACK", "ALTRI", "HITCH", "GETAN", "MITEN", "MAVIS", "ACIDS", "BEGET", "GARTH", "TIERS", "ANTON", - "NICER", "TANKS", "CESAR", "CANES", "PLUMB", "VANCE", "BEGOT", "DETER", "FLARE", "BOMBS", "FITLY", "JENEN", - "FLIRT", "HABER", "JOITA", "CHUTE", "READE", "STETS", "STEAK", "GAUZE", "SPARS", "PEARS", "PIQUE", "GLACE", - "PATSY", "HOVER", "RIPEN", "SWANS", "VARRO", "MARSE", "SONGE", "GUILE", "CANNA", "NACHT", "SALLE", "CARTE", - "AMMON", "TASSO", "RAZOR", "WHIST", "OMENS", "NIMMT", "GOEDE", "GRUNT", "DIRAI", "SPIKE", "COCKS", "DIEUX", - "SUURI", "UTILE", "SEREZ", "SOTTO", "STAAN", "DACHT", "LUNAR", "WHOSO", "DINNA", "CRETE", "PADUA", "SERGE", - "CACHE", "CHILI", "TIBER", "TAGEN", "RIGOR", "BIDDY", "AIMER", "MOUSE", "OXIDE", "PESOS", "SLING", "SPILT", - "BOWEN", "SINON", "SINAI", "TULLA", "TONIC", "LOIRE", "JUGER", "HAUPT", "CADET", "COSMO", "OCCHI", "CLANS", - "MUSTY", "MUMMY", "NIGER", "GOULD", "SQUAW", "UNTEN", "PENSA", "BIJNA", "CONTI", "NASAL", "TAUNT", "SILKY", - "NUNCA", "KAKSI", "DUCHY", "MELTS", "VASTA", "DAVIE", "QUELS", "ANCHE", "LIEUT", "BARDS", "BUNNY", "RILEY", - "TIENS", "MOTHS", "OMNES", "TANTA", "WOOED", "BRENT", "RASCH", "JADED", "ERRED", "BRAGG", "GUIDO", "HURON", - "LEILA", "VEDAS", "DEEPS", "RHONE", "SOWED", "BADEN", "NOMME", "SLUNK", "DEARS", "GUTER", "HIELD", "FEUER", - "LUPIN", "HAGEN", "JAMIE", "PLUMS", "GUSTO", "WETEN", "TENER", "HAWKS", "FOLLE", "CHILE", "SAADA", "SQUAT", - "KNACK", "MOORS", "SCOWL", "JENEM", "PLAZA", "SHEEN", "UNSRE", "DETTE", "WEBER", "BITES", "MASSA", "TUOTA", - "WOORD", "DOCKS", "AGILE", "SERFS", "OASIS", "BEGON", "LACES", "CORDE", "MEYER", "REISE", "FOGGY", "DINER", - "HUMID", "QUAIL", "LILLY", "FADES", "WRAPT", "GRUFF", "WEEPS", "ETTAE", "MARAT", "REICH", "LUSTS", "DICHT", - "APACE", "BUENA", "SLIMY", "TRUMP", "JOUES", "DUCAL", "COVET", "GIEBT", "AMANT", "FEIGN", "MEIST", "RATED", - "DOIGT", "ELLAS", "CHOIX", "GLOWS", "PALED", "MOOSE", "SHEAF", "SWARD", "HAREM", "DORIA", "PIKES", "WREST", - "MITTE", "TOOKE", "CHUMS", "TENGO", "LOTUS", "CUFFS", "KIRBY", "BARKS", "GYPSY", "BLOIS", "URBAN", "SIBYL", - "TIRES", "JOSTA", "TEILS", "ODIUM", "PURGE", "DOSES", "WEZEN", "NEPHI", "LIBRO", "SLYLY", "VIGIL", "HADES", - "THUMP", "BUYER", "RYDER", "ARYAN", "SNARL", "CHIME", "ROBED", "AUREZ", "DESTO", "SLEET", "CUBAN", "RARER", - "SEVER", "WOLLE", "FOYER", "SHAKY", "CAGES", "VANHA", "BETER", "TARDE", "VNDER", "FOURS", "KARNA", "POORE", - "OMNIA", "ACTES", "GOWER", "NINTH", "GENAU", "CLAUS", "BIENS", "KOHTA", "NUITS", "BRAID", "FEUDS", "GANGS", - "ILMAN", "LEECH", "NEQUE", "ANTAA", "BARBE", "MAGNA", "PILLS", "PANTS", "EARLE", "MERCI", "PERTE", "BABEL", - "LUCHT", "TENIA", "SEUIL", "DEGLI", "HARES", "VILDE", "RIOTS", "GJORT", "PIPER", "ZEIGT", "TIBET", "KICKS", - "JADIS", "SPELT", "PLAID", "CERCA", "FALLU", "MURKY", "HEINE", "REVUE", "CAMPO", "AMAZE", "LOOMS", "COSTA", - "STYPE", "JONAH", "CHUCK", "RELAY", "FIBER", "GLINT", "SLOTH", "MUSIK", "MOTIF", "BATCH", "SOWIE", "PATER", - "DEANE", "DOETH", "OBJET", "GAIUS", "LAMPE", "DOZED", "GULLY", "SEAMS", "BOYCE", "MAILS", "USEIN", "HENDE", - "PSHAW", "WHINE", "PRINZ", "WILLY", "HALFE", "MILKY", "ANVIL", "SHRED", "CHIDE", "EFFIE", "MOANS", "LEITH", - "COPSE", "DRAGS", "FARRE", "BANDE", "BHIMA", "YATES", "NIVER", "DEVEZ", "LAGEN", "OMBRE", "JONGE", "WATTS", - "ISTUI", "TUSKS", "AMACR", "JAREN", "VARIT", "REDDY", "PRIAM", "PRIMO", "COWED", "TOWED", "AVERY", "AXIOM", - "HOVEL", "BRICE", "CONES", "KUNNA", "LEISE", "HOBBY", "MINUT", "POOLE", "SINGH", "BOILS", "SATTE", "CRABS", - "MERLE", "WARNS", "FOURE", "ACHES", "YEARN", "SAHEN", "AILES", "LORRY", "LOVEL", "LUEUR", "SLANT", "METER", - "PHONE", "TAGES", "SUPER", "FUSED", "GLOSS", "NULLA", "ADEPT", "COREY", "LEGER", "SUAVE", "GAZES", "NEMEN", - "NOGET", "PANDU", "CAREW", "XXXII", "SCAMP", "HEATS", "TIPSY", "HOWLS", "MEMES", "ROOMY", "SANOA", "HAYDN", - "JETTE", "AMORY", "CONTE", "VERRA", "TILLY", "WELCH", "PEALS", "SULLY", "BRINE", "MOINE", "FACTO", "BORDS", - "FIRMS", "FARES", "TUNSI", "WAXEN", "BULBS", "BLIVA", "MESSE", "BRISE", "BORIS", "KAUAN", "RAKED", "IBSEN", - "PINTS", "DENKT", "POPES", "WHIFF", "SYBIL", "QUITS", "ARGOS", "AGGIE", "ANDES", "DALLA", "ABOOT", "EINDE", - "GERNE", "TYROL", "FADER", "SORTI", "MEDES", "AMIGO", "CELTS", "LEONE", "LILLE", "AGLOW", "INJUN", "KEEPE", - "RUINE", "LIEUX", "GEENE", "AUDIO", "SEETH", "STREW", "GRAZE", "KHAKI", "ABRAM", "APTLY", "HEERE", "ETANT", - "GOOSE", "POSED", "CHOPS", "ALTRO", "TITHE", "ANGST", "WORIN", "HIGHT", "CREAK", "NULLE", "DOWER", "SENTI", - "QUELL", "CHINK", "DESIR", "SETZT", "JAUNE", "LYNCH", "SPOUT", "PORES", "GOMEZ", "GABLE", "ACRID", "COZEN", - "BROCK", "DOYLE", "ZWECK", "ACTON", "RARES", "PIPED", "ZONES", "LIARS", "BOYLE", "KAMPF", "ALBUM", "SNIFF", - "MARES", "SHACK", "OMITS", "CITES", "AROMA", "WITTE", "HILFE", "HEERD", "ARGUS", "PINED", "USURY", "EARLS", - "YKSIN", "CULTE", "NOBIS", "SPILL", "FLOTS", "PROIE", "LURCH", "ADOLF", "ARDEN", "CARNE", "INTET", "WREAK", - "VOLLE", "SLIME", "TONGS", "GUERE", "VALES", "MANNA", "ISLET", "MANER", "DUNNO", "EASEL", "ECRIT", "NOOKS", - "GLENN", "HOSEA", "BUSTS", "ENACT", "FETES", "CHAUD", "GUILD", "SIZED", "ENOCH", "SPIEL", "OILED", "NATAL", - "ROCHE", "BASER", "PODER", "QUOTA", "FINIR", "SELMA", "NADIE", "DRAME", "LARKS", "DOWNY", "GULLS", "ARBOR", - "CERES", "LANDE", "WYATT", "IDIOM", "QUILT", "TUTTA", "HEINZ", "MAMMY", "MONDO", "TACHE", "QUACK", "TALON", - "ENSIN", "VEUVE", "SODOM", "DUELS", "RANCH", "MINNA", "CRAPE", "WAVER", "HOOPS", "MEBBE", "VERSA", "KUTEN", - "AVIEZ", "WIJZE", "PLAIT", "BIGOT", "URSUS", "VOLTA", "DRONA", "PADDY", "AHMED", "GIBBS", "OGSAA", "PABLO", - "TOILE", "TERME", "GRAIL", "JUICY", "CINCO", "VOORT", "RERUM", "MUNRO", "SAMMA", "REMIS", "AVOND", "ALLOY", - "RIZAL", "SITTE", "NEWES", "ONZEN", "OFFEN", "SCOFF", "XXXIV", "NOUSI", "GLUED", "BEARE", "STARB", "FUITE", - "BRETT", "NOTCH", "AARDE", "BRUNT", "DEWEY", "GAMLE", "POBRE", "LAITY", "RAVED", "TROTZ", "SPRIG", "JEMMY", - "MEUSE", "FUEGO", "BUELL", "WARMS", "BIXBY", "TWIST", "ROLLO", "RISKY", "GROPE", "MEGET", "DANNY", "LONGE", - "LOOKE", "AYRES", "SUPRA", "CHANG", "FOLGE", "SWAIN", "ARRAS", "CHRON", "SEIEN", "OTTAA", "OBEYS", "HALTE", - "MOLTO", "HORUS", "HALLE", "DEVON", "HARPS", "PHIAL", "SELIM", "TIDAL", "TAPIS", "PRIER", "DENKE", "FONDO", - "OGDEN", "ENDOW", "ASTIR", "FAIRS", "BAKED", "ENGEL", "HANNA", "SZENE", "PIVOT", "WIRED", "RAFTS", "COTES", - "PLATS", "TIMON", "ANNAN", "MENTE", "RONDE", "STINT", "RAGES", "KENNY", "SADIE", "ZIJDE", "CALLE", "DIVIN", - "DUPED", "VOUCH", "FOOTE", "LEWES", "LIISA", "DARCY", "SIGER", "LUCAN", "IEDER", "ARMAS", "DONAL", "BRADY", - "WERKE", "ANNEX", "STATO", "HUNTS", "KAMER", "ALWAY", "PHILO", "SYNOD", "PROBE", "HORNY", "LEEDS", "FOUNT", - "NABOB", "ESTAR", "AVRIL", "DEFOE", "CAGED", "LOOPS", "NEVEU", "CLYDE", "GOEST", "MANES", "LEAUE", "TUNED", - "RISHI", "POSER", "STOIC", "NUEVO", "SAMOA", "SALIR", "SNORT", "ADIEU", "BOCHE", "LOGIS", "JAKOB", "LESSE", - "FILMY", "FEINT", "HOOGE", "LESEN", "TUTTE", "VEDIC", "BATON", "SCION", "SUBIR", "MASSY", "CLOVE", "WHOOP", - "TEILE", "DEBUT", "STOWE", "HELPE", "PAILS", "ADELE", "HERUM", "DESKS", "LIIAN", "HINZU", "SIDED", "TRITE", - "HORAS", "JINNY", "POPPY", "EQUIP", "NEERE", "URINE", "VIENE", "BERNE", "DUROC", "PERTH", "ARIEL", "GREGG", - "WOLLT", "MOREL", "DEEMS", "VIEIL", "MEZZO", "POETE", "HERON", "SHIRK", "SIETE", "HEMOS", "SORES", "EATON", - "ASHEN", "VERSO", "ALTON", "STORA", "SWOOP", "CZECH", "JOVEN", "SOFAS", "TIMED", "XXXVI", "KREIS", "WEILE", - "SCOUR", "JACKY", "ROLFE", "PIENI", "STIRN", "DREAR", "PIERO", "TRAYS", "SYLLA", "ERANO", "LURKS", "GOURD", - "MAAKT", "WANED", "LIBYA", "ALSOF", "FELON", "BREST", "ELENA", "WERDE", "JUNTO", "TENTE", "VERTE", "SILLE", - "VOLER", "ALBAN", "GALEN", "REDAN", "WALSH", "ARBRE", "ASTOR", "RAYON", "REEVE", "MUCHA", "NUORI", "LABAN", - "SIKSI", "LUCCA", "ALLUS", "PURTY", "BUDGE", "USURP", "MONTY", "YEMEN", "JOKED", "QUAYS", "LECOQ", "WYLIE", - "MACON", "UPTON", "NEALE", "SEENE", "OFFRE", "OTTER", "POING", "LEHRE", "ADOBE", "ABYSS", "SPURN", "PLATT", - "HUUSI", "PLUTO", "REMUS", "MOGUL", "DULLY", "BLOED", "SPICY", "GEEST", "FEIND", "KETCH", "LASSO", "ESTIS", - "BRYCE", "MENER", "PACHA", "ANTOI", "LEPER", "BAUER", "JOHNS", "FEREZ", "SWEDE", "HERAB", "VOTER", "SIREN", - "ELDON", "ROOKS", "EBERS", "NESTA", "YEERE", "ROLLE", "SLADE", "GLAND", "JOSEF", "BORES", "GALOP", "NELLE", - "HEGEL", "MEANE", "WIDEN", "DAWES", "PLUSH", "ERHOB", "FORSE", "SOLLE", "ZIEHT", "SUCHE", "BERGE", "HASAN", - "MANDY", "OEVER", "BAIRN", "ELGIN", "ZULKE", "AKBAR", "BUREN", "CRUMB", "BACKE", "LINGE", "WOHIN", "HACEN", - "BEENE", "LARVA", "REGEL", "BEVEL", "DUVAL", "KNAPP", "GEEFT", "REELS", "JUANA", "ESSER", "PLATA", "STIEG", - "RACEY", "KNELL", "LLAMA", "VOISI", "HINDI", "PASAR", "VENTS", "HOLDE", "PITIE", "PAGET", "SLUMS", "GIRTH", - "DROSS", "MAIRE", "LAGER", "FEHLT", "MAZES", "UNDID", "THYME", "WHORE", "WOGAN", "BLUES", "RIAIT", "PANZA", - "SALVE", "ZOUDE", "HERRE", "MIDAS", "PREIS", "SIRES", "GLUCK", "ROAST", "FABER", "ASIAN", "DEREK", "DIGBY", - "YARNS", "NUAGE", "SENCE", "HEWER", "SAWED", "SAVOR", "SWIMS", "CABOT", "TAFEL", "ANSON", "CAROL", "SNART", - "HUSKS", "SERRA", "LYMAN", "MARNE", "EDDIE", "GENII", "DAMEN", "RAMON", "FAUNA", "ARMEE", "CHUSE", "DEMIE", - "COMBS", "VENAL", "BLINK", "CYNIC", "JOLLA", "FUESE", "DUPES", "LOSER", "TERSE", "DUNES", "KENNE", "WINDE", - "GAYER", "CONAN", "MISSY", "BALLE", "RANGS", "REBUS", "FUSSY", "DRILY", "JUGES", "HABET", "IMACR", "CUZCO", - "FELLA", "HEWED", "AIENT", "SOULE", "CAPUA", "ULTRA", "EBBED", "VIPER", "CAFES", "KENNT", "CRAZE", "ELMER", - "DRONE", "SERAS", "SIENA", "WOLFF", "GILLS", "CRYPT", "GRAFT", "SANTE", "HALTS", "GAPED", "GELUK", "YOKED", - "FONDS", "FERNE", "NAOMI", "SECHS", "BUXOM", "TENEZ", "QUAKE", "TENUE", "ROUVA", "LOURD", "LYNDE", "REACT", - "SOUPS", "ODEUR", "KAZAN", "BOXED", "GRUEL", "ECLAT", "GLENS", "JOHON", "SOPRA", "COCKE", "RENEE", "SWISH", - "PFERD", "SCHIP", "KERRY", "ALDEN", "DANSE", "DELTA", "UNIDO", "SOORT", "EXULT", "ANDEN", "BLIVE", "HORNE", - "AMORE", "EORUM", "KINDE", "JEERS", "NAMUR", "ULICK", "SNAIL", "ALECK", "MEEST", "SUEUR", "DUMMY", "MARSA", - "CURRY", "ELTON", "NEDDA", "DOLOR", "GEVAL", "DEUIL", "THONG", "MULTA", "RESIN", "DIRGE", "LARGO", "INDUS", - "LEMMY", "BONUS", "PLEAS", "ALINE", "ABOUE", "CRAMP", "CORSE", "VERTY", "FALTA", "WOMIT", "EARNS", "PRATE", - "WEARE", "PHEBE", "OVERT", "BRACH", "CAMPS", "TRAUM", "TALTE", "MERGE", "SAUVE", "RIUER", "COALS", "FLOYD", - "ASSAI", "EMPOR", "NOTTE", "VINDT", "ENKEL", "SUCHT", "ZAGEN", "EPIST", "LYELL", "RILLS", "WOFUL", "MEKKA", - "ZULUS", "HUSSY", "GASPS", "JOTTA", "SOOTY", "ANNUL", "CAPES", "SHERE", "NENNT", "SORGE", "DUANE", "CALMS", - "DARBY", "TAHOE", "LIEST", "SAUTA", "SPURT", "VINCI", "ANNAT", "BOORD", "DROPT", "ZELVE", "DWARF", "ATTER", - "BLEUS", "DEANS", "JEWEL", "HEMEL", "AFIRE", "FLEAS", "ADAIR", "INSEL", "MUITA", "TALLY", "CABAL", "SAMME", - "MELON", "TEPID", "MIELI", "FOLGT", "POLLS", "ROSIE", "WALDO", "LEBTE", "HINGE", "SNOUT", "STURM", "BEALE", - "HAGAR", "SEWER", "SONIA", "TEXAN", "BEETS", "CHERS", "BURMA", "HOWEL", "EYING", "SAIDE", "PUNIC", "ANTIQ", - "UNTIE", "DILKE", "SOUPE", "WYNNE", "SKATE", "AMYAS", "AGUAS", "HESSE", "ADAGE", "ROOST", "WOLDE", "LEIPZ", - "TILED", "EAMES", "ASTUI", "SCOOP", "KNABE", "CLINK", "ELEVE", "RAZON", "VIEJO", "REYES", "ENDNU", "RIVEN", - "ORDEN", "PARRY", "GULFS", "EXTOL", "AUNTY", "TWEED", "RENDS", "STERK", "SIDON", "XXXIX", "SUDAN", "THANS", - "COMYN", "MATTI", "MONCK", "OHREN", "HADDE", "VREDE", "WAIVE", "LEJOS", "MITRE", "BEFEL", "PASTY", "WORKE", - "BLOTT", "LIEBT", "BARRE", "SOARS", "FISKE", "CERTO", "WRITS", "LENTE", "VAINE", "FALLE", "VERDE", "MOGEN", - "GRETA", "MOCKS", "TRANS", "CROAK", "DITTY", "VRAIS", "MINOS", "SALUA", "CHAOS", "PEKIN", "APPEL", "MAGIS", - "FINCH", "ELVES", "ISANG", "KOLME", "POESY", "DECOY", "STOPT", "LUCIE", "NETTA", "SOULT", "ALGUN", "IDIOT", - "DISKS", "CRISE", "EVOKE", "LIEVE", "KLEUR", "FERMA", "EENIG", "CIRCE", "SUELO", "MATER", "BATTU", "LINIE", - "MORNE", "SEGUN", "LARRY", "TOMAR", "GAGNE", "SCALY", "STAVE", "GLEAN", "KOVIN", "DORIC", "GORSE", "INDES", - "BEPPO", "GLANZ", "KUNTI", "OPFER", "DRIES", "TOTTA", "SHIRE", "SILVA", "OBRAS", "NAAST", "DETTO", "OMAHA", - "SHEBA", "TITAN", "BEAKS", "COWAN", "MARKT", "ADOWN", "GOUTY", "HAREN", "TOADS", "QUALI", "RENAN", "MANNE", - "SNORE", "COMMA", "ZYNDE", "LYGIA", "RUDER", "SCRIP", "WIERD", "ELEGY", "TWANG", "ANTTI", "SNIPE", "TABOO", - "MOHUN", "BRANT", "CAIRE", "DEMAS", "ARDAN", "ALTID", "COLLA", "BRAWL", "LEASH", "CHAFE", "PARED", "VOMIT", - "MYERS", "SOURD", "SANOO", "TAIRE", "ABDUL", "GNADE", "PALSY", "MIKSI", "JOHAN", "MELEE", "DRAWL", "HULOT", - "SOUCI", "WILLE", "AGATE", "NOMEN", "SEEKE", "GRIPS", "LEGEN", "SINEW", "JAMBE", "VRAAG", "EMACR", "FETID", - "LEWIN", "EDERS", "VIVES", "KALLE", "FILMS", "GUTES", "MARYA", "MYSIE", "JERKS", "PAARD", "NANNY", "EENER", - "ROVED", "CHICK", "BITCH", "HINAB", "IDEEN", "ESSET", "HOURE", "RHYME", "JULIO", "BELLO", "HAVIA", "DEARE", - "IDLER", "WOBEI", "JABEZ", "MIAMI", "ESTOY", "AULUS", "ANCOR", "MOVER", "WARRE", "IMPEL", "KRONE", "RACHE", - "FASTS", "NEPAL", "BANJO", "REINO", "DAWNS", "HETTA", "STAEL", "CHIRP", "PENTE", "SOLLT", "VOCES", "DEZER", - "LAINE", "MINDE", "ANENT", "ASSET", "PUITS", "SWAYS", "MOCHT", "WAHRE", "DEMUR", "BETTE", "LUZON", "TIESI", - "TONED", "FREDA", "TAINE", "DALLE", "SPITS", "DYKES", "TOLLS", "SOONE", "EURER", "ANDAR", "KITES", "KENBY", - "PROPS", "PUNKT", "TIEFE", "FLAPS", "NIMES", "ROHAN", "GUIDA", "GOODY", "LEROY", "BUCKS", "ONKEL", "ADLER", - "ALAIN", "SWIRL", "ZOORT", "GARRY", "AMAIN", "TEINT", "EERIE", "GUSTY", "AIDES", "ANIMO", "DOEST", "DREST", - "LAPSI", "FARBE", "RACKS", "VOEUX", "BELIE", "LEMON", "INCAS", "NECKS", "LOGIK", "RIVET", "DOTTY", "EWING", - "LENOX", "ZORZI", "TUNNE", "COLDS", "INNES", "CARON", "TULLY", "KULKI", "NIEUW", "RIPER", "HATER", "ROSEN", - "WAIFE", "FATTY", "VOGEL", "YPRES", "CASAS", "SINUT", "WINKS", "NOLAN", "BOARS", "BIRON", "MINAE", "KRAAL", - "STUHL", "PODIA", "MARLY", "MOROK", "DINES", "VENTE", "WHACK", "AMINE", "PALMA", "DANDO", "TAVIA", "QUILS", - "PATIO", "TRICE", "MARIN", "FOUET", "WAILS", "HAKON", "WALLY", "WINCE", "VOLGA", "TEKEE", "TURNE", "REFUS", - "SERRE", "LEAKY", "PROXY", "CAPPY", "BLARE", "BUSEN", "MAORI", "ALOES", "SLAVS", "BANCS", "VESTA", "METED", - "NEUES", "ALWYN", "GURTH", "JOSUE", "STOLZ", "JOZEF", "JOSIE", "MAYER", "STUBE", "ZADIG", "SLOEG", "STRUT", - "REARS", "MARTA", "BROIL", "CARTA", "DEGEN", "HURST", "MUNDE", "EPICS", "VILJA", "PESAR", "SPAWN", "MINUN", - "TALKE", "CROFT", "TESTA", "SEARS", "MYRRH", "CASED", "LISTE", "CHURL", "DICEN", "KURUS", "CORAL", "LIVIA", - "COLTS", "DRAYS", "ESTAN", "POETA", "CABUL", "JUDEN", "LATHE", "LITLE", "LEPIC", "EATER", "DIJON", "PAVIA", - "CLODS", "BYRNE", "JULEY", "GENTS", "VERNE", "ASSAY", "CHINS", "LIZZY", "PUHUI", "CANBY", "KNOBS", "KRING", - "BEZIG", "STINK", "STUND", "BUTTS", "DONOR", "SLICK", "WASPS", "HAMAN", "LIMES", "MEURT", "HAMEL", "CITIE", - "DIVIL", "SLAYS", "CHUMP", "DIKES", "PEEPS", "LISLE", "MOTTE", "FORET", "MACHE", "BREVE", "STROM", "BUENO", - "JEHAN", "BONTE", "GETAL", "HOBBS", "TAHDO", "VOLTE", "GIUEN", "TEXTE", "CLANK", "VITAE", "MAUVE", "LIDIA", - "SMEAR", "CIRCA", "JOUIR", "PRETE", "ARTEN", "PARKE", "CAVIL", "XLIII", "STUDS", "VALLE", "BOUTS", "KOMMO", - "NEITI", "GRIPE", "JOKES", "SOFIA", "ASPEN", "HEROS", "LOSSE", "BRAUT", "HOODS", "KRANK", "LINER", "TIDEN", - "FLAWS", "HAUTS", "UMHER", "MAATA", "HELAS", "SULLE", "JERKY", "OUTDO", "RABID", "CINNA", "MANIE", "TUSEN", - "BATED", "BELLY", "BORST", "MOLES", "YUKON", "STORK", "RAZED", "OMACR", "SLITS", "SERIE", "HAITI", "MORAN", - "RESTO", "WOVON", "BISON", "TULIP", "DARYA", "REEDY", "AILED", "DIESS", "RIRES", "SELBY", "ELITE", "FIXER", - "MIHIN", "NORMA", "MEADS", "TIERE", "OOZED", "POSES", "MIENE", "GEHAD", "DIENT", "RECIT", "AZTEC", "EILTE", - "GNATS", "JETTY", "JOIES", "KOHTI", "BARTO", "MCKEE", "SAUDI", "STAGS", "PARME", "SINGE", "ALVAN", "CLAMS", - "CRONE", "CORSO", "PUHUA", "SATIS", "DENRY", "FURRY", "FURZE", "PUEDO", "COUDE", "DIDNA", "SOIRS", "ACORN", - "KNOWE", "KREEG", "TOOTH", "SERBS", "DUNCE", "THEIL", "SANFT", "ASHER", "NOGEN", "TETES", "AFFIX", "CREER", - "PITHY", "MARIO", "WORDY", "SIMLA", "ROTEN", "VISOR", "ZAKEN", "BOGEN", "HERAN", "ILLUD", "JAHRH", "CUBIT", - "PRISM", "BAIRD", "DUPRE", "TORRE", "REGNE", "SUCRE", "WERRY", "MORIR", "SLASH", "MENEE", "SERUE", "WIENS", - "BUNKS", "SEERS", "ZOGEN", "CORKS", "HOGAN", "GARDA", "TARTS", "FROME", "POCOS", "SPRAY", "CAIRN", "BAITH", - "DAGAR", "DEJAH", "ICFTU", "ZUNGE", "CHARY", "CHURN", "FLOES", "SIENS", "VAPID", "GAVIN", "GRIER", "BLOTS", - "GRAPH", "CUBES", "BIGHT", "ARLES", "MICAH", "NEBEL", "DIVER", "STABS", "BESSY", "KUULI", "SITZT", "SENAT", - "PINKS", "SPANS", "VINER", "ALKAA", "HAELT", "BAYLE", "CREUX", "ABBAS", "CROCE", "JAFFA", "ABLER", "GULCH", - "KANSA", "LIENS", "TITEL", "MATED", "MEGAN", "ALACK", "COHEN", "HELGI", "SAMOS", "ALDER", "FORAN", "LANKY", - "GRISE", "OATES", "LAVER", "IONIC", "ALIBI", "OUVRE", "GERTY", "PONER", "KEENE", "FESTE", "SANAT", "ESSEN", - "ADDER", "KURZE", "REALS", "CRAYE", "SANON", "TULEN", "VIDES", "POATE", "WIPES", "ANITA", "GAMLA", "DELFT", - "CESSA", "FORDS", "SERIA", "ASAPH", "BUDDY", "GRAAF", "BENNY", "KREBS", "CLAPS", "AMIES", "DUPIN", "VEXES", - "COMUS", "ROUES", "IRATE", "MANSE", "TACKS", "BRACY", "TABOR", "STORT", "JINKS", "SHEIK", "PARVA", "SKUNK", - "ALPES", "VERUS", "BOGUS", "RIVES", "NINON", "FILLY", "JENKS", "DOLCE", "ZETTE", "EPSOM", "DENYS", "LOBES", - "SLUSH", "BONUM", "CANOT", "MYLES", "OTWAY", "TREUE", "DANKE", "FREIE", "FITCH", "SUMME", "CAPER", "BETTS", - "SPREE", "TEENS", "IRWIN", "STAHL", "MIXES", "TIARA", "MOTTO", "NOGHT", "STURT", "BLEUE", "DALES", "STING", - "SMOCK", "BLOCH", "FUROR", "SATED", "VENNE", "JAPON", "RIGBY", "DULCE", "HEIRS", "WISPS", "PROWL", "REGEN", - "PENIS", "SICUT", "TOSTO", "HULDA", "HOFFE", "SEEDY", "DRIER", "LILLA", "ZEVEN", "LADLE", "SAURA", "VESTE", - "AINOA", "ALICK", "RUGBY", "CORPO", "BLYTH", "JANUS", "DICTA", "VAUNT", "LAVAL", "DIGHT", "PHOTO", "ZUVOR", - "ELSJE", "LUNGE", "TOMBS", "STOFF", "SURAT", "BEVOR", "OUDEN", "TRESS", "WIRES", "TIMOR", "RUSSE", "TABAC", - "WIRFT", "BERYL", "CACAO", "HEALS", "ROODE", "SWARE", "LESER", "LIDDY", "ANIMA", "ILLIS", "LUONA", "PECHE", - "ROPER", "TORAH", "GOTTA", "SOMIT", "KOMIK", "ANNEE", "ERDEN", "BANGS", "PREST", "HOUDT", "ONZER", "RAKES", - "PAIRE", "SOLVE", "TYSON", "UTICA", "TROUS", "GAMUT", "PEPIN", "CHUNK", "MAMIE", "GUYON", "ORIEL", "MIEDO", - "BUTTE", "OMNIS", "QUISO", "GERDA", "DIETH", "SIRUP", "WHISK", "FAERY", "REGIS", "INANE", "MULLE", "SHINS", - "TAMAR", "ETTEN", "CIBOT", "BOGGY", "CAPUT", "ETEEN", "GRETE", "WADNA", "CRUST", "IPSUM", "BLIGH", "LOUER", - "ONKIN", "BOGGS", "HOLDT", "MEINT", "SKEIN", "TOCHT", "VIALS", "AIDER", "RECUT", "GRECS", "LOUGH", "SPOOR", - "SEEME", "KNOPF", "STACY", "FIEFS", "WEEDY", "BENIN", "CHINE", "BREDA", "BEKAM", "SPINS", "WARST", "BOXER", - "TROST", "STUNT", "ANIMI", "AURAS", "SUMMA", "YHDEN", "YOUSE", "GROOM", "PETTO", "POMPS", "MIRZA", "HALLA", - "DENNY", "JAILS", "PUFFY", "DELLS", "MUREN", "UNCUT", "LEACH", "LIKEN", "MURAL", "OSMAN", "TARDA", "DESEO", - "MODER", "MODUS", "MANON", "COLIC", "PRUNE", "VIVIR", "CLINT", "RIGGS", "CANNE", "SALVO", "AIRES", "PIKKU", - "SAMEN", "BASTE", "ARCIS", "EASIE", "SHAMS", "MATTY", "CANNY", "NEIGH", "HYMEN", "DAUNT", "BRUNE", "FORZA", - "YAZOO", "VENDU", "NICKY", "WINNA", "PESTS", "WARME", "BENET", "TOSIN", "VASCO", "BETES", "LIEUE", "PETAL", - "ANNOS", "COLLE", "TRIEB", "KLAUS", "PUNIR", "VODKA", "THRON", "DECIA", "BRAUN", "SHUNS", "UUDEN", "ALPHA", - "FLOUT", "SELLE", "STOOL", "NEUEM", "BASLE", "HERNE", "INFRA", "ERASE", "NESTS", "BERGS", "MEDAN", "NOITA", - "SUCKS", "IESUS", "CITER", "FRAGT", "VEDER", "DRUID", "SCALD", "ALIDA", "QUITO", "HOERT", "JUNOT", "PERDU", - "GEARY", "BOOKE", "KENDE", "CHICO", "MEATH", "ROGUE", "SMYTH", "DIVES", "FILET", "FLAKE", "PUTTY", "SIDAN", - "DANAE", "ILAND", "BORDE", "HERBS", "LIMBO", "PEINT", "ROLES", "BRINY", "ETRES", "FLANC", "BLATT", "DERRY", - "MUEHE", "RODDY", "DAMES", "INGER", "XLVII", "AIRED", "CHATS", "PAULO", "EJECT", "FUNGI", "SIKES", "CADRE", - "COOLS", "SNAPS", "BLENT", "BOOBY", "VIIME", "DALLY", "HIVES", "LEDDY", "BALDY", "PRANK", "NONCE", "OVENS", - "SCHAL", "AUGUR", "CALYX", "MOWED", "TOITS", "CIMON", "SWEYN", "EDLEN", "PALES", "SPERM", "THANE", "BEZIT", - "EILEN", "UUTTA", "NAMAN", "MACKO", "SHANG", "LITEN", "BAZIN", "THORD", "ARMES", "YEARE", "CALLY", "TRYST", - "CAPRI", "ETHAN", "HOEHE", "AOUDA", "HALEY", "BERTA", "KAREL", "CLUES", "CURST", "FORAY", "ITHER", "LASST", - "RICHT", "YAWNS", "WILKS", "GRIFF", "JOKER", "MOCHE", "ATLEE", "BAIZE", "SITAE", "TEMOR", "TENET", "HAYTI", - "MANGY", "LEVIS", "SHAKS", "CURIO", "ERICA", "FRETS", "TAKAA", "KLAAS", "FROCK", "SHIED", "TRUSS", "WACHT", - "LAMME", "SAMAA", "ARMER", "SLINK", "BLEEK", "BIBBS", "ARMIE", "BANDY", "HARDI", "MINNE", "MORIN", "COURU", - "CLUNY", "MOLTI", "PLOMB", "AHMAD", "ROCCO", "SLAKE", "HAARE", "ARIAN", "WOCHE", "ZILAH", "LURES", "KLEID", - "NUOVO", "TUMOR", "CLEON", "CORTE", "SUNNE", "ELFIN", "GLAZE", "HEDEN", "VREES", "BODED", "HVIDE", "TRIBU", - "ZACHT", "YUSUF", "FRUEH", "LEDEN", "SITIO", "LITET", "MAURY", "PRYOR", "ROBUR", "GRIST", "RUFEN", "VAERE", - "VILKA", "JORAM", "FONTE", "NOGLE", "BASEL", "FIRMA", "HUNCH", "STAVA", "ALLIE", "FIERI", "HOLEN", "TROTS", - "KNEAD", "MANGO", "OEUFS", "PONCE", "QUEED", "KLANG", "AMENI", "PONTE", "EIGNE", "BRILL", "LOCUM", "NERFS", - "CLEVE", "COPIE", "EHREN", "BOTHE", "SIKHS", "SHREW", "HODGE", "WILEY", "ALTRE", "SIGLO", "CORFU", "HAIRS", - "MALER", "OZONE", "IGUAL", "VORST", "ANGES", "INNAN", "NOMAD", "DOMIN", "FUTUR", "ROPED", "HALED", "NAVEL", - "PEASE", "SAMBO", "HABLA", "ALTYD", "BREIT", "RENTE", "BAGGY", "LANGT", "NOCES", "SHEAR", "BULGE", "SLEDS", - "TRURO", "ZWARE", "BANNS", "NAPPE", "PLASH", "TARES", "WHIRR", "SCOTS", "WANDA", "CREDO", "FUMED", "GABEN", - "POJAT", "SISTA", "VIVIE", "HALVE", "SICKE", "SIMUL", "WHELP", "BREEN", "EBENE", "HAWKE", "STAUB", "MOVIE", - "PECHO", "BUONA", "CULPA", "GAGNA", "HEELE", "FLITS", "HIESS", "SOLUM", "STEHN", "FIRME", "ESTER", "MACAO", - "LIBER", "MCKAY", "PETRA", "TOKYO", "SOLCH", "BAYOU", "HELGA", "LOCUS", "ZWART", "FABIO", "ROSNY", "CORNE", - "MESMO", "BASTA", "DISCS", "GRINS", "FLEES", "PIGMY", "MAUER", "FRISK", "HUNDE", "LOGOS", "TROMP", "HOHER", - "SOGEN", "BOEUF", "CUORE", "FACIE", "RANDY", "ALTRA", "ANKER", "DROLE", "LARGA", "POJAN", "ORTON", "DEJAR", - "GIBES", "MARIS", "SETTE", "SUBIT", "VARMA", "WISHT", "GUPPY", "THOTH", "MANUS", "TRILL", "FIRTH", "BANAL", - "METTE", "SNAKY", "ESIIN", "LUNGA", "BOOMS", "GRUBS", "NEEMT", "TASSE", "OHEIM", "ENCOR", "FELLS", "VERTS", - "GUNST", "SYKES", "VESTS", "COLON", "AGONE", "AIMEZ", "BAHIA", "LEDGE", "LICET", "BEGAB", "FOAMY", "HULKS", - "NEGER", "KEYED", "MAIST", "FABEL", "LORNA", "DICHA", "SACRE", "SITEN", "GODEN", "DARKY", "DRAVE", "NIXON", - "LAULU", "FIGUR", "GANGE", "KLAMM", "LACEY", "KORTE", "RUDES", "TUERE", "LUNGO", "VERRY", "VAURA", "CUANT", - "DRAUF", "OFFAL", "GORGO", "MENUS", "MINED", "OPTIC", "DRANG", "GRIME", "CORNY", "KETTE", "HEADY", "IRAIT", - "PAPEL", "BLITZ", "HUGHS", "DROEG", "TENDU", "WIERP", "DOANE", "ELATE", "LACHE", "PIEUX", "TULLE", "TONGA", - "NAEHE", "CLASP", "FANNS", "WAFER", "DRUCK", "SAUTE", "NEVIS", "PITTI", "FALDT", "USQUE", "EWELL", "GUNGA", - "MORAY", "FISHY", "FLAIL", "MEBBY", "DAGNY", "CLOGS", "ELEND", "MEAUX", "GOLFE", "HACHE", "SAILE", "GIRTY", - "NADIA", "PAVEL", "PAHAA", "SAEPE", "KIRKE", "WALDE", "EXITS", "DOBBS", "HAMAR", "MARCO", "NOYES", "HAILS", - "MALEN", "SHALE", "TAGER", "VELDT", "GREIF", "MASHA", "LOUIE", "DEDEN", "LEZEN", "NETTE", "CROWE", "MOYNE", - "GILDS", "CREON", "GOTHA", "KAREN", "BRUME", "VERIE", "BOSCH", "MEURS", "HYENA", "LLENO", "HOCHE", "CONDY", - "PYOTR", "CALMA", "SAMAN", "STEHE", "BERIC", "LIBBY", "MONTS", "TRONE", "JUSSI", "ALCUN", "BEGGE", "COVES", - "NYODA", "ALLZU", "DAIES", "MULTE", "JUNKS", "MUSST", "DRAPS", "HAAST", "JACKS", "FAYRE", "RUNDT", "FREUD", - "COBRA", "FUMEE", "SAGST", "PURDY", "BEELD", "TORTS", "VERVE", "BRAZO", "JOYED", "WAGTE", "JOURN", "MUNGO", - "PAPUA", "FUELS", "HALBE", "PALMY", "REGLE", "ELISA", "JUSTO", "TWIRL", "CONST", "PASSO", "AMANG", "LICKS", - "NEDER", "DEGRE", "DIREZ", "FRILL", "KORAK", "KREUZ", "TYCHO", "POVAS", "ASTON", "GOSSE", "ASILE", "BOOZE", - "BUMPS", "SOMOS", "WASNA", "LORCS", "CRATE", "GROEN", "WOOER", "HEBER", "JUNTA", "TURCS", "VOLTO", "WHILK", - "DEEPE", "NEBST", "CODEX", "JUIFS", "ASIAA", "FULCO", "DOMED", "OUTEN", "STEPT", "GALBA", "CHEAT", "COULE", - "MEETE", "PAWED", "BUNCE", "SANIN", "JOLIS", "PORED", "PYYSI", "HOWAT", "LAURE", "ONNEN", "PRIED", "SCHAR", - "BAZAR", "MITAD", "MOUND", "FUERE", "TRYON", "BAISA", "CROUP", "KUNNE", "DOONE", "FOLEY", "NEHME", "DUOMO", - "SONNA", "FACIT", "KENYA", "GIVIT", "BLEAT", "HURLS", "EIFER", "AILIE", "BASIL", "HARMS", "SUTRA", "LARME", - "BRITT", "GRADY", "MOROS", "TRIPE", "ADMIS", "HERTE", "VOIES", "LANKA", "FUSSE", "GLOAT", "GROAT", "CLEEK", - "MESES", "MOOIE", "HAMIL", "FORDI", "MENOR", "SATYR", "TODDY", "TANNO", "GAMIN", "SOLOS", "YOURN", "AHMET", - "FATTA", "ERWIN", "POWIS", "SEGEN", "AMENE", "BASAL", "MOSTE", "TOYED", "MAGDA", "DIEPE", "GENOU", "OCHRE", - "WEITE", "VOLKE", "POORT", "AIKEN", "JOPPA", "SLUYS", "FIERE", "JOUST", "LACHT", "SAYDE", "DELOS", "LETHE", - "TAGUS", "ALIIS", "HIVER", "TENGA", "ECOLE", "FUENF", "ROTHA", "VETCH", "GLANS", "LAIDE", "FLYNN", "KHASI", - "KELLS", "BOONS", "SLUGS", "ULCER", "VENTO", "BRAWN", "ENTRO", "VAIMO", "AGNEW", "CREES", "MEIGS", "GOUGH", - "PUSSY", "VERBA", "RICKS", "ROWAN", "FERRO", "LAGDE", "PRESO", "RUPEE", "FRANS", "ALDUS", "CLARO", "HANNO", - "VIEJA", "MARFA", "OSTEN", "GAUZY", "BOULE", "DRAFT", "GREVE", "SETON", "HEEDS", "SONNY", "HANSE", "LUISE", - "ALIUD", "AMASS", "EARES", "ROWDY", "STEWS", "KLARA", "ROSSI", "HENKI", "NOSTI", "ZOUDT", "GROTE", "DEALE", - "FREMD", "KLONK", "LIBRI", "CAPET", "FLOSI", "SONST", "CULTS", "DIKKE", "ALGER", "DIXIE", "NUNEZ", "QUAFF", - "QUALM", "TAFFY", "QUEDA", "SEDGE", "GUERT", "ETIEZ", "ICING", "PHIPS", "PICTS", "ABBIA", "THEER", "COREA", - "KABUL", "COMEN", "GOLDE", "VIXEN", "ZWAAR", "EADEM", "FREES", "MARGE", "MEREN", "SANAN", "EDLER", "LUOGO", - "PEDAL", "VAMOS", "OSTIA", "ALTES", "COUTE", "MANDO", "AESOP", "SAPOR", "PIEDI", "GIRLE", "EMORY", "MADDY", - "HULLS", "REVES", "BAITS", "MARTS", "PROUE", "RIFTS", "IFRCS", "SANER", "EISEN", "MORGA", "WISSE", "DANCY", - "HAZEN", "LASKI", "ESTAS", "AVOUE", "CACHA", "EWIGE", "LOOPT", "WAMBA", "ACASO", "MOLLE", "JUXON", "BOCCA", - "COLUI", "TRONC", "DAUER", "PARET", "CIEUX", "DOERS", "MICHT", "NADAT", "ROULE", "DURER", "GRADO", "SUFFI", - "WAYES", "HAWES", "VOREN", "MILCH", "HVERT", "COTTA", "ZAIRE", "ALLOT", "PANSY", "FOILS", "LEAKS", "SEKIN", - "SEUEN", "WHOME", "BERRI", "BIGGS", "PARAS", "PROSY", "RACER", "BOHUN", "OOTAH", "NIOBE", "COPRA", "PLAGE", - "SPEKE", "AUDIT", "FINIS", "ROMPU", "BANDA", "DAMPS", "QUARE", "TOQUE", "BRIER", "DOREN", "NANON", "BELLI", - "TYRAN", "HILFT", "PUEDA", "MOLDS", "OELIG", "ATHEN", "LARCH", "PUNTA", "CAMEO", "MALIN", "SONYA", "JAUNT", - "LAPIS", "FINIE", "POMPE", "SIMMS", "BEADY", "GUANO", "KEHRT", "RUIDO", "WRANG", "FAHRT", "URIAH", "BLEVO", - "DUQUE", "HACKS", "TASTY", "MAINZ", "COWER", "FUORI", "TEHTY", "ARRAN", "TOOTS", "UTHER", "CROCK", "HELLE", - "QUUNE", "LINGO", "CLLIA", "ELIHU", "APPUI", "BLUER", "DIRIS", "BYLES", "FEDER", "INJIN", "STENO", "AGORA", - "COMUN", "GILET", "GRAUE", "MISES", "WOHER", "MENKO", "AMBOS", "MUTES", "MOREA", "YONGE", "LAKOU", "TECLE", - "BRATS", "LAMED", "PENDU", "TOPAZ", "DRUSE", "LAPEL", "WANDS", "AINDA", "FOOLE", "FUSES", "DEBBY", "EUGEN", - "FABRE", "MALTE", "FEINE", "GANTS", "HETKI", "SENTE", "AMICI", "LOUPS", "OPDAT", "LAUNE", "TOULD", "DURAS", - "VERDI", "STUMM", "CHENG", "FLUSS", "SACRA", "SPADA", "CRONY", "LEGDE", "OIKEA", "ALVAR", "ABAJO", "CURDS", - "PRINS", "THEYR", "BOWIE", "REDET", "WRACK", "FLORE", "ABAFT", "ANDET", "CENCI", "CLAES", "GONGS", "OWENS", - "CUIUS", "EXIGE", "IGNIS", "SLAAP", "OWAIN", "MALLE", "SACHS", "SIGNE", "BOESE", "MUSTA", "TORNO", "GORKA", - "ERANT", "REGNO", "SWART", "DORMI", "MASAI", "FIDES", "KARMA", "RAIDE", "ILIUM", "MARTY", "ROLLA", "DEERE", - "TOMES", "KNOOP", "DOLCH", "PAIGE", "ISTUU", "WAIFS", "COLES", "ASKEW", "EUREN", "HELST", "CLAUD", "QUENU", - "ROJAS", "VYASA", "KULTA", "LEYES", "BOYER", "FUCHS", "NAWAB", "BIDED", "BUSCA", "LORNE", "AIKOI", "BEFAL", - "DUCAT", "ELOPE", "FERAS", "LLEVA", "BEINE", "EMERY", "BRIAR", "DIXIT", "MURAD", "TODES", "OLELO", "SALUE", - "TOTEM", "DILLY", "HAFEN", "NOEMI", "UNCAS", "JUGEA", "QUIVI", "ALIXE", "CASSE", "GAVEN", "GRAMS", "TORSO", - "DAMIS", "NILES", "NORTE", "OXLEY", "PASOS", "AGIAS", "ESSAI", "REFIT", "FAGIN", "HARUN", "AENNU", "DECRY", - "HOOSE", "PONTS", "RENDE", "UARDA", "DRUMS", "PLIES", "PRIUS", "USKOA", "GAHAN", "MIRAR", "MELAS", "ECHTE", - "DARIO", "RHEIN", "ROSAS", "FRUTO", "SLAPS", "BRAHE", "OGIER", "ROQUE", "BEFIT", "VIAJE", "ETATS", "FINOT", - "LINDE", "ZISKA", "LAHAT", "MITAE", "AUTOR", "FERRE", "LAMES", "LLENA", "HOLME", "MAHON", "MUDGE", "SAVVA", - "SEDER", "THUGS", "CERTE", "EILIG", "RAMPE", "GHANA", "TALER", "LEERE", "BURGH", "CLASE", "DIRAS", "MITES", - "PULPY", "CUFFE", "PLADS", "HALEN", "PAWNS", "FAKIR", "MANET", "BRUNG", "TEPEE", "CERTA", "LULLS", "BUTCH", - "ARSON", "DUMPS", "EMITS", "FORTO", "PESTE", "SHANK", "HARPE", "KAMES", "MEERE", "ENGEN", "SUING", "VITAM", - "LLOYD", "THEOL", "WITWE", "DOTED", "DUETS", "MONET", "AVERS", "CADDY", "SINFI", "CALOR", "CORNS", "GEBET", - "HAYNE", "TOTEN", "GUTEM", "TILLS", "JULES", "ROSSE", "LECKY", "STAMM", "WAUGH", "ALLEE", "COMED", "COVEY", - "LONGA", "ZATEN", "BEVIS", "MATTH", "AVERE", "VERUM", "MIRAH", "SOREL", "BOURG", "CREPE", "PRUDE", "MAGUS", - "TAPIO", "ZOCHT", "TOMAS", "TURAN", "BLOUD", "BOORS", "DRAGA", "HELLS", "DECKE", "GEMMA", "CONGE", "ETAIS", - "SANAA", "SLONE", "AXLES", "FAVRE", "ORTEN", "CAPON", "STODO", "POSTO", "DANBY", "SLIGO", "VACHE", "BOTHA", - "CANTY", "CONEY", "LAVES", "NEUVE", "TALAR", "CAKED", "POCHI", "CAULD", "DAAGS", "GNRAL", "RAVES", "OLSEN", - "AMICO", "CINCH", "TIGRE", "MIKKO", "SABIN", "FECIT", "GARNI", "ILLUM", "OOREN", "RUBAN", "BEGAF", "QUIRE", - "TALUS", "FINNA", "HARER", "MORTO", "BHAER", "KOPFE", "LEMME", "BOITE", "UUSIA", "VENIA", "ASAKO", "BOTTE", - "CLEFS", "FAWNS", "MUNDI", "NUBES", "SEGUR", "ANTIC", "JURER", "CRAIK", "BANGE", "KOUDE", "PARER", "VOBIS", - "YOKES", "SAKRA", "SAULT", "BODEM", "PASSI", "SMIRK", "WHAUR", "GYGES", "MONNY", "REIMS", "KOPJE", "POSSA", - "CHIOS", "VARUS", "CLOUT", "LOUSE", "PLAIE", "PROWS", "HOLTE", "DANEY", "WONNE", "FESTA", "SPOOL", "TOREN", - "MATEO", "NINNY", "JUPES", "PLEBE", "TRAHI", "DACRE", "LEIBE", "LUBIN", "AGAPE", "HIDES", "OPERE", "LAWES", - "GALLS", "ASHBY", "ASSAM", "SHIEL", "FREYA", "RODEN", "TYLOR", "CUIRE", "RAKAS", "SPAET", "THUIS", "MINGO", - "NILUS", "FIERS", "MOLTE", "SCAPE", "SKIPS", "ZAMEN", "FINNS", "JUMBO", "PAPST", "ZIELE", "ABASE", "GREEP", - "HONRA", "TESTY", "ZYNEN", "BRUIN", "FLUCH", "PRUDY", "DEPIT", "LUNDI", "TUULI", "UEBEL", "DAGON", "COUPA", - "LUULI", "MORES", "STATA", "KEBLE", "PEKKA", "KLAAR", "MOURN", "DRAXY", "CLAVE", "JOLLE", "VENGA", "OSAGE", - "POLEN", "DIGNO", "HELFT", "GABON", "KHOJA", "SIDEN", "DARKE", "LUMPY", "POPPA", "HECLA", "AGING", "FREUT", - "HALES", "TANIS", "DRAMS", "NEGEN", "STEER", "EULER", "ETOIT", "MAAND", "PHAON", "CREME", "FULLE", "SALUD", - "CHUNG", "FELDE", "RUBEN", "FALLA", "PELEG", "CUYAS", "FACTA", "KNITS", "NOEMT", "TUPIA", "URREA", "SYNES", - "TROUW", "JEWRY", "KAMAR", "PETRI", "STEEN", "HECHA", "WEAKE", "AVILA", "HAFIZ", "HARDE", "MOQUE", "THEIM", - "FARIA", "GRYCE", "PASSY", "OPHIR", "ROSEY", "WEIBE", "GANGA", "PALUS", "AGENS", "COOED", "TRINA", "BETEL", - "UNDAN", "BALLY", "DELLO", "HAIRE", "PEONS", "RAVIN", "WAXES", "VIDAL", "ELKEN", "MAGNO", "RINSE", "VERBO", - "RIPON", "ADORE", "FAGOT", "ROAMS", "BOTEN", "NOBLY", "ROULA", "THAIS", "LOGER", "VOLGT", "MOSCA", "SABES", - "DAMER", "SCALA", "BASSO", "GORED", "NOEUD", "PISTI", "SLATS", "SIXTE", "BUOYS", "PAYED", "REAPS", "RUSES", - "HEALY", "AILLE", "BLASE", "BRULE", "OASES", "PLUGS", "HEARN", "IYONG", "PRIMI", "GRAYS", "PALOS", "TICKS", - "ESTRE", "POUCE", "DAMME", "FLECK", "GENET", "HOUGH", "JEBEL", "CLAPP", "HYDRA", "TAMER", "BLUME", "AVIDE", - "DOWDY", "GIMME", "ESCAP", "POSEY", "CRASS", "EGARD", "NUDGE", "TURBA", "WARTS", "KLAGE", "LENNY", "ELLEI", - "GONNA", "LETTO", "IONIA", "SCHAM", "EPHOD", "MIENS", "TAGIT", "DOUAY", "NICOL", "STOKE", "FLICK", "HUOLI", - "REEKS", "FAROE", "FRUEN", "FICHU", "KERTA", "JARED", "WUNDE", "AYONS", "PAESE", "PATTE", "SINGT", "SCAND", - "ADHUC", "LYMPH", "PARVE", "SCHOB", "STRAX", "PERON", "BUSTE", "DARLE", "EODEM", "MOTES", "ORSON", "RAUME", - "BRINK", "GNOME", "LASTA", "CRUMP", "SKENE", "GRILL", "NIEVE", "BUBEN", "JOSUA", "CEDER", "INNEN", "MANGA", - "OLEVA", "SIXES", "ERICK", "GERRY", "STUFE", "CLAYS", "FACHE", "GELYK", "SALTY", "TEEMS", "AISNE", "RANCE", - "KAUAS", "VYING", "DOUAI", "CASUS", "CORES", "GENIO", "JEUDI", "RADIC", "RUNES", "CLARY", "BRISA", "MUCUS", - "NATUR", "COGIA", "MOXON", "LUTES", "MARKE", "POOTY", "SLAAN", "HAVAS", "MALOS", "SOWER", "DOMUS", "SEGNO", - "DONDE", "ETHIE", "CONCH", "MERES", "CHING", "KITTS", "MARCY", "REPAY", "COMBE", "PAOLI", "ARDEA", "ENCYC", - "KONGE", "MCCOY", "VESEY", "BURRO", "FURIE", "GEZAG", "JIFFY", "QUIPS", "COLLO", "MAGIE", "MYNEN", "COWES", - "OMEGA", "SKEAT", "BELGE", "PAEAN", "EMLYN", "CALDO", "KINDA", "AFTRE", "CITTA", "FETUS", "ONHAN", "TULTA", - "WINCH", "ADIGE", "EMMET", "EUREM", "NATTY", "TALMA", "WILNA", "FORBI", "BARTH", "MONRO", "ZADOK", "BARBA", - "BARCO", "GAZON", "VAART", "ANNAS", "MAHDI", "WERTH", "RAHAA", "SMAAK", "WIRKT", "YEELD", "YERBA", "FINGO", - "HEBEN", "VEINE", "LLEGA", "USKON", "SEMEN", "STOEL", "ZZZZZ", "HOWDY", "REGAN", "CLACK", "SELVE", "GLEGG", - "RAUCH", "DESTA", "FUMER", "KOMST", "BLOCS", "TRIAD", "VENIT", "BALIN", "UPANI", "VISST", "LOMAX", "RYMER", - "SOTTE", "ZOETE", "FOKER", "MILDE", "TATEN", "AKING", "DAVOR", "NITRE", "ERROL", "CLERC", "CONTA", "DEBAR", - "KREET", "MODEM", "CULCH", "FLERE", "OLIKO", "PREYS", "SLOPS", "WHIPT", "ADDIE", "BEULE", "BIJOU", "ECHAR", - "FUOCO", "HATHE", "QUAIS", "SOLDE", "FUZZY", "PISTE", "SAINE", "WEREN", "ZEBRA", "MASKE", "ANIME", "SAYST", - "LVIII", "MOIRA", "RAINA", "WOTAN", "ARMIS", "ASIAT", "BAGUE", "PRESE", "VOLSE", "NUOVA", "PARIA", "ALTOS", - "ENSAM", "TROLL", "ABBIE", "WILTS", "CESTE", "CUYOS", "VIGNE", "BEEBE", "GLIED", "RASHI", "DEVRA", "HOERE", - "LEZER", "NAHEN", "BERUF", "HIGGS", "LEEKS", "POLKA", "UMACR", "DOOMS", "FASTE", "GLEBE", "NOSED", "LOWTH", - "DOORE", "HIRES", "SKULK", "BILDE", "CHEBE", "WAITE", "ACEST", "DAMAS", "HOJAS", "MOMMA", "PERLE", "BALAK", - "LADIE", "MARCK", "YAQUI", "YEATS", "RECAL", "SINAE", "RUDRA", "AMENA", "CUJUS", "SIGNA", "SOLUS", "KISSE", - "NULLO", "PALJO", "SIEHE", "TOLDE", "VISCH", "HOVEY", "ROOME", "ETZEL", "SHURE", "FIFES", "BALTY", "FRITH", - "MICHU", "MOCHA", "TURKE", "FATTI", "GULES", "GALLO", "HANNE", "IZAAK", "MINTO", "ROYCE", "SARUM", "DEBIT", - "TENNE", "MANDI", "WELNU", "GOLLY", "JEUGD", "KELLO", "FULKE", "GUEUX", "VALSE", "WOEDE", "BORGO", "GALLE", - "HEMAN", "LODER", "SHANE", "FUGUE", "PESER", "CYRUS", "HOVED", "MACES", "NADER", "BUCKY", "HOFER", "KARIN", - "MELUN", "WAITZ", "ACTIF", "THEWS", "BOSIO", "MARYS", "CEOUS", "WOHNT", "OISIN", "ARTES", "ILLAM", "MASTE", - "LAMAR", "MULTO", "SABIA", "SAGAS", "SCANS", "VITRE", "ZUMAL", "KRIPA", "MORUS", "PINTO", "COCHE", "DEDIT", - "TALAT", "LEBEL", "SLOOT", "WUCHS", "BABET", "BEUTE", "FESCH", "NUBIA", "DELVE", "DEVAS", "NOHOW", "BAKOM", - "CIOUS", "MANDA", "OVALS", "PURES", "DYAKS", "ECTOR", "HERBE", "HUVUD", "LAIRS", "LIUES", "PLENA", "DOLED", - "MINST", "VELEN", "COROT", "OLNEY", "TELLO", "CARLE", "GNAWS", "HAAND", "ISSUS", "VINCY", "ARAGO", "BORAX", - "CENTO", "NAGOT", "SAUVA", "SOUSE", "VOUTE", "GRECE", "HEYST", "MILNE", "NANDA", "PRETS", "SLIPT", "STATU", - "WEICH", "ACTUS", "LOTYS", "HOORT", "PERES", "SOGGY", "BUSCH", "PUSEY", "MENEN", "SACAR", "GESTO", "TOTUM", - "AUBRY", "BEARN", "KAUAI", "BRAHM", "LAING", "BODIE", "LACKE", "OVARY", "SAYES", "SNAGS", "THATS", "ALPEN", - "MAYNE", "SELAH", "AUGER", "BETEN", "CORRE", "RUNNE", "TRANK", "TYPED", "MULEY", "SPOTT", "IGLOO", "KEELS", - "CASSY", "FAGON", "ACCES", "CXIUJ", "INTRA", "PITTY", "AHURA", "BAMBI", "BAYNE", "HIERO", "MAGOG", "MOONE", - "TODAS", "WENNA", "CAPAZ", "FONDE", "NAGON", "SLAGS", "RILLA", "GIVET", "POMME", "ANNAL", "BARTY", "CURIA", - "LEGGE", "PERSE", "SAALE", "THORA", "EDELE", "PERDS", "POULE", "BOGAN", "KAABA", "EGALE", "VEDEN", "PINKY", - "GERME", "MOULT", "VARIE", "DEWAN", "TEKLA", "FUDGE", "OPINE", "OSIER", "LAUFE", "LERAT", "PINUS", "THULE", - "EUERN", "PERRO", "SENDE", "WRENN", "DATUM", "AILSA", "GHITA", "WACHE", "ATOLL", "PELTS", "PLOWS", "POCAS", - "SHOON", "BEBER", "KATSO", "SOLEN", "HORRY", "PLASE", "EDIFY", "LADER", "TENUS", "JIMSY", "MAIJA", "SOLIS", - "AIMEE", "KUOLI", "ORTER", "OLAVI", "ORMUZ", "COSEY", "JAMAS", "PUOLI", "HEDDA", "TAMPA", "GRABS", "GRATA", - "LOUSY", "TRATO", "UDDER", "BEZUG", "BINET", "AMBLE", "HELMS", "LEVAI", "PAPPI", "COPIA", "SIRVE", "LIGUE", - "ORMUS", "GOUTS", "SINKT", "VRIJE", "LOTTA", "GENOT", "GRUPO", "MOEGE", "SLILY", "ARIES", "FRIST", "KYNGE", - "LLEGO", "MESSO", "NUEVE", "ALAMO", "OJEDA", "ROCCA", "THIER", "ALIOS", "BIDES", "DIEDE", "RUHEN", "TESTE", - "OZIAS", "QATAR", "PILES", "ALMAS", "BARBS", "BODDE", "BRIGS", "GWYNE", "SUELE", "GEOFF", "OAKUM", "ROCAS", - "GESTA", "HUBER", "VILNA", "CLAMP", "KUKIN", "NUQUE", "PELOS", "VIDEO", "WEKEN", "CRECY", "GRABE", "WESEL", - "ABORD", "AIMES", "HOHEM", "TULIN", "AUTUN", "BASHA", "JADIN", "CORAM", "LOOKT", "NEARS", "CLYST", "INIGO", - "NEPOS", "THERM", "FLUES", "FOCAL", "MUETS", "NOYER", "OCKSA", "SHOLD", "FRITS", "HITZE", "LAMAS", "MAHAL", - "MOMUS", "POWYS", "MAGST", "MENNE", "GOLPE", "WAFTS", "DORFE", "IRVIN", "RYDAL", "WYMAN", "CAVED", "CUEUR", - "FLIER", "SIGUE", "STRAF", "TRAJE", "DIRCK", "KANAG", "WESER", "WHYTE", "NKARA", "PECKS", "SAFER", "TRONO", - "ASURA", "MENTZ", "SASHA", "APPAL", "ETAGE", "LASTE", "USETH", "VOIMA", "VORAN", "LEMAN", "SHOOP", "AGITE", - "DABAN", "OMAAN", "BEATS", "TUBBS", "COLPA", "MATAR", "JORGE", "MCGEE", "AVOWS", "HUSET", "JOASH", "KARTE", - "AVISO", "MEATE", "MKHYA", "MUCHE", "ASCOT", "GALLA", "ISORA", "ROMER", "EPOUX", "FALSO", "BALBI", "SUNDA", - "LECON", "SPIJT", "SPUNK", "TRAMS", "VARAS", "LOTTE", "MAZDA", "MOXOS", "AMADO", "BEURT", "BOOST", "CATER", - "NEBER", "NEGRA", "BREDE", "MYRON", "ARTIG", "CIEGO", "FILER", "GAYON", "HABLO", "HINDS", "KUULU", "LIGGA", - "MARDI", "PRIVE", "VLOOG", "CRABB", "SOANE", "DALEN", "DIGNA", "IDLED", "INNIG", "LATHS", "SANZA", "TRAGE", - "WELKS", "BERKS", "BRECK", "ELIAB", "HAUCH", "HERAT", "AVARE", "HAFVA", "PRIJS", "GROIN", "NUIRE", "OBESE", - "OLTRE", "RIJKE", "WEEPE", "SNELL", "SYRIE", "CONTO", "FURIA", "ILLOS", "MEALY", "QUEDO", "ROSIN", "SNOBS", - "JUNIA", "LUKIN", "MESTY", "HELIX", "WALKE", "BUSSI", "TIBBY", "KURTZ", "DOTES", "FAUVE", "GAINE", "LEEFT", - "TAELS", "ACHAB", "EMIRS", "JEPPE", "KNIPP", "PILAR", "REUSS", "WEGES", "DROOM", "EYRIE", "JAGEN", "SLUMP", - "MAREN", "MODUM", "TREIN", "BEBEL", "KRUPP", "LAURI", "LYNNE", "VINET", "FANNO", "HADNA", "MENDS", "TIOUS", - "AMREI", "ARNOT", "FLOSS", "HYLDA", "DEBEN", "OUBLI", "TYLKO", "AVUTO", "BOATE", "LUKEA", "TRATA", "ECLAC", - "WIESE", "ZWEIG", "LINKE", "PRESA", "VIRTU", "BINES", "HERSE", "LUPUS", "MATTA", "FIVES", "INSTR", "KLEED", - "POLVO", "GWILT", "UNHCR", "DOSED", "JUROR", "RUNGS", "SILKE", "ISHAM", "KURDS", "SILLA", "SPINK", "ACTOS", - "DORST", "RIMES", "VIISI", "ZUCHT", "ATTEN", "BUCHE", "BYERS", "CISSY", "FIONN", "KATRI", "ASSIM", "HUIUS", - "MENTI", "PARSE", "VIDER", "KAYAN", "KRANZ", "NIELS", "FOIRE", "KIOSK", "OBERN", "RECTE", "LIPPO", "CABBY", - "IMBUE", "DOLPH", "RADHA", "FELIZ", "HOMEM", "TONTY", "YAKOV", "AEGIS", "PEDIR", "ROUTS", "UNICO", "VELUT", - "CROLL", "ALZOO", "FALSA", "KLARE", "PONEN", "AMEER", "BOSSU", "ORAGE", "POSEE", "PUTEA", "SELIG", "CHAKA", - "DISKO", "FALCO", "TEXEL", "ASCHE", "DONAU", "DUFFY", "LERMA", "QUINN", "SISSY", "TORCY", "ALTAS", "BEEST", - "BOUGE", "MONEN", "REGNA", "SNACK", "LADKO", "PIPPA", "WEALD", "ILLAN", "KALLA", "KOTIA", "BARCA", "CENIS", - "KATZE", "KORAH", "PRADO", "SANNA", "BURNE", "IDYLL", "SITTA", "VIRUM", "BEATA", "GUION", "RASSI", "SHAND", - "GAGES", "PUHUU", "RAHAT", "CERRO", "FARAO", "PAZZI", "RIZZO", "SCENA", "KALTE", "PATES", "TEUER", "CASOS", - "COLPO", "FLUME", "MAIOR", "SABIO", "VULGO", "ARIUS", "FLAMM", "MARET", "COTON", "DUCTS", "FORGO", "GRITO", - "POSSO", "ZULKS", "ETAIN", "POSEN", "TAMIL", "ZUEGE", "BIPED", "BLURT", "THEOS", "FLUKE", "MUNCH", "YOURE", - "BANTU", "BAREE", "BEGUM", "CHILO", "DINKS", "FADEN", "MAHAN", "PEAUX", "REGNI", "EINAR", "ISLAS", "RAYNE", - "TAAVI", "ARABE", "SCEAU", "DUCIE", "LORDY", "CHOUX", "GAMMA", "HAIES", "HORTE", "REMUE", "SCULL", "ANTAR", - "ENGLE", "NOVUM", "TONYA", "BAUEN", "IPSIS", "LOIKE", "LOUES", "SINGW", "STRID", "VILER", "SHAWN", "AURAI", - "GUISA", "KUULE", "SALEN", "WONEN", "CARBO", "MELKY", "DUMPY", "SPICK", "SPORE", "BARAK", "ONGAR", "VOLKS", - "DIETS", "LAVAS", "MUUAN", "ANMUT", "BEMIS", "BOLES", "GOOCH", "KAFIR", "LAGOS", "LUNDY", "BESOM", "JURIS", - "MESAS", "TYMES", "WORAN", "FOUAN", "HOOKE", "PUGET", "SIGEL", "DEARY", "LUMEN", "MEURE", "SERUM", "GREIS", - "PFEIL", "BONIS", "EIKAE", "ZWANG", "ASIEN", "CREAN", "LENIN", "CYTEE", "HALVT", "LOCIS", "NENNE", "SPINY", - "TOXIC", "FERDY", "MAROT", "WOVEN", "AEONS", "DEBIA", "DRAPE", "REAMS", "RISER", "RONDS", "TAGET", "AMASA", - "JANIE", "NIZZA", "ICILY", "TENTA", "FUNDY", "FANES", "GOERA", "VALDE", "PINTA", "PITTS", "SEIDE", "BILGE", - "DOMUM", "GEHST", "GNASH", "HETTE", "DORAN", "FABRY", "HEIGH", "SARTO", "SCHAU", "DIENS", "DOGME", "FAUNS", - "FROMM", "TAPES", "DREUX", "FAGAN", "HELLA", "SKYLD", "ARMUT", "JUBAL", "LUISA", "NOYON", "PIOTR", "PYLOS", - "BRAGT", "MEENT", "VAREN", "ILION", "NARES", "STUBB", "ASTER", "DANNO", "MOPED", "ONNEA", "PALAA", "WORDE", - "DONAT", "MACAN", "POVEY", "BODES", "DRIPS", "EGGED", "FEELE", "KLUGE", "MUROS", "ORBIS", "TABBY", "WICKS", - "JUMNA", "SYLVI", "SUNNI", "SUYOS", "JINGO", "MONAT", "PIPPO", "APING", "CHACE", "FLUFF", "POLIS", "AUBER", - "RAMEE", "SEOUL", "LYDER", "RICOS", "UNSAY", "ELSON", "SNOOP", "ANNIS", "FANGE", "FIJNE", "HUMUS", "LENTS", - "PUREE", "STOUP", "YERES", "JONDO", "LIGNY", "BLOKE", "PROPE", "ELLIE", "ERNIE", "OAKES", "DURES", "NEEDE", - "PLIER", "GONDI", "HAMET", "DUNKT", "FOAMS", "LOGES", "SERES", "STELT", "ARRAH", "GRATZ", "VEDIA", "DICIT", - "HARTS", "REINA", "SIELU", "KAPPA", "LIPPE", "OLINE", "PROUT", "INGOT", "LENTO", "NEGLI", "PARIE", "VEZES", - "RUSTY", "SUKEY", "SURRY", "ANIGH", "CIMES", "CLOUS", "FARSI", "GRAAG", "TINHA", "ARLEE", "DONGO", "HAGAN", - "LXIII", "RAMAH", "CLEPT", "MULTI", "NIAIS", "VAINS", "LEONI", "LECHE", "RAINE", "HYDER", "LARES", "HAVET", - "MINHA", "BATTY", "ERICH", "KUGEL", "BRAGS", "CRIBS", "FAINE", "IHANA", "KAHTA", "SAATU", "SAYLE", "SEINS", - "SLATY", "SQUIB", "VIZIR", "YELPS", "ENDEN", "HINDE", "CORTA", "CRICK", "FLERA", "SVARA", "HIPPO", "DRONG", - "FETED", "LEHRT", "TULIT", "ADERN", "DIDON", "QUILP", "TREAS", "CARRE", "MALUM", "SULKS", "LEARY", "LYDDY", - "PETYA", "SALIM", "TOPSY", "BOWEL", "CRESS", "DESTE", "ENROL", "FLAKY", "BARBY", "BEERS", "SOMAL", "TIMMY", - "YEGOR", "JONNE", "POILS", "SOLDI", "ZARTE", "KROOL", "MARKO", "DERER", "TIETO", "VOSSA", "BULOW", "ALENE", - "DIMES", "DOIVE", "HOPEN", "MOLDY", "TOIVO", "AVICE", "HERMY", "BAINS", "DAGUE", "FLINK", "FRASE", "HANGT", - "LEGUA", "TEMPI", "VAITI", "CUTTS", "VANEL", "ALTHO", "EINIG", "FIORD", "HARME", "MOWER", "PAGAR", "ROEPT", - "FULDA", "MOSUL", "VIRGO", "DINAR", "KATSE", "KURJA", "LINEA", "NEARE", "RUINA", "TUVAN", "ASSUR", "LANDA", - "EVERE", "LIVRA", "PENSO", "TILTS", "VSQUE", "MILER", "NERVA", "SHINT", "STICH", "BEDST", "BLAUW", "COLDE", - "GLAUB", "MOATS", "PRISA", "SIGMA", "MAHEU", "CHENE", "MUITO", "POEME", "PUDGY", "RILED", "SILLS", "VERTA", - "AMRAM", "BONAR", "SVAVA", "CURSO", "FIDEM", "CAPEL", "JAINS", "PRAED", "GAVEL", "KIVEN", "PEDIS", "WAARD", - "FIUME", "GLYNN", "PIGOT", "BICHE", "DOOTY", "LADYE", "VENGE", "JUKES", "PETRO", "BONIE", "FOWLE", "INURE", - "KONDE", "POKES", "SIMBA", "LITRE", "PAREA", "HOREB", "TIMUR", "HEATE", "NAGRA", "NOYSE", "PAURA", "PEECE", - "AGNUS", "HYNDS", "MOSER", "GAWKY", "HYDRO", "ISTUA", "JACHT", "SCUDO", "UUREN", "MOSCO", "BLAUE", "LIVET", - "SECHE", "MAYDE", "PIEDE", "RUFFS", "KAPOR", "MARTE", "QUADE", "GLATT", "INDIO", "OMNEM", "SALIO", "SADOC", - "VOBAN", "ZICCI", "BRAES", "COELO", "CORTO", "IRKED", "SABEN", "DWYER", "JOUIT", "OFFRA", "SCIRE", "SELVA", - "TULET", "FLAGG", "ETUDE", "GOUGE", "HUJUS", "OLHOS", "ASOKA", "HURRA", "KEVIN", "NELLO", "FICHE", "JETAI", - "LECHO", "LIENE", "MUMPS", "ORICE", "VIZOR", "HARAN", "LUCRE", "MAGEN", "MOORD", "WEEKE", "GOREE", "KIMON", - "AVASI", "FINNY", "POSTA", "REGTE", "REMET", "SILEX", "TELCO", "TERVE", "SOHNE", "TOBIN", "GEZET", "GRIDO", - "IVIED", "ODDER", "FLACK", "JESSY", "DIECI", "EPAIS", "THRUE", "DICES", "GREYS", "MORTI", "PERCE", "BASSA", - "FASTI", "MCTEE", "SOUSA", "VIGNY", "WOLKE", "DUROS", "GRAVI", "PROVA", "SWILL", "VEELE", "WANES", "BASAN", - "CUFFY", "XVIIE", "HUZZA", "VIDAS", "ALLIS", "BEUST", "BRESL", "SWASH", "PEPLE", "GOWDY", "HADJI", "NAHUM", - "THESS", "FROND", "HAINT", "AMOOR", "LARNE", "NADAB", "NEILL", "SAGET", "CREDE", "HUMPS", "MOSSE", "MUTED", - "OLLEN", "VERBE", "ALVIN", "CLERY", "JOVIS", "SEYNS", "SONEY", "SYLPH", "VITRY", "WIGAN", "HILTS", "LINTU", - "SINDS", "UMBRA", "VISER", "CASCA", "MERAN", "MONNA", "PINEY", "BRAUE", "LAPIN", "MUEDE", "POWRE", "STAAR", - "BONEY", "MAGUA", "MAULE", "COYLY", "GUMMY", "HAULS", "RUEGO", "ARCHY", "BAUME", "BEVAN", "DEGAS", "KAISA", - "LESTE", "MOULE", "REDLY", "VETUS", "AFRIC", "BINGO", "LXVII", "OLLIE", "SURYA", "ABRIR", "DEDOS", "EXCES", - "LAITA", "MINTS", "POTER", "VARDA", "BACHE", "SALIC", "HURLA", "MEWED", "NECKE", "NEITO", "PARKA", "VAPAA", - "WAPEN", "BIJAH", "GUETE", "ZANTE", "SCUDI", "IDUNA", "KONTO", "LUXOR", "DIEST", "REGEM", "SPANK", "TIEDE", - "URSEL", "GENTI", "NAVES", "ONDAS", "PRZED", "FOLCO", "NABAL", "NOSEY", "PAULI", "TOKIO", "ATING", "DICTE", - "GUDAR", "RHEUM", "CUTTY", "OSMIA", "MAGER", "SANOT", "TIBIA", "TRAER", "LIANE", "MATHO", "MUNDY", "PATTI", - "REESE", "SEELY", "HARPY", "HUTTE", "LEGES", "LIETO", "LOBEN", "RAVIE", "SOAPY", "SOUGH", "VUOLE", "BOGES", - "LUTHA", "MAPPO", "BONED", "FLYER", "VISTE", "ENDOR", "FAYAL", "HALSE", "KEANE", "SIHON", "ANCHO", "ATTAR", - "AVETE", "KROPP", "MIMES", "OPALS", "PLUMA", "UNLIT", "AKLIS", "ELSLI", "HERDE", "SKALE", "ANDAN", "JEANS", - "PREVU", "BAIAE", "GOETZ", "HYLAS", "JANIN", "CHYLE", "DAUBS", "HANDE", "KNAAP", "NATUS", "ZONEN", "MONTI", - "SUDRA", "LIKER", "LYCKA", "UKASE", "VIMOS", "VOYCE", "AMIEL", "PANGO", "RANDE", "SHIVA", "SPION", "TIECK", - "DECEM", "MOLAR", "WENTE", "IMLAC", "PONTO", "WASTL", "BORTA", "FANDT", "JOKIN", "LYFTE", "RESET", "RIENS", - "SEMEL", "TWIXT", "WISST", "BEORN", "CEUTA", "NABAB", "EVIGT", "LEGNO", "LERNT", "LILAS", "MYLEN", "PAPPA", - "PREUX", "SPOOK", "FOGER", "TILDA", "CIVET", "ERRER", "GEARS", "JUGEZ", "MAGNI", "SEZEE", "SPISE", "AYEAR", - "BYEAR", "DUNNE", "JEZUS", "LUCIO", "MATEY", "SKALA", "TIBUR", "BAADE", "HOOTS", "LOONS", "TUNGA", "BIORN", - "DAGLI", "OUTRO", "GAETA", "JANEY", "MENIN", "SITKA", "FOIST", "JEHER", "LAVED", "LINUS", "PAWLE", "VICKY", - "RIDES", "TREED", "ARIST", "AWEEL", "CELIE", "BURRS", "CLIPS", "DICEA", "MOLTA", "NAVVY", "OLIKA", "RAYOS", - "SEYDE", "VANES", "ALLYN", "GEBOT", "GROSE", "BAHAY", "DUREE", "FAZER", "GOADS", "GULPH", "HAVER", "NIGRA", - "NOCTE", "PARUM", "SAATA", "SKIMS", "WAART", "WRAAK", "MUZIO", "OLSON", "RABAT", "REGIN", "RUNIC", "MANDE", - "RAUHA", "AYMER", "NAURU", "DREEF", "GAAET", "IRREN", "LESKI", "MECUM", "SAVON", "ARIAS", "HANKS", "ROZEL", - "ZEUGE", "DEUGD", "ETHIC", "GOODE", "TROVA", "AETNA", "BENJY", "ORMES", "TUBAL", "BRIMS", "BULKS", "FILCH", - "MONAD", "OOZES", "REGIA", "SARVE", "VENTI", "VOERT", "ARION", "COOTE", "SNECK", "TESSA", "BASON", "GIENG", - "INTIL", "NABIJ", "PRZEZ", "RAMAS", "SITUE", "CATHY", "GREER", "LINUX", "TOBIT", "DIDNT", "DROGO", "NEHMT", - "TIGES", "EDRIC", "TROUP", "AYUDA", "BEVAT", "DAPAT", "OVATE", "BALCH", "COGAN", "AIRTH", "GOWNE", "JUEGO", - "REBUT", "SALSA", "WIJDE", "JUBEL", "NEDDY", "RONNY", "FLAIR", "PASEO", "BRONX", "EWALD", "STURZ", "WODEN", - "KUMMA", "LETTE", "VANTE", "CAERE", "MISHA", "AMBAS", "FAAET", "RUNDE", "EYLAU", "MENGS", "FEEST", "LOWED", - "VENDE", "CUMAE", "GASSE", "HILMA", "IZUMO", "PISAN", "SOFIE", "CARRO", "CONIC", "CRIMP", "MUSKY", "SAYTH", - "STANK", "BOWES", "FAHNE", "THOME", "VIION", "ASTUU", "DICKE", "FASST", "PAROI", "LAWRY", "PRATO", "TENCH", - "CERTI", "ISCHT", "NOONE", "SENTO", "TAULD", "VLOOT", "VOIRE", "VOLTS", "BELUS", "CAVOR", "JORIS", "SEPPI", - "ERGAB", "BABIE", "CATON", "ABAHT", "BEDRE", "OPPIA", "POUCO", "SCHEU", "SEALE", "SUMUS", "VASTY", "FRIDA", - "LIPPI", "LOUVE", "BANCO", "BUONO", "CLOMB", "EDELN", "HOULD", "PELAS", "BODIN", "FAUGH", "JUDAR", "SEITZ", - "TOTTY", "WERTE", "STANE", "WINNE", "COCOS", "RIATT", "WERFF", "FIERA", "HINAN", "MATKA", "OATEN", "PLACA", - "CONOR", "DARRY", "HERTZ", "LAMON", "BEYNG", "CONTR", "INEPT", "SAKEN", "SIAMO", "STEDE", "MORNA", "AGERE", - "LANCA", "OSASI", "SADAN", "WOONT", "EVERS", "TROVE", "FACIL", "GRENE", "KUUSI", "PAPAS", "RECUS", "UUDET", - "VENTA", "ALOIS", "GILLY", "GONDY", "NAIRN", "BAILE", "CLAPT", "CLUCK", "CULTO", "EETEN", "GAPES", "NIVEL", - "TREKT", "WARLD", "ABIES", "AUTOS", "BARKE", "BRDER", "EASES", "GIRDS", "HAILL", "HIJAS", "RETRO", "SOMMA", - "STEEG", "BAYER", "CAMUS", "DEVIN", "HAMID", "HUANG", "IMOLA", "DICED", "HOCHA", "KASTA", "MULGA", "MYNDE", - "PIENO", "YAONG", "FURST", "GATTE", "SPASS", "SVEIN", "THEMA", "ANSAH", "CHAUX", "HEVIG", "PRIES", "ZOMER", - "BAGGS", "DOURO", "HIPPY", "SAONE", "VAVEL", "WETTE", "ALNAR", "EUILL", "PEATY", "VENIS", "CRITO", "LYNDA", - "OGRAM", "TEMPE", "ABOON", "FACEA", "FINEM", "KNOUT", "LLAMO", "LOAMY", "MENAR", "TISSU", "VERAS", "VIRES", - "COLBY", "JAINA", "TEUSE", "TYNAN", "YANKS", "BEZAT", "KLOOF", "SEAMY", "SIDDE", "STEIL", "TYYNI", "CARIA", - "HENTY", "LOWRY", "SOUZA", "ALIUS", "FEETE", "GRANO", "HALKI", "KELPO", "PESKY", "PLEBS", "POVIS", "TOPER", - "VLOER", "VOULD", "EDMEE", "GAZEN", "GITON", "HOLLO", "KAROL", "REIZE", "ROACH", "ULLOA", "VERBY", "CLEWS", - "EVANG", "FERAL", "GLICH", "THROE", "ZIJNS", "EURIE", "FERIA", "JEEMS", "PAAVO", "SAUTI", "DOODE", "PIENA", - "RADII", "UNICA", "CROLY", "HADAD", "KEYES", "NIKKY", "SANDE", "SEGEL", "SOFYA", "TEXAR", "GAZER", "LEDIG", - "NOTAR", "OBEIR", "VIMES", "GENUA", "KANAL", "LIMON", "RAHAB", "SUOMI", "VEJEN", "BATEN", "FJORD", "LEVES", - "PHARE", "RECTO", "AAGOT", "GIZUR", "NADJA", "RUGGE", "SNEYD", "DECKT", "FAILE", "GAOLS", "MELER", "PACTO", - "PAHAN", "CALIF", "MENON", "SEPOY", "WADDY", "ZELLE", "AENDA", "ASTUA", "KROON", "LETRA", "MINIT", "NEEWA", - "PATNA", "URIEL", "HITTE", "HOMOJ", "JOUET", "KOSKI", "LYSTE", "MINAS", "RUHTE", "SETZE", "LASER", "SONIC", +possible_list = [ + "AAHED", "AALII", "AARGH", "AARTI", "ABACA", "ABACI", "ABACS", "ABAFT", "ABAKA", "ABAMP", "ABAND", "ABASH", + "ABASK", "ABAYA", "ABBAS", "ABBED", "ABBES", "ABCEE", "ABEAM", "ABEAR", "ABELE", "ABERS", "ABETS", "ABIES", + "ABLER", "ABLES", "ABLET", "ABLOW", "ABMHO", "ABOHM", "ABOIL", "ABOMA", "ABOON", "ABORD", "ABORE", "ABRAM", + "ABRAY", "ABRIM", "ABRIN", "ABRIS", "ABSEY", "ABSIT", "ABUNA", "ABUNE", "ABUTS", "ABUZZ", "ABYES", "ABYSM", + "ACAIS", "ACARI", "ACCAS", "ACCOY", "ACERB", "ACERS", "ACETA", "ACHAR", "ACHED", "ACHES", "ACHOO", "ACIDS", + "ACIDY", "ACING", "ACINI", "ACKEE", "ACKER", "ACMES", "ACMIC", "ACNED", "ACNES", "ACOCK", "ACOLD", "ACRED", + "ACRES", "ACROS", "ACTED", "ACTIN", "ACTON", "ACYLS", "ADAWS", "ADAYS", "ADBOT", "ADDAX", "ADDED", "ADDER", + "ADDIO", "ADDLE", "ADEEM", "ADHAN", "ADIEU", "ADIOS", "ADITS", "ADMAN", "ADMEN", "ADMIX", "ADOBO", "ADOWN", + "ADOZE", "ADRAD", "ADRED", "ADSUM", "ADUKI", "ADUNC", "ADUST", "ADVEW", "ADYTA", "ADZED", "ADZES", "AECIA", + "AEDES", "AEGIS", "AEONS", "AERIE", "AEROS", "AESIR", "AFALD", "AFARA", "AFARS", "AFEAR", "AFLAJ", "AFORE", + "AFRIT", "AFROS", "AGAMA", "AGAMI", "AGARS", "AGAST", "AGAVE", "AGAZE", "AGENE", "AGERS", "AGGER", "AGGIE", + "AGGRI", "AGGRO", "AGGRY", "AGHAS", "AGILA", "AGIOS", "AGISM", "AGIST", "AGITA", "AGLEE", "AGLET", "AGLEY", + "AGLOO", "AGLUS", "AGMAS", "AGOGE", "AGONE", "AGONS", "AGOOD", "AGORA", "AGRIA", "AGRIN", "AGROS", "AGUED", + "AGUES", "AGUNA", "AGUTI", "AHEAP", "AHENT", "AHIGH", "AHIND", "AHING", "AHINT", "AHOLD", "AHULL", "AHURU", + "AIDAS", "AIDED", "AIDES", "AIDOI", "AIDOS", "AIERY", "AIGAS", "AIGHT", "AILED", "AIMED", "AIMER", "AINEE", + "AINGA", "AIOLI", "AIRED", "AIRER", "AIRNS", "AIRTH", "AIRTS", "AITCH", "AITUS", "AIVER", "AIYEE", "AIZLE", + "AJIES", "AJIVA", "AJUGA", "AJWAN", "AKEES", "AKELA", "AKENE", "AKING", "AKITA", "AKKAS", "ALAAP", "ALACK", + "ALAMO", "ALAND", "ALANE", "ALANG", "ALANS", "ALANT", "ALAPA", "ALAPS", "ALARY", "ALATE", "ALAYS", "ALBAS", + "ALBEE", "ALCID", "ALCOS", "ALDEA", "ALDER", "ALDOL", "ALECK", "ALECS", "ALEFS", "ALEFT", "ALEPH", "ALEWS", + "ALEYE", "ALFAS", "ALGAL", "ALGAS", "ALGID", "ALGIN", "ALGOR", "ALGUM", "ALIAS", "ALIFS", "ALINE", "ALIST", + "ALIYA", "ALKIE", "ALKOS", "ALKYD", "ALKYL", "ALLEE", "ALLEL", "ALLIS", "ALLOD", "ALLYL", "ALMAH", "ALMAS", + "ALMEH", "ALMES", "ALMUD", "ALMUG", "ALODS", "ALOED", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ALOWE", "ALTHO", + "ALTOS", "ALULA", "ALUMS", "ALURE", "ALVAR", "ALWAY", "AMAHS", "AMAIN", "AMATE", "AMAUT", "AMBAN", "AMBIT", + "AMBOS", "AMBRY", "AMEBA", "AMEER", "AMENE", "AMENS", "AMENT", "AMIAS", "AMICE", "AMICI", "AMIDE", "AMIDO", + "AMIDS", "AMIES", "AMIGA", "AMIGO", "AMINE", "AMINO", "AMINS", "AMIRS", "AMLAS", "AMMAN", "AMMON", "AMMOS", + "AMNIA", "AMNIC", "AMNIO", "AMOKS", "AMOLE", "AMORT", "AMOUR", "AMOVE", "AMOWT", "AMPED", "AMPUL", "AMRIT", + "AMUCK", "AMYLS", "ANANA", "ANATA", "ANCHO", "ANCLE", "ANCON", "ANDRO", "ANEAR", "ANELE", "ANENT", "ANGAS", + "ANGLO", "ANIGH", "ANILE", "ANILS", "ANIMA", "ANIMI", "ANION", "ANISE", "ANKER", "ANKHS", "ANKUS", "ANLAS", + "ANNAL", "ANNAS", "ANNAT", "ANOAS", "ANOLE", "ANOMY", "ANSAE", "ANTAE", "ANTAR", "ANTAS", "ANTED", "ANTES", + "ANTIS", "ANTRA", "ANTRE", "ANTSY", "ANURA", "ANYON", "APACE", "APAGE", "APAID", "APAYD", "APAYS", "APEAK", + "APEEK", "APERS", "APERT", "APERY", "APGAR", "APHIS", "APIAN", "APIOL", "APISH", "APISM", "APODE", "APODS", + "APOOP", "APORT", "APPAL", "APPAY", "APPEL", "APPRO", "APPUI", "APPUY", "APRES", "APSES", "APSIS", "APSOS", + "APTED", "APTER", "AQUAE", "AQUAS", "ARABA", "ARAKS", "ARAME", "ARARS", "ARBAS", "ARCED", "ARCHI", "ARCOS", + "ARCUS", "ARDEB", "ARDRI", "AREAD", "AREAE", "AREAL", "AREAR", "AREAS", "ARECA", "AREDD", "AREDE", "AREFY", + "AREIC", "ARENE", "AREPA", "ARERE", "ARETE", "ARETS", "ARETT", "ARGAL", "ARGAN", "ARGIL", "ARGLE", "ARGOL", + "ARGON", "ARGOT", "ARGUS", "ARHAT", "ARIAS", "ARIEL", "ARIKI", "ARILS", "ARIOT", "ARISH", "ARKED", "ARLED", + "ARLES", "ARMED", "ARMER", "ARMET", "ARMIL", "ARNAS", "ARNUT", "AROBA", "AROHA", "AROID", "ARPAS", "ARPEN", + "ARRAH", "ARRAS", "ARRET", "ARRIS", "ARROZ", "ARSED", "ARSES", "ARSEY", "ARSIS", "ARTAL", "ARTEL", "ARTIC", + "ARTIS", "ARUHE", "ARUMS", "ARVAL", "ARVEE", "ARVOS", "ARYLS", "ASANA", "ASCON", "ASCUS", "ASDIC", "ASHED", + "ASHES", "ASHET", "ASKED", "ASKER", "ASKOI", "ASKOS", "ASPEN", "ASPER", "ASPIC", "ASPIE", "ASPIS", "ASPRO", + "ASSAI", "ASSAM", "ASSES", "ASSEZ", "ASSOT", "ASTER", "ASTIR", "ASTUN", "ASURA", "ASWAY", "ASWIM", "ASYLA", + "ATAPS", "ATAXY", "ATIGI", "ATILT", "ATIMY", "ATLAS", "ATMAN", "ATMAS", "ATMOS", "ATOCS", "ATOKE", "ATOKS", + "ATOMS", "ATOMY", "ATONY", "ATOPY", "ATRIA", "ATRIP", "ATTAP", "ATTAR", "ATUAS", "AUDAD", "AUGER", "AUGHT", + "AULAS", "AULIC", "AULOI", "AULOS", "AUMIL", "AUNES", "AUNTS", "AURAE", "AURAL", "AURAR", "AURAS", "AUREI", + "AURES", "AURIC", "AURIS", "AURUM", "AUTOS", "AUXIN", "AVALE", "AVANT", "AVAST", "AVELS", "AVENS", "AVERS", + "AVGAS", "AVINE", "AVION", "AVISE", "AVISO", "AVIZE", "AVOWS", "AVYZE", "AWARN", "AWATO", "AWAVE", "AWAYS", + "AWDLS", "AWEEL", "AWETO", "AWING", "AWMRY", "AWNED", "AWNER", "AWOLS", "AWORK", "AXELS", "AXILE", "AXILS", + "AXING", "AXITE", "AXLED", "AXLES", "AXMAN", "AXMEN", "AXOID", "AXONE", "AXONS", "AYAHS", "AYAYA", "AYELP", + "AYGRE", "AYINS", "AYONT", "AYRES", "AYRIE", "AZANS", "AZIDE", "AZIDO", "AZINE", "AZLON", "AZOIC", "AZOLE", + "AZONS", "AZOTE", "AZOTH", "AZUKI", "AZURN", "AZURY", "AZYGY", "AZYME", "AZYMS", "BAAED", "BAALS", "BABAS", + "BABEL", "BABES", "BABKA", "BABOO", "BABUL", "BABUS", "BACCA", "BACCO", "BACCY", "BACHA", "BACHS", "BACKS", + "BADDY", "BAELS", "BAFFS", "BAFFY", "BAFTS", "BAGHS", "BAGIE", "BAHTS", "BAHUS", "BAHUT", "BAILS", "BAIRN", + "BAISA", "BAITH", "BAITS", "BAIZA", "BAIZE", "BAJAN", "BAJRA", "BAJRI", "BAJUS", "BAKED", "BAKEN", "BAKES", + "BAKRA", "BALAS", "BALDS", "BALDY", "BALED", "BALES", "BALKS", "BALKY", "BALLS", "BALLY", "BALMS", "BALOO", + "BALSA", "BALTI", "BALUN", "BALUS", "BAMBI", "BANAK", "BANCO", "BANCS", "BANDA", "BANDH", "BANDS", "BANDY", + "BANED", "BANES", "BANGS", "BANIA", "BANKS", "BANNS", "BANTS", "BANTU", "BANTY", "BANYA", "BAPUS", "BARBE", + "BARBS", "BARBY", "BARCA", "BARDE", "BARDO", "BARDS", "BARDY", "BARED", "BARER", "BARES", "BARFI", "BARFS", + "BARIC", "BARKS", "BARKY", "BARMS", "BARMY", "BARNS", "BARNY", "BARPS", "BARRA", "BARRE", "BARRO", "BARRY", + "BARYE", "BASAN", "BASED", "BASEN", "BASER", "BASES", "BASHO", "BASIJ", "BASKS", "BASON", "BASSE", "BASSI", + "BASSO", "BASSY", "BASTA", "BASTI", "BASTO", "BASTS", "BATED", "BATES", "BATHS", "BATIK", "BATTA", "BATTS", + "BATTU", "BAUDS", "BAUKS", "BAULK", "BAURS", "BAVIN", "BAWDS", "BAWKS", "BAWLS", "BAWNS", "BAWRS", "BAWTY", + "BAYED", "BAYER", "BAYES", "BAYLE", "BAYTS", "BAZAR", "BAZOO", "BEADS", "BEAKS", "BEAKY", "BEALS", "BEAMS", + "BEAMY", "BEANO", "BEANS", "BEANY", "BEARE", "BEARS", "BEATH", "BEATS", "BEATY", "BEAUS", "BEAUT", "BEAUX", + "BEBOP", "BECAP", "BECKE", "BECKS", "BEDAD", "BEDEL", "BEDES", "BEDEW", "BEDIM", "BEDYE", "BEEDI", "BEEFS", + "BEEPS", "BEERS", "BEERY", "BEETS", "BEFOG", "BEGAD", "BEGAR", "BEGEM", "BEGOT", "BEGUM", "BEIGE", "BEIGY", + "BEINS", "BEKAH", "BELAH", "BELAR", "BELAY", "BELEE", "BELGA", "BELLS", "BELON", "BELTS", "BEMAD", "BEMAS", + "BEMIX", "BEMUD", "BENDS", "BENDY", "BENES", "BENET", "BENGA", "BENIS", "BENNE", "BENNI", "BENNY", "BENTO", + "BENTS", "BENTY", "BEPAT", "BERAY", "BERES", "BERGS", "BERKO", "BERKS", "BERME", "BERMS", "BEROB", "BERYL", + "BESAT", "BESAW", "BESEE", "BESES", "BESIT", "BESOM", "BESOT", "BESTI", "BESTS", "BETAS", "BETED", "BETES", + "BETHS", "BETID", "BETON", "BETTA", "BETTY", "BEVER", "BEVOR", "BEVUE", "BEVVY", "BEWET", "BEWIG", "BEZES", + "BEZIL", "BEZZY", "BHAIS", "BHAJI", "BHANG", "BHATS", "BHELS", "BHOOT", "BHUNA", "BHUTS", "BIACH", "BIALI", + "BIALY", "BIBBS", "BIBES", "BICCY", "BICES", "BIDED", "BIDER", "BIDES", "BIDET", "BIDIS", "BIDON", "BIELD", + "BIERS", "BIFFO", "BIFFS", "BIFFY", "BIFID", "BIGAE", "BIGGS", "BIGGY", "BIGHA", "BIGHT", "BIGLY", "BIGOS", + "BIJOU", "BIKED", "BIKER", "BIKES", "BIKIE", "BILBO", "BILBY", "BILED", "BILES", "BILGY", "BILKS", "BILLS", + "BIMAH", "BIMAS", "BIMBO", "BINAL", "BINDI", "BINDS", "BINER", "BINES", "BINGS", "BINGY", "BINIT", "BINKS", + "BINTS", "BIOGS", "BIONT", "BIOTA", "BIPED", "BIPOD", "BIRDS", "BIRKS", "BIRLE", "BIRLS", "BIROS", "BIRRS", + "BIRSE", "BIRSY", "BISES", "BISKS", "BISOM", "BITCH", "BITER", "BITES", "BITOS", "BITOU", "BITSY", "BITTE", + "BITTS", "BIVIA", "BIVVY", "BIZES", "BIZZO", "BIZZY", "BLABS", "BLADS", "BLADY", "BLAER", "BLAES", "BLAFF", + "BLAGS", "BLAHS", "BLAIN", "BLAMS", "BLART", "BLASE", "BLASH", "BLATE", "BLATS", "BLATT", "BLAUD", "BLAWN", + "BLAWS", "BLAYS", "BLEAR", "BLEBS", "BLECH", "BLEES", "BLENT", "BLERT", "BLEST", "BLETS", "BLEYS", "BLIMY", + "BLING", "BLINI", "BLINS", "BLINY", "BLIPS", "BLIST", "BLITE", "BLITS", "BLIVE", "BLOBS", "BLOCS", "BLOGS", + "BLOOK", "BLOOP", "BLORE", "BLOTS", "BLOWS", "BLOWY", "BLUBS", "BLUDE", "BLUDS", "BLUDY", "BLUED", "BLUES", + "BLUET", "BLUEY", "BLUID", "BLUME", "BLUNK", "BLURS", "BLYPE", "BOABS", "BOAKS", "BOARS", "BOART", "BOATS", + "BOBAC", "BOBAK", "BOBAS", "BOBOL", "BOBOS", "BOCCA", "BOCCE", "BOCCI", "BOCHE", "BOCKS", "BODED", "BODES", + "BODGE", "BODHI", "BODLE", "BOEPS", "BOETS", "BOEUF", "BOFFO", "BOFFS", "BOGAN", "BOGEY", "BOGGY", "BOGIE", + "BOGLE", "BOGUE", "BOGUS", "BOHEA", "BOHOS", "BOILS", "BOING", "BOINK", "BOITE", "BOKED", "BOKEH", "BOKES", + "BOKOS", "BOLAR", "BOLAS", "BOLDS", "BOLES", "BOLIX", "BOLLS", "BOLOS", "BOLTS", "BOLUS", "BOMAS", "BOMBE", + "BOMBO", "BOMBS", "BONCE", "BONDS", "BONED", "BONER", "BONES", "BONGS", "BONIE", "BONKS", "BONNE", "BONNY", + "BONZA", "BONZE", "BOOAI", "BOOAY", "BOOBS", "BOODY", "BOOED", "BOOFY", "BOOGY", "BOOHS", "BOOKS", "BOOKY", + "BOOLS", "BOOMS", "BOOMY", "BOONG", "BOONS", "BOORD", "BOORS", "BOOSE", "BOOTS", "BOPPY", "BORAK", "BORAL", + "BORAS", "BORDE", "BORDS", "BORED", "BOREE", "BOREL", "BORER", "BORES", "BORGO", "BORIC", "BORKS", "BORMS", + "BORNA", "BORON", "BORTS", "BORTY", "BORTZ", "BOSIE", "BOSKS", "BOSKY", "BOSON", "BOSUN", "BOTAS", "BOTEL", + "BOTES", "BOTHY", "BOTTE", "BOTTS", "BOTTY", "BOUGE", "BOUKS", "BOULT", "BOUNS", "BOURD", "BOURG", "BOURN", + "BOUSE", "BOUSY", "BOUTS", "BOVID", "BOWAT", "BOWED", "BOWER", "BOWES", "BOWET", "BOWIE", "BOWLS", "BOWNE", + "BOWRS", "BOWSE", "BOXED", "BOXEN", "BOXES", "BOXLA", "BOXTY", "BOYAR", "BOYAU", "BOYED", "BOYFS", "BOYGS", + "BOYLA", "BOYOS", "BOYSY", "BOZOS", "BRAAI", "BRACH", "BRACK", "BRACT", "BRADS", "BRAES", "BRAGS", "BRAIL", + "BRAKS", "BRAKY", "BRAME", "BRANE", "BRANK", "BRANS", "BRANT", "BRAST", "BRATS", "BRAVA", "BRAVI", "BRAWS", + "BRAXY", "BRAYS", "BRAZA", "BRAZE", "BREAM", "BREDE", "BREDS", "BREEM", "BREER", "BREES", "BREID", "BREIS", + "BREME", "BRENS", "BRENT", "BRERE", "BRERS", "BREVE", "BREWS", "BREYS", "BRIER", "BRIES", "BRIGS", "BRIKI", + "BRIKS", "BRILL", "BRIMS", "BRINS", "BRIOS", "BRISE", "BRISS", "BRITH", "BRITS", "BRITT", "BRIZE", "BROCH", + "BROCK", "BRODS", "BROGH", "BROGS", "BROME", "BROMO", "BRONC", "BROND", "BROOL", "BROOS", "BROSE", "BROSY", + "BROWS", "BRUGH", "BRUIN", "BRUIT", "BRULE", "BRUME", "BRUNG", "BRUSK", "BRUST", "BRUTS", "BUATS", "BUAZE", + "BUBAL", "BUBAS", "BUBBA", "BUBBE", "BUBBY", "BUBUS", "BUCHU", "BUCKO", "BUCKS", "BUCKU", "BUDAS", "BUDIS", + "BUDOS", "BUFFA", "BUFFE", "BUFFI", "BUFFO", "BUFFS", "BUFFY", "BUFOS", "BUFTY", "BUHLS", "BUHRS", "BUIKS", + "BUIST", "BUKES", "BULBS", "BULGY", "BULKS", "BULLA", "BULLS", "BULSE", "BUMBO", "BUMFS", "BUMPH", "BUMPS", + "BUMPY", "BUNAS", "BUNCE", "BUNCO", "BUNDE", "BUNDH", "BUNDS", "BUNDT", "BUNDU", "BUNDY", "BUNGS", "BUNGY", + "BUNIA", "BUNJE", "BUNJY", "BUNKO", "BUNKS", "BUNNS", "BUNTS", "BUNTY", "BUNYA", "BUOYS", "BUPPY", "BURAN", + "BURAS", "BURBS", "BURDS", "BURET", "BURFI", "BURGH", "BURGS", "BURIN", "BURKA", "BURKE", "BURKS", "BURLS", + "BURNS", "BUROO", "BURPS", "BURQA", "BURRO", "BURRS", "BURRY", "BURSA", "BURSE", "BUSBY", "BUSES", "BUSKS", + "BUSKY", "BUSSU", "BUSTI", "BUSTS", "BUSTY", "BUTEO", "BUTES", "BUTLE", "BUTOH", "BUTTS", "BUTTY", "BUTUT", + "BUTYL", "BUZZY", "BWANA", "BWAZI", "BYDED", "BYDES", "BYKED", "BYKES", "BYRES", "BYRLS", "BYSSI", "BYTES", + "BYWAY", "CAAED", "CABAS", "CABER", "CABOB", "CABOC", "CABRE", "CACAS", "CACKS", "CACKY", "CADEE", "CADES", + "CADGE", "CADGY", "CADIE", "CADIS", "CADRE", "CAECA", "CAESE", "CAFES", "CAFFS", "CAGED", "CAGER", "CAGES", + "CAGOT", "CAHOW", "CAIDS", "CAINS", "CAIRD", "CAJON", "CAJUN", "CAKED", "CAKES", "CAKEY", "CALFS", "CALID", + "CALIF", "CALIX", "CALKS", "CALLA", "CALLS", "CALMS", "CALMY", "CALOS", "CALPA", "CALPS", "CALVE", "CALYX", + "CAMAN", "CAMAS", "CAMES", "CAMIS", "CAMOS", "CAMPI", "CAMPO", "CAMPS", "CAMPY", "CAMUS", "CANED", "CANEH", + "CANER", "CANES", "CANGS", "CANID", "CANNA", "CANNS", "CANSO", "CANST", "CANTO", "CANTS", "CANTY", "CAPAS", + "CAPED", "CAPES", "CAPEX", "CAPHS", "CAPIZ", "CAPLE", "CAPON", "CAPOS", "CAPOT", "CAPRI", "CAPUL", "CARAP", + "CARBO", "CARBS", "CARBY", "CARDI", "CARDS", "CARDY", "CARED", "CARER", "CARES", "CARET", "CAREX", "CARKS", + "CARLE", "CARLS", "CARNS", "CARNY", "CAROB", "CAROM", "CARON", "CARPI", "CARPS", "CARRS", "CARSE", "CARTA", + "CARTE", "CARTS", "CARVY", "CASAS", "CASCO", "CASED", "CASES", "CASKS", "CASKY", "CASTS", "CASUS", "CATES", + "CAUDA", "CAUKS", "CAULD", "CAULS", "CAUMS", "CAUPS", "CAURI", "CAUSA", "CAVAS", "CAVED", "CAVEL", "CAVER", + "CAVES", "CAVIE", "CAWED", "CAWKS", "CAXON", "CEAZE", "CEBID", "CECAL", "CECUM", "CEDED", "CEDER", "CEDES", + "CEDIS", "CEIBA", "CEILI", "CEILS", "CELEB", "CELLA", "CELLI", "CELLS", "CELOM", "CELTS", "CENSE", "CENTO", + "CENTS", "CENTU", "CEORL", "CEPES", "CERCI", "CERED", "CERES", "CERGE", "CERIA", "CERIC", "CERNE", "CEROC", + "CEROS", "CERTS", "CERTY", "CESSE", "CESTA", "CESTI", "CETES", "CETYL", "CEZVE", "CHACE", "CHACK", "CHACO", + "CHADO", "CHADS", "CHAFT", "CHAIS", "CHALS", "CHAMS", "CHANA", "CHANG", "CHANK", "CHAPE", "CHAPS", "CHAPT", + "CHARA", "CHARE", "CHARK", "CHARR", "CHARS", "CHARY", "CHATS", "CHAVE", "CHAVS", "CHAWK", "CHAWS", "CHAYA", + "CHAYS", "CHEEP", "CHEFS", "CHEKA", "CHELA", "CHELP", "CHEMO", "CHEMS", "CHERE", "CHERT", "CHETH", "CHEVY", + "CHEWS", "CHEWY", "CHIAO", "CHIAS", "CHIBS", "CHICA", "CHICH", "CHICO", "CHICS", "CHIEL", "CHIKS", "CHILE", + "CHIMB", "CHIMO", "CHIMP", "CHINE", "CHING", "CHINK", "CHINO", "CHINS", "CHIPS", "CHIRK", "CHIRL", "CHIRM", + "CHIRO", "CHIRR", "CHIRT", "CHIRU", "CHITS", "CHIVE", "CHIVS", "CHIVY", "CHIZZ", "CHOCO", "CHOCS", "CHODE", + "CHOGS", "CHOIL", "CHOKO", "CHOKY", "CHOLA", "CHOLI", "CHOLO", "CHOMP", "CHONS", "CHOOF", "CHOOK", "CHOOM", + "CHOON", "CHOPS", "CHOTA", "CHOTT", "CHOUT", "CHOUX", "CHOWK", "CHOWS", "CHUBS", "CHUFA", "CHUFF", "CHUGS", + "CHUMS", "CHURL", "CHURR", "CHUSE", "CHUTS", "CHYLE", "CHYME", "CHYND", "CIBOL", "CIDED", "CIDES", "CIELS", + "CIGGY", "CILIA", "CILLS", "CIMAR", "CIMEX", "CINCT", "CINES", "CINQS", "CIONS", "CIPPI", "CIRCS", "CIRES", + "CIRLS", "CIRRI", "CISCO", "CISSY", "CISTS", "CITAL", "CITED", "CITER", "CITES", "CIVES", "CIVET", "CIVIE", + "CIVVY", "CLACH", "CLADE", "CLADS", "CLAES", "CLAGS", "CLAME", "CLAMS", "CLANS", "CLAPS", "CLAPT", "CLARO", + "CLART", "CLARY", "CLAST", "CLATS", "CLAUT", "CLAVE", "CLAVI", "CLAWS", "CLAYS", "CLECK", "CLEEK", "CLEEP", + "CLEFS", "CLEGS", "CLEIK", "CLEMS", "CLEPE", "CLEPT", "CLEVE", "CLEWS", "CLIED", "CLIES", "CLIFT", "CLIME", + "CLINE", "CLINT", "CLIPE", "CLIPS", "CLIPT", "CLITS", "CLOAM", "CLODS", "CLOFF", "CLOGS", "CLOKE", "CLOMB", + "CLOMP", "CLONK", "CLONS", "CLOOP", "CLOOT", "CLOPS", "CLOTE", "CLOTS", "CLOUR", "CLOUS", "CLOWS", "CLOYE", + "CLOYS", "CLOZE", "CLUBS", "CLUES", "CLUEY", "CLUNK", "CLYPE", "CNIDA", "COACT", "COADY", "COALA", "COALS", + "COALY", "COAPT", "COARB", "COATE", "COATI", "COATS", "COBBS", "COBBY", "COBIA", "COBLE", "COBZA", "COCAS", + "COCCI", "COCCO", "COCKS", "COCKY", "COCOS", "CODAS", "CODEC", "CODED", "CODEN", "CODER", "CODES", "CODEX", + "CODON", "COEDS", "COFFS", "COGIE", "COGON", "COGUE", "COHAB", "COHEN", "COHOE", "COHOG", "COHOS", "COIFS", + "COIGN", "COILS", "COINS", "COIRS", "COITS", "COKED", "COKES", "COLAS", "COLBY", "COLDS", "COLED", "COLES", + "COLEY", "COLIC", "COLIN", "COLLS", "COLLY", "COLOG", "COLTS", "COLZA", "COMAE", "COMAL", "COMAS", "COMBE", + "COMBI", "COMBO", "COMBS", "COMBY", "COMER", "COMES", "COMIX", "COMMO", "COMMS", "COMMY", "COMPO", "COMPS", + "COMPT", "COMTE", "COMUS", "CONED", "CONES", "CONEY", "CONFS", "CONGA", "CONGE", "CONGO", "CONIA", "CONIN", + "CONKS", "CONKY", "CONNE", "CONNS", "CONTE", "CONTO", "CONUS", "CONVO", "COOCH", "COOED", "COOEE", "COOER", + "COOEY", "COOFS", "COOKS", "COOKY", "COOLS", "COOLY", "COOMB", "COOMS", "COOMY", "COONS", "COOPS", "COOPT", + "COOST", "COOTS", "COOZE", "COPAL", "COPAY", "COPED", "COPEN", "COPER", "COPES", "COPPY", "COPRA", "COPSY", + "COQUI", "CORAM", "CORBE", "CORBY", "CORDS", "CORED", "CORES", "COREY", "CORGI", "CORIA", "CORKS", "CORKY", + "CORMS", "CORNI", "CORNO", "CORNS", "CORNU", "CORPS", "CORSE", "CORSO", "COSEC", "COSED", "COSES", "COSET", + "COSEY", "COSIE", "COSTA", "COSTE", "COSTS", "COTAN", "COTED", "COTES", "COTHS", "COTTA", "COTTS", "COUDE", + "COUPS", "COURB", "COURD", "COURE", "COURS", "COUTA", "COUTH", "COVED", "COVES", "COVIN", "COWAL", "COWAN", + "COWED", "COWKS", "COWLS", "COWPS", "COWRY", "COXAE", "COXAL", "COXED", "COXES", "COXIB", "COYAU", "COYED", + "COYER", "COYPU", "COZED", "COZEN", "COZES", "COZEY", "COZIE", "CRAAL", "CRABS", "CRAGS", "CRAIC", "CRAIG", + "CRAKE", "CRAME", "CRAMS", "CRANS", "CRAPE", "CRAPS", "CRAPY", "CRARE", "CRAWS", "CRAYS", "CREDS", "CREEL", + "CREES", "CREMS", "CRENA", "CREPS", "CREPY", "CREWE", "CREWS", "CRIAS", "CRIBS", "CRIES", "CRIMS", "CRINE", + "CRIOS", "CRIPE", "CRIPS", "CRISE", "CRITH", "CRITS", "CROCI", "CROCS", "CROFT", "CROGS", "CROMB", "CROME", + "CRONK", "CRONS", "CROOL", "CROON", "CROPS", "CRORE", "CROST", "CROUT", "CROWS", "CROZE", "CRUCK", "CRUDO", + "CRUDS", "CRUDY", "CRUES", "CRUET", "CRUFT", "CRUNK", "CRUOR", "CRURA", "CRUSE", "CRUSY", "CRUVE", "CRWTH", + "CRYER", "CTENE", "CUBBY", "CUBEB", "CUBED", "CUBER", "CUBES", "CUBIT", "CUDDY", "CUFFO", "CUFFS", "CUIFS", + "CUING", "CUISH", "CUITS", "CUKES", "CULCH", "CULET", "CULEX", "CULLS", "CULLY", "CULMS", "CULPA", "CULTI", + "CULTS", "CULTY", "CUMEC", "CUNDY", "CUNEI", "CUNIT", "CUNTS", "CUPEL", "CUPID", "CUPPA", "CUPPY", "CURAT", + "CURBS", "CURCH", "CURDS", "CURDY", "CURED", "CURER", "CURES", "CURET", "CURFS", "CURIA", "CURIE", "CURLI", + "CURLS", "CURNS", "CURNY", "CURRS", "CURSI", "CURST", "CUSEC", "CUSHY", "CUSKS", "CUSPS", "CUSPY", "CUSSO", + "CUSUM", "CUTCH", "CUTER", "CUTES", "CUTEY", "CUTIN", "CUTIS", "CUTTO", "CUTTY", "CUTUP", "CUVEE", "CUZES", + "CWTCH", "CYANO", "CYANS", "CYCAD", "CYCAS", "CYCLO", "CYDER", "CYLIX", "CYMAE", "CYMAR", "CYMAS", "CYMES", + "CYMOL", "CYSTS", "CYTES", "CYTON", "CZARS", "DAALS", "DABBA", "DACES", "DACHA", "DACKS", "DADAH", "DADAS", + "DADOS", "DAFFS", "DAFFY", "DAGGA", "DAGGY", "DAGOS", "DAHLS", "DAIKO", "DAINE", "DAINT", "DAKER", "DALED", + "DALES", "DALIS", "DALLE", "DALTS", "DAMAN", "DAMAR", "DAMES", "DAMME", "DAMNS", "DAMPS", "DAMPY", "DANCY", + "DANGS", "DANIO", "DANKS", "DANNY", "DANTS", "DARAF", "DARBS", "DARCY", "DARED", "DARER", "DARES", "DARGA", + "DARGS", "DARIC", "DARIS", "DARKS", "DARKY", "DARNS", "DARRE", "DARTS", "DARZI", "DASHI", "DASHY", "DATAL", + "DATED", "DATER", "DATES", "DATOS", "DATTO", "DAUBE", "DAUBS", "DAUBY", "DAUDS", "DAULT", "DAURS", "DAUTS", + "DAVEN", "DAVIT", "DAWAH", "DAWDS", "DAWED", "DAWEN", "DAWKS", "DAWNS", "DAWTS", "DAYAN", "DAYCH", "DAYNT", + "DAZED", "DAZER", "DAZES", "DEADS", "DEAIR", "DEALS", "DEANS", "DEARE", "DEARN", "DEARS", "DEARY", "DEASH", + "DEAVE", "DEAWS", "DEAWY", "DEBAG", "DEBBY", "DEBEL", "DEBES", "DEBTS", "DEBUD", "DEBUR", "DEBUS", "DEBYE", + "DECAD", "DECAF", "DECAN", "DECKO", "DECKS", "DECOS", "DEDAL", "DEEDS", "DEEDY", "DEELY", "DEEMS", "DEENS", + "DEEPS", "DEERE", "DEERS", "DEETS", "DEEVE", "DEEVS", "DEFAT", "DEFFO", "DEFIS", "DEFOG", "DEGAS", "DEGUM", + "DEGUS", "DEICE", "DEIDS", "DEIFY", "DEILS", "DEISM", "DEIST", "DEKED", "DEKES", "DEKKO", "DELED", "DELES", + "DELFS", "DELFT", "DELIS", "DELLS", "DELLY", "DELOS", "DELPH", "DELTS", "DEMAN", "DEMES", "DEMIC", "DEMIT", + "DEMOB", "DEMOI", "DEMOS", "DEMPT", "DENAR", "DENAY", "DENCH", "DENES", "DENET", "DENIS", "DENTS", "DEOXY", + "DERAT", "DERAY", "DERED", "DERES", "DERIG", "DERMA", "DERMS", "DERNS", "DERNY", "DEROS", "DERRO", "DERRY", + "DERTH", "DERVS", "DESEX", "DESHI", "DESIS", "DESKS", "DESSE", "DEVAS", "DEVEL", "DEVIS", "DEVON", "DEVOS", + "DEVOT", "DEWAN", "DEWAR", "DEWAX", "DEWED", "DEXES", "DEXIE", "DHABA", "DHAKS", "DHALS", "DHIKR", "DHOBI", + "DHOLE", "DHOLL", "DHOLS", "DHOTI", "DHOWS", "DHUTI", "DIACT", "DIALS", "DIANE", "DIAZO", "DIBBS", "DICED", + "DICER", "DICES", "DICHT", "DICKS", "DICKY", "DICOT", "DICTA", "DICTS", "DICTY", "DIDDY", "DIDIE", "DIDOS", + "DIDST", "DIEBS", "DIELS", "DIENE", "DIETS", "DIFFS", "DIGHT", "DIKAS", "DIKED", "DIKER", "DIKES", "DIKEY", + "DILDO", "DILLI", "DILLS", "DIMBO", "DIMER", "DIMES", "DIMPS", "DINAR", "DINED", "DINES", "DINGE", "DINGS", + "DINIC", "DINKS", "DINKY", "DINNA", "DINOS", "DINTS", "DIOLS", "DIOTA", "DIPPY", "DIPSO", "DIRAM", "DIRER", + "DIRKE", "DIRKS", "DIRLS", "DIRTS", "DISAS", "DISCI", "DISCS", "DISHY", "DISKS", "DISME", "DITAL", "DITAS", + "DITED", "DITES", "DITSY", "DITTS", "DITZY", "DIVAN", "DIVAS", "DIVED", "DIVES", "DIVIS", "DIVNA", "DIVOS", + "DIVOT", "DIVVY", "DIWAN", "DIXIE", "DIXIT", "DIYAS", "DIZEN", "DJINN", "DJINS", "DOABS", "DOATS", "DOBBY", + "DOBES", "DOBIE", "DOBLA", "DOBRA", "DOBRO", "DOCHT", "DOCKS", "DOCOS", "DOCUS", "DODDY", "DODOS", "DOEKS", + "DOERS", "DOEST", "DOETH", "DOFFS", "DOGAN", "DOGES", "DOGEY", "DOGGO", "DOGGY", "DOGIE", "DOHYO", "DOILT", + "DOILY", "DOITS", "DOJOS", "DOLCE", "DOLCI", "DOLED", "DOLES", "DOLIA", "DOLLS", "DOLMA", "DOLOR", "DOLOS", + "DOLTS", "DOMAL", "DOMED", "DOMES", "DOMIC", "DONAH", "DONAS", "DONEE", "DONER", "DONGA", "DONGS", "DONKO", + "DONNA", "DONNE", "DONNY", "DONSY", "DOOBS", "DOOCE", "DOODY", "DOOKS", "DOOLE", "DOOLS", "DOOLY", "DOOMS", + "DOOMY", "DOONA", "DOORN", "DOORS", "DOOZY", "DOPAS", "DOPED", "DOPER", "DOPES", "DORAD", "DORBA", "DORBS", + "DOREE", "DORES", "DORIC", "DORIS", "DORKS", "DORKY", "DORMS", "DORMY", "DORPS", "DORRS", "DORSA", "DORSE", + "DORTS", "DORTY", "DOSAI", "DOSAS", "DOSED", "DOSEH", "DOSER", "DOSES", "DOSHA", "DOTAL", "DOTED", "DOTER", + "DOTES", "DOTTY", "DOUAR", "DOUCE", "DOUCS", "DOUKS", "DOULA", "DOUMA", "DOUMS", "DOUPS", "DOURA", "DOUSE", + "DOUTS", "DOVED", "DOVEN", "DOVER", "DOVES", "DOVIE", "DOWAR", "DOWDS", "DOWED", "DOWER", "DOWIE", "DOWLE", + "DOWLS", "DOWLY", "DOWNA", "DOWNS", "DOWPS", "DOWSE", "DOWTS", "DOXED", "DOXES", "DOXIE", "DOYEN", "DOYLY", + "DOZED", "DOZER", "DOZES", "DRABS", "DRACK", "DRACO", "DRAFF", "DRAGS", "DRAIL", "DRAMS", "DRANT", "DRAPS", + "DRATS", "DRAVE", "DRAWS", "DRAYS", "DREAR", "DRECK", "DREED", "DREER", "DREES", "DREGS", "DREKS", "DRENT", + "DRERE", "DREST", "DREYS", "DRIBS", "DRICE", "DRIES", "DRILY", "DRIPS", "DRIPT", "DROID", "DROIL", "DROKE", + "DROLE", "DROME", "DRONY", "DROOB", "DROOG", "DROOK", "DROPS", "DROPT", "DROUK", "DROWS", "DRUBS", "DRUGS", + "DRUMS", "DRUPE", "DRUSE", "DRUSY", "DRUXY", "DRYAD", "DRYAS", "DSOBO", "DSOMO", "DUADS", "DUALS", "DUANS", + "DUARS", "DUBBO", "DUCAL", "DUCAT", "DUCES", "DUCKS", "DUCKY", "DUCTS", "DUDDY", "DUDED", "DUDES", "DUELS", + "DUETS", "DUETT", "DUFFS", "DUFUS", "DUING", "DUITS", "DUKAS", "DUKED", "DUKES", "DUKKA", "DULCE", "DULES", + "DULIA", "DULLS", "DULSE", "DUMAS", "DUMBO", "DUMBS", "DUMKA", "DUMKY", "DUMPS", "DUNAM", "DUNCH", "DUNES", + "DUNGS", "DUNGY", "DUNKS", "DUNNO", "DUNNY", "DUNSH", "DUNTS", "DUOMI", "DUOMO", "DUPED", "DUPER", "DUPES", + "DUPLE", "DUPLY", "DUPPY", "DURAL", "DURAS", "DURED", "DURES", "DURGY", "DURNS", "DUROC", "DUROS", "DUROY", + "DURRA", "DURRS", "DURRY", "DURST", "DURUM", "DURZI", "DUSKS", "DUSTS", "DUXES", "DWAAL", "DWALE", "DWALM", + "DWAMS", "DWANG", "DWAUM", "DWEEB", "DWILE", "DWINE", "DYADS", "DYERS", "DYKED", "DYKES", "DYKEY", "DYKON", + "DYNEL", "DYNES", "DZHOS", "EAGRE", "EALED", "EALES", "EANED", "EARDS", "EARED", "EARLS", "EARNS", "EARNT", + "EARST", "EASED", "EASER", "EASES", "EASLE", "EASTS", "EATHE", "EAVED", "EAVES", "EBBED", "EBBET", "EBONS", + "EBOOK", "ECADS", "ECHED", "ECHES", "ECHOS", "ECRUS", "EDEMA", "EDGED", "EDGER", "EDGES", "EDILE", "EDITS", + "EDUCE", "EDUCT", "EEJIT", "EENSY", "EEVEN", "EEVNS", "EFFED", "EGADS", "EGERS", "EGEST", "EGGAR", "EGGED", + "EGGER", "EGMAS", "EHING", "EIDER", "EIDOS", "EIGNE", "EIKED", "EIKON", "EILDS", "EISEL", "EJIDO", "EKKAS", + "ELAIN", "ELAND", "ELANS", "ELCHI", "ELDIN", "ELEMI", "ELFED", "ELIAD", "ELINT", "ELMEN", "ELOGE", "ELOGY", + "ELOIN", "ELOPS", "ELPEE", "ELSIN", "ELUTE", "ELVAN", "ELVEN", "ELVER", "ELVES", "EMACS", "EMBAR", "EMBAY", + "EMBOG", "EMBOW", "EMBOX", "EMBUS", "EMEER", "EMEND", "EMERG", "EMERY", "EMEUS", "EMICS", "EMIRS", "EMITS", + "EMMAS", "EMMER", "EMMET", "EMMEW", "EMMYS", "EMOJI", "EMONG", "EMOTE", "EMOVE", "EMPTS", "EMULE", "EMURE", + "EMYDE", "EMYDS", "ENARM", "ENATE", "ENDED", "ENDER", "ENDEW", "ENDUE", "ENEWS", "ENFIX", "ENIAC", "ENLIT", + "ENMEW", "ENNOG", "ENOKI", "ENOLS", "ENORM", "ENOWS", "ENROL", "ENSEW", "ENSKY", "ENTIA", "ENURE", "ENURN", + "ENVOI", "ENZYM", "EORLS", "EOSIN", "EPACT", "EPEES", "EPHAH", "EPHAS", "EPHOD", "EPHOR", "EPICS", "EPODE", + "EPOPT", "EPRIS", "EQUES", "EQUID", "ERBIA", "EREVS", "ERGON", "ERGOS", "ERGOT", "ERHUS", "ERICA", "ERICK", + "ERICS", "ERING", "ERNED", "ERNES", "EROSE", "ERRED", "ERSES", "ERUCT", "ERUGO", "ERUVS", "ERVEN", "ERVIL", + "ESCAR", "ESCOT", "ESILE", "ESKAR", "ESKER", "ESNES", "ESSES", "ESTOC", "ESTOP", "ESTRO", "ETAGE", "ETAPE", + "ETATS", "ETENS", "ETHAL", "ETHNE", "ETHYL", "ETICS", "ETNAS", "ETTIN", "ETTLE", "ETUIS", "ETWEE", "ETYMA", + "EUGHS", "EUKED", "EUPAD", "EUROS", "EUSOL", "EVENS", "EVERT", "EVETS", "EVHOE", "EVILS", "EVITE", "EVOHE", + "EWERS", "EWEST", "EWHOW", "EWKED", "EXAMS", "EXEAT", "EXECS", "EXEEM", "EXEME", "EXFIL", "EXIES", "EXINE", + "EXING", "EXITS", "EXODE", "EXOME", "EXONS", "EXPAT", "EXPOS", "EXUDE", "EXULS", "EXURB", "EYASS", "EYERS", + "EYOTS", "EYRAS", "EYRES", "EYRIE", "EYRIR", "EZINE", "FABBY", "FACED", "FACER", "FACES", "FACIA", "FACTA", + "FACTS", "FADDY", "FADED", "FADER", "FADES", "FADGE", "FADOS", "FAENA", "FAERY", "FAFFS", "FAFFY", "FAGGY", + "FAGIN", "FAGOT", "FAIKS", "FAILS", "FAINE", "FAINS", "FAIRS", "FAKED", "FAKER", "FAKES", "FAKEY", "FAKIE", + "FAKIR", "FALAJ", "FALLS", "FAMED", "FAMES", "FANAL", "FANDS", "FANES", "FANGA", "FANGO", "FANGS", "FANKS", + "FANON", "FANOS", "FANUM", "FAQIR", "FARAD", "FARCI", "FARCY", "FARDS", "FARED", "FARER", "FARES", "FARLE", + "FARLS", "FARMS", "FAROS", "FARRO", "FARSE", "FARTS", "FASCI", "FASTI", "FASTS", "FATED", "FATES", "FATLY", + "FATSO", "FATWA", "FAUGH", "FAULD", "FAUNS", "FAURD", "FAUTS", "FAUVE", "FAVAS", "FAVEL", "FAVER", "FAVES", + "FAVUS", "FAWNS", "FAWNY", "FAXED", "FAXES", "FAYED", "FAYER", "FAYNE", "FAYRE", "FAZED", "FAZES", "FEALS", + "FEARE", "FEARS", "FEART", "FEASE", "FEATS", "FEAZE", "FECES", "FECHT", "FECIT", "FECKS", "FEDEX", "FEEBS", + "FEEDS", "FEELS", "FEENS", "FEERS", "FEESE", "FEEZE", "FEHME", "FEINT", "FEIST", "FELCH", "FELID", "FELLS", + "FELLY", "FELTS", "FELTY", "FEMAL", "FEMES", "FEMMY", "FENDS", "FENDY", "FENIS", "FENKS", "FENNY", "FENTS", + "FEODS", "FEOFF", "FERER", "FERES", "FERIA", "FERLY", "FERMI", "FERMS", "FERNS", "FERNY", "FESSE", "FESTA", + "FESTS", "FESTY", "FETAS", "FETED", "FETES", "FETOR", "FETTA", "FETTS", "FETWA", "FEUAR", "FEUDS", "FEUED", + "FEYED", "FEYER", "FEYLY", "FEZES", "FEZZY", "FIARS", "FIATS", "FIBRE", "FIBRO", "FICES", "FICHE", "FICHU", + "FICIN", "FICOS", "FIDES", "FIDGE", "FIDOS", "FIEFS", "FIENT", "FIERE", "FIERS", "FIEST", "FIFED", "FIFER", + "FIFES", "FIFIS", "FIGGY", "FIGOS", "FIKED", "FIKES", "FILAR", "FILCH", "FILED", "FILES", "FILII", "FILKS", + "FILLE", "FILLO", "FILLS", "FILMI", "FILMS", "FILOS", "FILUM", "FINCA", "FINDS", "FINED", "FINES", "FINIS", + "FINKS", "FINNY", "FINOS", "FIORD", "FIQHS", "FIQUE", "FIRED", "FIRER", "FIRES", "FIRIE", "FIRKS", "FIRMS", + "FIRNS", "FIRRY", "FIRTH", "FISCS", "FISKS", "FISTS", "FISTY", "FITCH", "FITLY", "FITNA", "FITTE", "FITTS", + "FIVER", "FIVES", "FIXED", "FIXES", "FIXIT", "FJELD", "FLABS", "FLAFF", "FLAGS", "FLAKS", "FLAMM", "FLAMS", + "FLAMY", "FLANE", "FLANS", "FLAPS", "FLARY", "FLATS", "FLAVA", "FLAWN", "FLAWS", "FLAWY", "FLAXY", "FLAYS", + "FLEAM", "FLEAS", "FLEEK", "FLEER", "FLEES", "FLEGS", "FLEME", "FLEUR", "FLEWS", "FLEXI", "FLEXO", "FLEYS", + "FLICS", "FLIED", "FLIES", "FLIMP", "FLIMS", "FLIPS", "FLIRS", "FLISK", "FLITE", "FLITS", "FLITT", "FLOBS", + "FLOCS", "FLOES", "FLOGS", "FLONG", "FLOPS", "FLORS", "FLORY", "FLOSH", "FLOTA", "FLOTE", "FLOWS", "FLUBS", + "FLUED", "FLUES", "FLUEY", "FLUKY", "FLUMP", "FLUOR", "FLURR", "FLUTY", "FLUYT", "FLYBY", "FLYPE", "FLYTE", + "FOALS", "FOAMS", "FOEHN", "FOGEY", "FOGIE", "FOGLE", "FOGOU", "FOHNS", "FOIDS", "FOILS", "FOINS", "FOLDS", + "FOLEY", "FOLIA", "FOLIC", "FOLIE", "FOLKS", "FOLKY", "FOMES", "FONDA", "FONDS", "FONDU", "FONES", "FONLY", + "FONTS", "FOODS", "FOODY", "FOOLS", "FOOTS", "FOOTY", "FORAM", "FORBS", "FORBY", "FORDO", "FORDS", "FOREL", + "FORES", "FOREX", "FORKS", "FORKY", "FORME", "FORMS", "FORTS", "FORZA", "FORZE", "FOSSA", "FOSSE", "FOUAT", + "FOUDS", "FOUER", "FOUET", "FOULE", "FOULS", "FOUNT", "FOURS", "FOUTH", "FOVEA", "FOWLS", "FOWTH", "FOXED", + "FOXES", "FOXIE", "FOYLE", "FOYNE", "FRABS", "FRACK", "FRACT", "FRAGS", "FRAIM", "FRANC", "FRAPE", "FRAPS", + "FRASS", "FRATE", "FRATI", "FRATS", "FRAUS", "FRAYS", "FREES", "FREET", "FREIT", "FREMD", "FRENA", "FREON", + "FRERE", "FRETS", "FRIBS", "FRIER", "FRIES", "FRIGS", "FRISE", "FRIST", "FRITH", "FRITS", "FRITT", "FRIZE", + "FRIZZ", "FROES", "FROGS", "FRONS", "FRORE", "FRORN", "FRORY", "FROSH", "FROWS", "FROWY", "FRUGS", "FRUMP", + "FRUSH", "FRUST", "FRYER", "FUBAR", "FUBBY", "FUBSY", "FUCKS", "FUCUS", "FUDDY", "FUDGY", "FUELS", "FUERO", + "FUFFS", "FUFFY", "FUGAL", "FUGGY", "FUGIE", "FUGIO", "FUGLE", "FUGLY", "FUGUS", "FUJIS", "FULLS", "FUMED", + "FUMER", "FUMES", "FUMET", "FUNDI", "FUNDS", "FUNDY", "FUNGO", "FUNGS", "FUNKS", "FURAL", "FURAN", "FURCA", + "FURLS", "FUROL", "FURRS", "FURTH", "FURZE", "FURZY", "FUSED", "FUSEE", "FUSEL", "FUSES", "FUSIL", "FUSKS", + "FUSTS", "FUSTY", "FUTON", "FUZED", "FUZEE", "FUZES", "FUZIL", "FYCES", "FYKED", "FYKES", "FYLES", "FYRDS", + "FYTTE", "GABBA", "GABBY", "GABLE", "GADDI", "GADES", "GADGE", "GADID", "GADIS", "GADJE", "GADJO", "GADSO", + "GAFFS", "GAGED", "GAGER", "GAGES", "GAIDS", "GAINS", "GAIRS", "GAITA", "GAITS", "GAITT", "GAJOS", "GALAH", + "GALAS", "GALAX", "GALEA", "GALED", "GALES", "GALLS", "GALLY", "GALOP", "GALUT", "GALVO", "GAMAS", "GAMAY", + "GAMBA", "GAMBE", "GAMBO", "GAMBS", "GAMED", "GAMES", "GAMEY", "GAMIC", "GAMIN", "GAMME", "GAMMY", "GAMPS", + "GANCH", "GANDY", "GANEF", "GANEV", "GANGS", "GANJA", "GANOF", "GANTS", "GAOLS", "GAPED", "GAPER", "GAPES", + "GAPOS", "GAPPY", "GARBE", "GARBO", "GARBS", "GARDA", "GARES", "GARIS", "GARMS", "GARNI", "GARRE", "GARTH", + "GARUM", "GASES", "GASPS", "GASPY", "GASTS", "GATCH", "GATED", "GATER", "GATES", "GATHS", "GATOR", "GAUCH", + "GAUCY", "GAUDS", "GAUJE", "GAULT", "GAUMS", "GAUMY", "GAUPS", "GAURS", "GAUSS", "GAUZY", "GAVOT", "GAWCY", + "GAWDS", "GAWKS", "GAWPS", "GAWSY", "GAYAL", "GAZAL", "GAZAR", "GAZED", "GAZES", "GAZON", "GAZOO", "GEALS", + "GEANS", "GEARE", "GEARS", "GEATS", "GEBUR", "GECKS", "GEEKS", "GEEPS", "GEEST", "GEIST", "GEITS", "GELDS", + "GELEE", "GELID", "GELLY", "GELTS", "GEMEL", "GEMMA", "GEMMY", "GEMOT", "GENAL", "GENAS", "GENES", "GENET", + "GENIC", "GENII", "GENIP", "GENNY", "GENOA", "GENOM", "GENRO", "GENTS", "GENTY", "GENUA", "GENUS", "GEODE", + "GEOID", "GERAH", "GERBE", "GERES", "GERLE", "GERMS", "GERMY", "GERNE", "GESSE", "GESSO", "GESTE", "GESTS", + "GETAS", "GETUP", "GEUMS", "GEYAN", "GEYER", "GHAST", "GHATS", "GHAUT", "GHAZI", "GHEES", "GHEST", "GHYLL", + "GIBED", "GIBEL", "GIBER", "GIBES", "GIBLI", "GIBUS", "GIFTS", "GIGAS", "GIGHE", "GIGOT", "GIGUE", "GILAS", + "GILDS", "GILET", "GILLS", "GILLY", "GILPY", "GILTS", "GIMEL", "GIMME", "GIMPS", "GIMPY", "GINCH", "GINGE", + "GINGS", "GINKS", "GINNY", "GINZO", "GIPON", "GIPPO", "GIPPY", "GIRDS", "GIRLS", "GIRNS", "GIRON", "GIROS", + "GIRRS", "GIRSH", "GIRTS", "GISMO", "GISMS", "GISTS", "GITCH", "GITES", "GIUST", "GIVED", "GIVES", "GIZMO", + "GLACE", "GLADS", "GLADY", "GLAIK", "GLAIR", "GLAMS", "GLANS", "GLARY", "GLAUM", "GLAUR", "GLAZY", "GLEBA", + "GLEBE", "GLEBY", "GLEDE", "GLEDS", "GLEED", "GLEEK", "GLEES", "GLEET", "GLEIS", "GLENS", "GLENT", "GLEYS", + "GLIAL", "GLIAS", "GLIBS", "GLIFF", "GLIFT", "GLIKE", "GLIME", "GLIMS", "GLISK", "GLITS", "GLITZ", "GLOAM", + "GLOBI", "GLOBS", "GLOBY", "GLODE", "GLOGG", "GLOMS", "GLOOP", "GLOPS", "GLOST", "GLOUT", "GLOWS", "GLOZE", + "GLUED", "GLUER", "GLUES", "GLUEY", "GLUGS", "GLUME", "GLUMS", "GLUON", "GLUTE", "GLUTS", "GNARL", "GNARR", + "GNARS", "GNATS", "GNAWN", "GNAWS", "GNOWS", "GOADS", "GOAFS", "GOALS", "GOARY", "GOATS", "GOATY", "GOBAN", + "GOBAR", "GOBBI", "GOBBO", "GOBBY", "GOBIS", "GOBOS", "GODET", "GODSO", "GOELS", "GOERS", "GOEST", "GOETH", + "GOETY", "GOFER", "GOFFS", "GOGGA", "GOGOS", "GOIER", "GOJIS", "GOLDS", "GOLDY", "GOLES", "GOLFS", "GOLPE", + "GOLPS", "GOMBO", "GOMER", "GOMPA", "GONCH", "GONEF", "GONGS", "GONIA", "GONIF", "GONKS", "GONNA", "GONOF", + "GONYS", "GONZO", "GOOBY", "GOODS", "GOOFS", "GOOGS", "GOOKS", "GOOKY", "GOOLD", "GOOLS", "GOOLY", "GOONS", + "GOONY", "GOOPS", "GOOPY", "GOORS", "GOORY", "GOOSY", "GOPAK", "GOPIK", "GORAL", "GORAS", "GORED", "GORES", + "GORIS", "GORMS", "GORMY", "GORPS", "GORSE", "GORSY", "GOSHT", "GOSSE", "GOTCH", "GOTHS", "GOTHY", "GOTTA", + "GOUCH", "GOUKS", "GOURA", "GOUTS", "GOUTY", "GOWAN", "GOWDS", "GOWFS", "GOWKS", "GOWLS", "GOWNS", "GOXES", + "GOYIM", "GOYLE", "GRAAL", "GRABS", "GRADS", "GRAFF", "GRAIP", "GRAMA", "GRAME", "GRAMP", "GRAMS", "GRANA", + "GRANS", "GRAPY", "GRAVS", "GRAYS", "GREBE", "GREBO", "GRECE", "GREEK", "GREES", "GREGE", "GREGO", "GREIN", + "GRENS", "GRESE", "GREVE", "GREWS", "GREYS", "GRICE", "GRIDE", "GRIDS", "GRIFF", "GRIFT", "GRIGS", "GRIKE", + "GRINS", "GRIOT", "GRIPS", "GRIPT", "GRIPY", "GRISE", "GRIST", "GRISY", "GRITH", "GRITS", "GRIZE", "GROAT", + "GRODY", "GROGS", "GROKS", "GROMA", "GRONE", "GROOF", "GROSZ", "GROTS", "GROUF", "GROVY", "GROWS", "GRRLS", + "GRRRL", "GRUBS", "GRUED", "GRUES", "GRUFE", "GRUME", "GRUMP", "GRUND", "GRYCE", "GRYDE", "GRYKE", "GRYPE", + "GRYPT", "GUACO", "GUANA", "GUANO", "GUANS", "GUARS", "GUCKS", "GUCKY", "GUDES", "GUFFS", "GUGAS", "GUIDS", + "GUIMP", "GUIRO", "GULAG", "GULAR", "GULAS", "GULES", "GULET", "GULFS", "GULFY", "GULLS", "GULPH", "GULPS", + "GULPY", "GUMMA", "GUMMI", "GUMPS", "GUNDY", "GUNGE", "GUNGY", "GUNKS", "GUNKY", "GUNNY", "GUQIN", "GURDY", + "GURGE", "GURLS", "GURLY", "GURNS", "GURRY", "GURSH", "GURUS", "GUSHY", "GUSLA", "GUSLE", "GUSLI", "GUSSY", + "GUSTS", "GUTSY", "GUTTA", "GUTTY", "GUYED", "GUYLE", "GUYOT", "GUYSE", "GWINE", "GYALS", "GYANS", "GYBED", + "GYBES", "GYELD", "GYMPS", "GYNAE", "GYNIE", "GYNNY", "GYNOS", "GYOZA", "GYPOS", "GYPPO", "GYPPY", "GYRAL", + "GYRED", "GYRES", "GYRON", "GYROS", "GYRUS", "GYTES", "GYVED", "GYVES", "HAAFS", "HAARS", "HABLE", "HABUS", + "HACEK", "HACKS", "HADAL", "HADED", "HADES", "HADJI", "HADST", "HAEMS", "HAETS", "HAFFS", "HAFIZ", "HAFTS", + "HAGGS", "HAHAS", "HAICK", "HAIKA", "HAIKS", "HAIKU", "HAILS", "HAILY", "HAINS", "HAINT", "HAIRS", "HAITH", + "HAJES", "HAJIS", "HAJJI", "HAKAM", "HAKAS", "HAKEA", "HAKES", "HAKIM", "HAKUS", "HALAL", "HALED", "HALER", + "HALES", "HALFA", "HALFS", "HALID", "HALLO", "HALLS", "HALMA", "HALMS", "HALON", "HALOS", "HALSE", "HALTS", + "HALVA", "HALWA", "HAMAL", "HAMBA", "HAMED", "HAMES", "HAMMY", "HAMZA", "HANAP", "HANCE", "HANCH", "HANDS", + "HANGI", "HANGS", "HANKS", "HANKY", "HANSA", "HANSE", "HANTS", "HAOLE", "HAOMA", "HAPAX", "HAPLY", "HAPPI", + "HAPUS", "HARAM", "HARDS", "HARED", "HARES", "HARIM", "HARKS", "HARLS", "HARMS", "HARNS", "HAROS", "HARPS", + "HARTS", "HASHY", "HASKS", "HASPS", "HASTA", "HATED", "HATES", "HATHA", "HAUDS", "HAUFS", "HAUGH", "HAULD", + "HAULM", "HAULS", "HAULT", "HAUNS", "HAUSE", "HAVER", "HAVES", "HAWED", "HAWKS", "HAWMS", "HAWSE", "HAYED", + "HAYER", "HAYEY", "HAYLE", "HAZAN", "HAZED", "HAZER", "HAZES", "HEADS", "HEALD", "HEALS", "HEAME", "HEAPS", + "HEAPY", "HEARE", "HEARS", "HEAST", "HEATS", "HEBEN", "HEBES", "HECHT", "HECKS", "HEDER", "HEDGY", "HEEDS", + "HEEDY", "HEELS", "HEEZE", "HEFTE", "HEFTS", "HEIDS", "HEIGH", "HEILS", "HEIRS", "HEJAB", "HEJRA", "HELED", + "HELES", "HELIO", "HELLS", "HELMS", "HELOS", "HELOT", "HELPS", "HELVE", "HEMAL", "HEMES", "HEMIC", "HEMIN", + "HEMPS", "HEMPY", "HENCH", "HENDS", "HENGE", "HENNA", "HENNY", "HENRY", "HENTS", "HEPAR", "HERBS", "HERBY", + "HERDS", "HERES", "HERLS", "HERMA", "HERMS", "HERNS", "HEROS", "HERRY", "HERSE", "HERTZ", "HERYE", "HESPS", + "HESTS", "HETES", "HETHS", "HEUCH", "HEUGH", "HEVEA", "HEWED", "HEWER", "HEWGH", "HEXAD", "HEXED", "HEXER", + "HEXES", "HEXYL", "HEYED", "HIANT", "HICKS", "HIDED", "HIDER", "HIDES", "HIEMS", "HIGHS", "HIGHT", "HIJAB", + "HIJRA", "HIKED", "HIKER", "HIKES", "HIKOI", "HILAR", "HILCH", "HILLO", "HILLS", "HILTS", "HILUM", "HILUS", + "HIMBO", "HINAU", "HINDS", "HINGS", "HINKY", "HINNY", "HINTS", "HIOIS", "HIPLY", "HIRED", "HIREE", "HIRER", + "HIRES", "HISSY", "HISTS", "HITHE", "HIVED", "HIVER", "HIVES", "HIZEN", "HOAED", "HOAGY", "HOARS", "HOARY", + "HOAST", "HOBOS", "HOCKS", "HOCUS", "HODAD", "HODJA", "HOERS", "HOGAN", "HOGEN", "HOGGS", "HOGHS", "HOHED", + "HOICK", "HOIED", "HOIKS", "HOING", "HOISE", "HOKAS", "HOKED", "HOKES", "HOKEY", "HOKIS", "HOKKU", "HOKUM", + "HOLDS", "HOLED", "HOLES", "HOLEY", "HOLKS", "HOLLA", "HOLLO", "HOLME", "HOLMS", "HOLON", "HOLOS", "HOLTS", + "HOMAS", "HOMED", "HOMES", "HOMEY", "HOMIE", "HOMME", "HOMOS", "HONAN", "HONDA", "HONDS", "HONED", "HONER", + "HONES", "HONGI", "HONGS", "HONKS", "HONKY", "HOOCH", "HOODS", "HOODY", "HOOEY", "HOOFS", "HOOKA", "HOOKS", + "HOOKY", "HOOLY", "HOONS", "HOOPS", "HOORD", "HOORS", "HOOSH", "HOOTS", "HOOTY", "HOOVE", "HOPAK", "HOPED", + "HOPER", "HOPES", "HOPPY", "HORAH", "HORAL", "HORAS", "HORIS", "HORKS", "HORME", "HORNS", "HORST", "HORSY", + "HOSED", "HOSEL", "HOSEN", "HOSER", "HOSES", "HOSEY", "HOSTA", "HOSTS", "HOTCH", "HOTEN", "HOTTY", "HOUFF", + "HOUFS", "HOUGH", "HOURI", "HOURS", "HOUTS", "HOVEA", "HOVED", "HOVEN", "HOVES", "HOWBE", "HOWES", "HOWFF", + "HOWFS", "HOWKS", "HOWLS", "HOWRE", "HOWSO", "HOXED", "HOXES", "HOYAS", "HOYED", "HOYLE", "HUBBY", "HUCKS", + "HUDNA", "HUDUD", "HUERS", "HUFFS", "HUFFY", "HUGER", "HUGGY", "HUHUS", "HUIAS", "HULAS", "HULES", "HULKS", + "HULKY", "HULLO", "HULLS", "HULLY", "HUMAS", "HUMFS", "HUMIC", "HUMPS", "HUMPY", "HUNKS", "HUNTS", "HURDS", + "HURLS", "HURLY", "HURRA", "HURST", "HURTS", "HUSHY", "HUSKS", "HUSOS", "HUTIA", "HUZZA", "HUZZY", "HWYLS", + "HYDRA", "HYENS", "HYGGE", "HYING", "HYKES", "HYLAS", "HYLEG", "HYLES", "HYLIC", "HYMNS", "HYNDE", "HYOID", + "HYPED", "HYPES", "HYPHA", "HYPHY", "HYPOS", "HYRAX", "HYSON", "HYTHE", "IAMBI", "IAMBS", "IBRIK", "ICERS", + "ICHED", "ICHES", "ICHOR", "ICIER", "ICKER", "ICKLE", "ICONS", "ICTAL", "ICTIC", "ICTUS", "IDANT", "IDEAS", + "IDEES", "IDENT", "IDLED", "IDLES", "IDOLA", "IDOLS", "IDYLS", "IFTAR", "IGAPO", "IGGED", "IGLUS", "IHRAM", + "IKANS", "IKATS", "IKONS", "ILEAC", "ILEAL", "ILEUM", "ILEUS", "ILIAD", "ILIAL", "ILIUM", "ILLER", "ILLTH", + "IMAGO", "IMAMS", "IMARI", "IMAUM", "IMBAR", "IMBED", "IMIDE", "IMIDO", "IMIDS", "IMINE", "IMINO", "IMMEW", + "IMMIT", "IMMIX", "IMPED", "IMPIS", "IMPOT", "IMPRO", "IMSHI", "IMSHY", "INAPT", "INARM", "INBYE", "INCEL", + "INCLE", "INCOG", "INCUS", "INCUT", "INDEW", "INDIA", "INDIE", "INDOL", "INDOW", "INDRI", "INDUE", "INERM", + "INFIX", "INFOS", "INFRA", "INGAN", "INGLE", "INION", "INKED", "INKER", "INKLE", "INNED", "INNIT", "INORB", + "INRUN", "INSET", "INSPO", "INTEL", "INTIL", "INTIS", "INTRA", "INULA", "INURE", "INURN", "INUST", "INVAR", + "INWIT", "IODIC", "IODID", "IODIN", "IOTAS", "IPPON", "IRADE", "IRIDS", "IRING", "IRKED", "IROKO", "IRONE", + "IRONS", "ISBAS", "ISHES", "ISLED", "ISLES", "ISNAE", "ISSEI", "ISTLE", "ITEMS", "ITHER", "IVIED", "IVIES", + "IXIAS", "IXNAY", "IXORA", "IXTLE", "IZARD", "IZARS", "IZZAT", "JAAPS", "JABOT", "JACAL", "JACKS", "JACKY", + "JADED", "JADES", "JAFAS", "JAFFA", "JAGAS", "JAGER", "JAGGS", "JAGGY", "JAGIR", "JAGRA", "JAILS", "JAKER", + "JAKES", "JAKEY", "JALAP", "JALOP", "JAMBE", "JAMBO", "JAMBS", "JAMBU", "JAMES", "JAMMY", "JAMON", "JANES", + "JANNS", "JANNY", "JANTY", "JAPAN", "JAPED", "JAPER", "JAPES", "JARKS", "JARLS", "JARPS", "JARTA", "JARUL", + "JASEY", "JASPE", "JASPS", "JATOS", "JAUKS", "JAUPS", "JAVAS", "JAVEL", "JAWAN", "JAWED", "JAXIE", "JEANS", + "JEATS", "JEBEL", "JEDIS", "JEELS", "JEELY", "JEEPS", "JEERS", "JEEZE", "JEFES", "JEFFS", "JEHAD", "JEHUS", + "JELAB", "JELLO", "JELLS", "JEMBE", "JEMMY", "JENNY", "JEONS", "JERID", "JERKS", "JERRY", "JESSE", "JESTS", + "JESUS", "JETES", "JETON", "JEUNE", "JEWED", "JEWIE", "JHALA", "JIAOS", "JIBBA", "JIBBS", "JIBED", "JIBER", + "JIBES", "JIFFS", "JIGGY", "JIGOT", "JIHAD", "JILLS", "JILTS", "JIMMY", "JIMPY", "JINGO", "JINKS", "JINNE", + "JINNI", "JINNS", "JIRDS", "JIRGA", "JIRRE", "JISMS", "JIVED", "JIVER", "JIVES", "JIVEY", "JNANA", "JOBED", + "JOBES", "JOCKO", "JOCKS", "JOCKY", "JOCOS", "JODEL", "JOEYS", "JOHNS", "JOINS", "JOKED", "JOKES", "JOKEY", + "JOKOL", "JOLED", "JOLES", "JOLLS", "JOLTS", "JOLTY", "JOMON", "JOMOS", "JONES", "JONGS", "JONTY", "JOOKS", + "JORAM", "JORUM", "JOTAS", "JOTTY", "JOTUN", "JOUAL", "JOUGS", "JOUKS", "JOULE", "JOURS", "JOWAR", "JOWED", + "JOWLS", "JOWLY", "JOYED", "JUBAS", "JUBES", "JUCOS", "JUDAS", "JUDGY", "JUDOS", "JUGAL", "JUGUM", "JUJUS", + "JUKED", "JUKES", "JUKUS", "JULEP", "JUMAR", "JUMBY", "JUMPS", "JUNCO", "JUNKS", "JUNKY", "JUPES", "JUPON", + "JURAL", "JURAT", "JUREL", "JURES", "JUSTS", "JUTES", "JUTTY", "JUVES", "JUVIE", "KAAMA", "KABAB", "KABAR", + "KABOB", "KACHA", "KACKS", "KADAI", "KADES", "KADIS", "KAFIR", "KAGOS", "KAGUS", "KAHAL", "KAIAK", "KAIDS", + "KAIES", "KAIFS", "KAIKA", "KAIKS", "KAILS", "KAIMS", "KAING", "KAINS", "KAKAS", "KAKIS", "KALAM", "KALES", + "KALIF", "KALIS", "KALPA", "KAMAS", "KAMES", "KAMIK", "KAMIS", "KAMME", "KANAE", "KANAS", "KANDY", "KANEH", + "KANES", "KANGA", "KANGS", "KANJI", "KANTS", "KANZU", "KAONS", "KAPAS", "KAPHS", "KAPOK", "KAPOW", "KAPUS", + "KAPUT", "KARAS", "KARAT", "KARKS", "KARNS", "KAROO", "KAROS", "KARRI", "KARST", "KARSY", "KARTS", "KARZY", + "KASHA", "KASME", "KATAL", "KATAS", "KATIS", "KATTI", "KAUGH", "KAURI", "KAURU", "KAURY", "KAVAL", "KAVAS", + "KAWAS", "KAWAU", "KAWED", "KAYLE", "KAYOS", "KAZIS", "KAZOO", "KBARS", "KEBAR", "KEBOB", "KECKS", "KEDGE", + "KEDGY", "KEECH", "KEEFS", "KEEKS", "KEELS", "KEEMA", "KEENO", "KEENS", "KEEPS", "KEETS", "KEEVE", "KEFIR", + "KEHUA", "KEIRS", "KELEP", "KELIM", "KELLS", "KELLY", "KELPS", "KELPY", "KELTS", "KELTY", "KEMBO", "KEMBS", + "KEMPS", "KEMPT", "KEMPY", "KENAF", "KENCH", "KENDO", "KENOS", "KENTE", "KENTS", "KEPIS", "KERBS", "KEREL", + "KERFS", "KERKY", "KERMA", "KERNE", "KERNS", "KEROS", "KERRY", "KERVE", "KESAR", "KESTS", "KETAS", "KETCH", + "KETES", "KETOL", "KEVEL", "KEVIL", "KEXES", "KEYED", "KEYER", "KHADI", "KHAFS", "KHANS", "KHAPH", "KHATS", + "KHAYA", "KHAZI", "KHEDA", "KHETH", "KHETS", "KHOJA", "KHORS", "KHOUM", "KHUDS", "KIAAT", "KIACK", "KIANG", + "KIBBE", "KIBBI", "KIBEI", "KIBES", "KIBLA", "KICKS", "KICKY", "KIDDO", "KIDDY", "KIDEL", "KIDGE", "KIEFS", + "KIERS", "KIEVE", "KIEVS", "KIGHT", "KIKES", "KIKOI", "KILEY", "KILIM", "KILLS", "KILNS", "KILOS", "KILPS", + "KILTS", "KILTY", "KIMBO", "KINAS", "KINDA", "KINDS", "KINDY", "KINES", "KINGS", "KININ", "KINKS", "KINOS", + "KIORE", "KIPES", "KIPPA", "KIPPS", "KIRBY", "KIRKS", "KIRNS", "KIRRI", "KISAN", "KISSY", "KISTS", "KITED", + "KITER", "KITES", "KITHE", "KITHS", "KITUL", "KIVAS", "KIWIS", "KLANG", "KLAPS", "KLETT", "KLICK", "KLIEG", + "KLIKS", "KLONG", "KLOOF", "KLUGE", "KLUTZ", "KNAGS", "KNAPS", "KNARL", "KNARS", "KNAUR", "KNAWE", "KNEES", + "KNELL", "KNISH", "KNITS", "KNIVE", "KNOBS", "KNOPS", "KNOSP", "KNOTS", "KNOUT", "KNOWE", "KNOWS", "KNUBS", + "KNURL", "KNURR", "KNURS", "KNUTS", "KOANS", "KOAPS", "KOBAN", "KOBOS", "KOELS", "KOFFS", "KOFTA", "KOGAL", + "KOHAS", "KOHEN", "KOHLS", "KOINE", "KOJIS", "KOKAM", "KOKAS", "KOKER", "KOKRA", "KOKUM", "KOLAS", "KOLOS", + "KOMBU", "KONBU", "KONDO", "KONKS", "KOOKS", "KOOKY", "KOORI", "KOPEK", "KOPHS", "KOPJE", "KOPPA", "KORAI", + "KORAN", "KORAS", "KORAT", "KORES", "KORMA", "KOROS", "KORUN", "KORUS", "KOSES", "KOTCH", "KOTOS", "KOTOW", + "KOURA", "KRAAL", "KRABS", "KRAFT", "KRAIS", "KRAIT", "KRANG", "KRANS", "KRANZ", "KRAUT", "KRAYS", "KREEP", + "KRENG", "KREWE", "KRONA", "KRONE", "KROON", "KRUBI", "KRUNK", "KSARS", "KUBIE", "KUDOS", "KUDUS", "KUDZU", + "KUFIS", "KUGEL", "KUIAS", "KUKRI", "KUKUS", "KULAK", "KULAN", "KULAS", "KULFI", "KUMIS", "KUMYS", "KURIS", + "KURRE", "KURTA", "KURUS", "KUSSO", "KUTAS", "KUTCH", "KUTIS", "KUTUS", "KUZUS", "KVASS", "KVELL", "KWELA", + "KYACK", "KYAKS", "KYANG", "KYARS", "KYATS", "KYBOS", "KYDST", "KYLES", "KYLIE", "KYLIN", "KYLIX", "KYLOE", + "KYNDE", "KYNDS", "KYPES", "KYRIE", "KYTES", "KYTHE", "LAARI", "LABDA", "LABIA", "LABIS", "LABRA", "LACED", + "LACER", "LACES", "LACET", "LACEY", "LACKS", "LADDY", "LADED", "LADER", "LADES", "LAERS", "LAEVO", "LAGAN", + "LAHAL", "LAHAR", "LAICH", "LAICS", "LAIDS", "LAIGH", "LAIKA", "LAIKS", "LAIRD", "LAIRS", "LAIRY", "LAITH", + "LAITY", "LAKED", "LAKER", "LAKES", "LAKHS", "LAKIN", "LAKSA", "LALDY", "LALLS", "LAMAS", "LAMBS", "LAMBY", + "LAMED", "LAMER", "LAMES", "LAMIA", "LAMMY", "LAMPS", "LANAI", "LANAS", "LANCH", "LANDE", "LANDS", "LANES", + "LANKS", "LANTS", "LAPIN", "LAPIS", "LAPJE", "LARCH", "LARDS", "LARDY", "LAREE", "LARES", "LARGO", "LARIS", + "LARKS", "LARKY", "LARNS", "LARNT", "LARUM", "LASED", "LASER", "LASES", "LASSI", "LASSU", "LASSY", "LASTS", + "LATAH", "LATED", "LATEN", "LATEX", "LATHI", "LATHS", "LATHY", "LATKE", "LATUS", "LAUAN", "LAUCH", "LAUDS", + "LAUFS", "LAUND", "LAURA", "LAVAL", "LAVAS", "LAVED", "LAVER", "LAVES", "LAVRA", "LAVVY", "LAWED", "LAWER", + "LAWIN", "LAWKS", "LAWNS", "LAWNY", "LAXED", "LAXER", "LAXES", "LAXLY", "LAYED", "LAYIN", "LAYUP", "LAZAR", + "LAZED", "LAZES", "LAZOS", "LAZZI", "LAZZO", "LEADS", "LEADY", "LEAFS", "LEAKS", "LEAMS", "LEANS", "LEANY", + "LEAPS", "LEARE", "LEARS", "LEARY", "LEATS", "LEAVY", "LEAZE", "LEBEN", "LECCY", "LEDES", "LEDGY", "LEDUM", + "LEEAR", "LEEKS", "LEEPS", "LEERS", "LEESE", "LEETS", "LEEZE", "LEFTE", "LEFTS", "LEGER", "LEGES", "LEGGE", + "LEGGO", "LEGIT", "LEHRS", "LEHUA", "LEIRS", "LEISH", "LEMAN", "LEMED", "LEMEL", "LEMES", "LEMMA", "LEMME", + "LENDS", "LENES", "LENGS", "LENIS", "LENOS", "LENSE", "LENTI", "LENTO", "LEONE", "LEPID", "LEPRA", "LEPTA", + "LERED", "LERES", "LERPS", "LESBO", "LESES", "LESTS", "LETCH", "LETHE", "LETUP", "LEUCH", "LEUCO", "LEUDS", + "LEUGH", "LEVAS", "LEVEE", "LEVES", "LEVIN", "LEVIS", "LEWIS", "LEXES", "LEXIS", "LEZES", "LEZZA", "LEZZY", + "LIANA", "LIANE", "LIANG", "LIARD", "LIARS", "LIART", "LIBER", "LIBRA", "LIBRI", "LICHI", "LICHT", "LICIT", + "LICKS", "LIDAR", "LIDOS", "LIEFS", "LIENS", "LIERS", "LIEUS", "LIEVE", "LIFER", "LIFES", "LIFTS", "LIGAN", + "LIGER", "LIGGE", "LIGNE", "LIKED", "LIKER", "LIKES", "LIKIN", "LILLS", "LILOS", "LILTS", "LIMAN", "LIMAS", + "LIMAX", "LIMBA", "LIMBI", "LIMBS", "LIMBY", "LIMED", "LIMEN", "LIMES", "LIMEY", "LIMMA", "LIMNS", "LIMOS", + "LIMPA", "LIMPS", "LINAC", "LINCH", "LINDS", "LINDY", "LINED", "LINES", "LINEY", "LINGA", "LINGS", "LINGY", + "LININ", "LINKS", "LINKY", "LINNS", "LINNY", "LINOS", "LINTS", "LINTY", "LINUM", "LINUX", "LIONS", "LIPAS", + "LIPES", "LIPIN", "LIPOS", "LIPPY", "LIRAS", "LIRKS", "LIROT", "LISKS", "LISLE", "LISPS", "LISTS", "LITAI", + "LITAS", "LITED", "LITER", "LITES", "LITHO", "LITHS", "LITRE", "LIVED", "LIVEN", "LIVES", "LIVOR", "LIVRE", + "LLANO", "LOACH", "LOADS", "LOAFS", "LOAMS", "LOANS", "LOAST", "LOAVE", "LOBAR", "LOBED", "LOBES", "LOBOS", + "LOBUS", "LOCHE", "LOCHS", "LOCIE", "LOCIS", "LOCKS", "LOCOS", "LOCUM", "LODEN", "LODES", "LOESS", "LOFTS", + "LOGAN", "LOGES", "LOGGY", "LOGIA", "LOGIE", "LOGOI", "LOGON", "LOGOS", "LOHAN", "LOIDS", "LOINS", "LOIPE", + "LOIRS", "LOKES", "LOLLS", "LOLLY", "LOLOG", "LOMAS", "LOMED", "LOMES", "LONER", "LONGA", "LONGE", "LONGS", + "LOOBY", "LOOED", "LOOEY", "LOOFA", "LOOFS", "LOOIE", "LOOKS", "LOOKY", "LOOMS", "LOONS", "LOONY", "LOOPS", + "LOORD", "LOOTS", "LOPED", "LOPER", "LOPES", "LOPPY", "LORAL", "LORAN", "LORDS", "LORDY", "LOREL", "LORES", + "LORIC", "LORIS", "LOSED", "LOSEL", "LOSEN", "LOSES", "LOSSY", "LOTAH", "LOTAS", "LOTES", "LOTIC", "LOTOS", + "LOTSA", "LOTTA", "LOTTE", "LOTTO", "LOTUS", "LOUED", "LOUGH", "LOUIE", "LOUIS", "LOUMA", "LOUND", "LOUNS", + "LOUPE", "LOUPS", "LOURE", "LOURS", "LOURY", "LOUTS", "LOVAT", "LOVED", "LOVES", "LOVEY", "LOVIE", "LOWAN", + "LOWED", "LOWES", "LOWND", "LOWNE", "LOWNS", "LOWPS", "LOWRY", "LOWSE", "LOWTS", "LOXED", "LOXES", "LOZEN", + "LUACH", "LUAUS", "LUBED", "LUBES", "LUBRA", "LUCES", "LUCKS", "LUCRE", "LUDES", "LUDIC", "LUDOS", "LUFFA", + "LUFFS", "LUGED", "LUGER", "LUGES", "LULLS", "LULUS", "LUMAS", "LUMBI", "LUMME", "LUMMY", "LUMPS", "LUNAS", + "LUNES", "LUNET", "LUNGI", "LUNGS", "LUNKS", "LUNTS", "LUPIN", "LURED", "LURER", "LURES", "LUREX", "LURGI", + "LURGY", "LURKS", "LURRY", "LURVE", "LUSER", "LUSHY", "LUSKS", "LUSTS", "LUSUS", "LUTEA", "LUTED", "LUTER", + "LUTES", "LUVVY", "LUXED", "LUXER", "LUXES", "LWEIS", "LYAMS", "LYARD", "LYART", "LYASE", "LYCEA", "LYCEE", + "LYCRA", "LYMES", "LYNCH", "LYNES", "LYRES", "LYSED", "LYSES", "LYSIN", "LYSIS", "LYSOL", "LYSSA", "LYTED", + "LYTES", "LYTHE", "LYTIC", "LYTTA", "MAAED", "MAARE", "MAARS", "MABES", "MACAS", "MACED", "MACER", "MACES", + "MACHE", "MACHI", "MACHS", "MACKS", "MACLE", "MACON", "MADGE", "MADID", "MADRE", "MAERL", "MAFIC", "MAGES", + "MAGGS", "MAGOT", "MAGUS", "MAHOE", "MAHUA", "MAHWA", "MAIDS", "MAIKO", "MAIKS", "MAILE", "MAILL", "MAILS", + "MAIMS", "MAINS", "MAIRE", "MAIRS", "MAISE", "MAIST", "MAKAR", "MAKES", "MAKIS", "MAKOS", "MALAM", "MALAR", + "MALAS", "MALAX", "MALES", "MALIC", "MALIK", "MALIS", "MALLS", "MALMS", "MALMY", "MALTS", "MALTY", "MALUS", + "MALVA", "MALWA", "MAMAS", "MAMBA", "MAMEE", "MAMEY", "MAMIE", "MANAS", "MANAT", "MANDI", "MANEB", "MANED", + "MANEH", "MANES", "MANET", "MANGS", "MANIS", "MANKY", "MANNA", "MANOS", "MANSE", "MANTA", "MANTO", "MANTY", + "MANUL", "MANUS", "MAPAU", "MAQUI", "MARAE", "MARAH", "MARAS", "MARCS", "MARDY", "MARES", "MARGE", "MARGS", + "MARIA", "MARID", "MARKA", "MARKS", "MARLE", "MARLS", "MARLY", "MARMS", "MARON", "MAROR", "MARRA", "MARRI", + "MARSE", "MARTS", "MARVY", "MASAS", "MASED", "MASER", "MASES", "MASHY", "MASKS", "MASSA", "MASSY", "MASTS", + "MASTY", "MASUS", "MATAI", "MATED", "MATER", "MATES", "MATHS", "MATIN", "MATLO", "MATTE", "MATTS", "MATZA", + "MATZO", "MAUBY", "MAUDS", "MAULS", "MAUND", "MAURI", "MAUSY", "MAUTS", "MAUZY", "MAVEN", "MAVIE", "MAVIN", + "MAVIS", "MAWED", "MAWKS", "MAWKY", "MAWNS", "MAWRS", "MAXED", "MAXES", "MAXIS", "MAYAN", "MAYAS", "MAYED", + "MAYOS", "MAYST", "MAZED", "MAZER", "MAZES", "MAZEY", "MAZUT", "MBIRA", "MEADS", "MEALS", "MEANE", "MEANS", + "MEANY", "MEARE", "MEASE", "MEATH", "MEATS", "MEBOS", "MECHS", "MECKS", "MEDII", "MEDLE", "MEEDS", "MEERS", + "MEETS", "MEFFS", "MEINS", "MEINT", "MEINY", "MEITH", "MEKKA", "MELAS", "MELBA", "MELDS", "MELIC", "MELIK", + "MELLS", "MELTS", "MELTY", "MEMES", "MEMOS", "MENAD", "MENDS", "MENED", "MENES", "MENGE", "MENGS", "MENSA", + "MENSE", "MENSH", "MENTA", "MENTO", "MENUS", "MEOUS", "MEOWS", "MERCH", "MERCS", "MERDE", "MERED", "MEREL", + "MERER", "MERES", "MERIL", "MERIS", "MERKS", "MERLE", "MERLS", "MERSE", "MESAL", "MESAS", "MESEL", "MESES", + "MESHY", "MESIC", "MESNE", "MESON", "MESSY", "MESTO", "METED", "METES", "METHO", "METHS", "METIC", "METIF", + "METIS", "METOL", "METRE", "MEUSE", "MEVED", "MEVES", "MEWED", "MEWLS", "MEYNT", "MEZES", "MEZZE", "MEZZO", + "MHORR", "MIAOU", "MIAOW", "MIASM", "MIAUL", "MICAS", "MICHE", "MICHT", "MICKS", "MICKY", "MICOS", "MICRA", + "MIDDY", "MIDGY", "MIDIS", "MIENS", "MIEVE", "MIFFS", "MIFFY", "MIFTY", "MIGGS", "MIHAS", "MIHIS", "MIKED", + "MIKES", "MIKRA", "MIKVA", "MILCH", "MILDS", "MILER", "MILES", "MILFS", "MILIA", "MILKO", "MILKS", "MILLE", + "MILLS", "MILOR", "MILOS", "MILPA", "MILTS", "MILTY", "MILTZ", "MIMED", "MIMEO", "MIMER", "MIMES", "MIMSY", + "MINAE", "MINAR", "MINAS", "MINCY", "MINDS", "MINED", "MINES", "MINGE", "MINGS", "MINGY", "MINIS", "MINKE", + "MINKS", "MINNY", "MINOS", "MINTS", "MIRED", "MIRES", "MIREX", "MIRID", "MIRIN", "MIRKS", "MIRKY", "MIRLY", + "MIROS", "MIRVS", "MIRZA", "MISCH", "MISDO", "MISES", "MISGO", "MISOS", "MISSA", "MISTS", "MISTY", "MITCH", + "MITER", "MITES", "MITIS", "MITRE", "MITTS", "MIXED", "MIXEN", "MIXER", "MIXES", "MIXTE", "MIXUP", "MIZEN", + "MIZZY", "MNEME", "MOANS", "MOATS", "MOBBY", "MOBES", "MOBEY", "MOBIE", "MOBLE", "MOCHI", "MOCHS", "MOCHY", + "MOCKS", "MODER", "MODES", "MODGE", "MODII", "MODUS", "MOERS", "MOFOS", "MOGGY", "MOHEL", "MOHOS", "MOHRS", + "MOHUA", "MOHUR", "MOILE", "MOILS", "MOIRA", "MOIRE", "MOITS", "MOJOS", "MOKES", "MOKIS", "MOKOS", "MOLAL", + "MOLAS", "MOLDS", "MOLED", "MOLES", "MOLLA", "MOLLS", "MOLLY", "MOLTO", "MOLTS", "MOLYS", "MOMES", "MOMMA", + "MOMMY", "MOMUS", "MONAD", "MONAL", "MONAS", "MONDE", "MONDO", "MONER", "MONGO", "MONGS", "MONIC", "MONIE", + "MONKS", "MONOS", "MONTE", "MONTY", "MOOBS", "MOOCH", "MOODS", "MOOED", "MOOKS", "MOOLA", "MOOLI", "MOOLS", + "MOOLY", "MOONG", "MOONS", "MOONY", "MOOPS", "MOORS", "MOORY", "MOOTS", "MOOVE", "MOPED", "MOPER", "MOPES", + "MOPEY", "MOPPY", "MOPSY", "MOPUS", "MORAE", "MORAS", "MORAT", "MORAY", "MOREL", "MORES", "MORIA", "MORNE", + "MORNS", "MORRA", "MORRO", "MORSE", "MORTS", "MOSED", "MOSES", "MOSEY", "MOSKS", "MOSSO", "MOSTE", "MOSTS", + "MOTED", "MOTEN", "MOTES", "MOTET", "MOTEY", "MOTHS", "MOTHY", "MOTIS", "MOTTE", "MOTTS", "MOTTY", "MOTUS", + "MOTZA", "MOUCH", "MOUES", "MOULD", "MOULS", "MOUPS", "MOUST", "MOUSY", "MOVED", "MOVES", "MOWAS", "MOWED", + "MOWRA", "MOXAS", "MOXIE", "MOYAS", "MOYLE", "MOYLS", "MOZED", "MOZES", "MOZOS", "MPRET", "MUCHO", "MUCIC", + "MUCID", "MUCIN", "MUCKS", "MUCOR", "MUCRO", "MUDGE", "MUDIR", "MUDRA", "MUFFS", "MUFTI", "MUGGA", "MUGGS", + "MUGGY", "MUHLY", "MUIDS", "MUILS", "MUIRS", "MUIST", "MUJIK", "MULCT", "MULED", "MULES", "MULEY", "MULGA", + "MULIE", "MULLA", "MULLS", "MULSE", "MULSH", "MUMMS", "MUMPS", "MUMSY", "MUMUS", "MUNGA", "MUNGE", "MUNGO", + "MUNGS", "MUNIS", "MUNTS", "MUNTU", "MUONS", "MURAS", "MURED", "MURES", "MUREX", "MURID", "MURKS", "MURLS", + "MURLY", "MURRA", "MURRE", "MURRI", "MURRS", "MURRY", "MURTI", "MURVA", "MUSAR", "MUSCA", "MUSED", "MUSER", + "MUSES", "MUSET", "MUSHA", "MUSIT", "MUSKS", "MUSOS", "MUSSE", "MUSSY", "MUSTH", "MUSTS", "MUTCH", "MUTED", + "MUTER", "MUTES", "MUTHA", "MUTIS", "MUTON", "MUTTS", "MUXED", "MUXES", "MUZAK", "MUZZY", "MVULE", "MYALL", + "MYLAR", "MYNAH", "MYNAS", "MYOID", "MYOMA", "MYOPE", "MYOPS", "MYOPY", "MYSID", "MYTHI", "MYTHS", "MYTHY", + "MYXOS", "MZEES", "NAAMS", "NAANS", "NABES", "NABIS", "NABKS", "NABLA", "NABOB", "NACHE", "NACHO", "NACRE", + "NADAS", "NAEVE", "NAEVI", "NAFFS", "NAGAS", "NAGGY", "NAGOR", "NAHAL", "NAIAD", "NAIFS", "NAIKS", "NAILS", + "NAIRA", "NAIRU", "NAKED", "NAKER", "NAKFA", "NALAS", "NALED", "NALLA", "NAMED", "NAMER", "NAMES", "NAMMA", + "NAMUS", "NANAS", "NANCE", "NANCY", "NANDU", "NANNA", "NANOS", "NANUA", "NAPAS", "NAPED", "NAPES", "NAPOO", + "NAPPA", "NAPPE", "NAPPY", "NARAS", "NARCO", "NARCS", "NARDS", "NARES", "NARIC", "NARIS", "NARKS", "NARKY", + "NARRE", "NASHI", "NATCH", "NATES", "NATIS", "NATTY", "NAUCH", "NAUNT", "NAVAR", "NAVES", "NAVEW", "NAVVY", + "NAWAB", "NAZES", "NAZIR", "NAZIS", "NDUJA", "NEAFE", "NEALS", "NEAPS", "NEARS", "NEATH", "NEATS", "NEBEK", + "NEBEL", "NECKS", "NEDDY", "NEEDS", "NEELD", "NEELE", "NEEMB", "NEEMS", "NEEPS", "NEESE", "NEEZE", "NEGRO", + "NEGUS", "NEIFS", "NEIST", "NEIVE", "NELIS", "NELLY", "NEMAS", "NEMNS", "NEMPT", "NENES", "NEONS", "NEPER", + "NEPIT", "NERAL", "NERDS", "NERKA", "NERKS", "NEROL", "NERTS", "NERTZ", "NERVY", "NESTS", "NETES", "NETOP", + "NETTS", "NETTY", "NEUKS", "NEUME", "NEUMS", "NEVEL", "NEVES", "NEVUS", "NEWBS", "NEWED", "NEWEL", "NEWIE", + "NEWSY", "NEWTS", "NEXTS", "NEXUS", "NGAIO", "NGANA", "NGATI", "NGOMA", "NGWEE", "NICAD", "NICHT", "NICKS", + "NICOL", "NIDAL", "NIDED", "NIDES", "NIDOR", "NIDUS", "NIEFS", "NIEVE", "NIFES", "NIFFS", "NIFFY", "NIFTY", + "NIGER", "NIGHS", "NIHIL", "NIKAB", "NIKAH", "NIKAU", "NILLS", "NIMBI", "NIMBS", "NIMPS", "NINER", "NINES", + "NINON", "NIPAS", "NIPPY", "NIQAB", "NIRLS", "NIRLY", "NISEI", "NISSE", "NISUS", "NITER", "NITES", "NITID", + "NITON", "NITRE", "NITRO", "NITRY", "NITTY", "NIVAL", "NIXED", "NIXER", "NIXES", "NIXIE", "NIZAM", "NKOSI", + "NOAHS", "NOBBY", "NOCKS", "NODAL", "NODDY", "NODES", "NODUS", "NOELS", "NOGGS", "NOHOW", "NOILS", "NOILY", + "NOINT", "NOIRS", "NOLES", "NOLLS", "NOLOS", "NOMAS", "NOMEN", "NOMES", "NOMIC", "NOMOI", "NOMOS", "NONAS", + "NONCE", "NONES", "NONET", "NONGS", "NONIS", "NONNY", "NONYL", "NOOBS", "NOOIT", "NOOKS", "NOOKY", "NOONS", + "NOOPS", "NOPAL", "NORIA", "NORIS", "NORKS", "NORMA", "NORMS", "NOSED", "NOSER", "NOSES", "NOTAL", "NOTED", + "NOTER", "NOTES", "NOTUM", "NOULD", "NOULE", "NOULS", "NOUNS", "NOUNY", "NOUPS", "NOVAE", "NOVAS", "NOVUM", + "NOWAY", "NOWED", "NOWLS", "NOWTS", "NOWTY", "NOXAL", "NOXES", "NOYAU", "NOYED", "NOYES", "NUBBY", "NUBIA", + "NUCHA", "NUDDY", "NUDER", "NUDES", "NUDIE", "NUDZH", "NUFFS", "NUGAE", "NUKED", "NUKES", "NULLA", "NULLS", + "NUMBS", "NUMEN", "NUMMY", "NUNNY", "NURDS", "NURDY", "NURLS", "NURRS", "NUTSO", "NUTSY", "NYAFF", "NYALA", + "NYING", "NYSSA", "OAKED", "OAKER", "OAKUM", "OARED", "OASES", "OASIS", "OASTS", "OATEN", "OATER", "OATHS", + "OAVES", "OBANG", "OBEAH", "OBELI", "OBEYS", "OBIAS", "OBIED", "OBIIT", "OBITS", "OBJET", "OBOES", "OBOLE", + "OBOLI", "OBOLS", "OCCAM", "OCHER", "OCHES", "OCHRE", "OCHRY", "OCKER", "OCREA", "OCTAD", "OCTAN", "OCTAS", + "OCTYL", "OCULI", "ODAHS", "ODALS", "ODEON", "ODEUM", "ODISM", "ODIST", "ODIUM", "ODORS", "ODOUR", "ODYLE", + "ODYLS", "OFAYS", "OFFED", "OFFIE", "OFLAG", "OFTER", "OGAMS", "OGEED", "OGEES", "OGGIN", "OGHAM", "OGIVE", + "OGLED", "OGLER", "OGLES", "OGMIC", "OGRES", "OHIAS", "OHING", "OHMIC", "OHONE", "OIDIA", "OILED", "OILER", + "OINKS", "OINTS", "OJIME", "OKAPI", "OKAYS", "OKEHS", "OKRAS", "OKTAS", "OLDIE", "OLEIC", "OLEIN", "OLENT", + "OLEOS", "OLEUM", "OLIOS", "OLLAS", "OLLAV", "OLLER", "OLLIE", "OLOGY", "OLPAE", "OLPES", "OMASA", "OMBER", + "OMBUS", "OMENS", "OMERS", "OMITS", "OMLAH", "OMOVS", "OMRAH", "ONCER", "ONCES", "ONCET", "ONCUS", "ONELY", + "ONERS", "ONERY", "ONIUM", "ONKUS", "ONLAY", "ONNED", "ONTIC", "OOBIT", "OOHED", "OOMPH", "OONTS", "OOPED", + "OORIE", "OOSES", "OOTID", "OOZED", "OOZES", "OPAHS", "OPALS", "OPENS", "OPEPE", "OPING", "OPPOS", "OPSIN", + "OPTED", "OPTER", "ORACH", "ORACY", "ORALS", "ORANG", "ORANT", "ORATE", "ORBED", "ORCAS", "ORCIN", "ORDOS", + "OREAD", "ORFES", "ORGIA", "ORGIC", "ORGUE", "ORIBI", "ORIEL", "ORIXA", "ORLES", "ORLON", "ORLOP", "ORMER", + "ORNIS", "ORPIN", "ORRIS", "ORTHO", "ORVAL", "ORZOS", "OSCAR", "OSHAC", "OSIER", "OSMIC", "OSMOL", "OSSIA", + "OSTIA", "OTAKU", "OTARY", "OTTAR", "OTTOS", "OUBIT", "OUCHT", "OUENS", "OUIJA", "OULKS", "OUMAS", "OUNDY", + "OUPAS", "OUPED", "OUPHE", "OUPHS", "OURIE", "OUSEL", "OUSTS", "OUTBY", "OUTED", "OUTRE", "OUTRO", "OUTTA", + "OUZEL", "OUZOS", "OVALS", "OVELS", "OVENS", "OVERS", "OVIST", "OVOLI", "OVOLO", "OVULE", "OWCHE", "OWIES", + "OWLED", "OWLER", "OWLET", "OWNED", "OWRES", "OWRIE", "OWSEN", "OXBOW", "OXERS", "OXEYE", "OXIDS", "OXIES", + "OXIME", "OXIMS", "OXLIP", "OXTER", "OYERS", "OZEKI", "OZZIE", "PAALS", "PAANS", "PACAS", "PACED", "PACER", + "PACES", "PACEY", "PACHA", "PACKS", "PACOS", "PACTA", "PACTS", "PADIS", "PADLE", "PADMA", "PADRE", "PADRI", + "PAEAN", "PAEDO", "PAEON", "PAGED", "PAGER", "PAGES", "PAGLE", "PAGOD", "PAGRI", "PAIKS", "PAILS", "PAINS", + "PAIRE", "PAIRS", "PAISA", "PAISE", "PAKKA", "PALAS", "PALAY", "PALEA", "PALED", "PALES", "PALET", "PALIS", + "PALKI", "PALLA", "PALLS", "PALLY", "PALMS", "PALMY", "PALPI", "PALPS", "PALSA", "PAMPA", "PANAX", "PANCE", + "PANDA", "PANDS", "PANDY", "PANED", "PANES", "PANGA", "PANGS", "PANIM", "PANKO", "PANNE", "PANNI", "PANTO", + "PANTS", "PANTY", "PAOLI", "PAOLO", "PAPAS", "PAPAW", "PAPES", "PAPPI", "PAPPY", "PARAE", "PARAS", "PARCH", + "PARDI", "PARDS", "PARDY", "PARED", "PAREN", "PAREO", "PARES", "PAREU", "PAREV", "PARGE", "PARGO", "PARIS", + "PARKI", "PARKS", "PARKY", "PARLE", "PARLY", "PARMA", "PAROL", "PARPS", "PARRA", "PARRS", "PARTI", "PARTS", + "PARVE", "PARVO", "PASEO", "PASES", "PASHA", "PASHM", "PASKA", "PASPY", "PASSE", "PASTS", "PATED", "PATEN", + "PATER", "PATES", "PATHS", "PATIN", "PATKA", "PATLY", "PATTE", "PATUS", "PAUAS", "PAULS", "PAVAN", "PAVED", + "PAVEN", "PAVER", "PAVES", "PAVID", "PAVIN", "PAVIS", "PAWAS", "PAWAW", "PAWED", "PAWER", "PAWKS", "PAWKY", + "PAWLS", "PAWNS", "PAXES", "PAYED", "PAYOR", "PAYSD", "PEAGE", "PEAGS", "PEAKS", "PEAKY", "PEALS", "PEANS", + "PEARE", "PEARS", "PEART", "PEASE", "PEATS", "PEATY", "PEAVY", "PEAZE", "PEBAS", "PECHS", "PECKE", "PECKS", + "PECKY", "PEDES", "PEDIS", "PEDRO", "PEECE", "PEEKS", "PEELS", "PEENS", "PEEOY", "PEEPE", "PEEPS", "PEERS", + "PEERY", "PEEVE", "PEGGY", "PEGHS", "PEINS", "PEISE", "PEIZE", "PEKAN", "PEKES", "PEKIN", "PEKOE", "PELAS", + "PELAU", "PELES", "PELFS", "PELLS", "PELMA", "PELON", "PELTA", "PELTS", "PENDS", "PENDU", "PENED", "PENES", + "PENGO", "PENIE", "PENIS", "PENKS", "PENNA", "PENNI", "PENTS", "PEONS", "PEONY", "PEPLA", "PEPOS", "PEPPY", + "PEPSI", "PERAI", "PERCE", "PERCS", "PERDU", "PERDY", "PEREA", "PERES", "PERIS", "PERKS", "PERMS", "PERNS", + "PEROG", "PERPS", "PERRY", "PERSE", "PERST", "PERTS", "PERVE", "PERVO", "PERVS", "PERVY", "PESOS", "PESTS", + "PESTY", "PETAR", "PETER", "PETIT", "PETRE", "PETRI", "PETTI", "PETTO", "PEWEE", "PEWIT", "PEYSE", "PHAGE", + "PHANG", "PHARE", "PHARM", "PHEER", "PHENE", "PHEON", "PHESE", "PHIAL", "PHISH", "PHIZZ", "PHLOX", "PHOCA", + "PHONO", "PHONS", "PHOTS", "PHPHT", "PHUTS", "PHYLA", "PHYLE", "PIANI", "PIANS", "PIBAL", "PICAL", "PICAS", + "PICCY", "PICKS", "PICOT", "PICRA", "PICUL", "PIEND", "PIERS", "PIERT", "PIETA", "PIETS", "PIEZO", "PIGHT", + "PIGMY", "PIING", "PIKAS", "PIKAU", "PIKED", "PIKER", "PIKES", "PIKEY", "PIKIS", "PIKUL", "PILAE", "PILAF", + "PILAO", "PILAR", "PILAU", "PILAW", "PILCH", "PILEA", "PILED", "PILEI", "PILER", "PILES", "PILIS", "PILLS", + "PILOW", "PILUM", "PILUS", "PIMAS", "PIMPS", "PINAS", "PINED", "PINES", "PINGO", "PINGS", "PINKO", "PINKS", + "PINNA", "PINNY", "PINON", "PINOT", "PINTA", "PINTS", "PINUP", "PIONS", "PIONY", "PIOUS", "PIOYE", "PIOYS", + "PIPAL", "PIPAS", "PIPED", "PIPES", "PIPET", "PIPIS", "PIPIT", "PIPPY", "PIPUL", "PIRAI", "PIRLS", "PIRNS", + "PIROG", "PISCO", "PISES", "PISKY", "PISOS", "PISSY", "PISTE", "PITAS", "PITHS", "PITON", "PITOT", "PITTA", + "PIUMS", "PIXES", "PIZED", "PIZES", "PLAAS", "PLACK", "PLAGE", "PLANS", "PLAPS", "PLASH", "PLASM", "PLAST", + "PLATS", "PLATT", "PLATY", "PLAYA", "PLAYS", "PLEAS", "PLEBE", "PLEBS", "PLENA", "PLEON", "PLESH", "PLEWS", + "PLICA", "PLIES", "PLIMS", "PLING", "PLINK", "PLOAT", "PLODS", "PLONG", "PLONK", "PLOOK", "PLOPS", "PLOTS", + "PLOTZ", "PLOUK", "PLOWS", "PLOYE", "PLOYS", "PLUES", "PLUFF", "PLUGS", "PLUMS", "PLUMY", "PLUOT", "PLUTO", + "PLYER", "POACH", "POAKA", "POAKE", "POBOY", "POCKS", "POCKY", "PODAL", "PODDY", "PODEX", "PODGE", "PODGY", + "PODIA", "POEMS", "POEPS", "POETS", "POGEY", "POGGE", "POGOS", "POHED", "POILU", "POIND", "POKAL", "POKED", + "POKES", "POKEY", "POKIE", "POLED", "POLER", "POLES", "POLEY", "POLIO", "POLIS", "POLJE", "POLKS", "POLLS", + "POLLY", "POLOS", "POLTS", "POLYS", "POMBE", "POMES", "POMMY", "POMOS", "POMPS", "PONCE", "PONCY", "PONDS", + "PONES", "PONEY", "PONGA", "PONGO", "PONGS", "PONGY", "PONKS", "PONTS", "PONTY", "PONZU", "POODS", "POOED", + "POOFS", "POOFY", "POOHS", "POOJA", "POOKA", "POOKS", "POOLS", "POONS", "POOPS", "POOPY", "POORI", "POORT", + "POOTS", "POOVE", "POOVY", "POPES", "POPPA", "POPSY", "PORAE", "PORAL", "PORED", "PORER", "PORES", "PORGE", + "PORGY", "PORIN", "PORKS", "PORKY", "PORNO", "PORNS", "PORNY", "PORTA", "PORTS", "PORTY", "POSED", "POSES", + "POSEY", "POSHO", "POSTS", "POTAE", "POTCH", "POTED", "POTES", "POTIN", "POTOO", "POTSY", "POTTO", "POTTS", + "POTTY", "POUFF", "POUFS", "POUKE", "POUKS", "POULE", "POULP", "POULT", "POUPE", "POUPT", "POURS", "POUTS", + "POWAN", "POWIN", "POWND", "POWNS", "POWNY", "POWRE", "POXED", "POXES", "POYNT", "POYOU", "POYSE", "POZZY", + "PRAAM", "PRADS", "PRAHU", "PRAMS", "PRANA", "PRANG", "PRAOS", "PRASE", "PRATE", "PRATS", "PRATT", "PRATY", + "PRAUS", "PRAYS", "PREDY", "PREED", "PREES", "PREIF", "PREMS", "PREMY", "PRENT", "PREON", "PREOP", "PREPS", + "PRESA", "PRESE", "PREST", "PREVE", "PREXY", "PREYS", "PRIAL", "PRICY", "PRIEF", "PRIER", "PRIES", "PRIGS", + "PRILL", "PRIMA", "PRIMI", "PRIMP", "PRIMS", "PRIMY", "PRINK", "PRION", "PRISE", "PRISS", "PROAS", "PROBS", + "PRODS", "PROEM", "PROFS", "PROGS", "PROIN", "PROKE", "PROLE", "PROLL", "PROMO", "PROMS", "PRONK", "PROPS", + "PRORE", "PROSO", "PROSS", "PROST", "PROSY", "PROTO", "PROUL", "PROWS", "PROYN", "PRUNT", "PRUTA", "PRYER", + "PRYSE", "PSEUD", "PSHAW", "PSION", "PSOAE", "PSOAI", "PSOAS", "PSORA", "PSYCH", "PSYOP", "PUBCO", "PUBES", + "PUBIS", "PUCAN", "PUCER", "PUCES", "PUCKA", "PUCKS", "PUDDY", "PUDGE", "PUDIC", "PUDOR", "PUDSY", "PUDUS", + "PUERS", "PUFFA", "PUFFS", "PUGGY", "PUGIL", "PUHAS", "PUJAH", "PUJAS", "PUKAS", "PUKED", "PUKER", "PUKES", + "PUKEY", "PUKKA", "PUKUS", "PULAO", "PULAS", "PULED", "PULER", "PULES", "PULIK", "PULIS", "PULKA", "PULKS", + "PULLI", "PULLS", "PULLY", "PULMO", "PULPS", "PULUS", "PUMAS", "PUMIE", "PUMPS", "PUNAS", "PUNCE", "PUNGA", + "PUNGS", "PUNJI", "PUNKA", "PUNKS", "PUNKY", "PUNNY", "PUNTO", "PUNTS", "PUNTY", "PUPAE", "PUPAL", "PUPAS", + "PUPUS", "PURDA", "PURED", "PURES", "PURIN", "PURIS", "PURLS", "PURPY", "PURRS", "PURSY", "PURTY", "PUSES", + "PUSLE", "PUSSY", "PUTID", "PUTON", "PUTTI", "PUTTO", "PUTTS", "PUZEL", "PWNED", "PYATS", "PYETS", "PYGAL", + "PYINS", "PYLON", "PYNED", "PYNES", "PYOID", "PYOTS", "PYRAL", "PYRAN", "PYRES", "PYREX", "PYRIC", "PYROS", + "PYXED", "PYXES", "PYXIE", "PYXIS", "PZAZZ", "QADIS", "QAIDS", "QAJAQ", "QANAT", "QAPIK", "QIBLA", "QOPHS", + "QORMA", "QUADS", "QUAFF", "QUAGS", "QUAIR", "QUAIS", "QUAKY", "QUALE", "QUANT", "QUARE", "QUASS", "QUATE", + "QUATS", "QUAYD", "QUAYS", "QUBIT", "QUEAN", "QUEME", "QUENA", "QUERN", "QUEYN", "QUEYS", "QUICH", "QUIDS", + "QUIFF", "QUIMS", "QUINA", "QUINE", "QUINO", "QUINS", "QUINT", "QUIPO", "QUIPS", "QUIPU", "QUIRE", "QUIRT", + "QUIST", "QUITS", "QUOAD", "QUODS", "QUOIF", "QUOIN", "QUOIT", "QUOLL", "QUONK", "QUOPS", "QURAN", "QURSH", + "QUYTE", "RABAT", "RABIC", "RABIS", "RACED", "RACES", "RACHE", "RACKS", "RACON", "RADGE", "RADIX", "RADON", + "RAFFS", "RAFTS", "RAGAS", "RAGDE", "RAGED", "RAGEE", "RAGER", "RAGES", "RAGGA", "RAGGS", "RAGGY", "RAGIS", + "RAGUS", "RAHED", "RAHUI", "RAIAS", "RAIDS", "RAIKS", "RAILE", "RAILS", "RAINE", "RAINS", "RAIRD", "RAITA", + "RAITS", "RAJAS", "RAJES", "RAKED", "RAKEE", "RAKER", "RAKES", "RAKIA", "RAKIS", "RAKUS", "RALES", "RAMAL", + "RAMEE", "RAMET", "RAMIE", "RAMIN", "RAMIS", "RAMMY", "RAMPS", "RAMUS", "RANAS", "RANCE", "RANDS", "RANEE", + "RANGA", "RANGI", "RANGS", "RANGY", "RANID", "RANIS", "RANKE", "RANKS", "RANTS", "RAPED", "RAPER", "RAPES", + "RAPHE", "RAPPE", "RARED", "RAREE", "RARES", "RARKS", "RASED", "RASER", "RASES", "RASPS", "RASSE", "RASTA", + "RATAL", "RATAN", "RATAS", "RATCH", "RATED", "RATEL", "RATER", "RATES", "RATHA", "RATHE", "RATHS", "RATOO", + "RATOS", "RATUS", "RAUNS", "RAUPO", "RAVED", "RAVEL", "RAVER", "RAVES", "RAVEY", "RAVIN", "RAWER", "RAWIN", + "RAWLY", "RAWNS", "RAXED", "RAXES", "RAYAH", "RAYAS", "RAYED", "RAYLE", "RAYNE", "RAZED", "RAZEE", "RAZER", + "RAZES", "RAZOO", "READD", "READS", "REAIS", "REAKS", "REALO", "REALS", "REAME", "REAMS", "REAMY", "REANS", + "REAPS", "REARS", "REAST", "REATA", "REATE", "REAVE", "REBBE", "REBEC", "REBID", "REBIT", "REBOP", "REBUY", + "RECAL", "RECCE", "RECCO", "RECCY", "RECIT", "RECKS", "RECON", "RECTA", "RECTI", "RECTO", "REDAN", "REDDS", + "REDDY", "REDED", "REDES", "REDIA", "REDID", "REDIP", "REDLY", "REDON", "REDOS", "REDOX", "REDRY", "REDUB", + "REDUX", "REDYE", "REECH", "REEDE", "REEDS", "REEFS", "REEFY", "REEKS", "REEKY", "REELS", "REENS", "REEST", + "REEVE", "REFED", "REFEL", "REFFO", "REFIS", "REFIX", "REFLY", "REFRY", "REGAR", "REGES", "REGGO", "REGIE", + "REGMA", "REGNA", "REGOS", "REGUR", "REHEM", "REIFS", "REIFY", "REIKI", "REIKS", "REINK", "REINS", "REIRD", + "REIST", "REIVE", "REJIG", "REJON", "REKED", "REKES", "REKEY", "RELET", "RELIE", "RELIT", "RELLO", "REMAN", + "REMAP", "REMEN", "REMET", "REMEX", "REMIX", "RENAY", "RENDS", "RENEY", "RENGA", "RENIG", "RENIN", "RENNE", + "RENOS", "RENTE", "RENTS", "REOIL", "REORG", "REPEG", "REPIN", "REPLA", "REPOS", "REPOT", "REPPS", "REPRO", + "RERAN", "RERIG", "RESAT", "RESAW", "RESAY", "RESEE", "RESES", "RESEW", "RESID", "RESIT", "RESOD", "RESOW", + "RESTO", "RESTS", "RESTY", "RESUS", "RETAG", "RETAX", "RETEM", "RETIA", "RETIE", "RETOX", "REVET", "REVIE", + "REWAN", "REWAX", "REWED", "REWET", "REWIN", "REWON", "REWTH", "REXES", "REZES", "RHEAS", "RHEME", "RHEUM", + "RHIES", "RHIME", "RHINE", "RHODY", "RHOMB", "RHONE", "RHUMB", "RHYNE", "RHYTA", "RIADS", "RIALS", "RIANT", + "RIATA", "RIBAS", "RIBBY", "RIBES", "RICED", "RICER", "RICES", "RICEY", "RICHT", "RICIN", "RICKS", "RIDES", + "RIDGY", "RIDIC", "RIELS", "RIEMS", "RIEVE", "RIFER", "RIFFS", "RIFTE", "RIFTS", "RIFTY", "RIGGS", "RIGOL", + "RILED", "RILES", "RILEY", "RILLE", "RILLS", "RIMAE", "RIMED", "RIMER", "RIMES", "RIMUS", "RINDS", "RINDY", + "RINES", "RINGS", "RINKS", "RIOJA", "RIOTS", "RIPED", "RIPES", "RIPPS", "RISES", "RISHI", "RISKS", "RISPS", + "RISUS", "RITES", "RITTS", "RITZY", "RIVAS", "RIVED", "RIVEL", "RIVEN", "RIVES", "RIYAL", "RIZAS", "ROADS", + "ROAMS", "ROANS", "ROARS", "ROARY", "ROATE", "ROBED", "ROBES", "ROBLE", "ROCKS", "RODED", "RODES", "ROGUY", + "ROHES", "ROIDS", "ROILS", "ROILY", "ROINS", "ROIST", "ROJAK", "ROJIS", "ROKED", "ROKER", "ROKES", "ROLAG", + "ROLES", "ROLFS", "ROLLS", "ROMAL", "ROMAN", "ROMEO", "ROMPS", "RONDE", "RONDO", "RONEO", "RONES", "RONIN", + "RONNE", "RONTE", "RONTS", "ROODS", "ROOFS", "ROOFY", "ROOKS", "ROOKY", "ROOMS", "ROONS", "ROOPS", "ROOPY", + "ROOSA", "ROOSE", "ROOTS", "ROOTY", "ROPED", "ROPER", "ROPES", "ROPEY", "ROQUE", "RORAL", "RORES", "RORIC", + "RORID", "RORIE", "RORTS", "RORTY", "ROSED", "ROSES", "ROSET", "ROSHI", "ROSIN", "ROSIT", "ROSTI", "ROSTS", + "ROTAL", "ROTAN", "ROTAS", "ROTCH", "ROTED", "ROTES", "ROTIS", "ROTLS", "ROTON", "ROTOS", "ROTTE", "ROUEN", + "ROUES", "ROULE", "ROULS", "ROUMS", "ROUPS", "ROUPY", "ROUST", "ROUTH", "ROUTS", "ROVED", "ROVEN", "ROVES", + "ROWAN", "ROWED", "ROWEL", "ROWEN", "ROWIE", "ROWME", "ROWND", "ROWTH", "ROWTS", "ROYNE", "ROYST", "ROZET", + "ROZIT", "RUANA", "RUBAI", "RUBBY", "RUBEL", "RUBES", "RUBIN", "RUBLE", "RUBLI", "RUBUS", "RUCHE", "RUCKS", + "RUDAS", "RUDDS", "RUDES", "RUDIE", "RUDIS", "RUEDA", "RUERS", "RUFFE", "RUFFS", "RUGAE", "RUGAL", "RUGGY", + "RUING", "RUINS", "RUKHS", "RULED", "RULES", "RUMAL", "RUMBO", "RUMEN", "RUMES", "RUMLY", "RUMMY", "RUMPO", + "RUMPS", "RUMPY", "RUNCH", "RUNDS", "RUNED", "RUNES", "RUNGS", "RUNIC", "RUNNY", "RUNTS", "RUNTY", "RUPIA", + "RURPS", "RURUS", "RUSAS", "RUSES", "RUSHY", "RUSKS", "RUSMA", "RUSSE", "RUSTS", "RUTHS", "RUTIN", "RUTTY", + "RYALS", "RYBAT", "RYKED", "RYKES", "RYMME", "RYNDS", "RYOTS", "RYPER", "SAAGS", "SABAL", "SABED", "SABER", + "SABES", "SABHA", "SABIN", "SABIR", "SABLE", "SABOT", "SABRA", "SABRE", "SACKS", "SACRA", "SADDO", "SADES", + "SADHE", "SADHU", "SADIS", "SADOS", "SADZA", "SAFED", "SAFES", "SAGAS", "SAGER", "SAGES", "SAGGY", "SAGOS", + "SAGUM", "SAHEB", "SAHIB", "SAICE", "SAICK", "SAICS", "SAIDS", "SAIGA", "SAILS", "SAIMS", "SAINE", "SAINS", + "SAIRS", "SAIST", "SAITH", "SAJOU", "SAKAI", "SAKER", "SAKES", "SAKIA", "SAKIS", "SAKTI", "SALAL", "SALAT", + "SALEP", "SALES", "SALET", "SALIC", "SALIX", "SALLE", "SALMI", "SALOL", "SALOP", "SALPA", "SALPS", "SALSE", + "SALTO", "SALTS", "SALUE", "SALUT", "SAMAN", "SAMAS", "SAMBA", "SAMBO", "SAMEK", "SAMEL", "SAMEN", "SAMES", + "SAMEY", "SAMFU", "SAMMY", "SAMPI", "SAMPS", "SANDS", "SANED", "SANES", "SANGA", "SANGH", "SANGO", "SANGS", + "SANKO", "SANSA", "SANTO", "SANTS", "SAOLA", "SAPAN", "SAPID", "SAPOR", "SARAN", "SARDS", "SARED", "SAREE", + "SARGE", "SARGO", "SARIN", "SARIS", "SARKS", "SARKY", "SAROD", "SAROS", "SARUS", "SASER", "SASIN", "SASSE", + "SATAI", "SATAY", "SATED", "SATEM", "SATES", "SATIS", "SAUBA", "SAUCH", "SAUGH", "SAULS", "SAULT", "SAUNT", + "SAURY", "SAUTS", "SAVED", "SAVER", "SAVES", "SAVEY", "SAVIN", "SAWAH", "SAWED", "SAWER", "SAXES", "SAYED", + "SAYER", "SAYID", "SAYNE", "SAYON", "SAYST", "SAZES", "SCABS", "SCADS", "SCAFF", "SCAGS", "SCAIL", "SCALA", + "SCALL", "SCAMS", "SCAND", "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", "SCARS", "SCART", "SCATH", "SCATS", + "SCATT", "SCAUD", "SCAUP", "SCAUR", "SCAWS", "SCEAT", "SCENA", "SCEND", "SCHAV", "SCHMO", "SCHUL", "SCHWA", + "SCLIM", "SCODY", "SCOGS", "SCOOG", "SCOOT", "SCOPA", "SCOPS", "SCOTS", "SCOUG", "SCOUP", "SCOWP", "SCOWS", + "SCRAB", "SCRAE", "SCRAG", "SCRAN", "SCRAT", "SCRAW", "SCRAY", "SCRIM", "SCRIP", "SCROB", "SCROD", "SCROG", + "SCROW", "SCUDI", "SCUDO", "SCUDS", "SCUFF", "SCUFT", "SCUGS", "SCULK", "SCULL", "SCULP", "SCULS", "SCUMS", + "SCUPS", "SCURF", "SCURS", "SCUSE", "SCUTA", "SCUTE", "SCUTS", "SCUZZ", "SCYES", "SDAYN", "SDEIN", "SEALS", + "SEAME", "SEAMS", "SEAMY", "SEANS", "SEARE", "SEARS", "SEASE", "SEATS", "SEAZE", "SEBUM", "SECCO", "SECHS", + "SECTS", "SEDER", "SEDES", "SEDGE", "SEDGY", "SEDUM", "SEEDS", "SEEKS", "SEELD", "SEELS", "SEELY", "SEEMS", + "SEEPS", "SEEPY", "SEERS", "SEFER", "SEGAR", "SEGNI", "SEGNO", "SEGOL", "SEGOS", "SEHRI", "SEIFS", "SEILS", + "SEINE", "SEIRS", "SEISE", "SEISM", "SEITY", "SEIZA", "SEKOS", "SEKTS", "SELAH", "SELES", "SELFS", "SELLA", + "SELLE", "SELLS", "SELVA", "SEMEE", "SEMES", "SEMIE", "SEMIS", "SENAS", "SENDS", "SENES", "SENGI", "SENNA", + "SENOR", "SENSA", "SENSI", "SENTE", "SENTI", "SENTS", "SENVY", "SENZA", "SEPAD", "SEPAL", "SEPIC", "SEPOY", + "SEPTA", "SEPTS", "SERAC", "SERAI", "SERAL", "SERED", "SERER", "SERES", "SERFS", "SERGE", "SERIC", "SERIN", + "SERKS", "SERON", "SEROW", "SERRA", "SERRE", "SERRS", "SERRY", "SERVO", "SESEY", "SESSA", "SETAE", "SETAL", + "SETON", "SETTS", "SEWAN", "SEWAR", "SEWED", "SEWEL", "SEWEN", "SEWIN", "SEXED", "SEXER", "SEXES", "SEXTO", + "SEXTS", "SEYEN", "SHADS", "SHAGS", "SHAHS", "SHAKO", "SHAKT", "SHALM", "SHALY", "SHAMA", "SHAMS", "SHAND", + "SHANS", "SHAPS", "SHARN", "SHASH", "SHAUL", "SHAWM", "SHAWN", "SHAWS", "SHAYA", "SHAYS", "SHCHI", "SHEAF", + "SHEAL", "SHEAS", "SHEDS", "SHEEL", "SHEND", "SHENT", "SHEOL", "SHERD", "SHERE", "SHERO", "SHETS", "SHEVA", + "SHEWN", "SHEWS", "SHIAI", "SHIEL", "SHIER", "SHIES", "SHILL", "SHILY", "SHIMS", "SHINS", "SHIPS", "SHIRR", + "SHIRS", "SHISH", "SHISO", "SHIST", "SHITE", "SHITS", "SHIUR", "SHIVA", "SHIVE", "SHIVS", "SHLEP", "SHLUB", + "SHMEK", "SHMOE", "SHOAT", "SHOED", "SHOER", "SHOES", "SHOGI", "SHOGS", "SHOJI", "SHOJO", "SHOLA", "SHOOL", + "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", "SHOTE", "SHOTS", "SHOTT", "SHOWD", "SHOWS", "SHOYU", "SHRED", + "SHRIS", "SHROW", "SHTIK", "SHTUM", "SHTUP", "SHULE", "SHULN", "SHULS", "SHUNS", "SHURA", "SHUTE", "SHUTS", + "SHWAS", "SHYER", "SIALS", "SIBBS", "SIBYL", "SICES", "SICHT", "SICKO", "SICKS", "SICKY", "SIDAS", "SIDED", + "SIDER", "SIDES", "SIDHA", "SIDHE", "SIDLE", "SIELD", "SIENS", "SIENT", "SIETH", "SIEUR", "SIFTS", "SIGHS", + "SIGIL", "SIGLA", "SIGNA", "SIGNS", "SIJOS", "SIKAS", "SIKER", "SIKES", "SILDS", "SILED", "SILEN", "SILER", + "SILES", "SILEX", "SILKS", "SILLS", "SILOS", "SILTS", "SILTY", "SILVA", "SIMAR", "SIMAS", "SIMBA", "SIMIS", + "SIMPS", "SIMUL", "SINDS", "SINED", "SINES", "SINGS", "SINHS", "SINKS", "SINKY", "SINUS", "SIPED", "SIPES", + "SIPPY", "SIRED", "SIREE", "SIRES", "SIRIH", "SIRIS", "SIROC", "SIRRA", "SIRUP", "SISAL", "SISES", "SISTA", + "SISTS", "SITAR", "SITED", "SITES", "SITHE", "SITKA", "SITUP", "SITUS", "SIVER", "SIXER", "SIXES", "SIXMO", + "SIXTE", "SIZAR", "SIZED", "SIZEL", "SIZER", "SIZES", "SKAGS", "SKAIL", "SKALD", "SKANK", "SKART", "SKATS", + "SKATT", "SKAWS", "SKEAN", "SKEAR", "SKEDS", "SKEED", "SKEEF", "SKEEN", "SKEER", "SKEES", "SKEET", "SKEGG", + "SKEGS", "SKEIN", "SKELF", "SKELL", "SKELM", "SKELP", "SKENE", "SKENS", "SKEOS", "SKEPS", "SKERS", "SKETS", + "SKEWS", "SKIDS", "SKIED", "SKIES", "SKIEY", "SKIMO", "SKIMS", "SKINK", "SKINS", "SKINT", "SKIOS", "SKIPS", + "SKIRL", "SKIRR", "SKITE", "SKITS", "SKIVE", "SKIVY", "SKLIM", "SKOAL", "SKODY", "SKOFF", "SKOGS", "SKOLS", + "SKOOL", "SKORT", "SKOSH", "SKRAN", "SKRIK", "SKUAS", "SKUGS", "SKYED", "SKYER", "SKYEY", "SKYFS", "SKYRE", + "SKYRS", "SKYTE", "SLABS", "SLADE", "SLAES", "SLAGS", "SLAID", "SLAKE", "SLAMS", "SLANE", "SLANK", "SLAPS", + "SLART", "SLATS", "SLATY", "SLAVE", "SLAWS", "SLAYS", "SLEBS", "SLEDS", "SLEER", "SLEWS", "SLEYS", "SLIER", + "SLILY", "SLIMS", "SLIPE", "SLIPS", "SLIPT", "SLISH", "SLITS", "SLIVE", "SLOAN", "SLOBS", "SLOES", "SLOGS", + "SLOID", "SLOJD", "SLOMO", "SLOOM", "SLOOT", "SLOPS", "SLOPY", "SLORM", "SLOTS", "SLOVE", "SLOWS", "SLOYD", + "SLUBB", "SLUBS", "SLUED", "SLUES", "SLUFF", "SLUGS", "SLUIT", "SLUMS", "SLURB", "SLURS", "SLUSE", "SLUTS", + "SLYER", "SLYPE", "SMAAK", "SMAIK", "SMALM", "SMALT", "SMARM", "SMAZE", "SMEEK", "SMEES", "SMEIK", "SMEKE", + "SMERK", "SMEWS", "SMIRR", "SMIRS", "SMITS", "SMOGS", "SMOKO", "SMOLT", "SMOOR", "SMOOT", "SMORE", "SMORG", + "SMOUT", "SMOWT", "SMUGS", "SMURS", "SMUSH", "SMUTS", "SNABS", "SNAFU", "SNAGS", "SNAPS", "SNARF", "SNARK", + "SNARS", "SNARY", "SNASH", "SNATH", "SNAWS", "SNEAD", "SNEAP", "SNEBS", "SNECK", "SNEDS", "SNEED", "SNEES", + "SNELL", "SNIBS", "SNICK", "SNIES", "SNIFT", "SNIGS", "SNIPS", "SNIPY", "SNIRT", "SNITS", "SNOBS", "SNODS", + "SNOEK", "SNOEP", "SNOGS", "SNOKE", "SNOOD", "SNOOK", "SNOOL", "SNOOT", "SNOTS", "SNOWK", "SNOWS", "SNUBS", + "SNUGS", "SNUSH", "SNYES", "SOAKS", "SOAPS", "SOARE", "SOARS", "SOAVE", "SOBAS", "SOCAS", "SOCES", "SOCKO", + "SOCKS", "SOCLE", "SODAS", "SODDY", "SODIC", "SODOM", "SOFAR", "SOFAS", "SOFTA", "SOFTS", "SOFTY", "SOGER", + "SOHUR", "SOILS", "SOILY", "SOJAS", "SOJUS", "SOKAH", "SOKEN", "SOKES", "SOKOL", "SOLAH", "SOLAN", "SOLAS", + "SOLDE", "SOLDI", "SOLDO", "SOLDS", "SOLED", "SOLEI", "SOLER", "SOLES", "SOLON", "SOLOS", "SOLUM", "SOLUS", + "SOMAN", "SOMAS", "SONCE", "SONDE", "SONES", "SONGS", "SONLY", "SONNE", "SONNY", "SONSE", "SONSY", "SOOEY", + "SOOKS", "SOOKY", "SOOLE", "SOOLS", "SOOMS", "SOOPS", "SOOTE", "SOOTS", "SOPHS", "SOPHY", "SOPOR", "SOPPY", + "SOPRA", "SORAL", "SORAS", "SORBO", "SORBS", "SORDA", "SORDO", "SORDS", "SORED", "SOREE", "SOREL", "SORER", + "SORES", "SOREX", "SORGO", "SORNS", "SORRA", "SORTA", "SORTS", "SORUS", "SOTHS", "SOTOL", "SOUCE", "SOUCT", + "SOUGH", "SOUKS", "SOULS", "SOUMS", "SOUPS", "SOUPY", "SOURS", "SOUSE", "SOUTS", "SOWAR", "SOWCE", "SOWED", + "SOWFF", "SOWFS", "SOWLE", "SOWLS", "SOWMS", "SOWND", "SOWNE", "SOWPS", "SOWSE", "SOWTH", "SOYAS", "SOYLE", + "SOYUZ", "SOZIN", "SPACY", "SPADO", "SPAED", "SPAER", "SPAES", "SPAGS", "SPAHI", "SPAIL", "SPAIN", "SPAIT", + "SPAKE", "SPALD", "SPALE", "SPALL", "SPALT", "SPAMS", "SPANE", "SPANG", "SPANS", "SPARD", "SPARS", "SPART", + "SPATE", "SPATS", "SPAUL", "SPAWL", "SPAWS", "SPAYD", "SPAYS", "SPAZA", "SPAZZ", "SPEAL", "SPEAN", "SPEAT", + "SPECS", "SPECT", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEKS", "SPELD", "SPELK", "SPEOS", "SPETS", "SPEUG", + "SPEWS", "SPEWY", "SPIAL", "SPICA", "SPICK", "SPICS", "SPIDE", "SPIER", "SPIES", "SPIFF", "SPIFS", "SPIKS", + "SPILE", "SPIMS", "SPINA", "SPINK", "SPINS", "SPIRT", "SPIRY", "SPITS", "SPITZ", "SPIVS", "SPLAY", "SPLOG", + "SPODE", "SPODS", "SPOOM", "SPOOR", "SPOOT", "SPORK", "SPOSH", "SPOTS", "SPRAD", "SPRAG", "SPRAT", "SPRED", + "SPREW", "SPRIT", "SPROD", "SPROG", "SPRUE", "SPRUG", "SPUDS", "SPUED", "SPUER", "SPUES", "SPUGS", "SPULE", + "SPUME", "SPUMY", "SPURS", "SPUTA", "SPYAL", "SPYRE", "SQUAB", "SQUAW", "SQUEG", "SQUID", "SQUIT", "SQUIZ", + "STABS", "STADE", "STAGS", "STAGY", "STAIG", "STANE", "STANG", "STAPH", "STAPS", "STARN", "STARR", "STARS", + "STATS", "STAUN", "STAWS", "STAYS", "STEAN", "STEAR", "STEDD", "STEDE", "STEDS", "STEEK", "STEEM", "STEEN", + "STEIL", "STELA", "STELE", "STELL", "STEME", "STEMS", "STEND", "STENO", "STENS", "STENT", "STEPS", "STEPT", + "STERE", "STETS", "STEWS", "STEWY", "STEYS", "STICH", "STIED", "STIES", "STILB", "STILE", "STIME", "STIMS", + "STIMY", "STIPA", "STIPE", "STIRE", "STIRK", "STIRP", "STIRS", "STIVE", "STIVY", "STOAE", "STOAI", "STOAS", + "STOAT", "STOBS", "STOEP", "STOGY", "STOIT", "STOLN", "STOMA", "STOND", "STONG", "STONK", "STONN", "STOOK", + "STOOR", "STOPE", "STOPS", "STOPT", "STOSS", "STOTS", "STOTT", "STOUN", "STOUP", "STOUR", "STOWN", "STOWP", + "STOWS", "STRAD", "STRAE", "STRAG", "STRAK", "STREP", "STREW", "STRIA", "STRIG", "STRIM", "STROP", "STROW", + "STROY", "STRUM", "STUBS", "STUDE", "STUDS", "STULL", "STULM", "STUMM", "STUMS", "STUNS", "STUPA", "STUPE", + "STURE", "STURT", "STYED", "STYES", "STYLI", "STYLO", "STYME", "STYMY", "STYRE", "STYTE", "SUBAH", "SUBAS", + "SUBBY", "SUBER", "SUBHA", "SUCCI", "SUCKS", "SUCKY", "SUCRE", "SUDDS", "SUDOR", "SUDSY", "SUEDE", "SUENT", + "SUERS", "SUETE", "SUETS", "SUETY", "SUGAN", "SUGHS", "SUGOS", "SUHUR", "SUIDS", "SUINT", "SUITS", "SUJEE", + "SUKHS", "SUKUK", "SULCI", "SULFA", "SULFO", "SULKS", "SULPH", "SULUS", "SUMIS", "SUMMA", "SUMOS", "SUMPH", + "SUMPS", "SUNIS", "SUNKS", "SUNNA", "SUNNS", "SUNUP", "SUPES", "SUPRA", "SURAH", "SURAL", "SURAS", "SURAT", + "SURDS", "SURED", "SURES", "SURFS", "SURFY", "SURGY", "SURRA", "SUSED", "SUSES", "SUSUS", "SUTOR", "SUTRA", + "SUTTA", "SWABS", "SWACK", "SWADS", "SWAGE", "SWAGS", "SWAIL", "SWAIN", "SWALE", "SWALY", "SWAMY", "SWANG", + "SWANK", "SWANS", "SWAPS", "SWAPT", "SWARD", "SWARE", "SWARF", "SWART", "SWATS", "SWAYL", "SWAYS", "SWEAL", + "SWEDE", "SWEED", "SWEEL", "SWEER", "SWEES", "SWEIR", "SWELT", "SWERF", "SWEYS", "SWIES", "SWIGS", "SWILE", + "SWIMS", "SWINK", "SWIPE", "SWIRE", "SWISS", "SWITH", "SWITS", "SWIVE", "SWIZZ", "SWOBS", "SWOLE", "SWOLN", + "SWOPS", "SWOPT", "SWOTS", "SWOUN", "SYBBE", "SYBIL", "SYBOE", "SYBOW", "SYCEE", "SYCES", "SYCON", "SYENS", + "SYKER", "SYKES", "SYLIS", "SYLPH", "SYLVA", "SYMAR", "SYNCH", "SYNCS", "SYNDS", "SYNED", "SYNES", "SYNTH", + "SYPED", "SYPES", "SYPHS", "SYRAH", "SYREN", "SYSOP", "SYTHE", "SYVER", "TAALS", "TAATA", "TABER", "TABES", + "TABID", "TABIS", "TABLA", "TABOR", "TABUN", "TABUS", "TACAN", "TACES", "TACET", "TACHE", "TACHO", "TACHS", + "TACKS", "TACOS", "TACTS", "TAELS", "TAFIA", "TAGGY", "TAGMA", "TAHAS", "TAHRS", "TAIGA", "TAIGS", "TAIKO", + "TAILS", "TAINS", "TAIRA", "TAISH", "TAITS", "TAJES", "TAKAS", "TAKES", "TAKHI", "TAKIN", "TAKIS", "TAKKY", + "TALAK", "TALAQ", "TALAR", "TALAS", "TALCS", "TALCY", "TALEA", "TALER", "TALES", "TALKS", "TALKY", "TALLS", + "TALMA", "TALPA", "TALUK", "TALUS", "TAMAL", "TAMED", "TAMES", "TAMIN", "TAMIS", "TAMMY", "TAMPS", "TANAS", + "TANGA", "TANGI", "TANGS", "TANHS", "TANKA", "TANKS", "TANKY", "TANNA", "TANSY", "TANTI", "TANTO", "TANTY", + "TAPAS", "TAPED", "TAPEN", "TAPES", "TAPET", "TAPIS", "TAPPA", "TAPUS", "TARAS", "TARDO", "TARED", "TARES", + "TARGA", "TARGE", "TARNS", "TAROC", "TAROK", "TAROS", "TARPS", "TARRE", "TARRY", "TARSI", "TARTS", "TARTY", + "TASAR", "TASED", "TASER", "TASES", "TASKS", "TASSA", "TASSE", "TASSO", "TATAR", "TATER", "TATES", "TATHS", + "TATIE", "TATOU", "TATTS", "TATUS", "TAUBE", "TAULD", "TAUON", "TAUPE", "TAUTS", "TAVAH", "TAVAS", "TAVER", + "TAWAI", "TAWAS", "TAWED", "TAWER", "TAWIE", "TAWSE", "TAWTS", "TAXED", "TAXER", "TAXES", "TAXIS", "TAXOL", + "TAXON", "TAXOR", "TAXUS", "TAYRA", "TAZZA", "TAZZE", "TEADE", "TEADS", "TEAED", "TEAKS", "TEALS", "TEAMS", + "TEARS", "TEATS", "TEAZE", "TECHS", "TECHY", "TECTA", "TEELS", "TEEMS", "TEEND", "TEENE", "TEENS", "TEENY", + "TEERS", "TEFFS", "TEGGS", "TEGUA", "TEGUS", "TEHRS", "TEIID", "TEILS", "TEIND", "TEINS", "TELAE", "TELCO", + "TELES", "TELEX", "TELIA", "TELIC", "TELLS", "TELLY", "TELOI", "TELOS", "TEMED", "TEMES", "TEMPI", "TEMPS", + "TEMPT", "TEMSE", "TENCH", "TENDS", "TENDU", "TENES", "TENGE", "TENIA", "TENNE", "TENNO", "TENNY", "TENON", + "TENTS", "TENTY", "TENUE", "TEPAL", "TEPAS", "TEPOY", "TERAI", "TERAS", "TERCE", "TEREK", "TERES", "TERFE", + "TERFS", "TERGA", "TERMS", "TERNE", "TERNS", "TERRY", "TERTS", "TESLA", "TESTA", "TESTE", "TESTS", "TETES", + "TETHS", "TETRA", "TETRI", "TEUCH", "TEUGH", "TEWED", "TEWEL", "TEWIT", "TEXAS", "TEXES", "TEXTS", "THACK", + "THAGI", "THAIM", "THALE", "THALI", "THANA", "THANE", "THANG", "THANS", "THANX", "THARM", "THARS", "THAWS", + "THAWY", "THEBE", "THECA", "THEED", "THEEK", "THEES", "THEGN", "THEIC", "THEIN", "THELF", "THEMA", "THENS", + "THEOW", "THERM", "THESP", "THETE", "THEWS", "THEWY", "THIGS", "THILK", "THILL", "THINE", "THINS", "THIOL", + "THIRL", "THOFT", "THOLE", "THOLI", "THORO", "THORP", "THOUS", "THOWL", "THRAE", "THRAW", "THRID", "THRIP", + "THROE", "THUDS", "THUGS", "THUJA", "THUNK", "THURL", "THUYA", "THYMI", "THYMY", "TIANS", "TIARS", "TICAL", + "TICCA", "TICED", "TICES", "TICHY", "TICKS", "TICKY", "TIDDY", "TIDED", "TIDES", "TIERS", "TIFFS", "TIFOS", + "TIFTS", "TIGES", "TIGON", "TIKAS", "TIKES", "TIKIS", "TIKKA", "TILAK", "TILED", "TILER", "TILES", "TILLS", + "TILLY", "TILTH", "TILTS", "TIMBO", "TIMED", "TIMES", "TIMON", "TIMPS", "TINAS", "TINCT", "TINDS", "TINEA", + "TINED", "TINES", "TINGE", "TINGS", "TINKS", "TINNY", "TINTS", "TINTY", "TIPIS", "TIPPY", "TIRED", "TIRES", + "TIRLS", "TIROS", "TIRRS", "TITCH", "TITER", "TITIS", "TITRE", "TITTY", "TITUP", "TIYIN", "TIYNS", "TIZES", + "TIZZY", "TOADS", "TOADY", "TOAZE", "TOCKS", "TOCKY", "TOCOS", "TODDE", "TOEAS", "TOFFS", "TOFFY", "TOFTS", + "TOFUS", "TOGAE", "TOGAS", "TOGED", "TOGES", "TOGUE", "TOHOS", "TOILE", "TOILS", "TOING", "TOISE", "TOITS", + "TOKAY", "TOKED", "TOKER", "TOKES", "TOKOS", "TOLAN", "TOLAR", "TOLAS", "TOLED", "TOLES", "TOLLS", "TOLLY", + "TOLTS", "TOLUS", "TOLYL", "TOMAN", "TOMBS", "TOMES", "TOMIA", "TOMMY", "TOMOS", "TONDI", "TONDO", "TONED", + "TONER", "TONES", "TONEY", "TONGS", "TONKA", "TONKS", "TONNE", "TONUS", "TOOLS", "TOOMS", "TOONS", "TOOTS", + "TOPED", "TOPEE", "TOPEK", "TOPER", "TOPES", "TOPHE", "TOPHI", "TOPHS", "TOPIS", "TOPOI", "TOPOS", "TOPPY", + "TOQUE", "TORAH", "TORAN", "TORAS", "TORCS", "TORES", "TORIC", "TORII", "TOROS", "TOROT", "TORRS", "TORSE", + "TORSI", "TORSK", "TORTA", "TORTE", "TORTS", "TOSAS", "TOSED", "TOSES", "TOSHY", "TOSSY", "TOTED", "TOTER", + "TOTES", "TOTTY", "TOUKS", "TOUNS", "TOURS", "TOUSE", "TOUSY", "TOUTS", "TOUZE", "TOUZY", "TOWED", "TOWIE", + "TOWNS", "TOWNY", "TOWSE", "TOWSY", "TOWTS", "TOWZE", "TOWZY", "TOYED", "TOYER", "TOYON", "TOYOS", "TOZED", + "TOZES", "TOZIE", "TRABS", "TRADS", "TRAGI", "TRAIK", "TRAMS", "TRANK", "TRANQ", "TRANS", "TRANT", "TRAPE", + "TRAPS", "TRAPT", "TRASS", "TRATS", "TRATT", "TRAVE", "TRAYF", "TRAYS", "TRECK", "TREED", "TREEN", "TREES", + "TREFA", "TREIF", "TREKS", "TREMA", "TREMS", "TRESS", "TREST", "TRETS", "TREWS", "TREYF", "TREYS", "TRIAC", + "TRIDE", "TRIER", "TRIES", "TRIFF", "TRIGO", "TRIGS", "TRIKE", "TRILD", "TRILL", "TRIMS", "TRINE", "TRINS", + "TRIOL", "TRIOR", "TRIOS", "TRIPS", "TRIPY", "TRIST", "TROAD", "TROAK", "TROAT", "TROCK", "TRODE", "TRODS", + "TROGS", "TROIS", "TROKE", "TROMP", "TRONA", "TRONC", "TRONE", "TRONK", "TRONS", "TROOZ", "TROTH", "TROTS", + "TROWS", "TROYS", "TRUED", "TRUES", "TRUGO", "TRUGS", "TRULL", "TRYER", "TRYKE", "TRYMA", "TRYPS", "TSADE", + "TSADI", "TSARS", "TSKED", "TSUBA", "TSUBO", "TUANS", "TUART", "TUATH", "TUBAE", "TUBAR", "TUBAS", "TUBBY", + "TUBED", "TUBES", "TUCKS", "TUFAS", "TUFFE", "TUFFS", "TUFTS", "TUFTY", "TUGRA", "TUILE", "TUINA", "TUISM", + "TUKTU", "TULES", "TULPA", "TULSI", "TUMID", "TUMMY", "TUMPS", "TUMPY", "TUNAS", "TUNDS", "TUNED", "TUNER", + "TUNES", "TUNGS", "TUNNY", "TUPEK", "TUPIK", "TUPLE", "TUQUE", "TURDS", "TURFS", "TURFY", "TURKS", "TURME", + "TURMS", "TURNS", "TURNT", "TURPS", "TURRS", "TUSHY", "TUSKS", "TUSKY", "TUTEE", "TUTTI", "TUTTY", "TUTUS", + "TUXES", "TUYER", "TWAES", "TWAIN", "TWALS", "TWANK", "TWATS", "TWAYS", "TWEEL", "TWEEN", "TWEEP", "TWEER", + "TWERK", "TWERP", "TWIER", "TWIGS", "TWILL", "TWILT", "TWINK", "TWINS", "TWINY", "TWIRE", "TWIRP", "TWITE", + "TWITS", "TWOER", "TWYER", "TYEES", "TYERS", "TYIYN", "TYKES", "TYLER", "TYMPS", "TYNDE", "TYNED", "TYNES", + "TYPAL", "TYPED", "TYPES", "TYPEY", "TYPIC", "TYPOS", "TYPPS", "TYPTO", "TYRAN", "TYRED", "TYRES", "TYROS", + "TYTHE", "TZARS", "UDALS", "UDONS", "UGALI", "UGGED", "UHLAN", "UHURU", "UKASE", "ULAMA", "ULANS", "ULEMA", + "ULMIN", "ULNAD", "ULNAE", "ULNAR", "ULNAS", "ULPAN", "ULVAS", "ULYIE", "ULZIE", "UMAMI", "UMBEL", "UMBER", + "UMBLE", "UMBOS", "UMBRE", "UMIAC", "UMIAK", "UMIAQ", "UMMAH", "UMMAS", "UMMED", "UMPED", "UMPHS", "UMPIE", + "UMPTY", "UMRAH", "UMRAS", "UNAIS", "UNAPT", "UNARM", "UNARY", "UNAUS", "UNBAG", "UNBAN", "UNBAR", "UNBED", + "UNBID", "UNBOX", "UNCAP", "UNCES", "UNCIA", "UNCOS", "UNCOY", "UNCUS", "UNDAM", "UNDEE", "UNDOS", "UNDUG", + "UNETH", "UNFIX", "UNGAG", "UNGET", "UNGOD", "UNGOT", "UNGUM", "UNHAT", "UNHIP", "UNICA", "UNITS", "UNJAM", + "UNKED", "UNKET", "UNKID", "UNLAW", "UNLAY", "UNLED", "UNLET", "UNLID", "UNMAN", "UNMEW", "UNMIX", "UNPAY", + "UNPEG", "UNPEN", "UNPIN", "UNRED", "UNRID", "UNRIG", "UNRIP", "UNSAW", "UNSAY", "UNSEE", "UNSEW", "UNSEX", + "UNSOD", "UNTAX", "UNTIN", "UNWET", "UNWIT", "UNWON", "UPBOW", "UPBYE", "UPDOS", "UPDRY", "UPEND", "UPJET", + "UPLAY", "UPLED", "UPLIT", "UPPED", "UPRAN", "UPRUN", "UPSEE", "UPSEY", "UPTAK", "UPTER", "UPTIE", "URAEI", + "URALI", "URAOS", "URARE", "URARI", "URASE", "URATE", "URBEX", "URBIA", "URDEE", "UREAL", "UREAS", "UREDO", + "UREIC", "URENA", "URENT", "URGED", "URGER", "URGES", "URIAL", "URITE", "URMAN", "URNAL", "URNED", "URPED", + "URSAE", "URSID", "URSON", "URUBU", "URVAS", "USERS", "USNEA", "USQUE", "USURE", "USURY", "UTERI", "UVEAL", + "UVEAS", "UVULA", "VACUA", "VADED", "VADES", "VAGAL", "VAGUS", "VAILS", "VAIRE", "VAIRS", "VAIRY", "VAKAS", + "VAKIL", "VALES", "VALIS", "VALSE", "VAMPS", "VAMPY", "VANDA", "VANED", "VANES", "VANGS", "VANTS", "VAPED", + "VAPER", "VAPES", "VARAN", "VARAS", "VARDY", "VAREC", "VARES", "VARIA", "VARIX", "VARNA", "VARUS", "VARVE", + "VASAL", "VASES", "VASTS", "VASTY", "VATIC", "VATUS", "VAUCH", "VAUTE", "VAUTS", "VAWTE", "VAXES", "VEALE", + "VEALS", "VEALY", "VEENA", "VEEPS", "VEERS", "VEERY", "VEGAS", "VEGES", "VEGIE", "VEGOS", "VEHME", "VEILS", + "VEILY", "VEINS", "VEINY", "VELAR", "VELDS", "VELDT", "VELES", "VELLS", "VELUM", "VENAE", "VENAL", "VENDS", + "VENDU", "VENEY", "VENGE", "VENIN", "VENTS", "VENUS", "VERBS", "VERRA", "VERRY", "VERST", "VERTS", "VERTU", + "VESPA", "VESTA", "VESTS", "VETCH", "VEXED", "VEXER", "VEXES", "VEXIL", "VEZIR", "VIALS", "VIAND", "VIBES", + "VIBEX", "VIBEY", "VICED", "VICES", "VICHY", "VIERS", "VIEWS", "VIEWY", "VIFDA", "VIFFS", "VIGAS", "VIGIA", + "VILDE", "VILER", "VILLI", "VILLS", "VIMEN", "VINAL", "VINAS", "VINCA", "VINED", "VINER", "VINES", "VINEW", + "VINIC", "VINOS", "VINTS", "VIOLD", "VIOLS", "VIRED", "VIREO", "VIRES", "VIRGA", "VIRGE", "VIRID", "VIRLS", + "VIRTU", "VISAS", "VISED", "VISES", "VISIE", "VISNE", "VISON", "VISTO", "VITAE", "VITAS", "VITEX", "VITRO", + "VITTA", "VIVAS", "VIVAT", "VIVDA", "VIVER", "VIVES", "VIZIR", "VIZOR", "VLEIS", "VLIES", "VLOGS", "VOARS", + "VOCAB", "VOCES", "VODDY", "VODOU", "VODUN", "VOEMA", "VOGIE", "VOIDS", "VOILE", "VOIPS", "VOLAE", "VOLAR", + "VOLED", "VOLES", "VOLET", "VOLKS", "VOLTA", "VOLTE", "VOLTI", "VOLTS", "VOLVA", "VOLVE", "VOMER", "VOTED", + "VOTES", "VOUGE", "VOULU", "VOWED", "VOWER", "VOXEL", "VOZHD", "VRAIC", "VRILS", "VROOM", "VROUS", "VROUW", + "VROWS", "VUGGS", "VUGGY", "VUGHS", "VUGHY", "VULGO", "VULNS", "VULVA", "VUTTY", "WAACS", "WACKE", "WACKO", + "WACKS", "WADDS", "WADDY", "WADED", "WADER", "WADES", "WADGE", "WADIS", "WADTS", "WAFFS", "WAFTS", "WAGED", + "WAGES", "WAGGA", "WAGYU", "WAHOO", "WAIDE", "WAIFS", "WAIFT", "WAILS", "WAINS", "WAIRS", "WAITE", "WAITS", + "WAKAS", "WAKED", "WAKEN", "WAKER", "WAKES", "WAKFS", "WALDO", "WALDS", "WALED", "WALER", "WALES", "WALIE", + "WALIS", "WALKS", "WALLA", "WALLS", "WALLY", "WALTY", "WAMED", "WAMES", "WAMUS", "WANDS", "WANED", "WANES", + "WANEY", "WANGS", "WANKS", "WANKY", "WANLE", "WANLY", "WANNA", "WANTS", "WANTY", "WANZE", "WAQFS", "WARBS", + "WARBY", "WARDS", "WARED", "WARES", "WAREZ", "WARKS", "WARMS", "WARNS", "WARPS", "WARRE", "WARST", "WARTS", + "WASES", "WASHY", "WASMS", "WASPS", "WASPY", "WASTS", "WATAP", "WATTS", "WAUFF", "WAUGH", "WAUKS", "WAULK", + "WAULS", "WAURS", "WAVED", "WAVES", "WAVEY", "WAWAS", "WAWES", "WAWLS", "WAXED", "WAXER", "WAXES", "WAYED", + "WAZIR", "WAZOO", "WEALD", "WEALS", "WEAMB", "WEANS", "WEARS", "WEBBY", "WEBER", "WECHT", "WEDEL", "WEDGY", + "WEEDS", "WEEKE", "WEEKS", "WEELS", "WEEMS", "WEENS", "WEENY", "WEEPS", "WEEPY", "WEEST", "WEETE", "WEETS", + "WEFTE", "WEFTS", "WEIDS", "WEILS", "WEIRS", "WEISE", "WEIZE", "WEKAS", "WELDS", "WELKE", "WELKS", "WELKT", + "WELLS", "WELLY", "WELTS", "WEMBS", "WENCH", "WENDS", "WENGE", "WENNY", "WENTS", "WEROS", "WERSH", "WESTS", + "WETAS", "WETLY", "WEXED", "WEXES", "WHAMO", "WHAMS", "WHANG", "WHAPS", "WHARE", "WHATA", "WHATS", "WHAUP", + "WHAUR", "WHEAL", "WHEAR", "WHEEN", "WHEEP", "WHEFT", "WHELK", "WHELM", "WHENS", "WHETS", "WHEWS", "WHEYS", + "WHIDS", "WHIFT", "WHIGS", "WHILK", "WHIMS", "WHINS", "WHIOS", "WHIPS", "WHIPT", "WHIRR", "WHIRS", "WHISH", + "WHISS", "WHIST", "WHITS", "WHITY", "WHIZZ", "WHOMP", "WHOOF", "WHOOT", "WHOPS", "WHORE", "WHORL", "WHORT", + "WHOSO", "WHOWS", "WHUMP", "WHUPS", "WHYDA", "WICCA", "WICKS", "WICKY", "WIDDY", "WIDES", "WIELS", "WIFED", + "WIFES", "WIFEY", "WIFIE", "WIFTY", "WIGAN", "WIGGA", "WIGGY", "WIKIS", "WILCO", "WILDS", "WILED", "WILES", + "WILGA", "WILIS", "WILJA", "WILLS", "WILTS", "WIMPS", "WINDS", "WINED", "WINES", "WINEY", "WINGE", "WINGS", + "WINGY", "WINKS", "WINNA", "WINNS", "WINOS", "WINZE", "WIPED", "WIPER", "WIPES", "WIRED", "WIRER", "WIRES", + "WIRRA", "WISED", "WISES", "WISHA", "WISHT", "WISPS", "WISTS", "WITAN", "WITED", "WITES", "WITHE", "WITHS", + "WITHY", "WIVED", "WIVER", "WIVES", "WIZEN", "WIZES", "WOADS", "WOALD", "WOCKS", "WODGE", "WOFUL", "WOJUS", + "WOKER", "WOKKA", "WOLDS", "WOLFS", "WOLLY", "WOLVE", "WOMBS", "WOMBY", "WOMYN", "WONGA", "WONGI", "WONKS", + "WONKY", "WONTS", "WOODS", "WOOED", "WOOFS", "WOOFY", "WOOLD", "WOOLS", "WOONS", "WOOPS", "WOOPY", "WOOSE", + "WOOSH", "WOOTZ", "WORDS", "WORKS", "WORMS", "WORMY", "WORTS", "WOWED", "WOWEE", "WOXEN", "WRANG", "WRAPS", + "WRAPT", "WRAST", "WRATE", "WRAWL", "WRENS", "WRICK", "WRIED", "WRIER", "WRIES", "WRITS", "WROKE", "WROOT", + "WROTH", "WRYER", "WUDDY", "WUDUS", "WULLS", "WURST", "WUSES", "WUSHU", "WUSSY", "WUXIA", "WYLED", "WYLES", + "WYNDS", "WYNNS", "WYTED", "WYTES", "XEBEC", "XENIA", "XENIC", "XENON", "XERIC", "XEROX", "XERUS", "XOANA", + "XRAYS", "XYLAN", "XYLEM", "XYLIC", "XYLOL", "XYLYL", "XYSTI", "XYSTS", "YAARS", "YABAS", "YABBA", "YABBY", + "YACCA", "YACKA", "YACKS", "YAFFS", "YAGER", "YAGES", "YAGIS", "YAHOO", "YAIRD", "YAKKA", "YAKOW", "YALES", + "YAMEN", "YAMPY", "YAMUN", "YANGS", "YANKS", "YAPOK", "YAPON", "YAPPS", "YAPPY", "YARAK", "YARCO", "YARDS", + "YARER", "YARFA", "YARKS", "YARNS", "YARRS", "YARTA", "YARTO", "YATES", "YAUDS", "YAULD", "YAUPS", "YAWED", + "YAWEY", "YAWLS", "YAWNS", "YAWNY", "YAWPS", "YBORE", "YCLAD", "YCLED", "YCOND", "YDRAD", "YDRED", "YEADS", + "YEAHS", "YEALM", "YEANS", "YEARD", "YEARS", "YECCH", "YECHS", "YECHY", "YEDES", "YEEDS", "YEESH", "YEGGS", + "YELKS", "YELLS", "YELMS", "YELPS", "YELTS", "YENTA", "YENTE", "YERBA", "YERDS", "YERKS", "YESES", "YESKS", + "YESTS", "YESTY", "YETIS", "YETTS", "YEUKS", "YEUKY", "YEVEN", "YEVES", "YEWEN", "YEXED", "YEXES", "YFERE", + "YIKED", "YIKES", "YILLS", "YINCE", "YIPES", "YIPPY", "YIRDS", "YIRKS", "YIRRS", "YIRTH", "YITES", "YITIE", + "YLEMS", "YLIKE", "YLKES", "YMOLT", "YMPES", "YOBBO", "YOBBY", "YOCKS", "YODEL", "YODHS", "YODLE", "YOGAS", + "YOGEE", "YOGHS", "YOGIC", "YOGIN", "YOGIS", "YOICK", "YOJAN", "YOKED", "YOKEL", "YOKER", "YOKES", "YOKUL", + "YOLKS", "YOLKY", "YOMIM", "YOMPS", "YONIC", "YONIS", "YONKS", "YOOFS", "YOOPS", "YORES", "YORKS", "YORPS", + "YOUKS", "YOURN", "YOURS", "YOURT", "YOUSE", "YOWED", "YOWES", "YOWIE", "YOWLS", "YOWZA", "YRAPT", "YRENT", + "YRIVD", "YRNEH", "YSAME", "YTOST", "YUANS", "YUCAS", "YUCCA", "YUCCH", "YUCKO", "YUCKS", "YUCKY", "YUFTS", + "YUGAS", "YUKED", "YUKES", "YUKKY", "YUKOS", "YULAN", "YULES", "YUMMO", "YUMMY", "YUMPS", "YUPON", "YUPPY", + "YURTA", "YURTS", "YUZUS", "ZABRA", "ZACKS", "ZAIDA", "ZAIDY", "ZAIRE", "ZAKAT", "ZAMAN", "ZAMBO", "ZAMIA", + "ZANJA", "ZANTE", "ZANZA", "ZANZE", "ZAPPY", "ZARFS", "ZARIS", "ZATIS", "ZAXES", "ZAYIN", "ZAZEN", "ZEALS", + "ZEBEC", "ZEBUB", "ZEBUS", "ZEDAS", "ZEINS", "ZENDO", "ZERDA", "ZERKS", "ZEROS", "ZESTS", "ZETAS", "ZEXES", + "ZEZES", "ZHOMO", "ZIBET", "ZIFFS", "ZIGAN", "ZILAS", "ZILCH", "ZILLA", "ZILLS", "ZIMBI", "ZIMBS", "ZINCO", + "ZINCS", "ZINCY", "ZINEB", "ZINES", "ZINGS", "ZINGY", "ZINKE", "ZINKY", "ZIPPO", "ZIPPY", "ZIRAM", "ZITIS", + "ZIZEL", "ZIZIT", "ZLOTE", "ZLOTY", "ZOAEA", "ZOBOS", "ZOBUS", "ZOCCO", "ZOEAE", "ZOEAL", "ZOEAS", "ZOISM", + "ZOIST", "ZOMBI", "ZONAE", "ZONDA", "ZONED", "ZONER", "ZONES", "ZONKS", "ZOOEA", "ZOOEY", "ZOOID", "ZOOKS", + "ZOOMS", "ZOONS", "ZOOTY", "ZOPPA", "ZOPPO", "ZORIL", "ZORIS", "ZORRO", "ZOUKS", "ZOWEE", "ZOWIE", "ZULUS", + "ZUPAN", "ZUPAS", "ZUPPA", "ZURFS", "ZUZIM", "ZYGAL", "ZYGON", "ZYMES", "ZYMIC", ] alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] -def most_used_letters(words=legal_list): +def most_used_letters(words=valid_list): ''' Outputs how many times each letter is used in the words array. ''' - dicto = {} + use_each_letter = {} for i in alphabet: count = 0 for word in words: @@ -955,31 +1103,32 @@ def most_used_letters(words=legal_list): if i.upper() == letter.upper(): count+=1 break - dicto[i] = count - dicto = dict(sorted(dicto.items(), key=lambda item: item[1], reverse=True)) - print("Letter | Usage") - print("--------------") - for k in dicto: - print(f"{k.upper()} | {dicto[k]}") + use_each_letter[i] = count + use_each_letter = dict(sorted(use_each_letter.items(), key=lambda item: item[1], reverse=True)) + print("Letter | Usage | Percent") + print("----------------------------") + for k in use_each_letter: + print(f"{k.upper()} | {use_each_letter[k]:5} | {round((100 * use_each_letter[k]) / len(words)):2}%") + return use_each_letter -def list_of_valid_words(letters, words=legal_list): +def list_of_valid_words(letters, words=valid_list): ''' Outputs the array of valid words that can be made with the combination of letters. ''' letters = sorted(letters) for i, letter in enumerate(letters): # Force all letters to be capitalized letters[i] = letter.upper() - legal_words = [] + valid_words = [] for word in words: valid_word = True for letter in word: if letter.upper() not in letters: valid_word = False break - if valid_word and word not in legal_words: - legal_words.append(word) - return legal_words + if valid_word and word not in valid_words: + valid_words.append(word) + return valid_words def rearrange_words_by_uniqueness(words): @@ -997,52 +1146,56 @@ def capitalize_all_and_remove_duplicates(arr): return arr +def clean_chars(string): + # D looks bad on the watch when d does not. + string = string.replace('D', 'd') + return string + + def print_valid_words(letters=alphabet): ''' Prints the array of valid words that the wordle_face.c can use ''' items_per_row = 9 - legal_words = list_of_valid_words(letters, legal_list) - legal_words = capitalize_all_and_remove_duplicates(legal_words) - random.shuffle(legal_words) + valid_words = list_of_valid_words(letters, valid_list) + valid_words = capitalize_all_and_remove_duplicates(valid_words) + random.shuffle(valid_words) # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter - legal_words, num_uniq = rearrange_words_by_uniqueness(legal_words) + valid_words, num_uniq = rearrange_words_by_uniqueness(valid_words) print("static const char _valid_letters[] = {", end='') + letters = sorted(letters) for letter in letters[:-1]: - print(f"'{letter}', ", end='') + print(f"'{clean_chars(letter)}', ", end='') print(f"'{letters[-1]}'" + "};") print("") - print("// From: https://gist.github.com/shmookey/b28e342e1b1756c4700f42f17102c2ff") - print(f"// Number of words found: {len(legal_words)}") + print(f"// From: {source_link}") + print(f"// Number of words found: {len(valid_words)}") i = 0 - print("static const char _legal_words[][WORDLE_LENGTH + 1] = {") - while i < len(legal_words): + print("static const char _valid_words[][WORDLE_LENGTH + 1] = {") + while i < len(valid_words): print(" ", end='') - for _ in range(min(items_per_row, len(legal_words)-i)): - print(f'"{legal_words[i]}", ', end='') + for _ in range(min(items_per_row, len(valid_words)-i)): + print(f'"{clean_chars(valid_words[i])}", ', end='') i+=1 print('') - print("};\n") - - expanded_words = list_of_valid_words(letters, expanded_list) - expanded_words = [word for word in expanded_words if word not in legal_list] - expanded_words = capitalize_all_and_remove_duplicates(expanded_words) - - print("// These are words that'll never be used, but still need to be in the dictionary for guesses.") - print("// Top 100K most common words from Wiktionary https://gist.github.com/h3xx/1976236") - print(f"// Number of words found: {len(expanded_words)}") + possible_words = list_of_valid_words(letters, possible_list) + possible_words = [word for word in possible_words if word not in valid_list] + possible_words = capitalize_all_and_remove_duplicates(possible_words) + print("};") + print("\n// These are words that'll never be used, but still need to be in the dictionary for guesses.") + print("static const char _possible_words[][WORDLE_LENGTH + 1] = {") i = 0 - print("static const char _expanded_words[][WORDLE_LENGTH + 1] = {") - while i < len(expanded_words): + while i < len(possible_words): print(" ", end='') - for j in range(min(items_per_row, len(expanded_words)-i)): - print(f'"{expanded_words[i]}", ', end='') + for j in range(min(items_per_row, len(possible_words)-i)): + print(f'"{clean_chars(possible_words[i])}", ', end='') i+=1 print('') print("};") + print('') - print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _legal_words array begins with this many words where each letter is different.") + print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _valid_words array begins with this many words where each letter is different.") def get_sec_val_and_units(seconds): @@ -1059,28 +1212,33 @@ def get_sec_val_and_units(seconds): return f"{secs} sec" -def txt_of_all_letter_combos(num_letters_in_set): +def txt_of_all_letter_combos(num_letters_in_set, words=valid_list, min_letter_occ_percent_to_consider=10, txt_out=True): ''' Creates a txt file that shows every combination of letters and how many words their combo can make. num_letters_in_set - How many letters should be in each combination - ''' + ''' num_status_prints = 100 dict_combos_counts = {} print_iter = 0 prev = time.time() start = prev - letters_to_ignore = ['D','T'] # Don't diplay well on the watch - legal_letters = [item for item in alphabet if item not in letters_to_ignore] - print(f"Finding all {num_letters_in_set} letter combinations with the following letters: {legal_letters}") - all_combos = list(itertools.combinations(legal_letters, num_letters_in_set)) + letters_to_ignore = ['D','M','T','Y'] # Don't diplay well on the watch + letter_usage = most_used_letters(words=words) + for letter in letter_usage: + if (100 * letter_usage[letter])/len(words) < min_letter_occ_percent_to_consider: + letters_to_ignore.append(letter) + valid_letters = [item for item in alphabet if item not in letters_to_ignore] + num_letters_in_set = min(num_letters_in_set, len(valid_letters)) + print(f"Finding all {num_letters_in_set} letter combinations with the following letters: {valid_letters}") + all_combos = list(itertools.combinations(valid_letters, num_letters_in_set)) len_all_combos = len(all_combos) to_print = max(1, int(len_all_combos/ num_status_prints)) print(f"Amount of Combos: {len_all_combos}") estimated_prints = round(len_all_combos / to_print) for i, letters in enumerate(all_combos): letters = sorted(letters) - dict_combos_counts[repr(letters)] = len(list_of_valid_words(letters)) + dict_combos_counts[repr(letters)] = len(list_of_valid_words(letters, words=words)) print_iter+=1 if print_iter >= to_print: curr = time.time() @@ -1100,15 +1258,53 @@ def txt_of_all_letter_combos(num_letters_in_set): dict_combos_counts = dict(sorted(dict_combos_counts.items(), key=lambda item: item[1], reverse=True)) most_common_key = next(iter(dict_combos_counts)) - print(f"The Most Common Combo is: {most_common_key}") - print_valid_words(ast.literal_eval(most_common_key)) + print(f"The Most Common Combo is: {most_common_key} with {dict_combos_counts[most_common_key]} words.") + # print_valid_words(ast.literal_eval(most_common_key)) # Uncomment to display the text it creates - with open('output.txt', 'w') as file: - for key, value in dict_combos_counts.items(): - file.write(f'{key}: {value}\n') + if txt_out: + with open('output.txt', 'w') as file: + for key, value in dict_combos_counts.items(): + file.write(f'{key}: {value}\n') + return dict_combos_counts + + +def txt_of_all_letter_combos_differing_sizes(min=9, max=15, num_combos_print=20, words=valid_list): + max_each_count = {} + with open('output.txt', 'w') as file: + for x in range(max, min-1, -1): + dict_combos_counts = txt_of_all_letter_combos(x, words=words, min_letter_occ_percent_to_consider=10, txt_out=False) + file.write(f'{x} Letter Options\n') + for i, key in enumerate(dict_combos_counts): + file.write(f'{key}: {dict_combos_counts[key]}\n') + if i == 0: + max_each_count[x] = dict_combos_counts[key] + if i >= num_combos_print-1: + break + file.write('\n') + print("num_letters, count of words") + for key, item in max_each_count.items(): + print(f"{key}, {item}") + + +def location_of_letters(letters=alphabet, list=valid_list): + print(" 1 2 3 4 5 ") + print("-----------------------------------------") + letters = sorted(letters) + for letter in letters: + location = [0, 0, 0, 0, 0] + for word in list: + for i, char in enumerate(word): + if char.upper() == letter.upper(): + location[i]+=1 + location = [f"{round((100 * x) / sum(location)):2}%" for x in location] + print(f"{letter} : {location}") if __name__ == "__main__": + my_letters = ['A', 'C', 'E', 'G', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S'] + #print(f"{len(list_of_valid_words(my_letters, valid_list))} Words can be made with {my_letters}") #most_used_letters() - print_valid_words(['A', 'C', 'E', 'I', 'L', 'N', 'O', 'P', 'R', 'S']) - #txt_of_all_letter_combos(10) \ No newline at end of file + #location_of_letters(my_letters) + print_valid_words(my_letters) + #txt_of_all_letter_combos_differing_sizes(max = 16, min=10) + #txt_of_all_letter_combos(14) \ No newline at end of file From 099f78443e2397db4b5ec39d7dbd27c910eee2aa Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 23 Aug 2024 16:48:09 -0400 Subject: [PATCH 125/220] Added ability to use T in Wordle --- .../watch_faces/complication/wordle_face.c | 462 ++++++++++-------- utils/wordle_face/wordle_list.py | 4 +- 2 files changed, 262 insertions(+), 204 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f27ae4a..260860b 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -30,216 +30,274 @@ #endif -static const char _valid_letters[] = {'A', 'C', 'E', 'G', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S'}; +static const char _valid_letters[] = {'A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'}; // From: https://matthewminer.name/projects/calculators/wordle-words-left/ -// Number of words found: 298 +// Number of words found: 432 static const char _valid_words[][WORDLE_LENGTH + 1] = { - "PLACE", "SHONE", "POSER", "CHAIN", "CAPER", "POLAR", "LEARN", "SHORN", "PORCH", - "GRAPE", "GNASH", "CHAIR", "SCORE", "CIGAR", "GRASP", "SINCE", "SPIRE", "NEIGH", - "SHORE", "CHASE", "RAISE", "CAIRN", "PLIER", "LOSER", "GRACE", "LEASH", "PENAL", - "SLING", "RISEN", "LOGIC", "PRICE", "POISE", "SCALE", "SINGE", "SNARL", "LINER", - "ANGEL", "SNAIL", "PALER", "SCION", "ALONE", "AGILE", "APRON", "PERIL", "GRIPE", - "SPICE", "LOGIN", "REGAL", "CAROL", "SLICE", "CRONE", "LEACH", "COPSE", "SHEAR", - "ALIGN", "LARGE", "LAPSE", "AISLE", "NICER", "OCEAN", "OPERA", "ALIEN", "ACORN", - "ASHEN", "SHINE", "PANEL", "SPORE", "SCOPE", "SPACE", "PHASE", "AROSE", "CHOIR", - "SNIPE", "CHAOS", "RALPH", "EPOCH", "GRAIN", "SANER", "GROIN", "SLANG", "SLAIN", - "CRASH", "CLASP", "SHIRE", "SCONE", "ALONG", "APING", "NICHE", "CHEAP", "CHIRP", - "LAGER", "CHORE", "SNORE", "SHAPE", "RESIN", "PERCH", "PECAN", "GLARE", "GROAN", - "RHINO", "RENAL", "SALON", "GRAIL", "SEPIA", "LANCE", "PRONG", "RECAP", "CLONE", - "CLASH", "HORSE", "SOLAR", "HERON", "PEACH", "ARSON", "HINGE", "CLEAN", "CLING", - "PHONE", "CRANE", "CLANG", "SCORN", "SPEAR", "PLAIN", "PROSE", "SPOIL", "GONER", - "SHOAL", "REIGN", "CLEAR", "ANGER", "CHINA", "GRAPH", "PEARL", "CARGO", "CHOSE", - "SCALP", "CANOE", "RINSE", "RANGE", "LINGO", "RANCH", "PLANE", "SPINE", "REACH", - "CRISP", "PARSE", "RIPEN", "SNARE", "CLOSE", "SHARE", "CORAL", "NOISE", "SHARP", - "SPARE", "SONIC", "SCRAP", "SPIEL", "RELIC", "OPINE", "SCARE", "SPRIG", "SHALE", - "PANIC", "SONAR", "GROPE", "SLOPE", "ANGLE", "ORGAN", "PIANO", "PINCH", "GLEAN", - "PRONE", "ARISE", "ROACH", "SIREN", "CLASS", "POSSE", "INANE", "HENCE", "SNEER", - "PAGAN", "PREEN", "ROGER", "SPELL", "SHEEP", "SENSE", "INNER", "ALPHA", "SHEEN", - "SCREE", "CIRCA", "PRIOR", "RARER", "PEACE", "GENRE", "HELLO", "CACAO", "GORGE", - "GLOSS", "CRIER", "CROSS", "CREPE", "COLON", "CHILL", "ONION", "LINEN", "PIPER", - "SLOOP", "LEGAL", "SNOOP", "PAPER", "ALGAE", "LAPEL", "CHEER", "HIPPO", "PIECE", - "LILAC", "HONOR", "PAPAL", "ARENA", "APNEA", "RIPER", "SCENE", "SHALL", "NASAL", - "SPREE", "RIGOR", "EAGER", "LIEGE", "LEPER", "LEASE", "CORER", "SPOON", "GROSS", - "COACH", "CEASE", "GENIE", "HARSH", "PENCE", "CHILI", "SHELL", "CREEP", "RISER", - "ERASE", "CINCH", "SIEGE", "GOING", "SCOOP", "SPILL", "NOOSE", "EAGLE", "AGING", - "NIECE", "SPOOL", "APPLE", "SALSA", "LEECH", "GREEN", "IONIC", "LASSO", "CONCH", - "PENNE", "SLASH", "CANAL", "CRASS", "REPEL", "COCOA", "CRESS", "AGAPE", "EASEL", - "CELLO", "CONIC", "IGLOO", "RACER", "GOOSE", "ICING", "POOCH", "ILIAC", "GRASS", - "SHEER", "CANON", "ELOPE", "LOCAL", "EERIE", "COLOR", "AGREE", "PRESS", "GEESE", - "SLOSH", "SLEEP", "GRILL", "AGAIN", "GLASS", "PARER", "CHESS", "CACHE", "ERROR", - "LOOSE", + "STALE", "TRACE", "CLOSE", "ARISE", "SNIPE", "SHIRE", "LEASH", "SAINT", "CLEAN", + "RELIC", "CHORE", "CRONE", "REACH", "CHAOS", "TAPIR", "CAIRN", "TENOR", "STARE", + "HEART", "SCOPE", "SNARL", "SLEPT", "SINCE", "EPOCH", "SPACE", "SHARE", "SPOIL", + "LITER", "LEAPT", "LANCE", "RANCH", "HORSE", "LEACH", "LATER", "STEAL", "CHEAP", + "SHORT", "ETHIC", "CHANT", "ACTOR", "SPARE", "SEPIA", "ONSET", "SPLAT", "LEANT", + "REACT", "OCTAL", "SPORE", "IRATE", "CORAL", "NICER", "SPILT", "SCENT", "PANIC", + "SHIRT", "PECAN", "SLAIN", "SPLIT", "ROACH", "ASCOT", "PHONE", "LITHE", "STOIC", + "STRIP", "RENAL", "POISE", "ENACT", "CHEAT", "PITCH", "NOISE", "INLET", "PEARL", + "POLAR", "PEACH", "STOLE", "CASTE", "CREST", "SCARE", "ETHOS", "THEIR", "STONE", + "SLATE", "LATCH", "HASTE", "SNARE", "SPINE", "SLANT", "SPEAR", "SCALE", "CAPER", + "RETCH", "PESTO", "CHIRP", "SPORT", "OPTIC", "SNAIL", "PRICE", "PLANE", "TORCH", + "PASTE", "RECAP", "SOLAR", "CRASH", "LINER", "OPINE", "ASHEN", "PALER", "ECLAT", + "SPELT", "TRIAL", "PERIL", "SLICE", "SCANT", "RAISE", "POSIT", "ATONE", "SPIRE", + "COAST", "INEPT", "SHOAL", "CLASH", "THORN", "PHASE", "SCORE", "TRICE", "PERCH", + "PORCH", "SHEAR", "CHOIR", "RHINO", "PLANT", "SHONE", "SANER", "LEARN", "ALTER", + "CHAIN", "PANEL", "PLIER", "STEIN", "COPSE", "SONIC", "ALIEN", "CHOSE", "ACORN", + "ANTIC", "CHEST", "OTHER", "CHINA", "TALON", "SCORN", "PLAIN", "PILOT", "RIPEN", + "PATCH", "SPICE", "CLONE", "SCION", "SCONE", "STRAP", "PARSE", "SHALE", "RISEN", + "CANOE", "INTER", "CRATE", "ISLET", "PRINT", "SHINE", "NORTH", "CLEAT", "PLAIT", + "SCRAP", "CLEAR", "SLOTH", "LAPSE", "CHAIR", "SNORT", "SHARP", "OPERA", "STAIN", + "TEACH", "TRAIL", "TRAIN", "LATHE", "PIANO", "PINCH", "PETAL", "STERN", "PRONE", + "PROSE", "PLEAT", "TROPE", "PLACE", "POSER", "INERT", "CHASE", "CAROL", "STAIR", + "SATIN", "SPITE", "LOATH", "ROAST", "ARSON", "SHAPE", "CLASP", "LOSER", "SALON", + "CATER", "SHALT", "INTRO", "ALERT", "PENAL", "SHORE", "RINSE", "CREPT", "APRON", + "SONAR", "AISLE", "AROSE", "HATER", "NICHE", "POINT", "EARTH", "PINTO", "THOSE", + "CLOTH", "NOTCH", "TOPIC", "RESIN", "SCALP", "HEIST", "HERON", "TRIPE", "TONAL", + "TAPER", "SHORN", "TONIC", "HOIST", "SNORE", "STORE", "SLOPE", "OCEAN", "CHART", + "PAINT", "SPENT", "CRANE", "CRISP", "TRASH", "PATIO", "PLATE", "HOTEL", "LEAST", + "ALONE", "RALPH", "SPIEL", "SIREN", "RATIO", "STOOP", "TROLL", "ATOLL", "SLASH", + "RETRO", "CREEP", "STILT", "SPREE", "TASTE", "CACHE", "CANON", "EATEN", "TEPEE", + "SHEET", "SNEER", "ERROR", "NATAL", "SLEEP", "STINT", "TROOP", "SHALL", "STALL", + "PIPER", "TOAST", "NASAL", "CORER", "THERE", "POOCH", "SCREE", "ELITE", "ALTAR", + "PENCE", "EATER", "ALPHA", "TENTH", "LINEN", "SHEER", "TAINT", "HEATH", "CRIER", + "TENSE", "CARAT", "CANAL", "APNEA", "THESE", "HATCH", "SHELL", "CIRCA", "APART", + "SPILL", "STEEL", "LOCAL", "STOOL", "SHEEN", "RESET", "STEEP", "ELATE", "PRESS", + "SLEET", "CROSS", "TOTAL", "TREAT", "ONION", "STATE", "CINCH", "ASSET", "THREE", + "TORSO", "SNOOP", "PENNE", "SPOON", "SHEEP", "PAPAL", "STILL", "CHILL", "THETA", + "LEECH", "INNER", "HONOR", "LOOSE", "CONIC", "SCENE", "COACH", "CONCH", "LATTE", + "ERASE", "ESTER", "PEACE", "PASTA", "INANE", "SPOOL", "TEASE", "HARSH", "PIECE", + "STEER", "SCOOP", "NINTH", "OTTER", "OCTET", "EERIE", "RISER", "LAPEL", "HIPPO", + "PREEN", "ETHER", "AORTA", "SENSE", "TRACT", "SHOOT", "SLOOP", "REPEL", "TITHE", + "IONIC", "CELLO", "CHESS", "SOOTH", "COCOA", "TITAN", "TOOTH", "TIARA", "CRESS", + "SLOSH", "RARER", "TERSE", "ERECT", "HELLO", "PARER", "RIPER", "NOOSE", "CREPE", + "CACAO", "ILIAC", "POSSE", "CACTI", "EASEL", "LASSO", "ROOST", "ALLOT", "COLON", + "LEPER", "TEETH", "TITLE", "HENCE", "NIECE", "PAPER", "TRITE", "SPELL", "RACER", + "ATTIC", "CRASS", "HITCH", "LEASE", "CEASE", "ROTOR", "ELOPE", "APPLE", "CHILI", + "START", "PHOTO", "SALSA", "STASH", "PRIOR", "TAROT", "COLOR", "CHEER", "CLASS", + "ARENA", "ELECT", "ENTER", "CATCH", "TENET", "TACIT", "TRAIT", "TERRA", "LILAC", }; // These are words that'll never be used, but still need to be in the dictionary for guesses. static const char _possible_words[][WORDLE_LENGTH + 1] = { - "AALII", "AARGH", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACHAR", "ACHES", "ACHOO", - "ACING", "ACINI", "ACNES", "ACRES", "ACROS", "AECIA", "AEGIS", "AEONS", "AERIE", - "AEROS", "AESIR", "AGARS", "AGENE", "AGERS", "AGGER", "AGGIE", "AGGRI", "AGGRO", - "AGHAS", "AGILA", "AGIOS", "AGLEE", "AGLOO", "AGOGE", "AGONE", "AGONS", "AGORA", - "AGRIA", "AGRIN", "AGROS", "AHEAP", "AHIGH", "AHING", "AIGAS", "AINEE", "AINGA", - "AIOLI", "AIRER", "AIRNS", "ALAAP", "ALANE", "ALANG", "ALANS", "ALAPA", "ALAPS", - "ALCOS", "ALECS", "ALEPH", "ALGAL", "ALGAS", "ALGIN", "ALGOR", "ALIAS", "ALINE", - "ALLEE", "ALLEL", "ALLIS", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ANANA", "ANCHO", - "ANCLE", "ANCON", "ANEAR", "ANELE", "ANGAS", "ANGLO", "ANIGH", "ANILE", "ANILS", - "ANION", "ANISE", "ANLAS", "ANNAL", "ANNAS", "ANOAS", "ANOLE", "ANSAE", "APACE", - "APAGE", "APERS", "APGAR", "APHIS", "APIAN", "APIOL", "APISH", "APOOP", "APPAL", - "APPEL", "APPRO", "APRES", "APSES", "APSIS", "APSOS", "ARARS", "ARCHI", "ARCOS", + "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", + "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", + "AERIE", "AEROS", "AESIR", "AHEAP", "AHENT", "AHINT", "AINEE", "AIOLI", "AIRER", + "AIRNS", "AIRTH", "AIRTS", "AITCH", "ALAAP", "ALANE", "ALANS", "ALANT", "ALAPA", + "ALAPS", "ALATE", "ALCOS", "ALECS", "ALEPH", "ALIAS", "ALINE", "ALIST", "ALLEE", + "ALLEL", "ALLIS", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ALTHO", "ALTOS", "ANANA", + "ANATA", "ANCHO", "ANCLE", "ANCON", "ANEAR", "ANELE", "ANENT", "ANILE", "ANILS", + "ANION", "ANISE", "ANLAS", "ANNAL", "ANNAS", "ANNAT", "ANOAS", "ANOLE", "ANSAE", + "ANTAE", "ANTAR", "ANTAS", "ANTES", "ANTIS", "ANTRA", "ANTRE", "APACE", "APERS", + "APERT", "APHIS", "APIAN", "APIOL", "APISH", "APOOP", "APORT", "APPAL", "APPEL", + "APPRO", "APRES", "APSES", "APSIS", "APSOS", "APTER", "ARARS", "ARCHI", "ARCOS", "AREAE", "AREAL", "AREAR", "AREAS", "ARECA", "AREIC", "ARENE", "AREPA", "ARERE", - "ARGAL", "ARGAN", "ARGIL", "ARGLE", "ARGOL", "ARGON", "ARIAS", "ARIEL", "ARILS", - "ARISH", "ARLES", "ARNAS", "AROHA", "ARPAS", "ARPEN", "ARRAH", "ARRAS", "ARRIS", - "ARSES", "ARSIS", "ASANA", "ASCON", "ASHES", "ASPEN", "ASPER", "ASPIC", "ASPIE", - "ASPIS", "ASPRO", "ASSAI", "ASSES", "CACAS", "CAECA", "CAESE", "CAGER", "CAGES", - "CAINS", "CALLA", "CALLS", "CALOS", "CALPA", "CALPS", "CANEH", "CANER", "CANES", - "CANGS", "CANNA", "CANNS", "CANSO", "CAPAS", "CAPES", "CAPHS", "CAPLE", "CAPON", - "CAPOS", "CAPRI", "CARAP", "CARER", "CARES", "CARLE", "CARLS", "CARNS", "CARON", - "CARPI", "CARPS", "CARRS", "CARSE", "CASAS", "CASCO", "CASES", "CECAL", "CEILI", - "CEILS", "CELLA", "CELLI", "CELLS", "CENSE", "CEORL", "CEPES", "CERCI", "CERES", - "CERGE", "CERIA", "CERIC", "CERNE", "CEROC", "CEROS", "CESSE", "CHACE", "CHACO", - "CHAIS", "CHALS", "CHANA", "CHANG", "CHAPE", "CHAPS", "CHARA", "CHARE", "CHARR", - "CHARS", "CHEEP", "CHELA", "CHELP", "CHERE", "CHIAO", "CHIAS", "CHICA", "CHICH", - "CHICO", "CHICS", "CHIEL", "CHILE", "CHINE", "CHING", "CHINO", "CHINS", "CHIPS", - "CHIRL", "CHIRO", "CHIRR", "CHOCO", "CHOCS", "CHOGS", "CHOIL", "CHOLA", "CHOLI", - "CHOLO", "CHONS", "CHOON", "CHOPS", "CIELS", "CILIA", "CILLS", "CINES", "CIONS", - "CIPPI", "CIRCS", "CIRES", "CIRLS", "CIRRI", "CISCO", "CLACH", "CLAES", "CLAGS", - "CLANS", "CLAPS", "CLARO", "CLEEP", "CLEGS", "CLEPE", "CLIES", "CLINE", "CLIPE", - "CLIPS", "CLOGS", "CLONS", "CLOOP", "CLOPS", "COALA", "COALS", "COCAS", "COCCI", - "COCCO", "COCOS", "COGIE", "COGON", "COHEN", "COHOE", "COHOG", "COHOS", "COIGN", - "COILS", "COINS", "COIRS", "COLAS", "COLES", "COLIC", "COLIN", "COLLS", "COLOG", - "CONES", "CONGA", "CONGE", "CONGO", "CONIA", "CONIN", "CONNE", "CONNS", "COOCH", - "COOEE", "COOER", "COOLS", "COONS", "COOPS", "COPAL", "COPEN", "COPER", "COPES", - "COPRA", "CORES", "CORGI", "CORIA", "CORNI", "CORNO", "CORNS", "CORPS", "CORSE", - "CORSO", "COSEC", "COSES", "COSIE", "CRAAL", "CRAGS", "CRAIC", "CRAIG", "CRANS", - "CRAPE", "CRAPS", "CRARE", "CREEL", "CREES", "CRENA", "CREPS", "CRIAS", "CRIES", - "CRINE", "CRIOS", "CRIPE", "CRIPS", "CRISE", "CROCI", "CROCS", "CROGS", "CRONS", - "CROOL", "CROON", "CROPS", "CRORE", "EAGRE", "EALES", "EARLS", "EARNS", "EASER", - "EASES", "EASLE", "ECHES", "ECHOS", "EGERS", "EGGAR", "EGGER", "EHING", "EIGNE", - "EISEL", "ELAIN", "ELANS", "ELCHI", "ELOGE", "ELOIN", "ELOPS", "ELPEE", "ELSIN", - "ENIAC", "ENNOG", "ENOLS", "ENROL", "EORLS", "EOSIN", "EPEES", "EPHAH", "EPHAS", - "EPHOR", "EPICS", "EPRIS", "ERGON", "ERGOS", "ERICA", "ERICS", "ERING", "ERNES", - "EROSE", "ERSES", "ESCAR", "ESILE", "ESNES", "ESSES", "GAGER", "GAGES", "GAINS", - "GAIRS", "GALAH", "GALAS", "GALEA", "GALES", "GALLS", "GALOP", "GANCH", "GANGS", - "GAOLS", "GAPER", "GAPES", "GAPOS", "GARES", "GARIS", "GARNI", "GARRE", "GASES", - "GASPS", "GEALS", "GEANS", "GEARE", "GEARS", "GEEPS", "GELEE", "GENAL", "GENAS", - "GENES", "GENIC", "GENII", "GENIP", "GENOA", "GENRO", "GERAH", "GERES", "GERLE", - "GERNE", "GESSE", "GESSO", "GHEES", "GIGAS", "GIGHE", "GILAS", "GILLS", "GINCH", - "GINGE", "GINGS", "GIPON", "GIPPO", "GIRLS", "GIRNS", "GIRON", "GIROS", "GIRRS", - "GIRSH", "GLACE", "GLAIR", "GLANS", "GLEES", "GLEIS", "GLENS", "GLIAL", "GLIAS", - "GLOGG", "GLOOP", "GLOPS", "GNARL", "GNARR", "GNARS", "GOALS", "GOELS", "GOERS", - "GOGGA", "GOGOS", "GOIER", "GOLES", "GOLPE", "GOLPS", "GONCH", "GONGS", "GONIA", - "GONNA", "GOOGS", "GOOLS", "GOONS", "GOOPS", "GOORS", "GORAL", "GORAS", "GORES", - "GORIS", "GORPS", "GORSE", "GOSSE", "GRAAL", "GRAIP", "GRANA", "GRANS", "GRECE", - "GREES", "GREGE", "GREGO", "GREIN", "GRENS", "GRESE", "GRICE", "GRIGS", "GRINS", - "GRIPS", "GRISE", "GROGS", "GRONE", "GRRLS", "GRRRL", "HAARS", "HAGGS", "HAHAS", - "HAILS", "HAINS", "HAIRS", "HALAL", "HALER", "HALES", "HALLO", "HALLS", "HALON", - "HALOS", "HALSE", "HANAP", "HANCE", "HANCH", "HANGI", "HANGS", "HANSA", "HANSE", - "HAOLE", "HAPPI", "HARES", "HARLS", "HARNS", "HAROS", "HARPS", "HASPS", "HEALS", - "HEAPS", "HEARE", "HEARS", "HEELS", "HEIGH", "HEILS", "HEIRS", "HELES", "HELIO", - "HELLS", "HELOS", "HELPS", "HENCH", "HENGE", "HENNA", "HEPAR", "HERES", "HERLS", - "HERNS", "HEROS", "HERSE", "HESPS", "HIGHS", "HILAR", "HILCH", "HILLO", "HILLS", - "HINGS", "HIOIS", "HIREE", "HIRER", "HIRES", "HOARS", "HOERS", "HOGAN", "HOGEN", - "HOGGS", "HOGHS", "HOING", "HOISE", "HOLES", "HOLLA", "HOLLO", "HOLON", "HOLOS", - "HONAN", "HONER", "HONES", "HONGI", "HONGS", "HOOCH", "HOONS", "HOOPS", "HOORS", - "HOOSH", "HOPER", "HOPES", "HORAH", "HORAL", "HORAS", "HORIS", "HORNS", "HOSEL", - "HOSEN", "HOSER", "HOSES", "ICERS", "ICHES", "ICHOR", "ICIER", "ICONS", "IGAPO", - "ILEAC", "ILEAL", "ILIAL", "ILLER", "INCEL", "INCLE", "INCOG", "INGAN", "INGLE", - "INION", "INSPO", "IPPON", "IRING", "IRONE", "IRONS", "ISHES", "ISLES", "ISNAE", - "ISSEI", "LAARI", "LACER", "LACES", "LAERS", "LAGAN", "LAHAL", "LAHAR", "LAICH", - "LAICS", "LAIGH", "LAIRS", "LALLS", "LANAI", "LANAS", "LANCH", "LANES", "LAPIN", - "LAPIS", "LARCH", "LAREE", "LARES", "LARGO", "LARIS", "LARNS", "LASER", "LASES", - "LASSI", "LEANS", "LEAPS", "LEARE", "LEARS", "LEEAR", "LEEPS", "LEERS", "LEESE", - "LEGER", "LEGES", "LEGGE", "LEGGO", "LEHRS", "LEIRS", "LEISH", "LENES", "LENGS", - "LENIS", "LENOS", "LENSE", "LEONE", "LEPRA", "LERES", "LERPS", "LESES", "LIANA", - "LIANE", "LIANG", "LIARS", "LICHI", "LIENS", "LIERS", "LIGAN", "LIGER", "LIGGE", - "LIGNE", "LILLS", "LILOS", "LINAC", "LINCH", "LINES", "LINGA", "LINGS", "LININ", - "LINNS", "LINOS", "LIONS", "LIPAS", "LIPES", "LIPIN", "LIPOS", "LIRAS", "LISLE", - "LISPS", "LLANO", "LOACH", "LOANS", "LOCHE", "LOCHS", "LOCIE", "LOCIS", "LOCOS", - "LOESS", "LOGAN", "LOGES", "LOGIA", "LOGIE", "LOGOI", "LOGON", "LOGOS", "LOHAN", - "LOINS", "LOIPE", "LOIRS", "LOLLS", "LOLOG", "LONER", "LONGA", "LONGE", "LONGS", - "LOOIE", "LOONS", "LOOPS", "LOPER", "LOPES", "LORAL", "LORAN", "LOREL", "LORES", - "LORIC", "LORIS", "LOSEL", "LOSEN", "LOSES", "NAANS", "NACHE", "NACHO", "NACRE", - "NAGAS", "NAGOR", "NAHAL", "NAILS", "NAIRA", "NALAS", "NALLA", "NANAS", "NANCE", - "NANNA", "NANOS", "NAPAS", "NAPES", "NAPOO", "NAPPA", "NAPPE", "NARAS", "NARCO", - "NARCS", "NARES", "NARIC", "NARIS", "NARRE", "NASHI", "NEALS", "NEAPS", "NEARS", - "NEELE", "NEEPS", "NEESE", "NEGRO", "NELIS", "NENES", "NEONS", "NEPER", "NERAL", - "NEROL", "NGAIO", "NGANA", "NICOL", "NIGER", "NIGHS", "NIHIL", "NILLS", "NINER", - "NINES", "NINON", "NIPAS", "NIRLS", "NISEI", "NISSE", "NOAHS", "NOELS", "NOGGS", - "NOILS", "NOIRS", "NOLES", "NOLLS", "NOLOS", "NONAS", "NONCE", "NONES", "NONGS", - "NONIS", "NOONS", "NOOPS", "NOPAL", "NORIA", "NORIS", "NOSER", "NOSES", "OASES", - "OASIS", "OCHER", "OCHES", "OCHRE", "OCREA", "OGEES", "OGGIN", "OGLER", "OGLES", - "OGRES", "OHIAS", "OHING", "OHONE", "OILER", "OLEIC", "OLEIN", "OLEOS", "OLIOS", - "OLLAS", "OLLER", "OLLIE", "OLPAE", "OLPES", "ONCER", "ONCES", "ONERS", "OORIE", - "OOSES", "OPAHS", "OPALS", "OPENS", "OPEPE", "OPING", "OPPOS", "OPSIN", "ORACH", - "ORALS", "ORANG", "ORCAS", "ORCIN", "ORGIA", "ORGIC", "ORIEL", "ORLES", "ORLON", - "ORLOP", "ORNIS", "ORPIN", "ORRIS", "OSCAR", "OSHAC", "OSIER", "OSSIA", "PAALS", - "PAANS", "PACAS", "PACER", "PACES", "PACHA", "PACOS", "PAEAN", "PAEON", "PAGER", - "PAGES", "PAGLE", "PAGRI", "PAILS", "PAINS", "PAIRE", "PAIRS", "PAISA", "PAISE", - "PALAS", "PALEA", "PALES", "PALIS", "PALLA", "PALLS", "PALPI", "PALPS", "PALSA", - "PANCE", "PANES", "PANGA", "PANGS", "PANNE", "PANNI", "PAOLI", "PAOLO", "PAPAS", - "PAPES", "PAPPI", "PARAE", "PARAS", "PARCH", "PAREN", "PAREO", "PARES", "PARGE", - "PARGO", "PARIS", "PARLE", "PAROL", "PARPS", "PARRA", "PARRS", "PASEO", "PASES", - "PASHA", "PASSE", "PEAGE", "PEAGS", "PEALS", "PEANS", "PEARE", "PEARS", "PEASE", - "PECHS", "PEECE", "PEELS", "PEENS", "PEEPE", "PEEPS", "PEERS", "PEGHS", "PEINS", - "PEISE", "PELAS", "PELES", "PELLS", "PELON", "PENES", "PENGO", "PENIE", "PENIS", - "PENNA", "PENNI", "PEONS", "PEPLA", "PEPOS", "PEPSI", "PERAI", "PERCE", "PERCS", - "PEREA", "PERES", "PERIS", "PERNS", "PEROG", "PERPS", "PERSE", "PESOS", "PHAGE", - "PHANG", "PHARE", "PHEER", "PHENE", "PHEON", "PHESE", "PHIAL", "PHISH", "PHOCA", - "PHONO", "PHONS", "PIANI", "PIANS", "PICAL", "PICAS", "PICRA", "PIERS", "PIING", + "ARETE", "ARETS", "ARETT", "ARHAT", "ARIAS", "ARIEL", "ARILS", "ARIOT", "ARISH", + "ARLES", "ARNAS", "AROHA", "ARPAS", "ARPEN", "ARRAH", "ARRAS", "ARRET", "ARRIS", + "ARSES", "ARSIS", "ARTAL", "ARTEL", "ARTIC", "ARTIS", "ASANA", "ASCON", "ASHES", + "ASHET", "ASPEN", "ASPER", "ASPIC", "ASPIE", "ASPIS", "ASPRO", "ASSAI", "ASSES", + "ASSOT", "ASTER", "ASTIR", "ATAPS", "ATILT", "ATLAS", "ATOCS", "ATRIA", "ATRIP", + "ATTAP", "ATTAR", "CACAS", "CAECA", "CAESE", "CAINS", "CALLA", "CALLS", "CALOS", + "CALPA", "CALPS", "CANEH", "CANER", "CANES", "CANNA", "CANNS", "CANSO", "CANST", + "CANTO", "CANTS", "CAPAS", "CAPES", "CAPHS", "CAPLE", "CAPON", "CAPOS", "CAPOT", + "CAPRI", "CARAP", "CARER", "CARES", "CARET", "CARLE", "CARLS", "CARNS", "CARON", + "CARPI", "CARPS", "CARRS", "CARSE", "CARTA", "CARTE", "CARTS", "CASAS", "CASCO", + "CASES", "CASTS", "CATES", "CECAL", "CEILI", "CEILS", "CELLA", "CELLI", "CELLS", + "CELTS", "CENSE", "CENTO", "CENTS", "CEORL", "CEPES", "CERCI", "CERES", "CERIA", + "CERIC", "CERNE", "CEROC", "CEROS", "CERTS", "CESSE", "CESTA", "CESTI", "CETES", + "CHACE", "CHACO", "CHAIS", "CHALS", "CHANA", "CHAPE", "CHAPS", "CHAPT", "CHARA", + "CHARE", "CHARR", "CHARS", "CHATS", "CHEEP", "CHELA", "CHELP", "CHERE", "CHERT", + "CHETH", "CHIAO", "CHIAS", "CHICA", "CHICH", "CHICO", "CHICS", "CHIEL", "CHILE", + "CHINE", "CHINO", "CHINS", "CHIPS", "CHIRL", "CHIRO", "CHIRR", "CHIRT", "CHITS", + "CHOCO", "CHOCS", "CHOIL", "CHOLA", "CHOLI", "CHOLO", "CHONS", "CHOON", "CHOPS", + "CHOTA", "CHOTT", "CIELS", "CILIA", "CILLS", "CINCT", "CINES", "CIONS", "CIPPI", + "CIRCS", "CIRES", "CIRLS", "CIRRI", "CISCO", "CISTS", "CITAL", "CITER", "CITES", + "CLACH", "CLAES", "CLANS", "CLAPS", "CLAPT", "CLARO", "CLART", "CLAST", "CLATS", + "CLEEP", "CLEPE", "CLEPT", "CLIES", "CLINE", "CLINT", "CLIPE", "CLIPS", "CLIPT", + "CLITS", "CLONS", "CLOOP", "CLOOT", "CLOPS", "CLOTE", "CLOTS", "COACT", "COALA", + "COALS", "COAPT", "COATE", "COATI", "COATS", "COCAS", "COCCI", "COCCO", "COCOS", + "COHEN", "COHOE", "COHOS", "COILS", "COINS", "COIRS", "COITS", "COLAS", "COLES", + "COLIC", "COLIN", "COLLS", "COLTS", "CONES", "CONIA", "CONIN", "CONNE", "CONNS", + "CONTE", "CONTO", "COOCH", "COOEE", "COOER", "COOLS", "COONS", "COOPS", "COOPT", + "COOST", "COOTS", "COPAL", "COPEN", "COPER", "COPES", "COPRA", "CORES", "CORIA", + "CORNI", "CORNO", "CORNS", "CORPS", "CORSE", "CORSO", "COSEC", "COSES", "COSET", + "COSIE", "COSTA", "COSTE", "COSTS", "COTAN", "COTES", "COTHS", "COTTA", "COTTS", + "CRAAL", "CRAIC", "CRANS", "CRAPE", "CRAPS", "CRARE", "CREEL", "CREES", "CRENA", + "CREPS", "CRIAS", "CRIES", "CRINE", "CRIOS", "CRIPE", "CRIPS", "CRISE", "CRITH", + "CRITS", "CROCI", "CROCS", "CRONS", "CROOL", "CROON", "CROPS", "CRORE", "CROST", + "CTENE", "EALES", "EARLS", "EARNS", "EARNT", "EARST", "EASER", "EASES", "EASLE", + "EASTS", "EATHE", "ECHES", "ECHOS", "EISEL", "ELAIN", "ELANS", "ELCHI", "ELINT", + "ELOIN", "ELOPS", "ELPEE", "ELSIN", "ENATE", "ENIAC", "ENLIT", "ENOLS", "ENROL", + "ENTIA", "EORLS", "EOSIN", "EPACT", "EPEES", "EPHAH", "EPHAS", "EPHOR", "EPICS", + "EPOPT", "EPRIS", "ERICA", "ERICS", "ERNES", "EROSE", "ERSES", "ESCAR", "ESCOT", + "ESILE", "ESNES", "ESSES", "ESTOC", "ESTOP", "ESTRO", "ETAPE", "ETATS", "ETENS", + "ETHAL", "ETHNE", "ETICS", "ETNAS", "ETTIN", "ETTLE", "HAARS", "HAETS", "HAHAS", + "HAILS", "HAINS", "HAINT", "HAIRS", "HAITH", "HALAL", "HALER", "HALES", "HALLO", + "HALLS", "HALON", "HALOS", "HALSE", "HALTS", "HANAP", "HANCE", "HANCH", "HANSA", + "HANSE", "HANTS", "HAOLE", "HAPPI", "HARES", "HARLS", "HARNS", "HAROS", "HARPS", + "HARTS", "HASPS", "HASTA", "HATES", "HATHA", "HEALS", "HEAPS", "HEARE", "HEARS", + "HEAST", "HEATS", "HECHT", "HEELS", "HEILS", "HEIRS", "HELES", "HELIO", "HELLS", + "HELOS", "HELOT", "HELPS", "HENCH", "HENNA", "HENTS", "HEPAR", "HERES", "HERLS", + "HERNS", "HEROS", "HERSE", "HESPS", "HESTS", "HETES", "HETHS", "HIANT", "HILAR", + "HILCH", "HILLO", "HILLS", "HILTS", "HINTS", "HIOIS", "HIREE", "HIRER", "HIRES", + "HISTS", "HITHE", "HOARS", "HOAST", "HOERS", "HOISE", "HOLES", "HOLLA", "HOLLO", + "HOLON", "HOLOS", "HOLTS", "HONAN", "HONER", "HONES", "HOOCH", "HOONS", "HOOPS", + "HOORS", "HOOSH", "HOOTS", "HOPER", "HOPES", "HORAH", "HORAL", "HORAS", "HORIS", + "HORNS", "HORST", "HOSEL", "HOSEN", "HOSER", "HOSES", "HOSTA", "HOSTS", "HOTCH", + "HOTEN", "ICERS", "ICHES", "ICHOR", "ICIER", "ICONS", "ICTAL", "ICTIC", "ILEAC", + "ILEAL", "ILIAL", "ILLER", "ILLTH", "INAPT", "INCEL", "INCLE", "INION", "INNIT", + "INSET", "INSPO", "INTEL", "INTIL", "INTIS", "INTRA", "IOTAS", "IPPON", "IRONE", + "IRONS", "ISHES", "ISLES", "ISNAE", "ISSEI", "ISTLE", "ITHER", "LAARI", "LACER", + "LACES", "LACET", "LAERS", "LAHAL", "LAHAR", "LAICH", "LAICS", "LAIRS", "LAITH", + "LALLS", "LANAI", "LANAS", "LANCH", "LANES", "LANTS", "LAPIN", "LAPIS", "LARCH", + "LAREE", "LARES", "LARIS", "LARNS", "LARNT", "LASER", "LASES", "LASSI", "LASTS", + "LATAH", "LATEN", "LATHI", "LATHS", "LEANS", "LEAPS", "LEARE", "LEARS", "LEATS", + "LEEAR", "LEEPS", "LEERS", "LEESE", "LEETS", "LEHRS", "LEIRS", "LEISH", "LENES", + "LENIS", "LENOS", "LENSE", "LENTI", "LENTO", "LEONE", "LEPRA", "LEPTA", "LERES", + "LERPS", "LESES", "LESTS", "LETCH", "LETHE", "LIANA", "LIANE", "LIARS", "LIART", + "LICHI", "LICHT", "LICIT", "LIENS", "LIERS", "LILLS", "LILOS", "LILTS", "LINAC", + "LINCH", "LINES", "LININ", "LINNS", "LINOS", "LINTS", "LIONS", "LIPAS", "LIPES", + "LIPIN", "LIPOS", "LIRAS", "LIROT", "LISLE", "LISPS", "LISTS", "LITAI", "LITAS", + "LITES", "LITHO", "LITHS", "LITRE", "LLANO", "LOACH", "LOANS", "LOAST", "LOCHE", + "LOCHS", "LOCIE", "LOCIS", "LOCOS", "LOESS", "LOHAN", "LOINS", "LOIPE", "LOIRS", + "LOLLS", "LONER", "LOOIE", "LOONS", "LOOPS", "LOOTS", "LOPER", "LOPES", "LORAL", + "LORAN", "LOREL", "LORES", "LORIC", "LORIS", "LOSEL", "LOSEN", "LOSES", "LOTAH", + "LOTAS", "LOTES", "LOTIC", "LOTOS", "LOTSA", "LOTTA", "LOTTE", "LOTTO", "NAANS", + "NACHE", "NACHO", "NACRE", "NAHAL", "NAILS", "NAIRA", "NALAS", "NALLA", "NANAS", + "NANCE", "NANNA", "NANOS", "NAPAS", "NAPES", "NAPOO", "NAPPA", "NAPPE", "NARAS", + "NARCO", "NARCS", "NARES", "NARIC", "NARIS", "NARRE", "NASHI", "NATCH", "NATES", + "NATIS", "NEALS", "NEAPS", "NEARS", "NEATH", "NEATS", "NEELE", "NEEPS", "NEESE", + "NEIST", "NELIS", "NENES", "NEONS", "NEPER", "NEPIT", "NERAL", "NEROL", "NERTS", + "NESTS", "NETES", "NETOP", "NETTS", "NICHT", "NICOL", "NIHIL", "NILLS", "NINER", + "NINES", "NINON", "NIPAS", "NIRLS", "NISEI", "NISSE", "NITER", "NITES", "NITON", + "NITRE", "NITRO", "NOAHS", "NOELS", "NOILS", "NOINT", "NOIRS", "NOLES", "NOLLS", + "NOLOS", "NONAS", "NONCE", "NONES", "NONET", "NONIS", "NOOIT", "NOONS", "NOOPS", + "NOPAL", "NORIA", "NORIS", "NOSER", "NOSES", "NOTAL", "NOTER", "NOTES", "OASES", + "OASIS", "OASTS", "OATEN", "OATER", "OATHS", "OCHER", "OCHES", "OCHRE", "OCREA", + "OCTAN", "OCTAS", "OHIAS", "OHONE", "OILER", "OINTS", "OLEIC", "OLEIN", "OLENT", + "OLEOS", "OLIOS", "OLLAS", "OLLER", "OLLIE", "OLPAE", "OLPES", "ONCER", "ONCES", + "ONCET", "ONERS", "ONTIC", "OONTS", "OORIE", "OOSES", "OPAHS", "OPALS", "OPENS", + "OPEPE", "OPPOS", "OPSIN", "OPTER", "ORACH", "ORALS", "ORANT", "ORATE", "ORCAS", + "ORCIN", "ORIEL", "ORLES", "ORLON", "ORLOP", "ORNIS", "ORPIN", "ORRIS", "ORTHO", + "OSCAR", "OSHAC", "OSIER", "OSSIA", "OSTIA", "OTTAR", "OTTOS", "PAALS", "PAANS", + "PACAS", "PACER", "PACES", "PACHA", "PACOS", "PACTA", "PACTS", "PAEAN", "PAEON", + "PAILS", "PAINS", "PAIRE", "PAIRS", "PAISA", "PAISE", "PALAS", "PALEA", "PALES", + "PALET", "PALIS", "PALLA", "PALLS", "PALPI", "PALPS", "PALSA", "PANCE", "PANES", + "PANNE", "PANNI", "PANTO", "PANTS", "PAOLI", "PAOLO", "PAPAS", "PAPES", "PAPPI", + "PARAE", "PARAS", "PARCH", "PAREN", "PAREO", "PARES", "PARIS", "PARLE", "PAROL", + "PARPS", "PARRA", "PARRS", "PARTI", "PARTS", "PASEO", "PASES", "PASHA", "PASSE", + "PASTS", "PATEN", "PATER", "PATES", "PATHS", "PATIN", "PATTE", "PEALS", "PEANS", + "PEARE", "PEARS", "PEART", "PEASE", "PEATS", "PECHS", "PEECE", "PEELS", "PEENS", + "PEEPE", "PEEPS", "PEERS", "PEINS", "PEISE", "PELAS", "PELES", "PELLS", "PELON", + "PELTA", "PELTS", "PENES", "PENIE", "PENIS", "PENNA", "PENNI", "PENTS", "PEONS", + "PEPLA", "PEPOS", "PEPSI", "PERAI", "PERCE", "PERCS", "PEREA", "PERES", "PERIS", + "PERNS", "PERPS", "PERSE", "PERST", "PERTS", "PESOS", "PESTS", "PETAR", "PETER", + "PETIT", "PETRE", "PETRI", "PETTI", "PETTO", "PHARE", "PHEER", "PHENE", "PHEON", + "PHESE", "PHIAL", "PHISH", "PHOCA", "PHONO", "PHONS", "PHOTS", "PHPHT", "PIANI", + "PIANS", "PICAL", "PICAS", "PICOT", "PICRA", "PIERS", "PIERT", "PIETA", "PIETS", "PILAE", "PILAO", "PILAR", "PILCH", "PILEA", "PILEI", "PILER", "PILES", "PILIS", - "PILLS", "PINAS", "PINES", "PINGO", "PINGS", "PINNA", "PINON", "PIONS", "PIPAL", - "PIPAS", "PIPES", "PIPIS", "PIRAI", "PIRLS", "PIRNS", "PIROG", "PISCO", "PISES", - "PISOS", "PLAAS", "PLAGE", "PLANS", "PLAPS", "PLASH", "PLEAS", "PLENA", "PLEON", - "PLESH", "PLICA", "PLIES", "PLING", "PLONG", "PLOPS", "POACH", "POEPS", "POGGE", - "POGOS", "POLER", "POLES", "POLIO", "POLIS", "POLLS", "POLOS", "PONCE", "PONES", - "PONGA", "PONGO", "PONGS", "POOHS", "POOLS", "POONS", "POOPS", "POORI", "POPES", - "POPPA", "PORAE", "PORAL", "PORER", "PORES", "PORGE", "PORIN", "PORNO", "PORNS", - "POSES", "POSHO", "PRANA", "PRANG", "PRAOS", "PRASE", "PREES", "PREON", "PREOP", - "PREPS", "PRESA", "PRESE", "PRIAL", "PRIER", "PRIES", "PRIGS", "PRILL", "PRION", - "PRISE", "PRISS", "PROAS", "PROGS", "PROIN", "PROLE", "PROLL", "PROPS", "PRORE", - "PROSO", "PROSS", "PSION", "PSOAE", "PSOAI", "PSOAS", "PSORA", "RACES", "RACHE", - "RACON", "RAGAS", "RAGEE", "RAGER", "RAGES", "RAGGA", "RAGGS", "RAGIS", "RAIAS", - "RAILE", "RAILS", "RAINE", "RAINS", "RALES", "RANAS", "RANCE", "RANEE", "RANGA", - "RANGI", "RANGS", "RANIS", "RAPER", "RAPES", "RAPHE", "RAPPE", "RAREE", "RARES", - "RASER", "RASES", "RASPS", "RASSE", "REAIS", "REALO", "REALS", "REANS", "REAPS", - "REARS", "RECAL", "RECCE", "RECCO", "RECON", "REECH", "REELS", "REENS", "REGAR", - "REGES", "REGGO", "REGIE", "REGNA", "REGOS", "REINS", "RELIE", "RELLO", "RENGA", - "RENIG", "RENIN", "RENNE", "RENOS", "REOIL", "REORG", "REPEG", "REPIN", "REPLA", - "REPOS", "REPPS", "REPRO", "RERAN", "RERIG", "RESEE", "RESES", "RHEAS", "RHIES", - "RHINE", "RHONE", "RIALS", "RICER", "RICES", "RICIN", "RIELS", "RIGGS", "RIGOL", - "RILES", "RILLE", "RILLS", "RINES", "RINGS", "RIPES", "RIPPS", "RISES", "RISHI", - "RISPS", "ROANS", "ROARS", "ROHES", "ROILS", "ROINS", "ROLAG", "ROLES", "ROLLS", - "RONEO", "RONES", "RONIN", "RONNE", "ROONS", "ROOPS", "ROOSA", "ROOSE", "ROPER", - "ROPES", "RORAL", "RORES", "RORIC", "RORIE", "ROSES", "ROSHI", "ROSIN", "SAAGS", - "SACRA", "SAGAS", "SAGER", "SAGES", "SAGOS", "SAICE", "SAICS", "SAIGA", "SAILS", - "SAINE", "SAINS", "SAIRS", "SALAL", "SALEP", "SALES", "SALIC", "SALLE", "SALOL", - "SALOP", "SALPA", "SALPS", "SALSE", "SANES", "SANGA", "SANGH", "SANGO", "SANGS", - "SANSA", "SAOLA", "SAPAN", "SAPOR", "SARAN", "SAREE", "SARGE", "SARGO", "SARIN", - "SARIS", "SAROS", "SASER", "SASIN", "SASSE", "SCAGS", "SCAIL", "SCALA", "SCALL", - "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", "SCARS", "SCENA", "SCOGS", "SCOOG", - "SCOPA", "SCOPS", "SCRAE", "SCRAG", "SCRAN", "SCRIP", "SCROG", "SEALS", "SEANS", - "SEARE", "SEARS", "SEASE", "SECCO", "SECHS", "SEELS", "SEEPS", "SEERS", "SEGAR", - "SEGNI", "SEGNO", "SEGOL", "SEGOS", "SEHRI", "SEILS", "SEINE", "SEIRS", "SEISE", - "SELAH", "SELES", "SELLA", "SELLE", "SELLS", "SENAS", "SENES", "SENGI", "SENNA", - "SENOR", "SENSA", "SENSI", "SEPAL", "SEPIC", "SERAC", "SERAI", "SERAL", "SERER", - "SERES", "SERGE", "SERIC", "SERIN", "SERON", "SERRA", "SERRE", "SERRS", "SESSA", - "SHAGS", "SHAHS", "SHANS", "SHAPS", "SHARN", "SHASH", "SHCHI", "SHEAL", "SHEAS", - "SHEEL", "SHEOL", "SHERE", "SHERO", "SHIAI", "SHIEL", "SHIER", "SHIES", "SHILL", - "SHINS", "SHIPS", "SHIRR", "SHIRS", "SHISH", "SHISO", "SHLEP", "SHOER", "SHOES", - "SHOGI", "SHOGS", "SHOLA", "SHOOL", "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", - "SHRIS", "SIALS", "SICES", "SIENS", "SIGHS", "SIGIL", "SIGLA", "SIGNA", "SIGNS", - "SILEN", "SILER", "SILES", "SILLS", "SILOS", "SINES", "SINGS", "SINHS", "SIPES", - "SIREE", "SIRES", "SIRIH", "SIRIS", "SIROC", "SIRRA", "SISAL", "SISES", "SLAES", - "SLAGS", "SLANE", "SLAPS", "SLEER", "SLIER", "SLIPE", "SLIPS", "SLISH", "SLOAN", - "SLOES", "SLOGS", "SLOPS", "SNAGS", "SNAPS", "SNARS", "SNASH", "SNEAP", "SNEES", - "SNELL", "SNIES", "SNIGS", "SNIPS", "SNOEP", "SNOGS", "SNOOL", "SOAPS", "SOARE", - "SOARS", "SOCAS", "SOCES", "SOCLE", "SOGER", "SOILS", "SOLAH", "SOLAN", "SOLAS", - "SOLEI", "SOLER", "SOLES", "SOLON", "SOLOS", "SONCE", "SONES", "SONGS", "SONNE", - "SONSE", "SOOLE", "SOOLS", "SOOPS", "SOPHS", "SOPOR", "SOPRA", "SORAL", "SORAS", - "SOREE", "SOREL", "SORER", "SORES", "SORGO", "SORNS", "SORRA", "SPAER", "SPAES", - "SPAGS", "SPAHI", "SPAIL", "SPAIN", "SPALE", "SPALL", "SPANE", "SPANG", "SPANS", - "SPARS", "SPEAL", "SPEAN", "SPECS", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEOS", - "SPIAL", "SPICA", "SPICS", "SPIER", "SPIES", "SPILE", "SPINA", "SPINS", "SPLOG", - "SPOOR", "SPOSH", "SPRAG", "SPROG", + "PILLS", "PINAS", "PINES", "PINNA", "PINON", "PINOT", "PINTA", "PINTS", "PIONS", + "PIPAL", "PIPAS", "PIPES", "PIPET", "PIPIS", "PIPIT", "PIRAI", "PIRLS", "PIRNS", + "PISCO", "PISES", "PISOS", "PISTE", "PITAS", "PITHS", "PITON", "PITOT", "PITTA", + "PLAAS", "PLANS", "PLAPS", "PLASH", "PLAST", "PLATS", "PLATT", "PLEAS", "PLENA", + "PLEON", "PLESH", "PLICA", "PLIES", "PLOAT", "PLOPS", "PLOTS", "POACH", "POEPS", + "POETS", "POLER", "POLES", "POLIO", "POLIS", "POLLS", "POLOS", "POLTS", "PONCE", + "PONES", "PONTS", "POOHS", "POOLS", "POONS", "POOPS", "POORI", "POORT", "POOTS", + "POPES", "POPPA", "PORAE", "PORAL", "PORER", "PORES", "PORIN", "PORNO", "PORNS", + "PORTA", "PORTS", "POSES", "POSHO", "POSTS", "POTAE", "POTCH", "POTES", "POTIN", + "POTOO", "POTTO", "POTTS", "PRANA", "PRAOS", "PRASE", "PRATE", "PRATS", "PRATT", + "PREES", "PRENT", "PREON", "PREOP", "PREPS", "PRESA", "PRESE", "PREST", "PRIAL", + "PRIER", "PRIES", "PRILL", "PRION", "PRISE", "PRISS", "PROAS", "PROIN", "PROLE", + "PROLL", "PROPS", "PRORE", "PROSO", "PROSS", "PROST", "PROTO", "PSION", "PSOAE", + "PSOAI", "PSOAS", "PSORA", "RACES", "RACHE", "RACON", "RAIAS", "RAILE", "RAILS", + "RAINE", "RAINS", "RAITA", "RAITS", "RALES", "RANAS", "RANCE", "RANEE", "RANIS", + "RANTS", "RAPER", "RAPES", "RAPHE", "RAPPE", "RAREE", "RARES", "RASER", "RASES", + "RASPS", "RASSE", "RASTA", "RATAL", "RATAN", "RATAS", "RATCH", "RATEL", "RATER", + "RATES", "RATHA", "RATHE", "RATHS", "RATOO", "RATOS", "REAIS", "REALO", "REALS", + "REANS", "REAPS", "REARS", "REAST", "REATA", "REATE", "RECAL", "RECCE", "RECCO", + "RECIT", "RECON", "RECTA", "RECTI", "RECTO", "REECH", "REELS", "REENS", "REEST", + "REINS", "REIST", "RELET", "RELIE", "RELIT", "RELLO", "RENIN", "RENNE", "RENOS", + "RENTE", "RENTS", "REOIL", "REPIN", "REPLA", "REPOS", "REPOT", "REPPS", "REPRO", + "RERAN", "RESAT", "RESEE", "RESES", "RESIT", "RESTO", "RESTS", "RETIA", "RETIE", + "RHEAS", "RHIES", "RHINE", "RHONE", "RIALS", "RIANT", "RIATA", "RICER", "RICES", + "RICHT", "RICIN", "RIELS", "RILES", "RILLE", "RILLS", "RINES", "RIOTS", "RIPES", + "RIPPS", "RISES", "RISHI", "RISPS", "RITES", "RITTS", "ROANS", "ROARS", "ROATE", + "ROHES", "ROILS", "ROINS", "ROIST", "ROLES", "ROLLS", "RONEO", "RONES", "RONIN", + "RONNE", "RONTE", "RONTS", "ROONS", "ROOPS", "ROOSA", "ROOSE", "ROOTS", "ROPER", + "ROPES", "RORAL", "RORES", "RORIC", "RORIE", "RORTS", "ROSES", "ROSET", "ROSHI", + "ROSIN", "ROSIT", "ROSTI", "ROSTS", "ROTAL", "ROTAN", "ROTAS", "ROTCH", "ROTES", + "ROTIS", "ROTLS", "ROTON", "ROTOS", "ROTTE", "SACRA", "SAICE", "SAICS", "SAILS", + "SAINE", "SAINS", "SAIRS", "SAIST", "SAITH", "SALAL", "SALAT", "SALEP", "SALES", + "SALET", "SALIC", "SALLE", "SALOL", "SALOP", "SALPA", "SALPS", "SALSE", "SALTO", + "SALTS", "SANES", "SANSA", "SANTO", "SANTS", "SAOLA", "SAPAN", "SAPOR", "SARAN", + "SAREE", "SARIN", "SARIS", "SAROS", "SASER", "SASIN", "SASSE", "SATAI", "SATES", + "SATIS", "SCAIL", "SCALA", "SCALL", "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", + "SCARS", "SCART", "SCATH", "SCATS", "SCATT", "SCEAT", "SCENA", "SCOOT", "SCOPA", + "SCOPS", "SCOTS", "SCRAE", "SCRAN", "SCRAT", "SCRIP", "SEALS", "SEANS", "SEARE", + "SEARS", "SEASE", "SEATS", "SECCO", "SECHS", "SECTS", "SEELS", "SEEPS", "SEERS", + "SEHRI", "SEILS", "SEINE", "SEIRS", "SEISE", "SELAH", "SELES", "SELLA", "SELLE", + "SELLS", "SENAS", "SENES", "SENNA", "SENOR", "SENSA", "SENSI", "SENTE", "SENTI", + "SENTS", "SEPAL", "SEPIC", "SEPTA", "SEPTS", "SERAC", "SERAI", "SERAL", "SERER", + "SERES", "SERIC", "SERIN", "SERON", "SERRA", "SERRE", "SERRS", "SESSA", "SETAE", + "SETAL", "SETON", "SETTS", "SHAHS", "SHANS", "SHAPS", "SHARN", "SHASH", "SHCHI", + "SHEAL", "SHEAS", "SHEEL", "SHENT", "SHEOL", "SHERE", "SHERO", "SHETS", "SHIAI", + "SHIEL", "SHIER", "SHIES", "SHILL", "SHINS", "SHIPS", "SHIRR", "SHIRS", "SHISH", + "SHISO", "SHIST", "SHITE", "SHITS", "SHLEP", "SHOAT", "SHOER", "SHOES", "SHOLA", + "SHOOL", "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", "SHOTE", "SHOTS", "SHOTT", + "SHRIS", "SIALS", "SICES", "SICHT", "SIENS", "SIENT", "SIETH", "SILEN", "SILER", + "SILES", "SILLS", "SILOS", "SILTS", "SINES", "SINHS", "SIPES", "SIREE", "SIRES", + "SIRIH", "SIRIS", "SIROC", "SIRRA", "SISAL", "SISES", "SISTA", "SISTS", "SITAR", + "SITES", "SITHE", "SLAES", "SLANE", "SLAPS", "SLART", "SLATS", "SLEER", "SLIER", + "SLIPE", "SLIPS", "SLIPT", "SLISH", "SLITS", "SLOAN", "SLOES", "SLOOT", "SLOPS", + "SLOTS", "SNAPS", "SNARS", "SNASH", "SNATH", "SNEAP", "SNEES", "SNELL", "SNIES", + "SNIPS", "SNIRT", "SNITS", "SNOEP", "SNOOL", "SNOOT", "SNOTS", "SOAPS", "SOARE", + "SOARS", "SOCAS", "SOCES", "SOCLE", "SOILS", "SOLAH", "SOLAN", "SOLAS", "SOLEI", + "SOLER", "SOLES", "SOLON", "SOLOS", "SONCE", "SONES", "SONNE", "SONSE", "SOOLE", + "SOOLS", "SOOPS", "SOOTE", "SOOTS", "SOPHS", "SOPOR", "SOPRA", "SORAL", "SORAS", + "SOREE", "SOREL", "SORER", "SORES", "SORNS", "SORRA", "SORTA", "SORTS", "SOTHS", + "SOTOL", "SPAER", "SPAES", "SPAHI", "SPAIL", "SPAIN", "SPAIT", "SPALE", "SPALL", + "SPALT", "SPANE", "SPANS", "SPARS", "SPART", "SPATE", "SPATS", "SPEAL", "SPEAN", + "SPEAT", "SPECS", "SPECT", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEOS", "SPETS", + "SPIAL", "SPICA", "SPICS", "SPIER", "SPIES", "SPILE", "SPINA", "SPINS", "SPIRT", + "SPITS", "SPOOR", "SPOOT", "SPOSH", "SPOTS", "SPRAT", "SPRIT", "STANE", "STAPH", + "STAPS", "STARN", "STARR", "STARS", "STATS", "STEAN", "STEAR", "STEEN", "STEIL", + "STELA", "STELE", "STELL", "STENO", "STENS", "STENT", "STEPS", "STEPT", "STERE", + "STETS", "STICH", "STIES", "STILE", "STIPA", "STIPE", "STIRE", "STIRP", "STIRS", + "STOAE", "STOAI", "STOAS", "STOAT", "STOEP", "STOIT", "STOLN", "STONN", "STOOR", + "STOPE", "STOPS", "STOPT", "STOSS", "STOTS", "STOTT", "STRAE", "STREP", "STRIA", + "STROP", "TAALS", "TAATA", "TACAN", "TACES", "TACET", "TACHE", "TACHO", "TACHS", + "TACOS", "TACTS", "TAELS", "TAHAS", "TAHRS", "TAILS", "TAINS", "TAIRA", "TAISH", + "TAITS", "TALAR", "TALAS", "TALCS", "TALEA", "TALER", "TALES", "TALLS", "TALPA", + "TANAS", "TANHS", "TANNA", "TANTI", "TANTO", "TAPAS", "TAPEN", "TAPES", "TAPET", + "TAPIS", "TAPPA", "TARAS", "TARES", "TARNS", "TAROC", "TAROS", "TARPS", "TARRE", + "TARSI", "TARTS", "TASAR", "TASER", "TASES", "TASSA", "TASSE", "TASSO", "TATAR", + "TATER", "TATES", "TATHS", "TATIE", "TATTS", "TEALS", "TEARS", "TEATS", "TECHS", + "TECTA", "TEELS", "TEENE", "TEENS", "TEERS", "TEHRS", "TEILS", "TEINS", "TELAE", + "TELCO", "TELES", "TELIA", "TELIC", "TELLS", "TELOI", "TELOS", "TENCH", "TENES", + "TENIA", "TENNE", "TENNO", "TENON", "TENTS", "TEPAL", "TEPAS", "TERAI", "TERAS", + "TERCE", "TERES", "TERNE", "TERNS", "TERTS", "TESLA", "TESTA", "TESTE", "TESTS", + "TETES", "TETHS", "TETRA", "TETRI", "THALE", "THALI", "THANA", "THANE", "THANS", + "THARS", "THECA", "THEES", "THEIC", "THEIN", "THENS", "THESP", "THETE", "THILL", + "THINE", "THINS", "THIOL", "THIRL", "THOLE", "THOLI", "THORO", "THORP", "THRAE", + "THRIP", "THROE", "TIANS", "TIARS", "TICAL", "TICCA", "TICES", "TIERS", "TILER", + "TILES", "TILLS", "TILTH", "TILTS", "TINAS", "TINCT", "TINEA", "TINES", "TINTS", + "TIPIS", "TIRES", "TIRLS", "TIROS", "TIRRS", "TITCH", "TITER", "TITIS", "TITRE", + "TOCOS", "TOEAS", "TOHOS", "TOILE", "TOILS", "TOISE", "TOITS", "TOLAN", "TOLAR", + "TOLAS", "TOLES", "TOLLS", "TOLTS", "TONER", "TONES", "TONNE", "TOOLS", "TOONS", + "TOOTS", "TOPEE", "TOPER", "TOPES", "TOPHE", "TOPHI", "TOPHS", "TOPIS", "TOPOI", + "TOPOS", "TORAH", "TORAN", "TORAS", "TORCS", "TORES", "TORIC", "TORII", "TOROS", + "TOROT", "TORRS", "TORSE", "TORSI", "TORTA", "TORTE", "TORTS", "TOSAS", "TOSES", + "TOTER", "TOTES", "TRANS", "TRANT", "TRAPE", "TRAPS", "TRAPT", "TRASS", "TRATS", + "TRATT", "TREEN", "TREES", "TRESS", "TREST", "TRETS", "TRIAC", "TRIER", "TRIES", + "TRILL", "TRINE", "TRINS", "TRIOL", "TRIOR", "TRIOS", "TRIPS", "TRIST", "TROAT", + "TROIS", "TRONA", "TRONC", "TRONE", "TRONS", "TROTH", "TROTS", "TSARS", }; static const uint16_t _num_words = (sizeof(_valid_words) / sizeof(_valid_words[0])); @@ -628,7 +686,7 @@ static void get_result(wordle_state_t *state) { } #if (USE_RANDOM_GUESS != 0) -static const uint16_t _num_unique_words = 157; // The _valid_words array begins with this many words where each letter is different. +static const uint16_t _num_unique_words = 257; // The _valid_words array begins with this many words where each letter is different. static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index ed90988..fec404b 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1223,7 +1223,7 @@ def txt_of_all_letter_combos(num_letters_in_set, words=valid_list, min_letter_oc print_iter = 0 prev = time.time() start = prev - letters_to_ignore = ['D','M','T','Y'] # Don't diplay well on the watch + letters_to_ignore = ['D','M'] # Don't diplay well on the watch letter_usage = most_used_letters(words=words) for letter in letter_usage: if (100 * letter_usage[letter])/len(words) < min_letter_occ_percent_to_consider: @@ -1301,7 +1301,7 @@ def location_of_letters(letters=alphabet, list=valid_list): if __name__ == "__main__": - my_letters = ['A', 'C', 'E', 'G', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S'] + my_letters = ['A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'] #print(f"{len(list_of_valid_words(my_letters, valid_list))} Words can be made with {my_letters}") #most_used_letters() #location_of_letters(my_letters) From 1d2fb20e994d297aa77c12aa29f333343a7ed262 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 24 Aug 2024 10:36:38 -0400 Subject: [PATCH 126/220] Readding comment showing count of possible words. --- movement/watch_faces/complication/wordle_face.c | 1 + utils/wordle_face/wordle_list.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 260860b..7396a17 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -86,6 +86,7 @@ static const char _valid_words[][WORDLE_LENGTH + 1] = { }; // These are words that'll never be used, but still need to be in the dictionary for guesses. +// Number of words found: 1898 static const char _possible_words[][WORDLE_LENGTH + 1] = { "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index fec404b..d951bac 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1171,8 +1171,8 @@ def print_valid_words(letters=alphabet): print("") print(f"// From: {source_link}") print(f"// Number of words found: {len(valid_words)}") - i = 0 print("static const char _valid_words[][WORDLE_LENGTH + 1] = {") + i = 0 while i < len(valid_words): print(" ", end='') for _ in range(min(items_per_row, len(valid_words)-i)): @@ -1184,6 +1184,7 @@ def print_valid_words(letters=alphabet): possible_words = capitalize_all_and_remove_duplicates(possible_words) print("};") print("\n// These are words that'll never be used, but still need to be in the dictionary for guesses.") + print(f"// Number of words found: {len(possible_words)}") print("static const char _possible_words[][WORDLE_LENGTH + 1] = {") i = 0 while i < len(possible_words): From 5149e7e1dd2941b7f9adc740280eb0f3fb4a3c62 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 06:50:00 -0400 Subject: [PATCH 127/220] the dict of words to its own header --- .../watch_faces/complication/wordle_face.c | 279 +---------------- .../watch_faces/complication/wordle_face.h | 5 + .../complication/wordle_face_dict.h | 289 ++++++++++++++++++ 3 files changed, 295 insertions(+), 278 deletions(-) create mode 100644 movement/watch_faces/complication/wordle_face_dict.h diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 7396a17..9ce29bd 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -29,282 +29,6 @@ #include "watch_utility.h" #endif - -static const char _valid_letters[] = {'A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'}; - -// From: https://matthewminer.name/projects/calculators/wordle-words-left/ -// Number of words found: 432 -static const char _valid_words[][WORDLE_LENGTH + 1] = { - "STALE", "TRACE", "CLOSE", "ARISE", "SNIPE", "SHIRE", "LEASH", "SAINT", "CLEAN", - "RELIC", "CHORE", "CRONE", "REACH", "CHAOS", "TAPIR", "CAIRN", "TENOR", "STARE", - "HEART", "SCOPE", "SNARL", "SLEPT", "SINCE", "EPOCH", "SPACE", "SHARE", "SPOIL", - "LITER", "LEAPT", "LANCE", "RANCH", "HORSE", "LEACH", "LATER", "STEAL", "CHEAP", - "SHORT", "ETHIC", "CHANT", "ACTOR", "SPARE", "SEPIA", "ONSET", "SPLAT", "LEANT", - "REACT", "OCTAL", "SPORE", "IRATE", "CORAL", "NICER", "SPILT", "SCENT", "PANIC", - "SHIRT", "PECAN", "SLAIN", "SPLIT", "ROACH", "ASCOT", "PHONE", "LITHE", "STOIC", - "STRIP", "RENAL", "POISE", "ENACT", "CHEAT", "PITCH", "NOISE", "INLET", "PEARL", - "POLAR", "PEACH", "STOLE", "CASTE", "CREST", "SCARE", "ETHOS", "THEIR", "STONE", - "SLATE", "LATCH", "HASTE", "SNARE", "SPINE", "SLANT", "SPEAR", "SCALE", "CAPER", - "RETCH", "PESTO", "CHIRP", "SPORT", "OPTIC", "SNAIL", "PRICE", "PLANE", "TORCH", - "PASTE", "RECAP", "SOLAR", "CRASH", "LINER", "OPINE", "ASHEN", "PALER", "ECLAT", - "SPELT", "TRIAL", "PERIL", "SLICE", "SCANT", "RAISE", "POSIT", "ATONE", "SPIRE", - "COAST", "INEPT", "SHOAL", "CLASH", "THORN", "PHASE", "SCORE", "TRICE", "PERCH", - "PORCH", "SHEAR", "CHOIR", "RHINO", "PLANT", "SHONE", "SANER", "LEARN", "ALTER", - "CHAIN", "PANEL", "PLIER", "STEIN", "COPSE", "SONIC", "ALIEN", "CHOSE", "ACORN", - "ANTIC", "CHEST", "OTHER", "CHINA", "TALON", "SCORN", "PLAIN", "PILOT", "RIPEN", - "PATCH", "SPICE", "CLONE", "SCION", "SCONE", "STRAP", "PARSE", "SHALE", "RISEN", - "CANOE", "INTER", "CRATE", "ISLET", "PRINT", "SHINE", "NORTH", "CLEAT", "PLAIT", - "SCRAP", "CLEAR", "SLOTH", "LAPSE", "CHAIR", "SNORT", "SHARP", "OPERA", "STAIN", - "TEACH", "TRAIL", "TRAIN", "LATHE", "PIANO", "PINCH", "PETAL", "STERN", "PRONE", - "PROSE", "PLEAT", "TROPE", "PLACE", "POSER", "INERT", "CHASE", "CAROL", "STAIR", - "SATIN", "SPITE", "LOATH", "ROAST", "ARSON", "SHAPE", "CLASP", "LOSER", "SALON", - "CATER", "SHALT", "INTRO", "ALERT", "PENAL", "SHORE", "RINSE", "CREPT", "APRON", - "SONAR", "AISLE", "AROSE", "HATER", "NICHE", "POINT", "EARTH", "PINTO", "THOSE", - "CLOTH", "NOTCH", "TOPIC", "RESIN", "SCALP", "HEIST", "HERON", "TRIPE", "TONAL", - "TAPER", "SHORN", "TONIC", "HOIST", "SNORE", "STORE", "SLOPE", "OCEAN", "CHART", - "PAINT", "SPENT", "CRANE", "CRISP", "TRASH", "PATIO", "PLATE", "HOTEL", "LEAST", - "ALONE", "RALPH", "SPIEL", "SIREN", "RATIO", "STOOP", "TROLL", "ATOLL", "SLASH", - "RETRO", "CREEP", "STILT", "SPREE", "TASTE", "CACHE", "CANON", "EATEN", "TEPEE", - "SHEET", "SNEER", "ERROR", "NATAL", "SLEEP", "STINT", "TROOP", "SHALL", "STALL", - "PIPER", "TOAST", "NASAL", "CORER", "THERE", "POOCH", "SCREE", "ELITE", "ALTAR", - "PENCE", "EATER", "ALPHA", "TENTH", "LINEN", "SHEER", "TAINT", "HEATH", "CRIER", - "TENSE", "CARAT", "CANAL", "APNEA", "THESE", "HATCH", "SHELL", "CIRCA", "APART", - "SPILL", "STEEL", "LOCAL", "STOOL", "SHEEN", "RESET", "STEEP", "ELATE", "PRESS", - "SLEET", "CROSS", "TOTAL", "TREAT", "ONION", "STATE", "CINCH", "ASSET", "THREE", - "TORSO", "SNOOP", "PENNE", "SPOON", "SHEEP", "PAPAL", "STILL", "CHILL", "THETA", - "LEECH", "INNER", "HONOR", "LOOSE", "CONIC", "SCENE", "COACH", "CONCH", "LATTE", - "ERASE", "ESTER", "PEACE", "PASTA", "INANE", "SPOOL", "TEASE", "HARSH", "PIECE", - "STEER", "SCOOP", "NINTH", "OTTER", "OCTET", "EERIE", "RISER", "LAPEL", "HIPPO", - "PREEN", "ETHER", "AORTA", "SENSE", "TRACT", "SHOOT", "SLOOP", "REPEL", "TITHE", - "IONIC", "CELLO", "CHESS", "SOOTH", "COCOA", "TITAN", "TOOTH", "TIARA", "CRESS", - "SLOSH", "RARER", "TERSE", "ERECT", "HELLO", "PARER", "RIPER", "NOOSE", "CREPE", - "CACAO", "ILIAC", "POSSE", "CACTI", "EASEL", "LASSO", "ROOST", "ALLOT", "COLON", - "LEPER", "TEETH", "TITLE", "HENCE", "NIECE", "PAPER", "TRITE", "SPELL", "RACER", - "ATTIC", "CRASS", "HITCH", "LEASE", "CEASE", "ROTOR", "ELOPE", "APPLE", "CHILI", - "START", "PHOTO", "SALSA", "STASH", "PRIOR", "TAROT", "COLOR", "CHEER", "CLASS", - "ARENA", "ELECT", "ENTER", "CATCH", "TENET", "TACIT", "TRAIT", "TERRA", "LILAC", -}; - -// These are words that'll never be used, but still need to be in the dictionary for guesses. -// Number of words found: 1898 -static const char _possible_words[][WORDLE_LENGTH + 1] = { - "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", - "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", - "AERIE", "AEROS", "AESIR", "AHEAP", "AHENT", "AHINT", "AINEE", "AIOLI", "AIRER", - "AIRNS", "AIRTH", "AIRTS", "AITCH", "ALAAP", "ALANE", "ALANS", "ALANT", "ALAPA", - "ALAPS", "ALATE", "ALCOS", "ALECS", "ALEPH", "ALIAS", "ALINE", "ALIST", "ALLEE", - "ALLEL", "ALLIS", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ALTHO", "ALTOS", "ANANA", - "ANATA", "ANCHO", "ANCLE", "ANCON", "ANEAR", "ANELE", "ANENT", "ANILE", "ANILS", - "ANION", "ANISE", "ANLAS", "ANNAL", "ANNAS", "ANNAT", "ANOAS", "ANOLE", "ANSAE", - "ANTAE", "ANTAR", "ANTAS", "ANTES", "ANTIS", "ANTRA", "ANTRE", "APACE", "APERS", - "APERT", "APHIS", "APIAN", "APIOL", "APISH", "APOOP", "APORT", "APPAL", "APPEL", - "APPRO", "APRES", "APSES", "APSIS", "APSOS", "APTER", "ARARS", "ARCHI", "ARCOS", - "AREAE", "AREAL", "AREAR", "AREAS", "ARECA", "AREIC", "ARENE", "AREPA", "ARERE", - "ARETE", "ARETS", "ARETT", "ARHAT", "ARIAS", "ARIEL", "ARILS", "ARIOT", "ARISH", - "ARLES", "ARNAS", "AROHA", "ARPAS", "ARPEN", "ARRAH", "ARRAS", "ARRET", "ARRIS", - "ARSES", "ARSIS", "ARTAL", "ARTEL", "ARTIC", "ARTIS", "ASANA", "ASCON", "ASHES", - "ASHET", "ASPEN", "ASPER", "ASPIC", "ASPIE", "ASPIS", "ASPRO", "ASSAI", "ASSES", - "ASSOT", "ASTER", "ASTIR", "ATAPS", "ATILT", "ATLAS", "ATOCS", "ATRIA", "ATRIP", - "ATTAP", "ATTAR", "CACAS", "CAECA", "CAESE", "CAINS", "CALLA", "CALLS", "CALOS", - "CALPA", "CALPS", "CANEH", "CANER", "CANES", "CANNA", "CANNS", "CANSO", "CANST", - "CANTO", "CANTS", "CAPAS", "CAPES", "CAPHS", "CAPLE", "CAPON", "CAPOS", "CAPOT", - "CAPRI", "CARAP", "CARER", "CARES", "CARET", "CARLE", "CARLS", "CARNS", "CARON", - "CARPI", "CARPS", "CARRS", "CARSE", "CARTA", "CARTE", "CARTS", "CASAS", "CASCO", - "CASES", "CASTS", "CATES", "CECAL", "CEILI", "CEILS", "CELLA", "CELLI", "CELLS", - "CELTS", "CENSE", "CENTO", "CENTS", "CEORL", "CEPES", "CERCI", "CERES", "CERIA", - "CERIC", "CERNE", "CEROC", "CEROS", "CERTS", "CESSE", "CESTA", "CESTI", "CETES", - "CHACE", "CHACO", "CHAIS", "CHALS", "CHANA", "CHAPE", "CHAPS", "CHAPT", "CHARA", - "CHARE", "CHARR", "CHARS", "CHATS", "CHEEP", "CHELA", "CHELP", "CHERE", "CHERT", - "CHETH", "CHIAO", "CHIAS", "CHICA", "CHICH", "CHICO", "CHICS", "CHIEL", "CHILE", - "CHINE", "CHINO", "CHINS", "CHIPS", "CHIRL", "CHIRO", "CHIRR", "CHIRT", "CHITS", - "CHOCO", "CHOCS", "CHOIL", "CHOLA", "CHOLI", "CHOLO", "CHONS", "CHOON", "CHOPS", - "CHOTA", "CHOTT", "CIELS", "CILIA", "CILLS", "CINCT", "CINES", "CIONS", "CIPPI", - "CIRCS", "CIRES", "CIRLS", "CIRRI", "CISCO", "CISTS", "CITAL", "CITER", "CITES", - "CLACH", "CLAES", "CLANS", "CLAPS", "CLAPT", "CLARO", "CLART", "CLAST", "CLATS", - "CLEEP", "CLEPE", "CLEPT", "CLIES", "CLINE", "CLINT", "CLIPE", "CLIPS", "CLIPT", - "CLITS", "CLONS", "CLOOP", "CLOOT", "CLOPS", "CLOTE", "CLOTS", "COACT", "COALA", - "COALS", "COAPT", "COATE", "COATI", "COATS", "COCAS", "COCCI", "COCCO", "COCOS", - "COHEN", "COHOE", "COHOS", "COILS", "COINS", "COIRS", "COITS", "COLAS", "COLES", - "COLIC", "COLIN", "COLLS", "COLTS", "CONES", "CONIA", "CONIN", "CONNE", "CONNS", - "CONTE", "CONTO", "COOCH", "COOEE", "COOER", "COOLS", "COONS", "COOPS", "COOPT", - "COOST", "COOTS", "COPAL", "COPEN", "COPER", "COPES", "COPRA", "CORES", "CORIA", - "CORNI", "CORNO", "CORNS", "CORPS", "CORSE", "CORSO", "COSEC", "COSES", "COSET", - "COSIE", "COSTA", "COSTE", "COSTS", "COTAN", "COTES", "COTHS", "COTTA", "COTTS", - "CRAAL", "CRAIC", "CRANS", "CRAPE", "CRAPS", "CRARE", "CREEL", "CREES", "CRENA", - "CREPS", "CRIAS", "CRIES", "CRINE", "CRIOS", "CRIPE", "CRIPS", "CRISE", "CRITH", - "CRITS", "CROCI", "CROCS", "CRONS", "CROOL", "CROON", "CROPS", "CRORE", "CROST", - "CTENE", "EALES", "EARLS", "EARNS", "EARNT", "EARST", "EASER", "EASES", "EASLE", - "EASTS", "EATHE", "ECHES", "ECHOS", "EISEL", "ELAIN", "ELANS", "ELCHI", "ELINT", - "ELOIN", "ELOPS", "ELPEE", "ELSIN", "ENATE", "ENIAC", "ENLIT", "ENOLS", "ENROL", - "ENTIA", "EORLS", "EOSIN", "EPACT", "EPEES", "EPHAH", "EPHAS", "EPHOR", "EPICS", - "EPOPT", "EPRIS", "ERICA", "ERICS", "ERNES", "EROSE", "ERSES", "ESCAR", "ESCOT", - "ESILE", "ESNES", "ESSES", "ESTOC", "ESTOP", "ESTRO", "ETAPE", "ETATS", "ETENS", - "ETHAL", "ETHNE", "ETICS", "ETNAS", "ETTIN", "ETTLE", "HAARS", "HAETS", "HAHAS", - "HAILS", "HAINS", "HAINT", "HAIRS", "HAITH", "HALAL", "HALER", "HALES", "HALLO", - "HALLS", "HALON", "HALOS", "HALSE", "HALTS", "HANAP", "HANCE", "HANCH", "HANSA", - "HANSE", "HANTS", "HAOLE", "HAPPI", "HARES", "HARLS", "HARNS", "HAROS", "HARPS", - "HARTS", "HASPS", "HASTA", "HATES", "HATHA", "HEALS", "HEAPS", "HEARE", "HEARS", - "HEAST", "HEATS", "HECHT", "HEELS", "HEILS", "HEIRS", "HELES", "HELIO", "HELLS", - "HELOS", "HELOT", "HELPS", "HENCH", "HENNA", "HENTS", "HEPAR", "HERES", "HERLS", - "HERNS", "HEROS", "HERSE", "HESPS", "HESTS", "HETES", "HETHS", "HIANT", "HILAR", - "HILCH", "HILLO", "HILLS", "HILTS", "HINTS", "HIOIS", "HIREE", "HIRER", "HIRES", - "HISTS", "HITHE", "HOARS", "HOAST", "HOERS", "HOISE", "HOLES", "HOLLA", "HOLLO", - "HOLON", "HOLOS", "HOLTS", "HONAN", "HONER", "HONES", "HOOCH", "HOONS", "HOOPS", - "HOORS", "HOOSH", "HOOTS", "HOPER", "HOPES", "HORAH", "HORAL", "HORAS", "HORIS", - "HORNS", "HORST", "HOSEL", "HOSEN", "HOSER", "HOSES", "HOSTA", "HOSTS", "HOTCH", - "HOTEN", "ICERS", "ICHES", "ICHOR", "ICIER", "ICONS", "ICTAL", "ICTIC", "ILEAC", - "ILEAL", "ILIAL", "ILLER", "ILLTH", "INAPT", "INCEL", "INCLE", "INION", "INNIT", - "INSET", "INSPO", "INTEL", "INTIL", "INTIS", "INTRA", "IOTAS", "IPPON", "IRONE", - "IRONS", "ISHES", "ISLES", "ISNAE", "ISSEI", "ISTLE", "ITHER", "LAARI", "LACER", - "LACES", "LACET", "LAERS", "LAHAL", "LAHAR", "LAICH", "LAICS", "LAIRS", "LAITH", - "LALLS", "LANAI", "LANAS", "LANCH", "LANES", "LANTS", "LAPIN", "LAPIS", "LARCH", - "LAREE", "LARES", "LARIS", "LARNS", "LARNT", "LASER", "LASES", "LASSI", "LASTS", - "LATAH", "LATEN", "LATHI", "LATHS", "LEANS", "LEAPS", "LEARE", "LEARS", "LEATS", - "LEEAR", "LEEPS", "LEERS", "LEESE", "LEETS", "LEHRS", "LEIRS", "LEISH", "LENES", - "LENIS", "LENOS", "LENSE", "LENTI", "LENTO", "LEONE", "LEPRA", "LEPTA", "LERES", - "LERPS", "LESES", "LESTS", "LETCH", "LETHE", "LIANA", "LIANE", "LIARS", "LIART", - "LICHI", "LICHT", "LICIT", "LIENS", "LIERS", "LILLS", "LILOS", "LILTS", "LINAC", - "LINCH", "LINES", "LININ", "LINNS", "LINOS", "LINTS", "LIONS", "LIPAS", "LIPES", - "LIPIN", "LIPOS", "LIRAS", "LIROT", "LISLE", "LISPS", "LISTS", "LITAI", "LITAS", - "LITES", "LITHO", "LITHS", "LITRE", "LLANO", "LOACH", "LOANS", "LOAST", "LOCHE", - "LOCHS", "LOCIE", "LOCIS", "LOCOS", "LOESS", "LOHAN", "LOINS", "LOIPE", "LOIRS", - "LOLLS", "LONER", "LOOIE", "LOONS", "LOOPS", "LOOTS", "LOPER", "LOPES", "LORAL", - "LORAN", "LOREL", "LORES", "LORIC", "LORIS", "LOSEL", "LOSEN", "LOSES", "LOTAH", - "LOTAS", "LOTES", "LOTIC", "LOTOS", "LOTSA", "LOTTA", "LOTTE", "LOTTO", "NAANS", - "NACHE", "NACHO", "NACRE", "NAHAL", "NAILS", "NAIRA", "NALAS", "NALLA", "NANAS", - "NANCE", "NANNA", "NANOS", "NAPAS", "NAPES", "NAPOO", "NAPPA", "NAPPE", "NARAS", - "NARCO", "NARCS", "NARES", "NARIC", "NARIS", "NARRE", "NASHI", "NATCH", "NATES", - "NATIS", "NEALS", "NEAPS", "NEARS", "NEATH", "NEATS", "NEELE", "NEEPS", "NEESE", - "NEIST", "NELIS", "NENES", "NEONS", "NEPER", "NEPIT", "NERAL", "NEROL", "NERTS", - "NESTS", "NETES", "NETOP", "NETTS", "NICHT", "NICOL", "NIHIL", "NILLS", "NINER", - "NINES", "NINON", "NIPAS", "NIRLS", "NISEI", "NISSE", "NITER", "NITES", "NITON", - "NITRE", "NITRO", "NOAHS", "NOELS", "NOILS", "NOINT", "NOIRS", "NOLES", "NOLLS", - "NOLOS", "NONAS", "NONCE", "NONES", "NONET", "NONIS", "NOOIT", "NOONS", "NOOPS", - "NOPAL", "NORIA", "NORIS", "NOSER", "NOSES", "NOTAL", "NOTER", "NOTES", "OASES", - "OASIS", "OASTS", "OATEN", "OATER", "OATHS", "OCHER", "OCHES", "OCHRE", "OCREA", - "OCTAN", "OCTAS", "OHIAS", "OHONE", "OILER", "OINTS", "OLEIC", "OLEIN", "OLENT", - "OLEOS", "OLIOS", "OLLAS", "OLLER", "OLLIE", "OLPAE", "OLPES", "ONCER", "ONCES", - "ONCET", "ONERS", "ONTIC", "OONTS", "OORIE", "OOSES", "OPAHS", "OPALS", "OPENS", - "OPEPE", "OPPOS", "OPSIN", "OPTER", "ORACH", "ORALS", "ORANT", "ORATE", "ORCAS", - "ORCIN", "ORIEL", "ORLES", "ORLON", "ORLOP", "ORNIS", "ORPIN", "ORRIS", "ORTHO", - "OSCAR", "OSHAC", "OSIER", "OSSIA", "OSTIA", "OTTAR", "OTTOS", "PAALS", "PAANS", - "PACAS", "PACER", "PACES", "PACHA", "PACOS", "PACTA", "PACTS", "PAEAN", "PAEON", - "PAILS", "PAINS", "PAIRE", "PAIRS", "PAISA", "PAISE", "PALAS", "PALEA", "PALES", - "PALET", "PALIS", "PALLA", "PALLS", "PALPI", "PALPS", "PALSA", "PANCE", "PANES", - "PANNE", "PANNI", "PANTO", "PANTS", "PAOLI", "PAOLO", "PAPAS", "PAPES", "PAPPI", - "PARAE", "PARAS", "PARCH", "PAREN", "PAREO", "PARES", "PARIS", "PARLE", "PAROL", - "PARPS", "PARRA", "PARRS", "PARTI", "PARTS", "PASEO", "PASES", "PASHA", "PASSE", - "PASTS", "PATEN", "PATER", "PATES", "PATHS", "PATIN", "PATTE", "PEALS", "PEANS", - "PEARE", "PEARS", "PEART", "PEASE", "PEATS", "PECHS", "PEECE", "PEELS", "PEENS", - "PEEPE", "PEEPS", "PEERS", "PEINS", "PEISE", "PELAS", "PELES", "PELLS", "PELON", - "PELTA", "PELTS", "PENES", "PENIE", "PENIS", "PENNA", "PENNI", "PENTS", "PEONS", - "PEPLA", "PEPOS", "PEPSI", "PERAI", "PERCE", "PERCS", "PEREA", "PERES", "PERIS", - "PERNS", "PERPS", "PERSE", "PERST", "PERTS", "PESOS", "PESTS", "PETAR", "PETER", - "PETIT", "PETRE", "PETRI", "PETTI", "PETTO", "PHARE", "PHEER", "PHENE", "PHEON", - "PHESE", "PHIAL", "PHISH", "PHOCA", "PHONO", "PHONS", "PHOTS", "PHPHT", "PIANI", - "PIANS", "PICAL", "PICAS", "PICOT", "PICRA", "PIERS", "PIERT", "PIETA", "PIETS", - "PILAE", "PILAO", "PILAR", "PILCH", "PILEA", "PILEI", "PILER", "PILES", "PILIS", - "PILLS", "PINAS", "PINES", "PINNA", "PINON", "PINOT", "PINTA", "PINTS", "PIONS", - "PIPAL", "PIPAS", "PIPES", "PIPET", "PIPIS", "PIPIT", "PIRAI", "PIRLS", "PIRNS", - "PISCO", "PISES", "PISOS", "PISTE", "PITAS", "PITHS", "PITON", "PITOT", "PITTA", - "PLAAS", "PLANS", "PLAPS", "PLASH", "PLAST", "PLATS", "PLATT", "PLEAS", "PLENA", - "PLEON", "PLESH", "PLICA", "PLIES", "PLOAT", "PLOPS", "PLOTS", "POACH", "POEPS", - "POETS", "POLER", "POLES", "POLIO", "POLIS", "POLLS", "POLOS", "POLTS", "PONCE", - "PONES", "PONTS", "POOHS", "POOLS", "POONS", "POOPS", "POORI", "POORT", "POOTS", - "POPES", "POPPA", "PORAE", "PORAL", "PORER", "PORES", "PORIN", "PORNO", "PORNS", - "PORTA", "PORTS", "POSES", "POSHO", "POSTS", "POTAE", "POTCH", "POTES", "POTIN", - "POTOO", "POTTO", "POTTS", "PRANA", "PRAOS", "PRASE", "PRATE", "PRATS", "PRATT", - "PREES", "PRENT", "PREON", "PREOP", "PREPS", "PRESA", "PRESE", "PREST", "PRIAL", - "PRIER", "PRIES", "PRILL", "PRION", "PRISE", "PRISS", "PROAS", "PROIN", "PROLE", - "PROLL", "PROPS", "PRORE", "PROSO", "PROSS", "PROST", "PROTO", "PSION", "PSOAE", - "PSOAI", "PSOAS", "PSORA", "RACES", "RACHE", "RACON", "RAIAS", "RAILE", "RAILS", - "RAINE", "RAINS", "RAITA", "RAITS", "RALES", "RANAS", "RANCE", "RANEE", "RANIS", - "RANTS", "RAPER", "RAPES", "RAPHE", "RAPPE", "RAREE", "RARES", "RASER", "RASES", - "RASPS", "RASSE", "RASTA", "RATAL", "RATAN", "RATAS", "RATCH", "RATEL", "RATER", - "RATES", "RATHA", "RATHE", "RATHS", "RATOO", "RATOS", "REAIS", "REALO", "REALS", - "REANS", "REAPS", "REARS", "REAST", "REATA", "REATE", "RECAL", "RECCE", "RECCO", - "RECIT", "RECON", "RECTA", "RECTI", "RECTO", "REECH", "REELS", "REENS", "REEST", - "REINS", "REIST", "RELET", "RELIE", "RELIT", "RELLO", "RENIN", "RENNE", "RENOS", - "RENTE", "RENTS", "REOIL", "REPIN", "REPLA", "REPOS", "REPOT", "REPPS", "REPRO", - "RERAN", "RESAT", "RESEE", "RESES", "RESIT", "RESTO", "RESTS", "RETIA", "RETIE", - "RHEAS", "RHIES", "RHINE", "RHONE", "RIALS", "RIANT", "RIATA", "RICER", "RICES", - "RICHT", "RICIN", "RIELS", "RILES", "RILLE", "RILLS", "RINES", "RIOTS", "RIPES", - "RIPPS", "RISES", "RISHI", "RISPS", "RITES", "RITTS", "ROANS", "ROARS", "ROATE", - "ROHES", "ROILS", "ROINS", "ROIST", "ROLES", "ROLLS", "RONEO", "RONES", "RONIN", - "RONNE", "RONTE", "RONTS", "ROONS", "ROOPS", "ROOSA", "ROOSE", "ROOTS", "ROPER", - "ROPES", "RORAL", "RORES", "RORIC", "RORIE", "RORTS", "ROSES", "ROSET", "ROSHI", - "ROSIN", "ROSIT", "ROSTI", "ROSTS", "ROTAL", "ROTAN", "ROTAS", "ROTCH", "ROTES", - "ROTIS", "ROTLS", "ROTON", "ROTOS", "ROTTE", "SACRA", "SAICE", "SAICS", "SAILS", - "SAINE", "SAINS", "SAIRS", "SAIST", "SAITH", "SALAL", "SALAT", "SALEP", "SALES", - "SALET", "SALIC", "SALLE", "SALOL", "SALOP", "SALPA", "SALPS", "SALSE", "SALTO", - "SALTS", "SANES", "SANSA", "SANTO", "SANTS", "SAOLA", "SAPAN", "SAPOR", "SARAN", - "SAREE", "SARIN", "SARIS", "SAROS", "SASER", "SASIN", "SASSE", "SATAI", "SATES", - "SATIS", "SCAIL", "SCALA", "SCALL", "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", - "SCARS", "SCART", "SCATH", "SCATS", "SCATT", "SCEAT", "SCENA", "SCOOT", "SCOPA", - "SCOPS", "SCOTS", "SCRAE", "SCRAN", "SCRAT", "SCRIP", "SEALS", "SEANS", "SEARE", - "SEARS", "SEASE", "SEATS", "SECCO", "SECHS", "SECTS", "SEELS", "SEEPS", "SEERS", - "SEHRI", "SEILS", "SEINE", "SEIRS", "SEISE", "SELAH", "SELES", "SELLA", "SELLE", - "SELLS", "SENAS", "SENES", "SENNA", "SENOR", "SENSA", "SENSI", "SENTE", "SENTI", - "SENTS", "SEPAL", "SEPIC", "SEPTA", "SEPTS", "SERAC", "SERAI", "SERAL", "SERER", - "SERES", "SERIC", "SERIN", "SERON", "SERRA", "SERRE", "SERRS", "SESSA", "SETAE", - "SETAL", "SETON", "SETTS", "SHAHS", "SHANS", "SHAPS", "SHARN", "SHASH", "SHCHI", - "SHEAL", "SHEAS", "SHEEL", "SHENT", "SHEOL", "SHERE", "SHERO", "SHETS", "SHIAI", - "SHIEL", "SHIER", "SHIES", "SHILL", "SHINS", "SHIPS", "SHIRR", "SHIRS", "SHISH", - "SHISO", "SHIST", "SHITE", "SHITS", "SHLEP", "SHOAT", "SHOER", "SHOES", "SHOLA", - "SHOOL", "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", "SHOTE", "SHOTS", "SHOTT", - "SHRIS", "SIALS", "SICES", "SICHT", "SIENS", "SIENT", "SIETH", "SILEN", "SILER", - "SILES", "SILLS", "SILOS", "SILTS", "SINES", "SINHS", "SIPES", "SIREE", "SIRES", - "SIRIH", "SIRIS", "SIROC", "SIRRA", "SISAL", "SISES", "SISTA", "SISTS", "SITAR", - "SITES", "SITHE", "SLAES", "SLANE", "SLAPS", "SLART", "SLATS", "SLEER", "SLIER", - "SLIPE", "SLIPS", "SLIPT", "SLISH", "SLITS", "SLOAN", "SLOES", "SLOOT", "SLOPS", - "SLOTS", "SNAPS", "SNARS", "SNASH", "SNATH", "SNEAP", "SNEES", "SNELL", "SNIES", - "SNIPS", "SNIRT", "SNITS", "SNOEP", "SNOOL", "SNOOT", "SNOTS", "SOAPS", "SOARE", - "SOARS", "SOCAS", "SOCES", "SOCLE", "SOILS", "SOLAH", "SOLAN", "SOLAS", "SOLEI", - "SOLER", "SOLES", "SOLON", "SOLOS", "SONCE", "SONES", "SONNE", "SONSE", "SOOLE", - "SOOLS", "SOOPS", "SOOTE", "SOOTS", "SOPHS", "SOPOR", "SOPRA", "SORAL", "SORAS", - "SOREE", "SOREL", "SORER", "SORES", "SORNS", "SORRA", "SORTA", "SORTS", "SOTHS", - "SOTOL", "SPAER", "SPAES", "SPAHI", "SPAIL", "SPAIN", "SPAIT", "SPALE", "SPALL", - "SPALT", "SPANE", "SPANS", "SPARS", "SPART", "SPATE", "SPATS", "SPEAL", "SPEAN", - "SPEAT", "SPECS", "SPECT", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEOS", "SPETS", - "SPIAL", "SPICA", "SPICS", "SPIER", "SPIES", "SPILE", "SPINA", "SPINS", "SPIRT", - "SPITS", "SPOOR", "SPOOT", "SPOSH", "SPOTS", "SPRAT", "SPRIT", "STANE", "STAPH", - "STAPS", "STARN", "STARR", "STARS", "STATS", "STEAN", "STEAR", "STEEN", "STEIL", - "STELA", "STELE", "STELL", "STENO", "STENS", "STENT", "STEPS", "STEPT", "STERE", - "STETS", "STICH", "STIES", "STILE", "STIPA", "STIPE", "STIRE", "STIRP", "STIRS", - "STOAE", "STOAI", "STOAS", "STOAT", "STOEP", "STOIT", "STOLN", "STONN", "STOOR", - "STOPE", "STOPS", "STOPT", "STOSS", "STOTS", "STOTT", "STRAE", "STREP", "STRIA", - "STROP", "TAALS", "TAATA", "TACAN", "TACES", "TACET", "TACHE", "TACHO", "TACHS", - "TACOS", "TACTS", "TAELS", "TAHAS", "TAHRS", "TAILS", "TAINS", "TAIRA", "TAISH", - "TAITS", "TALAR", "TALAS", "TALCS", "TALEA", "TALER", "TALES", "TALLS", "TALPA", - "TANAS", "TANHS", "TANNA", "TANTI", "TANTO", "TAPAS", "TAPEN", "TAPES", "TAPET", - "TAPIS", "TAPPA", "TARAS", "TARES", "TARNS", "TAROC", "TAROS", "TARPS", "TARRE", - "TARSI", "TARTS", "TASAR", "TASER", "TASES", "TASSA", "TASSE", "TASSO", "TATAR", - "TATER", "TATES", "TATHS", "TATIE", "TATTS", "TEALS", "TEARS", "TEATS", "TECHS", - "TECTA", "TEELS", "TEENE", "TEENS", "TEERS", "TEHRS", "TEILS", "TEINS", "TELAE", - "TELCO", "TELES", "TELIA", "TELIC", "TELLS", "TELOI", "TELOS", "TENCH", "TENES", - "TENIA", "TENNE", "TENNO", "TENON", "TENTS", "TEPAL", "TEPAS", "TERAI", "TERAS", - "TERCE", "TERES", "TERNE", "TERNS", "TERTS", "TESLA", "TESTA", "TESTE", "TESTS", - "TETES", "TETHS", "TETRA", "TETRI", "THALE", "THALI", "THANA", "THANE", "THANS", - "THARS", "THECA", "THEES", "THEIC", "THEIN", "THENS", "THESP", "THETE", "THILL", - "THINE", "THINS", "THIOL", "THIRL", "THOLE", "THOLI", "THORO", "THORP", "THRAE", - "THRIP", "THROE", "TIANS", "TIARS", "TICAL", "TICCA", "TICES", "TIERS", "TILER", - "TILES", "TILLS", "TILTH", "TILTS", "TINAS", "TINCT", "TINEA", "TINES", "TINTS", - "TIPIS", "TIRES", "TIRLS", "TIROS", "TIRRS", "TITCH", "TITER", "TITIS", "TITRE", - "TOCOS", "TOEAS", "TOHOS", "TOILE", "TOILS", "TOISE", "TOITS", "TOLAN", "TOLAR", - "TOLAS", "TOLES", "TOLLS", "TOLTS", "TONER", "TONES", "TONNE", "TOOLS", "TOONS", - "TOOTS", "TOPEE", "TOPER", "TOPES", "TOPHE", "TOPHI", "TOPHS", "TOPIS", "TOPOI", - "TOPOS", "TORAH", "TORAN", "TORAS", "TORCS", "TORES", "TORIC", "TORII", "TOROS", - "TOROT", "TORRS", "TORSE", "TORSI", "TORTA", "TORTE", "TORTS", "TOSAS", "TOSES", - "TOTER", "TOTES", "TRANS", "TRANT", "TRAPE", "TRAPS", "TRAPT", "TRASS", "TRATS", - "TRATT", "TREEN", "TREES", "TRESS", "TREST", "TRETS", "TRIAC", "TRIER", "TRIES", - "TRILL", "TRINE", "TRINS", "TRIOL", "TRIOR", "TRIOS", "TRIPS", "TRIST", "TROAT", - "TROIS", "TRONA", "TRONC", "TRONE", "TRONS", "TROTH", "TROTS", "TSARS", -}; - -static const uint16_t _num_words = (sizeof(_valid_words) / sizeof(_valid_words[0])); -static const uint16_t _num_possible_words = (sizeof(_possible_words) / sizeof(_possible_words[0])); -static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); - static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ return rand() % max; @@ -687,11 +411,10 @@ static void get_result(wordle_state_t *state) { } #if (USE_RANDOM_GUESS != 0) -static const uint16_t _num_unique_words = 257; // The _valid_words array begins with this many words where each letter is different. static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer - random_guess = get_random(USE_RANDOM_GUESS == 2 ? _num_unique_words : _num_words); + random_guess = get_random(_num_random_guess_words); } while (random_guess == state->curr_answer); for (size_t i = 0; i < WORDLE_LENGTH; i++) { for (size_t j = 0; j < _num_valid_letters; j++) diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 62a4571..6ae1fbd 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -70,6 +70,11 @@ * 2 = Allow using a random guess of any value that can be an answer where all of its letters are unique */ #define USE_RANDOM_GUESS 2 +#include "wordle_face_dict.h" + +static const uint16_t _num_words = (sizeof(_valid_words) / sizeof(_valid_words[0])); +static const uint16_t _num_possible_words = (sizeof(_possible_words) / sizeof(_possible_words[0])); +static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); typedef enum { WORDLE_LETTER_WRONG = 0, diff --git a/movement/watch_faces/complication/wordle_face_dict.h b/movement/watch_faces/complication/wordle_face_dict.h new file mode 100644 index 0000000..1942b16 --- /dev/null +++ b/movement/watch_faces/complication/wordle_face_dict.h @@ -0,0 +1,289 @@ +#ifndef WORDLE_FACE_DICT_H_ +#define WORDLE_FACE_DICT_H_ + +#ifndef WORDLE_LENGTH +#define WORDLE_LENGTH 5 +#endif + +#ifndef USE_RANDOM_GUESS +#define USE_RANDOM_GUESS 2 +#endif + +static const char _valid_letters[] = {'A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'}; + +// From: https://matthewminer.name/projects/calculators/wordle-words-left/ +// Number of words found: 432 +static const char _valid_words[][WORDLE_LENGTH + 1] = { + "STALE", "TRACE", "CLOSE", "ARISE", "SNIPE", "SHIRE", "LEASH", "SAINT", "CLEAN", + "RELIC", "CHORE", "CRONE", "REACH", "CHAOS", "TAPIR", "CAIRN", "TENOR", "STARE", + "HEART", "SCOPE", "SNARL", "SLEPT", "SINCE", "EPOCH", "SPACE", "SHARE", "SPOIL", + "LITER", "LEAPT", "LANCE", "RANCH", "HORSE", "LEACH", "LATER", "STEAL", "CHEAP", + "SHORT", "ETHIC", "CHANT", "ACTOR", "SPARE", "SEPIA", "ONSET", "SPLAT", "LEANT", + "REACT", "OCTAL", "SPORE", "IRATE", "CORAL", "NICER", "SPILT", "SCENT", "PANIC", + "SHIRT", "PECAN", "SLAIN", "SPLIT", "ROACH", "ASCOT", "PHONE", "LITHE", "STOIC", + "STRIP", "RENAL", "POISE", "ENACT", "CHEAT", "PITCH", "NOISE", "INLET", "PEARL", + "POLAR", "PEACH", "STOLE", "CASTE", "CREST", "SCARE", "ETHOS", "THEIR", "STONE", + "SLATE", "LATCH", "HASTE", "SNARE", "SPINE", "SLANT", "SPEAR", "SCALE", "CAPER", + "RETCH", "PESTO", "CHIRP", "SPORT", "OPTIC", "SNAIL", "PRICE", "PLANE", "TORCH", + "PASTE", "RECAP", "SOLAR", "CRASH", "LINER", "OPINE", "ASHEN", "PALER", "ECLAT", + "SPELT", "TRIAL", "PERIL", "SLICE", "SCANT", "RAISE", "POSIT", "ATONE", "SPIRE", + "COAST", "INEPT", "SHOAL", "CLASH", "THORN", "PHASE", "SCORE", "TRICE", "PERCH", + "PORCH", "SHEAR", "CHOIR", "RHINO", "PLANT", "SHONE", "SANER", "LEARN", "ALTER", + "CHAIN", "PANEL", "PLIER", "STEIN", "COPSE", "SONIC", "ALIEN", "CHOSE", "ACORN", + "ANTIC", "CHEST", "OTHER", "CHINA", "TALON", "SCORN", "PLAIN", "PILOT", "RIPEN", + "PATCH", "SPICE", "CLONE", "SCION", "SCONE", "STRAP", "PARSE", "SHALE", "RISEN", + "CANOE", "INTER", "CRATE", "ISLET", "PRINT", "SHINE", "NORTH", "CLEAT", "PLAIT", + "SCRAP", "CLEAR", "SLOTH", "LAPSE", "CHAIR", "SNORT", "SHARP", "OPERA", "STAIN", + "TEACH", "TRAIL", "TRAIN", "LATHE", "PIANO", "PINCH", "PETAL", "STERN", "PRONE", + "PROSE", "PLEAT", "TROPE", "PLACE", "POSER", "INERT", "CHASE", "CAROL", "STAIR", + "SATIN", "SPITE", "LOATH", "ROAST", "ARSON", "SHAPE", "CLASP", "LOSER", "SALON", + "CATER", "SHALT", "INTRO", "ALERT", "PENAL", "SHORE", "RINSE", "CREPT", "APRON", + "SONAR", "AISLE", "AROSE", "HATER", "NICHE", "POINT", "EARTH", "PINTO", "THOSE", + "CLOTH", "NOTCH", "TOPIC", "RESIN", "SCALP", "HEIST", "HERON", "TRIPE", "TONAL", + "TAPER", "SHORN", "TONIC", "HOIST", "SNORE", "STORE", "SLOPE", "OCEAN", "CHART", + "PAINT", "SPENT", "CRANE", "CRISP", "TRASH", "PATIO", "PLATE", "HOTEL", "LEAST", + "ALONE", "RALPH", "SPIEL", "SIREN", "RATIO", "STOOP", "TROLL", "ATOLL", "SLASH", + "RETRO", "CREEP", "STILT", "SPREE", "TASTE", "CACHE", "CANON", "EATEN", "TEPEE", + "SHEET", "SNEER", "ERROR", "NATAL", "SLEEP", "STINT", "TROOP", "SHALL", "STALL", + "PIPER", "TOAST", "NASAL", "CORER", "THERE", "POOCH", "SCREE", "ELITE", "ALTAR", + "PENCE", "EATER", "ALPHA", "TENTH", "LINEN", "SHEER", "TAINT", "HEATH", "CRIER", + "TENSE", "CARAT", "CANAL", "APNEA", "THESE", "HATCH", "SHELL", "CIRCA", "APART", + "SPILL", "STEEL", "LOCAL", "STOOL", "SHEEN", "RESET", "STEEP", "ELATE", "PRESS", + "SLEET", "CROSS", "TOTAL", "TREAT", "ONION", "STATE", "CINCH", "ASSET", "THREE", + "TORSO", "SNOOP", "PENNE", "SPOON", "SHEEP", "PAPAL", "STILL", "CHILL", "THETA", + "LEECH", "INNER", "HONOR", "LOOSE", "CONIC", "SCENE", "COACH", "CONCH", "LATTE", + "ERASE", "ESTER", "PEACE", "PASTA", "INANE", "SPOOL", "TEASE", "HARSH", "PIECE", + "STEER", "SCOOP", "NINTH", "OTTER", "OCTET", "EERIE", "RISER", "LAPEL", "HIPPO", + "PREEN", "ETHER", "AORTA", "SENSE", "TRACT", "SHOOT", "SLOOP", "REPEL", "TITHE", + "IONIC", "CELLO", "CHESS", "SOOTH", "COCOA", "TITAN", "TOOTH", "TIARA", "CRESS", + "SLOSH", "RARER", "TERSE", "ERECT", "HELLO", "PARER", "RIPER", "NOOSE", "CREPE", + "CACAO", "ILIAC", "POSSE", "CACTI", "EASEL", "LASSO", "ROOST", "ALLOT", "COLON", + "LEPER", "TEETH", "TITLE", "HENCE", "NIECE", "PAPER", "TRITE", "SPELL", "RACER", + "ATTIC", "CRASS", "HITCH", "LEASE", "CEASE", "ROTOR", "ELOPE", "APPLE", "CHILI", + "START", "PHOTO", "SALSA", "STASH", "PRIOR", "TAROT", "COLOR", "CHEER", "CLASS", + "ARENA", "ELECT", "ENTER", "CATCH", "TENET", "TACIT", "TRAIT", "TERRA", "LILAC", +}; + +// These are words that'll never be used, but still need to be in the dictionary for guesses. +// Number of words found: 1898 +static const char _possible_words[][WORDLE_LENGTH + 1] = { + "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", + "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", + "AERIE", "AEROS", "AESIR", "AHEAP", "AHENT", "AHINT", "AINEE", "AIOLI", "AIRER", + "AIRNS", "AIRTH", "AIRTS", "AITCH", "ALAAP", "ALANE", "ALANS", "ALANT", "ALAPA", + "ALAPS", "ALATE", "ALCOS", "ALECS", "ALEPH", "ALIAS", "ALINE", "ALIST", "ALLEE", + "ALLEL", "ALLIS", "ALOES", "ALOHA", "ALOIN", "ALOOS", "ALTHO", "ALTOS", "ANANA", + "ANATA", "ANCHO", "ANCLE", "ANCON", "ANEAR", "ANELE", "ANENT", "ANILE", "ANILS", + "ANION", "ANISE", "ANLAS", "ANNAL", "ANNAS", "ANNAT", "ANOAS", "ANOLE", "ANSAE", + "ANTAE", "ANTAR", "ANTAS", "ANTES", "ANTIS", "ANTRA", "ANTRE", "APACE", "APERS", + "APERT", "APHIS", "APIAN", "APIOL", "APISH", "APOOP", "APORT", "APPAL", "APPEL", + "APPRO", "APRES", "APSES", "APSIS", "APSOS", "APTER", "ARARS", "ARCHI", "ARCOS", + "AREAE", "AREAL", "AREAR", "AREAS", "ARECA", "AREIC", "ARENE", "AREPA", "ARERE", + "ARETE", "ARETS", "ARETT", "ARHAT", "ARIAS", "ARIEL", "ARILS", "ARIOT", "ARISH", + "ARLES", "ARNAS", "AROHA", "ARPAS", "ARPEN", "ARRAH", "ARRAS", "ARRET", "ARRIS", + "ARSES", "ARSIS", "ARTAL", "ARTEL", "ARTIC", "ARTIS", "ASANA", "ASCON", "ASHES", + "ASHET", "ASPEN", "ASPER", "ASPIC", "ASPIE", "ASPIS", "ASPRO", "ASSAI", "ASSES", + "ASSOT", "ASTER", "ASTIR", "ATAPS", "ATILT", "ATLAS", "ATOCS", "ATRIA", "ATRIP", + "ATTAP", "ATTAR", "CACAS", "CAECA", "CAESE", "CAINS", "CALLA", "CALLS", "CALOS", + "CALPA", "CALPS", "CANEH", "CANER", "CANES", "CANNA", "CANNS", "CANSO", "CANST", + "CANTO", "CANTS", "CAPAS", "CAPES", "CAPHS", "CAPLE", "CAPON", "CAPOS", "CAPOT", + "CAPRI", "CARAP", "CARER", "CARES", "CARET", "CARLE", "CARLS", "CARNS", "CARON", + "CARPI", "CARPS", "CARRS", "CARSE", "CARTA", "CARTE", "CARTS", "CASAS", "CASCO", + "CASES", "CASTS", "CATES", "CECAL", "CEILI", "CEILS", "CELLA", "CELLI", "CELLS", + "CELTS", "CENSE", "CENTO", "CENTS", "CEORL", "CEPES", "CERCI", "CERES", "CERIA", + "CERIC", "CERNE", "CEROC", "CEROS", "CERTS", "CESSE", "CESTA", "CESTI", "CETES", + "CHACE", "CHACO", "CHAIS", "CHALS", "CHANA", "CHAPE", "CHAPS", "CHAPT", "CHARA", + "CHARE", "CHARR", "CHARS", "CHATS", "CHEEP", "CHELA", "CHELP", "CHERE", "CHERT", + "CHETH", "CHIAO", "CHIAS", "CHICA", "CHICH", "CHICO", "CHICS", "CHIEL", "CHILE", + "CHINE", "CHINO", "CHINS", "CHIPS", "CHIRL", "CHIRO", "CHIRR", "CHIRT", "CHITS", + "CHOCO", "CHOCS", "CHOIL", "CHOLA", "CHOLI", "CHOLO", "CHONS", "CHOON", "CHOPS", + "CHOTA", "CHOTT", "CIELS", "CILIA", "CILLS", "CINCT", "CINES", "CIONS", "CIPPI", + "CIRCS", "CIRES", "CIRLS", "CIRRI", "CISCO", "CISTS", "CITAL", "CITER", "CITES", + "CLACH", "CLAES", "CLANS", "CLAPS", "CLAPT", "CLARO", "CLART", "CLAST", "CLATS", + "CLEEP", "CLEPE", "CLEPT", "CLIES", "CLINE", "CLINT", "CLIPE", "CLIPS", "CLIPT", + "CLITS", "CLONS", "CLOOP", "CLOOT", "CLOPS", "CLOTE", "CLOTS", "COACT", "COALA", + "COALS", "COAPT", "COATE", "COATI", "COATS", "COCAS", "COCCI", "COCCO", "COCOS", + "COHEN", "COHOE", "COHOS", "COILS", "COINS", "COIRS", "COITS", "COLAS", "COLES", + "COLIC", "COLIN", "COLLS", "COLTS", "CONES", "CONIA", "CONIN", "CONNE", "CONNS", + "CONTE", "CONTO", "COOCH", "COOEE", "COOER", "COOLS", "COONS", "COOPS", "COOPT", + "COOST", "COOTS", "COPAL", "COPEN", "COPER", "COPES", "COPRA", "CORES", "CORIA", + "CORNI", "CORNO", "CORNS", "CORPS", "CORSE", "CORSO", "COSEC", "COSES", "COSET", + "COSIE", "COSTA", "COSTE", "COSTS", "COTAN", "COTES", "COTHS", "COTTA", "COTTS", + "CRAAL", "CRAIC", "CRANS", "CRAPE", "CRAPS", "CRARE", "CREEL", "CREES", "CRENA", + "CREPS", "CRIAS", "CRIES", "CRINE", "CRIOS", "CRIPE", "CRIPS", "CRISE", "CRITH", + "CRITS", "CROCI", "CROCS", "CRONS", "CROOL", "CROON", "CROPS", "CRORE", "CROST", + "CTENE", "EALES", "EARLS", "EARNS", "EARNT", "EARST", "EASER", "EASES", "EASLE", + "EASTS", "EATHE", "ECHES", "ECHOS", "EISEL", "ELAIN", "ELANS", "ELCHI", "ELINT", + "ELOIN", "ELOPS", "ELPEE", "ELSIN", "ENATE", "ENIAC", "ENLIT", "ENOLS", "ENROL", + "ENTIA", "EORLS", "EOSIN", "EPACT", "EPEES", "EPHAH", "EPHAS", "EPHOR", "EPICS", + "EPOPT", "EPRIS", "ERICA", "ERICS", "ERNES", "EROSE", "ERSES", "ESCAR", "ESCOT", + "ESILE", "ESNES", "ESSES", "ESTOC", "ESTOP", "ESTRO", "ETAPE", "ETATS", "ETENS", + "ETHAL", "ETHNE", "ETICS", "ETNAS", "ETTIN", "ETTLE", "HAARS", "HAETS", "HAHAS", + "HAILS", "HAINS", "HAINT", "HAIRS", "HAITH", "HALAL", "HALER", "HALES", "HALLO", + "HALLS", "HALON", "HALOS", "HALSE", "HALTS", "HANAP", "HANCE", "HANCH", "HANSA", + "HANSE", "HANTS", "HAOLE", "HAPPI", "HARES", "HARLS", "HARNS", "HAROS", "HARPS", + "HARTS", "HASPS", "HASTA", "HATES", "HATHA", "HEALS", "HEAPS", "HEARE", "HEARS", + "HEAST", "HEATS", "HECHT", "HEELS", "HEILS", "HEIRS", "HELES", "HELIO", "HELLS", + "HELOS", "HELOT", "HELPS", "HENCH", "HENNA", "HENTS", "HEPAR", "HERES", "HERLS", + "HERNS", "HEROS", "HERSE", "HESPS", "HESTS", "HETES", "HETHS", "HIANT", "HILAR", + "HILCH", "HILLO", "HILLS", "HILTS", "HINTS", "HIOIS", "HIREE", "HIRER", "HIRES", + "HISTS", "HITHE", "HOARS", "HOAST", "HOERS", "HOISE", "HOLES", "HOLLA", "HOLLO", + "HOLON", "HOLOS", "HOLTS", "HONAN", "HONER", "HONES", "HOOCH", "HOONS", "HOOPS", + "HOORS", "HOOSH", "HOOTS", "HOPER", "HOPES", "HORAH", "HORAL", "HORAS", "HORIS", + "HORNS", "HORST", "HOSEL", "HOSEN", "HOSER", "HOSES", "HOSTA", "HOSTS", "HOTCH", + "HOTEN", "ICERS", "ICHES", "ICHOR", "ICIER", "ICONS", "ICTAL", "ICTIC", "ILEAC", + "ILEAL", "ILIAL", "ILLER", "ILLTH", "INAPT", "INCEL", "INCLE", "INION", "INNIT", + "INSET", "INSPO", "INTEL", "INTIL", "INTIS", "INTRA", "IOTAS", "IPPON", "IRONE", + "IRONS", "ISHES", "ISLES", "ISNAE", "ISSEI", "ISTLE", "ITHER", "LAARI", "LACER", + "LACES", "LACET", "LAERS", "LAHAL", "LAHAR", "LAICH", "LAICS", "LAIRS", "LAITH", + "LALLS", "LANAI", "LANAS", "LANCH", "LANES", "LANTS", "LAPIN", "LAPIS", "LARCH", + "LAREE", "LARES", "LARIS", "LARNS", "LARNT", "LASER", "LASES", "LASSI", "LASTS", + "LATAH", "LATEN", "LATHI", "LATHS", "LEANS", "LEAPS", "LEARE", "LEARS", "LEATS", + "LEEAR", "LEEPS", "LEERS", "LEESE", "LEETS", "LEHRS", "LEIRS", "LEISH", "LENES", + "LENIS", "LENOS", "LENSE", "LENTI", "LENTO", "LEONE", "LEPRA", "LEPTA", "LERES", + "LERPS", "LESES", "LESTS", "LETCH", "LETHE", "LIANA", "LIANE", "LIARS", "LIART", + "LICHI", "LICHT", "LICIT", "LIENS", "LIERS", "LILLS", "LILOS", "LILTS", "LINAC", + "LINCH", "LINES", "LININ", "LINNS", "LINOS", "LINTS", "LIONS", "LIPAS", "LIPES", + "LIPIN", "LIPOS", "LIRAS", "LIROT", "LISLE", "LISPS", "LISTS", "LITAI", "LITAS", + "LITES", "LITHO", "LITHS", "LITRE", "LLANO", "LOACH", "LOANS", "LOAST", "LOCHE", + "LOCHS", "LOCIE", "LOCIS", "LOCOS", "LOESS", "LOHAN", "LOINS", "LOIPE", "LOIRS", + "LOLLS", "LONER", "LOOIE", "LOONS", "LOOPS", "LOOTS", "LOPER", "LOPES", "LORAL", + "LORAN", "LOREL", "LORES", "LORIC", "LORIS", "LOSEL", "LOSEN", "LOSES", "LOTAH", + "LOTAS", "LOTES", "LOTIC", "LOTOS", "LOTSA", "LOTTA", "LOTTE", "LOTTO", "NAANS", + "NACHE", "NACHO", "NACRE", "NAHAL", "NAILS", "NAIRA", "NALAS", "NALLA", "NANAS", + "NANCE", "NANNA", "NANOS", "NAPAS", "NAPES", "NAPOO", "NAPPA", "NAPPE", "NARAS", + "NARCO", "NARCS", "NARES", "NARIC", "NARIS", "NARRE", "NASHI", "NATCH", "NATES", + "NATIS", "NEALS", "NEAPS", "NEARS", "NEATH", "NEATS", "NEELE", "NEEPS", "NEESE", + "NEIST", "NELIS", "NENES", "NEONS", "NEPER", "NEPIT", "NERAL", "NEROL", "NERTS", + "NESTS", "NETES", "NETOP", "NETTS", "NICHT", "NICOL", "NIHIL", "NILLS", "NINER", + "NINES", "NINON", "NIPAS", "NIRLS", "NISEI", "NISSE", "NITER", "NITES", "NITON", + "NITRE", "NITRO", "NOAHS", "NOELS", "NOILS", "NOINT", "NOIRS", "NOLES", "NOLLS", + "NOLOS", "NONAS", "NONCE", "NONES", "NONET", "NONIS", "NOOIT", "NOONS", "NOOPS", + "NOPAL", "NORIA", "NORIS", "NOSER", "NOSES", "NOTAL", "NOTER", "NOTES", "OASES", + "OASIS", "OASTS", "OATEN", "OATER", "OATHS", "OCHER", "OCHES", "OCHRE", "OCREA", + "OCTAN", "OCTAS", "OHIAS", "OHONE", "OILER", "OINTS", "OLEIC", "OLEIN", "OLENT", + "OLEOS", "OLIOS", "OLLAS", "OLLER", "OLLIE", "OLPAE", "OLPES", "ONCER", "ONCES", + "ONCET", "ONERS", "ONTIC", "OONTS", "OORIE", "OOSES", "OPAHS", "OPALS", "OPENS", + "OPEPE", "OPPOS", "OPSIN", "OPTER", "ORACH", "ORALS", "ORANT", "ORATE", "ORCAS", + "ORCIN", "ORIEL", "ORLES", "ORLON", "ORLOP", "ORNIS", "ORPIN", "ORRIS", "ORTHO", + "OSCAR", "OSHAC", "OSIER", "OSSIA", "OSTIA", "OTTAR", "OTTOS", "PAALS", "PAANS", + "PACAS", "PACER", "PACES", "PACHA", "PACOS", "PACTA", "PACTS", "PAEAN", "PAEON", + "PAILS", "PAINS", "PAIRE", "PAIRS", "PAISA", "PAISE", "PALAS", "PALEA", "PALES", + "PALET", "PALIS", "PALLA", "PALLS", "PALPI", "PALPS", "PALSA", "PANCE", "PANES", + "PANNE", "PANNI", "PANTO", "PANTS", "PAOLI", "PAOLO", "PAPAS", "PAPES", "PAPPI", + "PARAE", "PARAS", "PARCH", "PAREN", "PAREO", "PARES", "PARIS", "PARLE", "PAROL", + "PARPS", "PARRA", "PARRS", "PARTI", "PARTS", "PASEO", "PASES", "PASHA", "PASSE", + "PASTS", "PATEN", "PATER", "PATES", "PATHS", "PATIN", "PATTE", "PEALS", "PEANS", + "PEARE", "PEARS", "PEART", "PEASE", "PEATS", "PECHS", "PEECE", "PEELS", "PEENS", + "PEEPE", "PEEPS", "PEERS", "PEINS", "PEISE", "PELAS", "PELES", "PELLS", "PELON", + "PELTA", "PELTS", "PENES", "PENIE", "PENIS", "PENNA", "PENNI", "PENTS", "PEONS", + "PEPLA", "PEPOS", "PEPSI", "PERAI", "PERCE", "PERCS", "PEREA", "PERES", "PERIS", + "PERNS", "PERPS", "PERSE", "PERST", "PERTS", "PESOS", "PESTS", "PETAR", "PETER", + "PETIT", "PETRE", "PETRI", "PETTI", "PETTO", "PHARE", "PHEER", "PHENE", "PHEON", + "PHESE", "PHIAL", "PHISH", "PHOCA", "PHONO", "PHONS", "PHOTS", "PHPHT", "PIANI", + "PIANS", "PICAL", "PICAS", "PICOT", "PICRA", "PIERS", "PIERT", "PIETA", "PIETS", + "PILAE", "PILAO", "PILAR", "PILCH", "PILEA", "PILEI", "PILER", "PILES", "PILIS", + "PILLS", "PINAS", "PINES", "PINNA", "PINON", "PINOT", "PINTA", "PINTS", "PIONS", + "PIPAL", "PIPAS", "PIPES", "PIPET", "PIPIS", "PIPIT", "PIRAI", "PIRLS", "PIRNS", + "PISCO", "PISES", "PISOS", "PISTE", "PITAS", "PITHS", "PITON", "PITOT", "PITTA", + "PLAAS", "PLANS", "PLAPS", "PLASH", "PLAST", "PLATS", "PLATT", "PLEAS", "PLENA", + "PLEON", "PLESH", "PLICA", "PLIES", "PLOAT", "PLOPS", "PLOTS", "POACH", "POEPS", + "POETS", "POLER", "POLES", "POLIO", "POLIS", "POLLS", "POLOS", "POLTS", "PONCE", + "PONES", "PONTS", "POOHS", "POOLS", "POONS", "POOPS", "POORI", "POORT", "POOTS", + "POPES", "POPPA", "PORAE", "PORAL", "PORER", "PORES", "PORIN", "PORNO", "PORNS", + "PORTA", "PORTS", "POSES", "POSHO", "POSTS", "POTAE", "POTCH", "POTES", "POTIN", + "POTOO", "POTTO", "POTTS", "PRANA", "PRAOS", "PRASE", "PRATE", "PRATS", "PRATT", + "PREES", "PRENT", "PREON", "PREOP", "PREPS", "PRESA", "PRESE", "PREST", "PRIAL", + "PRIER", "PRIES", "PRILL", "PRION", "PRISE", "PRISS", "PROAS", "PROIN", "PROLE", + "PROLL", "PROPS", "PRORE", "PROSO", "PROSS", "PROST", "PROTO", "PSION", "PSOAE", + "PSOAI", "PSOAS", "PSORA", "RACES", "RACHE", "RACON", "RAIAS", "RAILE", "RAILS", + "RAINE", "RAINS", "RAITA", "RAITS", "RALES", "RANAS", "RANCE", "RANEE", "RANIS", + "RANTS", "RAPER", "RAPES", "RAPHE", "RAPPE", "RAREE", "RARES", "RASER", "RASES", + "RASPS", "RASSE", "RASTA", "RATAL", "RATAN", "RATAS", "RATCH", "RATEL", "RATER", + "RATES", "RATHA", "RATHE", "RATHS", "RATOO", "RATOS", "REAIS", "REALO", "REALS", + "REANS", "REAPS", "REARS", "REAST", "REATA", "REATE", "RECAL", "RECCE", "RECCO", + "RECIT", "RECON", "RECTA", "RECTI", "RECTO", "REECH", "REELS", "REENS", "REEST", + "REINS", "REIST", "RELET", "RELIE", "RELIT", "RELLO", "RENIN", "RENNE", "RENOS", + "RENTE", "RENTS", "REOIL", "REPIN", "REPLA", "REPOS", "REPOT", "REPPS", "REPRO", + "RERAN", "RESAT", "RESEE", "RESES", "RESIT", "RESTO", "RESTS", "RETIA", "RETIE", + "RHEAS", "RHIES", "RHINE", "RHONE", "RIALS", "RIANT", "RIATA", "RICER", "RICES", + "RICHT", "RICIN", "RIELS", "RILES", "RILLE", "RILLS", "RINES", "RIOTS", "RIPES", + "RIPPS", "RISES", "RISHI", "RISPS", "RITES", "RITTS", "ROANS", "ROARS", "ROATE", + "ROHES", "ROILS", "ROINS", "ROIST", "ROLES", "ROLLS", "RONEO", "RONES", "RONIN", + "RONNE", "RONTE", "RONTS", "ROONS", "ROOPS", "ROOSA", "ROOSE", "ROOTS", "ROPER", + "ROPES", "RORAL", "RORES", "RORIC", "RORIE", "RORTS", "ROSES", "ROSET", "ROSHI", + "ROSIN", "ROSIT", "ROSTI", "ROSTS", "ROTAL", "ROTAN", "ROTAS", "ROTCH", "ROTES", + "ROTIS", "ROTLS", "ROTON", "ROTOS", "ROTTE", "SACRA", "SAICE", "SAICS", "SAILS", + "SAINE", "SAINS", "SAIRS", "SAIST", "SAITH", "SALAL", "SALAT", "SALEP", "SALES", + "SALET", "SALIC", "SALLE", "SALOL", "SALOP", "SALPA", "SALPS", "SALSE", "SALTO", + "SALTS", "SANES", "SANSA", "SANTO", "SANTS", "SAOLA", "SAPAN", "SAPOR", "SARAN", + "SAREE", "SARIN", "SARIS", "SAROS", "SASER", "SASIN", "SASSE", "SATAI", "SATES", + "SATIS", "SCAIL", "SCALA", "SCALL", "SCANS", "SCAPA", "SCAPE", "SCAPI", "SCARP", + "SCARS", "SCART", "SCATH", "SCATS", "SCATT", "SCEAT", "SCENA", "SCOOT", "SCOPA", + "SCOPS", "SCOTS", "SCRAE", "SCRAN", "SCRAT", "SCRIP", "SEALS", "SEANS", "SEARE", + "SEARS", "SEASE", "SEATS", "SECCO", "SECHS", "SECTS", "SEELS", "SEEPS", "SEERS", + "SEHRI", "SEILS", "SEINE", "SEIRS", "SEISE", "SELAH", "SELES", "SELLA", "SELLE", + "SELLS", "SENAS", "SENES", "SENNA", "SENOR", "SENSA", "SENSI", "SENTE", "SENTI", + "SENTS", "SEPAL", "SEPIC", "SEPTA", "SEPTS", "SERAC", "SERAI", "SERAL", "SERER", + "SERES", "SERIC", "SERIN", "SERON", "SERRA", "SERRE", "SERRS", "SESSA", "SETAE", + "SETAL", "SETON", "SETTS", "SHAHS", "SHANS", "SHAPS", "SHARN", "SHASH", "SHCHI", + "SHEAL", "SHEAS", "SHEEL", "SHENT", "SHEOL", "SHERE", "SHERO", "SHETS", "SHIAI", + "SHIEL", "SHIER", "SHIES", "SHILL", "SHINS", "SHIPS", "SHIRR", "SHIRS", "SHISH", + "SHISO", "SHIST", "SHITE", "SHITS", "SHLEP", "SHOAT", "SHOER", "SHOES", "SHOLA", + "SHOOL", "SHOON", "SHOOS", "SHOPE", "SHOPS", "SHORL", "SHOTE", "SHOTS", "SHOTT", + "SHRIS", "SIALS", "SICES", "SICHT", "SIENS", "SIENT", "SIETH", "SILEN", "SILER", + "SILES", "SILLS", "SILOS", "SILTS", "SINES", "SINHS", "SIPES", "SIREE", "SIRES", + "SIRIH", "SIRIS", "SIROC", "SIRRA", "SISAL", "SISES", "SISTA", "SISTS", "SITAR", + "SITES", "SITHE", "SLAES", "SLANE", "SLAPS", "SLART", "SLATS", "SLEER", "SLIER", + "SLIPE", "SLIPS", "SLIPT", "SLISH", "SLITS", "SLOAN", "SLOES", "SLOOT", "SLOPS", + "SLOTS", "SNAPS", "SNARS", "SNASH", "SNATH", "SNEAP", "SNEES", "SNELL", "SNIES", + "SNIPS", "SNIRT", "SNITS", "SNOEP", "SNOOL", "SNOOT", "SNOTS", "SOAPS", "SOARE", + "SOARS", "SOCAS", "SOCES", "SOCLE", "SOILS", "SOLAH", "SOLAN", "SOLAS", "SOLEI", + "SOLER", "SOLES", "SOLON", "SOLOS", "SONCE", "SONES", "SONNE", "SONSE", "SOOLE", + "SOOLS", "SOOPS", "SOOTE", "SOOTS", "SOPHS", "SOPOR", "SOPRA", "SORAL", "SORAS", + "SOREE", "SOREL", "SORER", "SORES", "SORNS", "SORRA", "SORTA", "SORTS", "SOTHS", + "SOTOL", "SPAER", "SPAES", "SPAHI", "SPAIL", "SPAIN", "SPAIT", "SPALE", "SPALL", + "SPALT", "SPANE", "SPANS", "SPARS", "SPART", "SPATE", "SPATS", "SPEAL", "SPEAN", + "SPEAT", "SPECS", "SPECT", "SPEEL", "SPEER", "SPEIL", "SPEIR", "SPEOS", "SPETS", + "SPIAL", "SPICA", "SPICS", "SPIER", "SPIES", "SPILE", "SPINA", "SPINS", "SPIRT", + "SPITS", "SPOOR", "SPOOT", "SPOSH", "SPOTS", "SPRAT", "SPRIT", "STANE", "STAPH", + "STAPS", "STARN", "STARR", "STARS", "STATS", "STEAN", "STEAR", "STEEN", "STEIL", + "STELA", "STELE", "STELL", "STENO", "STENS", "STENT", "STEPS", "STEPT", "STERE", + "STETS", "STICH", "STIES", "STILE", "STIPA", "STIPE", "STIRE", "STIRP", "STIRS", + "STOAE", "STOAI", "STOAS", "STOAT", "STOEP", "STOIT", "STOLN", "STONN", "STOOR", + "STOPE", "STOPS", "STOPT", "STOSS", "STOTS", "STOTT", "STRAE", "STREP", "STRIA", + "STROP", "TAALS", "TAATA", "TACAN", "TACES", "TACET", "TACHE", "TACHO", "TACHS", + "TACOS", "TACTS", "TAELS", "TAHAS", "TAHRS", "TAILS", "TAINS", "TAIRA", "TAISH", + "TAITS", "TALAR", "TALAS", "TALCS", "TALEA", "TALER", "TALES", "TALLS", "TALPA", + "TANAS", "TANHS", "TANNA", "TANTI", "TANTO", "TAPAS", "TAPEN", "TAPES", "TAPET", + "TAPIS", "TAPPA", "TARAS", "TARES", "TARNS", "TAROC", "TAROS", "TARPS", "TARRE", + "TARSI", "TARTS", "TASAR", "TASER", "TASES", "TASSA", "TASSE", "TASSO", "TATAR", + "TATER", "TATES", "TATHS", "TATIE", "TATTS", "TEALS", "TEARS", "TEATS", "TECHS", + "TECTA", "TEELS", "TEENE", "TEENS", "TEERS", "TEHRS", "TEILS", "TEINS", "TELAE", + "TELCO", "TELES", "TELIA", "TELIC", "TELLS", "TELOI", "TELOS", "TENCH", "TENES", + "TENIA", "TENNE", "TENNO", "TENON", "TENTS", "TEPAL", "TEPAS", "TERAI", "TERAS", + "TERCE", "TERES", "TERNE", "TERNS", "TERTS", "TESLA", "TESTA", "TESTE", "TESTS", + "TETES", "TETHS", "TETRA", "TETRI", "THALE", "THALI", "THANA", "THANE", "THANS", + "THARS", "THECA", "THEES", "THEIC", "THEIN", "THENS", "THESP", "THETE", "THILL", + "THINE", "THINS", "THIOL", "THIRL", "THOLE", "THOLI", "THORO", "THORP", "THRAE", + "THRIP", "THROE", "TIANS", "TIARS", "TICAL", "TICCA", "TICES", "TIERS", "TILER", + "TILES", "TILLS", "TILTH", "TILTS", "TINAS", "TINCT", "TINEA", "TINES", "TINTS", + "TIPIS", "TIRES", "TIRLS", "TIROS", "TIRRS", "TITCH", "TITER", "TITIS", "TITRE", + "TOCOS", "TOEAS", "TOHOS", "TOILE", "TOILS", "TOISE", "TOITS", "TOLAN", "TOLAR", + "TOLAS", "TOLES", "TOLLS", "TOLTS", "TONER", "TONES", "TONNE", "TOOLS", "TOONS", + "TOOTS", "TOPEE", "TOPER", "TOPES", "TOPHE", "TOPHI", "TOPHS", "TOPIS", "TOPOI", + "TOPOS", "TORAH", "TORAN", "TORAS", "TORCS", "TORES", "TORIC", "TORII", "TOROS", + "TOROT", "TORRS", "TORSE", "TORSI", "TORTA", "TORTE", "TORTS", "TOSAS", "TOSES", + "TOTER", "TOTES", "TRANS", "TRANT", "TRAPE", "TRAPS", "TRAPT", "TRASS", "TRATS", + "TRATT", "TREEN", "TREES", "TRESS", "TREST", "TRETS", "TRIAC", "TRIER", "TRIES", + "TRILL", "TRINE", "TRINS", "TRIOL", "TRIOR", "TRIOS", "TRIPS", "TRIST", "TROAT", + "TROIS", "TRONA", "TRONC", "TRONE", "TRONS", "TROTH", "TROTS", "TSARS", +}; + +#if (USE_RANDOM_GUESS == 2) +static const uint16_t _num_random_guess_words = 257; // The _valid_words array begins with this many words where each letter is different. +#elif (USE_RANDOM_GUESS == 1) +static const uint16_t _num_random_guess_words = _num_words; +#endif + +#endif // WORDLE_FACE_DICT_H_ \ No newline at end of file From 0d16d126cd2017382a1f260fbf3b63f717f4b4d6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 07:15:08 -0400 Subject: [PATCH 128/220] Added ability to guess non-dict words and repeats as that can save 11.5KB of watch memory --- .../watch_faces/complication/wordle_face.c | 28 ++++++++++++------- .../watch_faces/complication/wordle_face.h | 3 ++ .../complication/wordle_face_dict.h | 2 ++ utils/wordle_face/wordle_list.py | 23 +++++++++++++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 9ce29bd..f44e4a6 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -95,6 +95,17 @@ static void display_all_letters(wordle_state_t *state) { state->position = prev_pos; } +#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +static void display_not_in_dict(wordle_state_t *state) { + state->curr_screen = SCREEN_NO_DICT; + watch_display_string("nodict", 4); +} + +static void display_already_guessed(wordle_state_t *state) { + state->curr_screen = SCREEN_ALREADY_GUESSED; + watch_display_string("GUESSD", 4); +} + static uint32_t check_word_in_dict(uint8_t *word_elements) { bool is_exact_match; for (uint16_t i = 0; i < _num_words; i++) { @@ -120,6 +131,9 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { return _num_words + _num_possible_words; } + +#endif + static bool check_word(wordle_state_t *state) { // Exact bool is_exact_match = true; @@ -167,9 +181,11 @@ static void reset_all_elements(wordle_state_t *state) { state->word_elements[i] = _num_valid_letters; state->word_elements_result[i] = WORDLE_LETTER_WRONG; } +#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { state->guessed_words[i] = _num_words + _num_possible_words; } +#endif state->using_random_guess = false; state->attempt = 0; } @@ -245,16 +261,6 @@ static uint32_t get_day_unix_time(void) { } #endif -static void display_not_in_dict(wordle_state_t *state) { - state->curr_screen = SCREEN_NO_DICT; - watch_display_string("nodict", 4); -} - -static void display_already_guessed(wordle_state_t *state) { - state->curr_screen = SCREEN_ALREADY_GUESSED; - watch_display_string("GUESSD", 4); -} - 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] : " "); @@ -373,6 +379,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { } static void get_result(wordle_state_t *state) { +#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES // Check if it's in the dict uint16_t in_dict = check_word_in_dict(state->word_elements); if (in_dict == _num_words + _num_possible_words) { @@ -389,6 +396,7 @@ static void get_result(wordle_state_t *state) { } state->guessed_words[state->attempt] = in_dict; +#endif bool exact_match = check_word(state); if (exact_match) { reset_all_elements(state); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 6ae1fbd..52fd71f 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -63,6 +63,7 @@ #define WORDLE_LENGTH 5 #define WORDLE_MAX_ATTEMPTS 6 #define USE_DAILY_STREAK false +#define ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. /* USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess @@ -103,7 +104,9 @@ typedef struct { // Anything you need to keep track of, put it here! uint8_t word_elements[WORDLE_LENGTH]; WordleLetterResult word_elements_result[WORDLE_LENGTH]; +#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; +#endif uint8_t attempt : 4; uint8_t position : 3; bool using_random_guess : 1; diff --git a/movement/watch_faces/complication/wordle_face_dict.h b/movement/watch_faces/complication/wordle_face_dict.h index 1942b16..00ae3ac 100644 --- a/movement/watch_faces/complication/wordle_face_dict.h +++ b/movement/watch_faces/complication/wordle_face_dict.h @@ -67,6 +67,7 @@ static const char _valid_words[][WORDLE_LENGTH + 1] = { // These are words that'll never be used, but still need to be in the dictionary for guesses. // Number of words found: 1898 static const char _possible_words[][WORDLE_LENGTH + 1] = { +#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", "AERIE", "AEROS", "AESIR", "AHEAP", "AHENT", "AHINT", "AINEE", "AIOLI", "AIRER", @@ -278,6 +279,7 @@ static const char _possible_words[][WORDLE_LENGTH + 1] = { "TRATT", "TREEN", "TREES", "TRESS", "TREST", "TRETS", "TRIAC", "TRIER", "TRIES", "TRILL", "TRINE", "TRINS", "TRIOL", "TRIOR", "TRIOS", "TRIPS", "TRIST", "TROAT", "TROIS", "TRONA", "TRONC", "TRONE", "TRONS", "TROTH", "TROTS", "TSARS", +#endif }; #if (USE_RANDOM_GUESS == 2) diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index d951bac..68481e0 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1156,6 +1156,17 @@ def print_valid_words(letters=alphabet): ''' Prints the array of valid words that the wordle_face.c can use ''' + print("#ifndef WORDLE_FACE_DICT_H_") + print("#define WORDLE_FACE_DICT_H_") + + print("\n#ifndef WORDLE_LENGTH") + print("#define WORDLE_LENGTH 5") + print("#endif") + + print("\n#ifndef USE_RANDOM_GUESS") + print("#define USE_RANDOM_GUESS 2") + print("#endif\n") + items_per_row = 9 valid_words = list_of_valid_words(letters, valid_list) valid_words = capitalize_all_and_remove_duplicates(valid_words) @@ -1186,6 +1197,7 @@ def print_valid_words(letters=alphabet): print("\n// These are words that'll never be used, but still need to be in the dictionary for guesses.") print(f"// Number of words found: {len(possible_words)}") print("static const char _possible_words[][WORDLE_LENGTH + 1] = {") + print("#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES") i = 0 while i < len(possible_words): print(" ", end='') @@ -1193,10 +1205,15 @@ def print_valid_words(letters=alphabet): print(f'"{clean_chars(possible_words[i])}", ', end='') i+=1 print('') - print("};") - print('') + print("#endif") + print("};\n") - print(f"\nstatic const uint16_t _num_unique_words = {num_uniq}; // The _valid_words array begins with this many words where each letter is different.") + print("#if (USE_RANDOM_GUESS == 2)") + print(f"static const uint16_t _num_random_guess_words = {num_uniq}; // The _valid_words array begins with this many words where each letter is different.") + print("#elif (USE_RANDOM_GUESS == 1)") + print("static const uint16_t _num_random_guess_words = _num_words;") + print("#endif") + print("\n#endif // WORDLE_FACE_DICT_H_") def get_sec_val_and_units(seconds): From 8342fef84fb4873aa4d258cf1046245ddbae51a1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 09:51:08 -0400 Subject: [PATCH 129/220] Added ability to skip already guessed letters that are wrong --- .../watch_faces/complication/wordle_face.c | 38 ++++++++++++++----- .../watch_faces/complication/wordle_face.h | 1 + 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index f44e4a6..77cd23d 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -62,14 +62,18 @@ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ return curr_pos; } -static void get_next_letter(uint8_t curr_pos, uint8_t *word_elements) { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; - else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; +static void get_next_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { + do { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; + else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; + } while (known_wrong_letters[word_elements[curr_pos]]); } -static void get_prev_letter(uint8_t curr_pos, uint8_t *word_elements) { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; - else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; +static void get_prev_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { + do { + if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; + else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; + } while (known_wrong_letters[word_elements[curr_pos]]); } static void display_letter(wordle_state_t *state, bool display_dash) { @@ -130,8 +134,6 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { } return _num_words + _num_possible_words; } - - #endif static bool check_word(wordle_state_t *state) { @@ -164,6 +166,18 @@ static bool check_word(wordle_state_t *state) { return false; } +static void update_known_wrong_letters(wordle_state_t *state) { + + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] == WORDLE_LETTER_WRONG) { + for (size_t j = 0; j < _num_valid_letters; j++) { + if (state->word_elements[i] == j) + state->known_wrong_letters[j] = true; + } + } + } +} + static void display_attempt(uint8_t attempt) { char buf[3]; sprintf(buf, "%d", attempt+1); @@ -181,6 +195,9 @@ static void reset_all_elements(wordle_state_t *state) { state->word_elements[i] = _num_valid_letters; state->word_elements_result[i] = WORDLE_LETTER_WRONG; } + for (size_t i = 0; i < _num_valid_letters; i++){ + state->known_wrong_letters[i] = false; + } #if !ALLOW_NON_WORD_AND_REPEAT_GUESSES for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { state->guessed_words[i] = _num_words + _num_possible_words; @@ -414,6 +431,7 @@ static void get_result(wordle_state_t *state) { state->streak = 0; return; } + update_known_wrong_letters(state); state->curr_screen = SCREEN_RESULT; return; } @@ -496,12 +514,12 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; case EVENT_LIGHT_BUTTON_UP: if (act_on_btn(state, BTN_LIGHT)) break; - get_next_letter(state->position, state->word_elements); + get_next_letter(state->position, state->word_elements, state->known_wrong_letters); display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: if (state->curr_screen != SCREEN_PLAYING) break; - get_prev_letter(state->position, state->word_elements); + get_prev_letter(state->position, state->word_elements, state->known_wrong_letters); display_letter(state, true); break; case EVENT_ALARM_BUTTON_UP: diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 52fd71f..a7b34dc 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -114,6 +114,7 @@ typedef struct { bool continuing : 1; uint8_t streak; WordleScreen curr_screen; + bool known_wrong_letters[_num_valid_letters]; #if USE_DAILY_STREAK uint32_t prev_day; uint32_t curr_day; From b58d6c0a2e7c77f360df748c407b9b5be53baab8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 09:59:04 -0400 Subject: [PATCH 130/220] Changed size of word lists from const int to #define to avoid folding constant array error --- .../watch_faces/complication/wordle_face.c | 66 +++++++++---------- .../watch_faces/complication/wordle_face.h | 26 ++++---- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 77cd23d..6957d5b 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -25,7 +25,7 @@ #include #include #include "wordle_face.h" -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK #include "watch_utility.h" #endif @@ -64,21 +64,21 @@ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ static void get_next_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { do { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = 0; - else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % _num_valid_letters; + if (word_elements[curr_pos] >= WORDLE_NUM_VALID_LETTERS) word_elements[curr_pos] = 0; + else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % WORDLE_NUM_VALID_LETTERS; } while (known_wrong_letters[word_elements[curr_pos]]); } static void get_prev_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { do { - if (word_elements[curr_pos] >= _num_valid_letters) word_elements[curr_pos] = _num_valid_letters - 1; - else word_elements[curr_pos] = (word_elements[curr_pos] + _num_valid_letters - 1) % _num_valid_letters; + if (word_elements[curr_pos] >= WORDLE_NUM_VALID_LETTERS) word_elements[curr_pos] = WORDLE_NUM_VALID_LETTERS - 1; + else word_elements[curr_pos] = (word_elements[curr_pos] + WORDLE_NUM_VALID_LETTERS - 1) % WORDLE_NUM_VALID_LETTERS; } while (known_wrong_letters[word_elements[curr_pos]]); } static void display_letter(wordle_state_t *state, bool display_dash) { char buf[1 + 1]; - if (state->word_elements[state->position] >= _num_valid_letters) { + if (state->word_elements[state->position] >= WORDLE_NUM_VALID_LETTERS) { if (display_dash) watch_display_string("-", state->position + 5); else @@ -99,7 +99,7 @@ static void display_all_letters(wordle_state_t *state) { state->position = prev_pos; } -#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES static void display_not_in_dict(wordle_state_t *state) { state->curr_screen = SCREEN_NO_DICT; watch_display_string("nodict", 4); @@ -112,7 +112,7 @@ static void display_already_guessed(wordle_state_t *state) { static uint32_t check_word_in_dict(uint8_t *word_elements) { bool is_exact_match; - for (uint16_t i = 0; i < _num_words; i++) { + for (uint16_t i = 0; i < WORDLE_NUM_WORDS; i++) { is_exact_match = true; for (size_t j = 0; j < WORDLE_LENGTH; j++) { if (_valid_letters[word_elements[j]] != _valid_words[i][j]) { @@ -122,7 +122,7 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { } if (is_exact_match) return i; } - for (uint16_t i = 0; i < _num_possible_words; i++) { + for (uint16_t i = 0; i < WORDLE_NUM_POSSIBLE_WORDS; i++) { is_exact_match = true; for (size_t j = 0; j < WORDLE_LENGTH; j++) { if (_valid_letters[word_elements[j]] != _possible_words[i][j]) { @@ -130,9 +130,9 @@ static uint32_t check_word_in_dict(uint8_t *word_elements) { break; } } - if (is_exact_match) return _num_words + i; + if (is_exact_match) return WORDLE_NUM_WORDS + i; } - return _num_words + _num_possible_words; + return WORDLE_NUM_WORDS + WORDLE_NUM_POSSIBLE_WORDS; } #endif @@ -170,7 +170,7 @@ static void update_known_wrong_letters(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] == WORDLE_LETTER_WRONG) { - for (size_t j = 0; j < _num_valid_letters; j++) { + for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { if (state->word_elements[i] == j) state->known_wrong_letters[j] = true; } @@ -192,15 +192,15 @@ static void display_playing(wordle_state_t *state) { static void reset_all_elements(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { - state->word_elements[i] = _num_valid_letters; + state->word_elements[i] = WORDLE_NUM_VALID_LETTERS; state->word_elements_result[i] = WORDLE_LETTER_WRONG; } - for (size_t i = 0; i < _num_valid_letters; i++){ + for (size_t i = 0; i < WORDLE_NUM_VALID_LETTERS; i++){ state->known_wrong_letters[i] = false; } -#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES for (size_t i = 0; i < WORDLE_MAX_ATTEMPTS; i++) { - state->guessed_words[i] = _num_words + _num_possible_words; + state->guessed_words[i] = WORDLE_NUM_WORDS + WORDLE_NUM_POSSIBLE_WORDS; } #endif state->using_random_guess = false; @@ -210,13 +210,13 @@ static void reset_all_elements(wordle_state_t *state) { static void reset_incorrect_elements(wordle_state_t *state) { for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] != WORDLE_LETTER_CORRECT) - state->word_elements[i] = _num_valid_letters; + state->word_elements[i] = WORDLE_NUM_VALID_LETTERS; } } static void reset_board(wordle_state_t *state) { reset_all_elements(state); - state->curr_answer = get_random(_num_words); + state->curr_answer = get_random(WORDLE_NUM_WORDS); watch_clear_colon(); state->position = get_first_pos(state->word_elements_result); display_playing(state); @@ -231,7 +231,7 @@ static void display_title(wordle_state_t *state) { watch_display_string("WO WordLE", 0); } -#if !USE_DAILY_STREAK +#if !WORDLE_USE_DAILY_STREAK static void display_continue_result(bool continuing) { watch_display_string(continuing ? "y" : "n", 9); } @@ -246,7 +246,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; -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK if (state->streak > 99) sprintf(buf, "WO St--dy"); else @@ -258,7 +258,7 @@ static void display_streak(wordle_state_t *state) { watch_set_colon(); } -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; if (state->streak < 40) { @@ -294,7 +294,7 @@ static void display_win(wordle_state_t *state, uint8_t subsecond) { static bool is_playing(const wordle_state_t *state) { if (state->attempt > 0) return true; for (size_t i = 0; i < WORDLE_LENGTH; i++) { - if (state->word_elements[i] != _num_valid_letters) + if (state->word_elements[i] != WORDLE_NUM_VALID_LETTERS) return true; } return false; @@ -333,7 +333,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { display_playing(state); return true; case SCREEN_TITLE: -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK if (state->prev_day == get_day_unix_time()) { display_wait(state); } @@ -351,7 +351,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { #endif return true; case SCREEN_STREAK: -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK state->curr_day = get_day_unix_time(); #endif reset_board(state); @@ -365,7 +365,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK case SCREEN_WAIT: (void) pin; display_title(state); @@ -396,10 +396,10 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { } static void get_result(wordle_state_t *state) { -#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES // Check if it's in the dict uint16_t in_dict = check_word_in_dict(state->word_elements); - if (in_dict == _num_words + _num_possible_words) { + if (in_dict == WORDLE_NUM_WORDS + WORDLE_NUM_POSSIBLE_WORDS) { display_not_in_dict(state); return; } @@ -420,7 +420,7 @@ static void get_result(wordle_state_t *state) { state->curr_screen = SCREEN_WIN; if (state->streak < 0x7F) state->streak++; -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK state->prev_day = get_day_unix_time(); #endif return; @@ -436,14 +436,14 @@ static void get_result(wordle_state_t *state) { return; } -#if (USE_RANDOM_GUESS != 0) +#if (WORDLE_USE_RANDOM_GUESS != 0) static void insert_random_guess(wordle_state_t *state) { uint16_t random_guess; do { // Don't allow the guess to be the same as the answer random_guess = get_random(_num_random_guess_words); } while (random_guess == state->curr_answer); for (size_t i = 0; i < WORDLE_LENGTH; i++) { - for (size_t j = 0; j < _num_valid_letters; j++) + for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { if (_valid_words[random_guess][i] == _valid_letters[j]) state->word_elements[i] = j; @@ -471,7 +471,7 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK uint32_t now = get_day_unix_time() ; if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; if (state->curr_day != now) reset_all_elements(state); @@ -525,8 +525,8 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case EVENT_ALARM_BUTTON_UP: if (act_on_btn(state, BTN_ALARM)) break; display_letter(state, true); - if (state->word_elements[state->position] == _num_valid_letters) break; -#if (USE_RANDOM_GUESS != 0) + if (state->word_elements[state->position] == WORDLE_NUM_VALID_LETTERS) break; +#if (WORDLE_USE_RANDOM_GUESS != 0) if (watch_get_pin_level(BTN_LIGHT) && (state->using_random_guess || (state->attempt == 0 && state->position == 0))) { insert_random_guess(state); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index a7b34dc..a29301e 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -37,7 +37,7 @@ * and the letters found in the word, but in the incorrect spot will blink. * The screen after the title screen if a new game is started shows the streak of games won in a row. * - * If USE_DAILY_STREAK is set to True, then the game can only be played once per day, + * If WORDLE_USE_DAILY_STREAK is set to True, then the game can only be played once per day, * and the streak resets to 0 if a day goes by without playing the game. * * Controls: @@ -49,7 +49,7 @@ * Else: None * * Alarm Press - * If Playing: If USE_RANDOM_GUESS is set and Light btn held and + * If Playing: If WORDLE_USE_RANDOM_GUESS is set and Light btn held and * (on first letter or already used a random guess) * and first attempt: Use a random 5 letter word with all letters that are different. * Else: Next position @@ -62,20 +62,20 @@ #define WORDLE_LENGTH 5 #define WORDLE_MAX_ATTEMPTS 6 -#define USE_DAILY_STREAK false -#define ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. +#define WORDLE_USE_DAILY_STREAK false +#define WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. -/* USE_RANDOM_GUESS +/* WORDLE_USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess * 1 = Allow using a random guess of any value that can be an answer * 2 = Allow using a random guess of any value that can be an answer where all of its letters are unique */ -#define USE_RANDOM_GUESS 2 +#define WORDLE_USE_RANDOM_GUESS 2 #include "wordle_face_dict.h" -static const uint16_t _num_words = (sizeof(_valid_words) / sizeof(_valid_words[0])); -static const uint16_t _num_possible_words = (sizeof(_possible_words) / sizeof(_possible_words[0])); -static const uint8_t _num_valid_letters = (sizeof(_valid_letters) / sizeof(_valid_letters[0])); +#define WORDLE_NUM_WORDS (sizeof(_valid_words) / sizeof(_valid_words[0])) +#define WORDLE_NUM_POSSIBLE_WORDS (sizeof(_possible_words) / sizeof(_possible_words[0])) +#define WORDLE_NUM_VALID_LETTERS (sizeof(_valid_letters) / sizeof(_valid_letters[0])) typedef enum { WORDLE_LETTER_WRONG = 0, @@ -89,7 +89,7 @@ typedef enum { SCREEN_TITLE, SCREEN_STREAK, SCREEN_CONTINUE, -#if USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK SCREEN_WAIT, #endif SCREEN_RESULT, @@ -104,7 +104,7 @@ typedef struct { // Anything you need to keep track of, put it here! uint8_t word_elements[WORDLE_LENGTH]; WordleLetterResult word_elements_result[WORDLE_LENGTH]; -#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES uint16_t guessed_words[WORDLE_MAX_ATTEMPTS]; #endif uint8_t attempt : 4; @@ -114,8 +114,8 @@ typedef struct { bool continuing : 1; uint8_t streak; WordleScreen curr_screen; - bool known_wrong_letters[_num_valid_letters]; -#if USE_DAILY_STREAK + bool known_wrong_letters[WORDLE_NUM_VALID_LETTERS]; +#if WORDLE_USE_DAILY_STREAK uint32_t prev_day; uint32_t curr_day; #endif From 1b887aea2bc5b85be0184144a7eb89870ab06f92 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 10:03:38 -0400 Subject: [PATCH 131/220] Made skipping a wrong letters a #define --- movement/watch_faces/complication/wordle_face.c | 3 ++- movement/watch_faces/complication/wordle_face.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 6957d5b..945bb76 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -167,7 +167,7 @@ static bool check_word(wordle_state_t *state) { } static void update_known_wrong_letters(wordle_state_t *state) { - +#if WORDLE_SKIP_WRONG_LETTERS for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] == WORDLE_LETTER_WRONG) { for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { @@ -176,6 +176,7 @@ static void update_known_wrong_letters(wordle_state_t *state) { } } } +#endif } static void display_attempt(uint8_t attempt) { diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index a29301e..7071c79 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -64,7 +64,7 @@ #define WORDLE_MAX_ATTEMPTS 6 #define WORDLE_USE_DAILY_STREAK false #define WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. - +#define WORDLE_SKIP_WRONG_LETTERS true // If true, already guessed letters that are known to be wrong will be skipped when cycling /* WORDLE_USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess * 1 = Allow using a random guess of any value that can be an answer From fdff6f581a58af966b70acb260883547681a4dfe Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 10:12:08 -0400 Subject: [PATCH 132/220] Name change in python script --- utils/wordle_face/wordle_list.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index 68481e0..ea2154c 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1163,8 +1163,8 @@ def print_valid_words(letters=alphabet): print("#define WORDLE_LENGTH 5") print("#endif") - print("\n#ifndef USE_RANDOM_GUESS") - print("#define USE_RANDOM_GUESS 2") + print("\n#ifndef WORDLE_USE_RANDOM_GUESS") + print("#define WORDLE_USE_RANDOM_GUESS 2") print("#endif\n") items_per_row = 9 @@ -1197,7 +1197,7 @@ def print_valid_words(letters=alphabet): print("\n// These are words that'll never be used, but still need to be in the dictionary for guesses.") print(f"// Number of words found: {len(possible_words)}") print("static const char _possible_words[][WORDLE_LENGTH + 1] = {") - print("#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES") + print("#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES") i = 0 while i < len(possible_words): print(" ", end='') @@ -1208,9 +1208,9 @@ def print_valid_words(letters=alphabet): print("#endif") print("};\n") - print("#if (USE_RANDOM_GUESS == 2)") + print("#if (WORDLE_USE_RANDOM_GUESS == 2)") print(f"static const uint16_t _num_random_guess_words = {num_uniq}; // The _valid_words array begins with this many words where each letter is different.") - print("#elif (USE_RANDOM_GUESS == 1)") + print("#elif (WORDLE_USE_RANDOM_GUESS == 1)") print("static const uint16_t _num_random_guess_words = _num_words;") print("#endif") print("\n#endif // WORDLE_FACE_DICT_H_") From d98f749f3b583b42659ebd294024daf27f61a376 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 10:35:58 -0400 Subject: [PATCH 133/220] Changed variable names for dictionary header --- movement/watch_faces/complication/wordle_face_dict.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face_dict.h b/movement/watch_faces/complication/wordle_face_dict.h index 00ae3ac..11a89f7 100644 --- a/movement/watch_faces/complication/wordle_face_dict.h +++ b/movement/watch_faces/complication/wordle_face_dict.h @@ -5,8 +5,8 @@ #define WORDLE_LENGTH 5 #endif -#ifndef USE_RANDOM_GUESS -#define USE_RANDOM_GUESS 2 +#ifndef WORDLE_USE_RANDOM_GUESS +#define WORDLE_USE_RANDOM_GUESS 2 #endif static const char _valid_letters[] = {'A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'}; @@ -67,7 +67,7 @@ static const char _valid_words[][WORDLE_LENGTH + 1] = { // These are words that'll never be used, but still need to be in the dictionary for guesses. // Number of words found: 1898 static const char _possible_words[][WORDLE_LENGTH + 1] = { -#if !ALLOW_NON_WORD_AND_REPEAT_GUESSES +#if !WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES "AALII", "AARTI", "ACAIS", "ACARI", "ACCAS", "ACERS", "ACETA", "ACHAR", "ACHES", "ACHOO", "ACINI", "ACNES", "ACRES", "ACROS", "ACTIN", "ACTON", "AECIA", "AEONS", "AERIE", "AEROS", "AESIR", "AHEAP", "AHENT", "AHINT", "AINEE", "AIOLI", "AIRER", @@ -282,9 +282,9 @@ static const char _possible_words[][WORDLE_LENGTH + 1] = { #endif }; -#if (USE_RANDOM_GUESS == 2) +#if (WORDLE_USE_RANDOM_GUESS == 2) static const uint16_t _num_random_guess_words = 257; // The _valid_words array begins with this many words where each letter is different. -#elif (USE_RANDOM_GUESS == 1) +#elif (WORDLE_USE_RANDOM_GUESS == 1) static const uint16_t _num_random_guess_words = _num_words; #endif From 733318c036147c989737dc489e1e0ad34a8d5f9b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 12:18:27 -0400 Subject: [PATCH 134/220] skip_wrong_letter is now a toggle --- .../watch_faces/complication/wordle_face.c | 36 ++++++++++++++----- .../watch_faces/complication/wordle_face.h | 8 ++--- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 945bb76..608fbe5 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -62,18 +62,18 @@ static uint8_t get_prev_pos(uint8_t curr_pos, WordleLetterResult *word_elements_ return curr_pos; } -static void get_next_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { +static void get_next_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters, const bool skip_wrong_letter) { do { if (word_elements[curr_pos] >= WORDLE_NUM_VALID_LETTERS) word_elements[curr_pos] = 0; else word_elements[curr_pos] = (word_elements[curr_pos] + 1) % WORDLE_NUM_VALID_LETTERS; - } while (known_wrong_letters[word_elements[curr_pos]]); + } while (skip_wrong_letter && known_wrong_letters[word_elements[curr_pos]]); } -static void get_prev_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters) { +static void get_prev_letter(const uint8_t curr_pos, uint8_t *word_elements, const bool *known_wrong_letters, const bool skip_wrong_letter) { do { if (word_elements[curr_pos] >= WORDLE_NUM_VALID_LETTERS) word_elements[curr_pos] = WORDLE_NUM_VALID_LETTERS - 1; else word_elements[curr_pos] = (word_elements[curr_pos] + WORDLE_NUM_VALID_LETTERS - 1) % WORDLE_NUM_VALID_LETTERS; - } while (known_wrong_letters[word_elements[curr_pos]]); + } while (skip_wrong_letter && known_wrong_letters[word_elements[curr_pos]]); } static void display_letter(wordle_state_t *state, bool display_dash) { @@ -166,8 +166,14 @@ static bool check_word(wordle_state_t *state) { return false; } +static void show_skip_wrong_letter_indicator(bool skipping, WordleScreen curr_screen) { + if (skipping && curr_screen <= SCREEN_CONTINUE) + watch_set_indicator(WATCH_INDICATOR_LAP); + else + watch_clear_indicator(WATCH_INDICATOR_LAP); +} + static void update_known_wrong_letters(wordle_state_t *state) { -#if WORDLE_SKIP_WRONG_LETTERS for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] == WORDLE_LETTER_WRONG) { for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { @@ -176,7 +182,6 @@ static void update_known_wrong_letters(wordle_state_t *state) { } } } -#endif } static void display_attempt(uint8_t attempt) { @@ -186,9 +191,10 @@ static void display_attempt(uint8_t attempt) { } static void display_playing(wordle_state_t *state) { + state->curr_screen = SCREEN_PLAYING; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_attempt(state->attempt); display_all_letters(state); - state->curr_screen = SCREEN_PLAYING; } static void reset_all_elements(wordle_state_t *state) { @@ -229,6 +235,7 @@ static void reset_board(wordle_state_t *state) { static void display_title(wordle_state_t *state) { state->curr_screen = SCREEN_TITLE; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("WO WordLE", 0); } @@ -239,6 +246,7 @@ static void display_continue_result(bool continuing) { static void display_continue(wordle_state_t *state) { state->curr_screen = SCREEN_CONTINUE; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("Cont ", 4); display_continue_result(state->continuing); } @@ -247,6 +255,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; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); #if WORDLE_USE_DAILY_STREAK if (state->streak > 99) sprintf(buf, "WO St--dy"); @@ -262,6 +271,7 @@ static void display_streak(wordle_state_t *state) { #if WORDLE_USE_DAILY_STREAK static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); if (state->streak < 40) { char buf[13]; sprintf(buf,"WO%2d WaIt ", state->streak); @@ -281,6 +291,7 @@ static uint32_t get_day_unix_time(void) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } @@ -288,6 +299,7 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -322,6 +334,7 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { break; } } + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string(buf, 5); } @@ -464,6 +477,7 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, 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. @@ -515,12 +529,16 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi break; case EVENT_LIGHT_BUTTON_UP: if (act_on_btn(state, BTN_LIGHT)) break; - get_next_letter(state->position, state->word_elements, state->known_wrong_letters); + get_next_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: + if (state->curr_screen <= SCREEN_CONTINUE) { + state->skip_wrong_letter = !state->skip_wrong_letter; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); + } if (state->curr_screen != SCREEN_PLAYING) break; - get_prev_letter(state->position, state->word_elements, state->known_wrong_letters); + get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); display_letter(state, true); break; case EVENT_ALARM_BUTTON_UP: diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 7071c79..d1c1a54 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -64,7 +64,6 @@ #define WORDLE_MAX_ATTEMPTS 6 #define WORDLE_USE_DAILY_STREAK false #define WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. -#define WORDLE_SKIP_WRONG_LETTERS true // If true, already guessed letters that are known to be wrong will be skipped when cycling /* WORDLE_USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess * 1 = Allow using a random guess of any value that can be an answer @@ -85,13 +84,13 @@ typedef enum { } WordleLetterResult; typedef enum { - SCREEN_PLAYING = 0, - SCREEN_TITLE, + SCREEN_TITLE = 0, SCREEN_STREAK, SCREEN_CONTINUE, #if WORDLE_USE_DAILY_STREAK SCREEN_WAIT, #endif + SCREEN_PLAYING, SCREEN_RESULT, SCREEN_WIN, SCREEN_LOSE, @@ -110,8 +109,9 @@ typedef struct { uint8_t attempt : 4; uint8_t position : 3; bool using_random_guess : 1; - uint16_t curr_answer : 15; + uint16_t curr_answer : 14; bool continuing : 1; + bool skip_wrong_letter : 1; uint8_t streak; WordleScreen curr_screen; bool known_wrong_letters[WORDLE_NUM_VALID_LETTERS]; From c87e814140fd27bf2743d6f1116bdd5b311f8758 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 12:23:35 -0400 Subject: [PATCH 135/220] LAP indicator now dispalys on all screens --- movement/watch_faces/complication/wordle_face.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 608fbe5..ef002ae 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -166,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 (skipping && curr_screen <= SCREEN_CONTINUE) +static void show_skip_wrong_letter_indicator(bool skipping) { + if (skipping) watch_set_indicator(WATCH_INDICATOR_LAP); else watch_clear_indicator(WATCH_INDICATOR_LAP); @@ -192,7 +192,6 @@ static void display_attempt(uint8_t attempt) { static void display_playing(wordle_state_t *state) { state->curr_screen = SCREEN_PLAYING; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_attempt(state->attempt); display_all_letters(state); } @@ -235,7 +234,6 @@ static void reset_board(wordle_state_t *state) { static void display_title(wordle_state_t *state) { state->curr_screen = SCREEN_TITLE; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("WO WordLE", 0); } @@ -246,7 +244,6 @@ static void display_continue_result(bool continuing) { static void display_continue(wordle_state_t *state) { state->curr_screen = SCREEN_CONTINUE; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("Cont ", 4); display_continue_result(state->continuing); } @@ -255,7 +252,6 @@ static void display_continue(wordle_state_t *state) { static void display_streak(wordle_state_t *state) { char buf[12]; state->curr_screen = SCREEN_STREAK; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); #if WORDLE_USE_DAILY_STREAK if (state->streak > 99) sprintf(buf, "WO St--dy"); @@ -271,7 +267,6 @@ static void display_streak(wordle_state_t *state) { #if WORDLE_USE_DAILY_STREAK static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); if (state->streak < 40) { char buf[13]; sprintf(buf,"WO%2d WaIt ", state->streak); @@ -291,7 +286,6 @@ static uint32_t get_day_unix_time(void) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } @@ -299,7 +293,6 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -334,7 +327,6 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { break; } } - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string(buf, 5); } @@ -497,6 +489,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { state->position = get_first_pos(state->word_elements_result); } movement_request_tick_frequency(2); + show_skip_wrong_letter_indicator(state->skip_wrong_letter); display_title(state); } @@ -535,7 +528,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case EVENT_LIGHT_LONG_PRESS: if (state->curr_screen <= SCREEN_CONTINUE) { state->skip_wrong_letter = !state->skip_wrong_letter; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); + show_skip_wrong_letter_indicator(state->skip_wrong_letter); } if (state->curr_screen != SCREEN_PLAYING) break; get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); From 4b8bd61408d3afb0d0b7e12fc81f1af40af8092b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 12:36:04 -0400 Subject: [PATCH 136/220] Added explanation on LAP icon --- movement/watch_faces/complication/wordle_face.c | 2 +- movement/watch_faces/complication/wordle_face.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index ef002ae..6de8cee 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -526,7 +526,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: - if (state->curr_screen <= SCREEN_CONTINUE) { + if (state->curr_screen < SCREEN_PLAYING) { state->skip_wrong_letter = !state->skip_wrong_letter; show_skip_wrong_letter_indicator(state->skip_wrong_letter); } diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index d1c1a54..a03f9ee 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -57,7 +57,7 @@ * Else: Next screen * Alarm Hold * If Playing: Previous position - * Else: None + * Else: Toggle skipping over letters that have been confirmed to not be in the word (indicated via the LAP icon) */ #define WORDLE_LENGTH 5 From 255ea97cc403aa70caa17619fee2927d201f6545 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 25 Aug 2024 12:38:08 -0400 Subject: [PATCH 137/220] Documentation fix --- movement/watch_faces/complication/wordle_face.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index a03f9ee..0d73a9b 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -46,7 +46,7 @@ * Else: Next screen * Light Hold * If Playing: Previous letter - * Else: None + * Else: Toggle skipping over letters that have been confirmed to not be in the word (indicated via the LAP icon) * * Alarm Press * If Playing: If WORDLE_USE_RANDOM_GUESS is set and Light btn held and @@ -57,7 +57,7 @@ * Else: Next screen * Alarm Hold * If Playing: Previous position - * Else: Toggle skipping over letters that have been confirmed to not be in the word (indicated via the LAP icon) + * Else: None */ #define WORDLE_LENGTH 5 From 8205abe5be65b15b60bb36ac32c59156f3133cd7 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 26 Aug 2024 19:52:15 -0400 Subject: [PATCH 138/220] Revert "LAP indicator now dispalys on all screens" This reverts commit 3bfa336b4d609668f6d8c71164f8f579f41240a5. --- movement/watch_faces/complication/wordle_face.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 6de8cee..ca62f30 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -166,8 +166,8 @@ static bool check_word(wordle_state_t *state) { return false; } -static void show_skip_wrong_letter_indicator(bool skipping) { - if (skipping) +static void show_skip_wrong_letter_indicator(bool skipping, WordleScreen curr_screen) { + if (skipping && curr_screen <= SCREEN_CONTINUE) watch_set_indicator(WATCH_INDICATOR_LAP); else watch_clear_indicator(WATCH_INDICATOR_LAP); @@ -192,6 +192,7 @@ static void display_attempt(uint8_t attempt) { static void display_playing(wordle_state_t *state) { state->curr_screen = SCREEN_PLAYING; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_attempt(state->attempt); display_all_letters(state); } @@ -234,6 +235,7 @@ static void reset_board(wordle_state_t *state) { static void display_title(wordle_state_t *state) { state->curr_screen = SCREEN_TITLE; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("WO WordLE", 0); } @@ -244,6 +246,7 @@ static void display_continue_result(bool continuing) { static void display_continue(wordle_state_t *state) { state->curr_screen = SCREEN_CONTINUE; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("Cont ", 4); display_continue_result(state->continuing); } @@ -252,6 +255,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; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); #if WORDLE_USE_DAILY_STREAK if (state->streak > 99) sprintf(buf, "WO St--dy"); @@ -267,6 +271,7 @@ static void display_streak(wordle_state_t *state) { #if WORDLE_USE_DAILY_STREAK static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); if (state->streak < 40) { char buf[13]; sprintf(buf,"WO%2d WaIt ", state->streak); @@ -286,6 +291,7 @@ static uint32_t get_day_unix_time(void) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } @@ -293,6 +299,7 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -327,6 +334,7 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { break; } } + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string(buf, 5); } @@ -489,7 +497,6 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { state->position = get_first_pos(state->word_elements_result); } movement_request_tick_frequency(2); - show_skip_wrong_letter_indicator(state->skip_wrong_letter); display_title(state); } @@ -528,7 +535,7 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi case EVENT_LIGHT_LONG_PRESS: if (state->curr_screen < SCREEN_PLAYING) { state->skip_wrong_letter = !state->skip_wrong_letter; - show_skip_wrong_letter_indicator(state->skip_wrong_letter); + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } if (state->curr_screen != SCREEN_PLAYING) break; get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); From b364a6cfabc78dd9469286c6ff87cfd04a9ce571 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 26 Aug 2024 21:10:00 -0400 Subject: [PATCH 139/220] Changed the lap to hard mode; fixed the ignore used letters --- .../watch_faces/complication/wordle_face.c | 41 +++++++++++-------- .../watch_faces/complication/wordle_face.h | 10 +++-- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index ca62f30..164c784 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -167,17 +167,29 @@ static bool check_word(wordle_state_t *state) { } static void show_skip_wrong_letter_indicator(bool skipping, WordleScreen curr_screen) { - if (skipping && curr_screen <= SCREEN_CONTINUE) - watch_set_indicator(WATCH_INDICATOR_LAP); + if (curr_screen >= SCREEN_PLAYING) return; + if (skipping) + watch_display_string("H", 3); else - watch_clear_indicator(WATCH_INDICATOR_LAP); + watch_display_string(" ", 3); } static void update_known_wrong_letters(wordle_state_t *state) { + bool wrong_loc[WORDLE_NUM_VALID_LETTERS] = {false}; + // To ignore letters that appear, but are in the wrong location, as letters that are guessed + // more often than they appear in the word will display as WORDLE_LETTER_WRONG + for (size_t i = 0; i < WORDLE_LENGTH; i++) { + if (state->word_elements_result[i] == WORDLE_LETTER_WRONG_LOC) { + for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { + if (state->word_elements[i] == j) + wrong_loc[j] = true; + } + } + } for (size_t i = 0; i < WORDLE_LENGTH; i++) { if (state->word_elements_result[i] == WORDLE_LETTER_WRONG) { for (size_t j = 0; j < WORDLE_NUM_VALID_LETTERS; j++) { - if (state->word_elements[i] == j) + if (state->word_elements[i] == j && !wrong_loc[j]) state->known_wrong_letters[j] = true; } } @@ -192,7 +204,6 @@ static void display_attempt(uint8_t attempt) { static void display_playing(wordle_state_t *state) { state->curr_screen = SCREEN_PLAYING; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_attempt(state->attempt); display_all_letters(state); } @@ -235,8 +246,8 @@ static void reset_board(wordle_state_t *state) { static void display_title(wordle_state_t *state) { state->curr_screen = SCREEN_TITLE; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("WO WordLE", 0); + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } #if !WORDLE_USE_DAILY_STREAK @@ -246,8 +257,8 @@ static void display_continue_result(bool continuing) { static void display_continue(wordle_state_t *state) { state->curr_screen = SCREEN_CONTINUE; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string("Cont ", 4); + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); display_continue_result(state->continuing); } #endif @@ -255,7 +266,6 @@ static void display_continue(wordle_state_t *state) { static void display_streak(wordle_state_t *state) { char buf[12]; state->curr_screen = SCREEN_STREAK; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); #if WORDLE_USE_DAILY_STREAK if (state->streak > 99) sprintf(buf, "WO St--dy"); @@ -266,12 +276,12 @@ static void display_streak(wordle_state_t *state) { #endif watch_display_string(buf, 0); watch_set_colon(); + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } #if WORDLE_USE_DAILY_STREAK static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); if (state->streak < 40) { char buf[13]; sprintf(buf,"WO%2d WaIt ", state->streak); @@ -280,6 +290,7 @@ static void display_wait(wordle_state_t *state) { else { // Streak too long to display in top-right watch_display_string("WO WaIt ", 0); } + show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } static uint32_t get_day_unix_time(void) { @@ -291,7 +302,6 @@ static uint32_t get_day_unix_time(void) { static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," L %s", subsecond % 2 ? _valid_words[state->curr_answer] : " "); watch_display_string(buf, 0); } @@ -299,7 +309,6 @@ static void display_lose(wordle_state_t *state, uint8_t subsecond) { static void display_win(wordle_state_t *state, uint8_t subsecond) { (void) state; char buf[13]; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); sprintf(buf," W %s ", subsecond % 2 ? "NICE" : "JOb "); watch_display_string(buf, 0); } @@ -334,7 +343,6 @@ static void display_result(wordle_state_t *state, uint8_t subsecond) { break; } } - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); watch_display_string(buf, 5); } @@ -533,10 +541,6 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: - if (state->curr_screen < SCREEN_PLAYING) { - state->skip_wrong_letter = !state->skip_wrong_letter; - show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); - } if (state->curr_screen != SCREEN_PLAYING) break; get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); display_letter(state, true); @@ -559,6 +563,11 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi } break; case EVENT_ALARM_LONG_PRESS: + if (state->curr_screen < 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; display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 0d73a9b..2a3cc1d 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -46,18 +46,22 @@ * Else: Next screen * Light Hold * If Playing: Previous letter - * Else: Toggle skipping over letters that have been confirmed to not be in the word (indicated via the LAP icon) + * Else: None * * Alarm Press * If Playing: If WORDLE_USE_RANDOM_GUESS is set and Light btn held and * (on first letter or already used a random guess) * and first attempt: Use a random 5 letter word with all letters that are different. * Else: Next position - * Next position * Else: Next screen * Alarm Hold * If Playing: Previous position - * Else: None + * Else: Toggle Hard-Mode. This is skipping over letters that have been confirmed + * to not be in the word (indicated via 'H' in the top-right) + * + * Note: Actual Hard Mode in Wordle game is "Any revealed hints must be used in subsequent guesses" + * But that came off as clunky UX on the Casio. So instead it only removes unused letters from the keyboard + * as that also simplifies the keyboard. */ #define WORDLE_LENGTH 5 From 41df6c113f86fc6a5477f56f274ed1419e216d3e Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Mon, 26 Aug 2024 22:24:33 -0400 Subject: [PATCH 140/220] Reset streak if don't continue --- movement/watch_faces/complication/wordle_face.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 164c784..09b5dc2 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -401,6 +401,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { display_playing(state); else { reset_board(state); + state->streak = 0; display_streak(state); } break; @@ -496,7 +497,7 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { wordle_state_t *state = (wordle_state_t *)context; #if WORDLE_USE_DAILY_STREAK uint32_t now = get_day_unix_time() ; - if (state->prev_day <= (now + (60 *60 * 24))) state->streak = 0; + if (now >= (state->prev_day + (60 *60 * 24))) state->streak = 0; if (state->curr_day != now) reset_all_elements(state); #endif state->using_random_guess = false; From c43820e75d225d56093f132295dbfef45b77e1d9 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 27 Aug 2024 12:08:24 -0400 Subject: [PATCH 141/220] Wordle game resets after 24hrs of not playing when not using daily streak --- .../watch_faces/complication/wordle_face.c | 18 ++++++++++++------ .../watch_faces/complication/wordle_face.h | 5 +++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 09b5dc2..1cf931b 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -25,9 +25,7 @@ #include #include #include "wordle_face.h" -#if WORDLE_USE_DAILY_STREAK #include "watch_utility.h" -#endif static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ @@ -298,6 +296,11 @@ static uint32_t get_day_unix_time(void) { now.unit.hour = now.unit.minute = now.unit.second = 0; return watch_utility_date_time_to_unix_time(now, 0); } +#else +static uint32_t get_day_unix_time(void) { + watch_date_time now = watch_rtc_get_date_time(); + return watch_utility_date_time_to_unix_time(now, 0); +} #endif static void display_lose(wordle_state_t *state, uint8_t subsecond) { @@ -373,9 +376,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { #endif return true; case SCREEN_STREAK: -#if WORDLE_USE_DAILY_STREAK state->curr_day = get_day_unix_time(); -#endif reset_board(state); return true; case SCREEN_WIN: @@ -495,10 +496,15 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; + uint32_t now = get_day_unix_time(); #if WORDLE_USE_DAILY_STREAK - uint32_t now = get_day_unix_time() ; - if (now >= (state->prev_day + (60 *60 * 24))) state->streak = 0; if (state->curr_day != now) reset_all_elements(state); + if (now >= (state->prev_day + (60 *60 * 24))) state->streak = 0; +#else + if (is_playing(state) && now >= (state->curr_day + (60 *60 * 24))) { + state->streak = 0; + reset_board(state); + } #endif state->using_random_guess = false; if (is_playing(state) && state->curr_screen >= SCREEN_RESULT) { diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 2a3cc1d..ffb3782 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -66,7 +66,8 @@ #define WORDLE_LENGTH 5 #define WORDLE_MAX_ATTEMPTS 6 -#define WORDLE_USE_DAILY_STREAK false +#define WORDLE_USE_DAILY_STREAK false // If true, the board will reset daily and the streak will go to zero if the game isn't played for a day + // If false, then the streak will still reset if the game is not completed within 24 hours #define WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. /* WORDLE_USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess @@ -119,9 +120,9 @@ typedef struct { uint8_t streak; WordleScreen curr_screen; bool known_wrong_letters[WORDLE_NUM_VALID_LETTERS]; + uint32_t curr_day; #if WORDLE_USE_DAILY_STREAK uint32_t prev_day; - uint32_t curr_day; #endif } wordle_state_t; From d1c19166a11d90cf9a08d8772ca6aebe28d4e958 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 27 Aug 2024 12:31:22 -0400 Subject: [PATCH 142/220] WORDLE_USE_DAILY_STREAK logic changed --- .../watch_faces/complication/wordle_face.c | 37 ++++++++----------- .../watch_faces/complication/wordle_face.h | 17 ++++++--- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index 1cf931b..bcab8f0 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -248,7 +248,7 @@ static void display_title(wordle_state_t *state) { show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } -#if !WORDLE_USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK != 2 static void display_continue_result(bool continuing) { watch_display_string(continuing ? "y" : "n", 9); } @@ -264,7 +264,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; -#if WORDLE_USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK != 2 if (state->streak > 99) sprintf(buf, "WO St--dy"); else @@ -277,7 +277,7 @@ static void display_streak(wordle_state_t *state) { show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } -#if WORDLE_USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK == 2 static void display_wait(wordle_state_t *state) { state->curr_screen = SCREEN_WAIT; if (state->streak < 40) { @@ -290,18 +290,15 @@ static void display_wait(wordle_state_t *state) { } show_skip_wrong_letter_indicator(state->skip_wrong_letter, state->curr_screen); } +#endif static uint32_t get_day_unix_time(void) { watch_date_time now = watch_rtc_get_date_time(); +#if WORDLE_USE_DAILY_STREAK == 2 now.unit.hour = now.unit.minute = now.unit.second = 0; - return watch_utility_date_time_to_unix_time(now, 0); -} -#else -static uint32_t get_day_unix_time(void) { - watch_date_time now = watch_rtc_get_date_time(); - return watch_utility_date_time_to_unix_time(now, 0); -} #endif + return watch_utility_date_time_to_unix_time(now, 0); +} static void display_lose(wordle_state_t *state, uint8_t subsecond) { char buf[WORDLE_LENGTH + 6]; @@ -358,8 +355,8 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { display_playing(state); return true; case SCREEN_TITLE: -#if WORDLE_USE_DAILY_STREAK - if (state->prev_day == get_day_unix_time()) { +#if WORDLE_USE_DAILY_STREAK == 2 + if (state->day_last_game_started == get_day_unix_time()) { display_wait(state); } else if (is_playing(state)) @@ -376,7 +373,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { #endif return true; case SCREEN_STREAK: - state->curr_day = get_day_unix_time(); + state->day_last_game_started = get_day_unix_time(); reset_board(state); return true; case SCREEN_WIN: @@ -388,7 +385,7 @@ static bool act_on_btn(wordle_state_t *state, const uint8_t pin) { state->position = get_first_pos(state->word_elements_result); display_playing(state); return true; -#if WORDLE_USE_DAILY_STREAK +#if WORDLE_USE_DAILY_STREAK == 2 case SCREEN_WAIT: (void) pin; display_title(state); @@ -444,8 +441,8 @@ static void get_result(wordle_state_t *state) { state->curr_screen = SCREEN_WIN; if (state->streak < 0x7F) state->streak++; -#if WORDLE_USE_DAILY_STREAK - state->prev_day = get_day_unix_time(); +#if WORDLE_USE_DAILY_STREAK == 2 + state->day_last_game_started = get_day_unix_time(); // On the edge-case where we solve the puzzle at midnight #endif return; } @@ -496,12 +493,10 @@ void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void wordle_face_activate(movement_settings_t *settings, void *context) { (void) settings; wordle_state_t *state = (wordle_state_t *)context; +#if WORDLE_USE_DAILY_STREAK != 0 uint32_t now = get_day_unix_time(); -#if WORDLE_USE_DAILY_STREAK - if (state->curr_day != now) reset_all_elements(state); - if (now >= (state->prev_day + (60 *60 * 24))) state->streak = 0; -#else - if (is_playing(state) && now >= (state->curr_day + (60 *60 * 24))) { + if (now >= (state->day_last_game_started + (60 *60 * 24)) && + (is_playing(state) || WORDLE_USE_DAILY_STREAK == 2)) { state->streak = 0; reset_board(state); } diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index ffb3782..b035f14 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -66,8 +66,16 @@ #define WORDLE_LENGTH 5 #define WORDLE_MAX_ATTEMPTS 6 -#define WORDLE_USE_DAILY_STREAK false // If true, the board will reset daily and the streak will go to zero if the game isn't played for a day - // If false, then the streak will still reset if the game is not completed within 24 hours +/* WORDLE_USE_DAILY_STREAK + * 0 = Don't ever reset the streak or the puzzle. + * 1 = Reset the streak and puzzle 24hrs after starting a puzzle and not finishing it. + * If the last puzzle was started at 8AM, it'll be considered failed at 8AM the next day. + * 2 = Reset the streak and puzzle if a puzzle goes unsolved or not started a day after the previous one. + * If the last puzzle was started at 8AM, it'll be considered failed at midnight the next day. + * This will not be the case if the puzzle is started at 8AM, continued at 11:59PM and solved at 12:01AM, the game will let that slide. + * Starting a new game instead of continuing is not allowed in this state. +*/ +#define WORDLE_USE_DAILY_STREAK 1 #define WORDLE_ALLOW_NON_WORD_AND_REPEAT_GUESSES false // This allows non-words to be entered and repeat guesses to be made. It saves ~11.5KB of ROM. /* WORDLE_USE_RANDOM_GUESS * 0 = Don't allow quickly choosing a random quess @@ -120,10 +128,7 @@ typedef struct { uint8_t streak; WordleScreen curr_screen; bool known_wrong_letters[WORDLE_NUM_VALID_LETTERS]; - uint32_t curr_day; -#if WORDLE_USE_DAILY_STREAK - uint32_t prev_day; -#endif + uint32_t day_last_game_started; } wordle_state_t; void wordle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 5435bc7f34350337fde476cbda2bba067ab29611 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 27 Aug 2024 13:08:13 -0400 Subject: [PATCH 143/220] Streak face fix --- movement/watch_faces/complication/wordle_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index bcab8f0..daaac20 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -264,7 +264,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; -#if WORDLE_USE_DAILY_STREAK != 2 +#if WORDLE_USE_DAILY_STREAK == 2 if (state->streak > 99) sprintf(buf, "WO St--dy"); else From dd719183cf9af58e3edbf487575c7ee2b6649ac8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 28 Aug 2024 21:38:09 -0400 Subject: [PATCH 144/220] hard mode btn changed; logic changed on daily streak so if puzzle wasn't started and completed the previous day, then drop the streak --- movement/watch_faces/complication/wordle_face.c | 15 ++++++++------- movement/watch_faces/complication/wordle_face.h | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.c b/movement/watch_faces/complication/wordle_face.c index daaac20..8381cec 100644 --- a/movement/watch_faces/complication/wordle_face.c +++ b/movement/watch_faces/complication/wordle_face.c @@ -495,8 +495,9 @@ void wordle_face_activate(movement_settings_t *settings, void *context) { wordle_state_t *state = (wordle_state_t *)context; #if WORDLE_USE_DAILY_STREAK != 0 uint32_t now = get_day_unix_time(); - if (now >= (state->day_last_game_started + (60 *60 * 24)) && - (is_playing(state) || WORDLE_USE_DAILY_STREAK == 2)) { + uint32_t one_day = 60 *60 * 24; + if ((WORDLE_USE_DAILY_STREAK == 2 && now >= (state->day_last_game_started + (2*one_day))) + || (now >= (state->day_last_game_started + one_day) && is_playing(state))) { state->streak = 0; reset_board(state); } @@ -543,6 +544,11 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi display_letter(state, true); break; case EVENT_LIGHT_LONG_PRESS: + if (state->curr_screen < 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; get_prev_letter(state->position, state->word_elements, state->known_wrong_letters, state->skip_wrong_letter); display_letter(state, true); @@ -565,11 +571,6 @@ bool wordle_face_loop(movement_event_t event, movement_settings_t *settings, voi } break; case EVENT_ALARM_LONG_PRESS: - if (state->curr_screen < 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; display_letter(state, true); state->position = get_prev_pos(state->position, state->word_elements_result); diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index b035f14..1b9a7fb 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -46,7 +46,8 @@ * Else: Next screen * Light Hold * If Playing: Previous letter - * Else: None + * Else: Toggle Hard-Mode. This is skipping over letters that have been confirmed + * to not be in the word (indicated via 'H' in the top-right) * * Alarm Press * If Playing: If WORDLE_USE_RANDOM_GUESS is set and Light btn held and @@ -56,8 +57,7 @@ * Else: Next screen * Alarm Hold * If Playing: Previous position - * Else: Toggle Hard-Mode. This is skipping over letters that have been confirmed - * to not be in the word (indicated via 'H' in the top-right) + * Else: None * * Note: Actual Hard Mode in Wordle game is "Any revealed hints must be used in subsequent guesses" * But that came off as clunky UX on the Casio. So instead it only removes unused letters from the keyboard From 4dedcb3a6d6914e7e9d5d20eb534b73df58c15b1 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 10:15:48 -0400 Subject: [PATCH 145/220] Added ability to find best starting word --- utils/wordle_face/wordle_list.py | 84 +++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index ea2154c..39e310c 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1,4 +1,4 @@ -import random, itertools, time, ast +import random, itertools, time, numpy source_link = "https://matthewminer.name/projects/calculators/wordle-words-left/" valid_list = [ @@ -1091,12 +1091,12 @@ possible_list = [ alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] -def most_used_letters(words=valid_list): +def most_used_letters(words=valid_list, letters=alphabet, print_result=True): ''' Outputs how many times each letter is used in the words array. ''' use_each_letter = {} - for i in alphabet: + for i in letters: count = 0 for word in words: for letter in word: @@ -1105,10 +1105,11 @@ def most_used_letters(words=valid_list): break use_each_letter[i] = count use_each_letter = dict(sorted(use_each_letter.items(), key=lambda item: item[1], reverse=True)) - print("Letter | Usage | Percent") - print("----------------------------") - for k in use_each_letter: - print(f"{k.upper()} | {use_each_letter[k]:5} | {round((100 * use_each_letter[k]) / len(words)):2}%") + if print_result: + print("Letter | Usage | Percent") + print("----------------------------") + for k in use_each_letter: + print(f"{k.upper()} | {use_each_letter[k]:5} | {round((100 * use_each_letter[k]) / len(words)):2}%") return use_each_letter @@ -1242,7 +1243,7 @@ def txt_of_all_letter_combos(num_letters_in_set, words=valid_list, min_letter_oc prev = time.time() start = prev letters_to_ignore = ['D','M'] # Don't diplay well on the watch - letter_usage = most_used_letters(words=words) + letter_usage = most_used_letters(words=words, print_result=False) for letter in letter_usage: if (100 * letter_usage[letter])/len(words) < min_letter_occ_percent_to_consider: letters_to_ignore.append(letter) @@ -1276,7 +1277,7 @@ def txt_of_all_letter_combos(num_letters_in_set, words=valid_list, min_letter_oc dict_combos_counts = dict(sorted(dict_combos_counts.items(), key=lambda item: item[1], reverse=True)) most_common_key = next(iter(dict_combos_counts)) - print(f"The Most Common Combo is: {most_common_key} with {dict_combos_counts[most_common_key]} words.") + print(f"The Most Common Combo for {num_letters_in_set} letters is: {most_common_key} with {dict_combos_counts[most_common_key]} words.") # print_valid_words(ast.literal_eval(most_common_key)) # Uncomment to display the text it creates if txt_out: @@ -1304,9 +1305,11 @@ def txt_of_all_letter_combos_differing_sizes(min=9, max=15, num_combos_print=20, print(f"{key}, {item}") -def location_of_letters(letters=alphabet, list=valid_list): - print(" 1 2 3 4 5 ") - print("-----------------------------------------") +def location_of_letters(letters=alphabet, list=valid_list, print_result=True): + letter_location_percentages = {} + if print_result: + print(" 1 2 3 4 5 ") + print("-----------------------------------------") letters = sorted(letters) for letter in letters: location = [0, 0, 0, 0, 0] @@ -1314,15 +1317,62 @@ def location_of_letters(letters=alphabet, list=valid_list): for i, char in enumerate(word): if char.upper() == letter.upper(): location[i]+=1 - location = [f"{round((100 * x) / sum(location)):2}%" for x in location] - print(f"{letter} : {location}") + location = [((100 * x) / sum(location)) for x in location] + letter_location_percentages[letter] = location + location_txt = [f"{round(x):2}%" for x in location] + if print_result: + print(f"{letter} : {location_txt}") + return letter_location_percentages + + +def best_first_word(letters=alphabet, list=valid_list, print_result=True, words_to_print=None): + ''' + Word_good has every word with only unique letters as keys and that values are: + 1. Take the usage of every letter, normalize the max to 100 and the min to 0. + 2. Go through each letter in the word and see how often that letter appears in that exact location. + 3. Multiply that occurrance in location with the normalized total usage. + 4. Do this for each letter and add it all together. + + Ex: SLATE + Normalized usage: S=38, L=42, A=79, T=46, E=100 + S in position 1: 55% + L in position 2: 27% + A in position 3: 31% + T in position 4: 19% + E in position 5: 34% + Total = (38*55) + (42*27) + (79*31) + (46*19) + (100*35) = 10047 + ''' + valid_words = list_of_valid_words(letters, list) + letter_usage = most_used_letters(words=list, letters=letters, print_result=False) + a=[[max(letter_usage.values()),1],[min(letter_usage.values()),1]] + b=[100,0] + m,b = numpy.linalg.solve(a,b).tolist() + letter_usage_normalized = {key: ((m * value) + b) for key, value in letter_usage.items()} + loc_letters = location_of_letters(letters=letters, list=list, print_result=False) + word_good = {} + valid_words_unique = [word for word in valid_words if len(word) == len(set(word))] + for word in valid_words_unique: + usage = 0 + for i, char in enumerate(word): + usage += loc_letters[char][i] * letter_usage_normalized[char] + word_good[word] = round(usage) + word_good = {k: v for k, v in sorted(word_good.items(), key=lambda item: item[1], reverse=True)} + if print_result: + print("Word, Usage Value") + print("------------------") + for i,[k,v] in enumerate(word_good.items()): + if words_to_print is not None and i+1 > words_to_print: + break + print(f"{k}, {v}") + return word_good if __name__ == "__main__": my_letters = ['A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P', 'R', 'S', 'T'] #print(f"{len(list_of_valid_words(my_letters, valid_list))} Words can be made with {my_letters}") - #most_used_letters() - #location_of_letters(my_letters) + #most_used_letters(letters=my_letters) + #location_of_letters(letters=my_letters) print_valid_words(my_letters) #txt_of_all_letter_combos_differing_sizes(max = 16, min=10) - #txt_of_all_letter_combos(14) \ No newline at end of file + #txt_of_all_letter_combos(14) + #best_first_word(letters=my_letters, print_result=True, words_to_print=10) \ No newline at end of file From 73a975d0d9e3045102ce74c9d4489ff0a19876ef Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 08:50:11 -0400 Subject: [PATCH 146/220] Added endless-runner face --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/endless_runner_face.c | 408 ++++++++++++++++++ .../complication/endless_runner_face.h | 63 +++ 4 files changed, 473 insertions(+) create mode 100644 movement/watch_faces/complication/endless_runner_face.c create mode 100644 movement/watch_faces/complication/endless_runner_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b..7114856 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/endless_runner_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 3557110..74856a6 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "endless_runner_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c new file mode 100644 index 0000000..6b2bb01 --- /dev/null +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -0,0 +1,408 @@ +/* + * MIT License + * + * Copyright (c) 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "endless_runner_face.h" + +typedef enum { + NOT_JUMPING = 0, + JUMP, + JUMPING_1, + JUMPING_2, + JUMP_COUNT +} ScrollingJumpState; + +typedef enum { + SCREEN_TITLE = 0, + SCREEN_PLAYING, + SCREEN_LOSE, + SCREEN_COUNT +} ScrollingCurrScreen; + +typedef enum { + DIFF_NORM = 0, // 8x speed; 4 0's min; + DIFF_HARD, // 8x speed; 3 0's min; 2 - Easy 4x speed; 4 0's min + DIFF_EASY, // 4x speed; 4 0's min + DIFF_COUNT +} ScrollingDifficulty; + +#define NUM_GRID 12 +#define FREQ 8 +#define FREQ_EASY 4 +#define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 + +typedef struct { + uint32_t obst_pattern; + int16_t obst_indx : 8; + int16_t jump_state : 3; + int16_t sec_before_moves : 3; + bool loc_2_on; + bool loc_3_on; +} game_state_t; + +static game_state_t game_state; +static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; + +static void print_binary(uint32_t value, int bits) { + for (int i = bits - 1; i >= 0; i--) { + // Print each bit + printf("%d", (value >> i) & 1); + // Optional: add a space every 4 bits for readability + if (i % 4 == 0 && i != 0) { + printf(" "); + } + } + printf("\r\n"); +} + +static uint32_t get_random(uint32_t max) { + #if __EMSCRIPTEN__ + return rand() % max; + #else + return arc4random_uniform(max); + #endif +} + +static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { +/** @brief A legal random number starts with the previous number (which should be the 12 bits on the screen). + * @param prev_val The previous value to tack onto. The return will have its first NUM_GRID MSBs be the same as prev_val, and the rest be new + * @param difficulty To dictate how spread apart the obsticles must be + * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val + */ + uint8_t min_zeros = difficulty == DIFF_HARD ? 3 : 4; + uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; + uint32_t rand = get_random(max); + uint32_t rand_legal = 0; + prev_val = prev_val & ~max; + + for (int i = (NUM_GRID + 1); i <= _num_bits_obst_pattern; i++) { + uint32_t mask = 1 << (_num_bits_obst_pattern - i); + bool msb = (rand & mask) >> (_num_bits_obst_pattern - i); + if (msb) { + rand_legal = rand_legal << min_zeros; + i+=min_zeros; + } + rand_legal |= msb; + rand_legal = rand_legal << 1; + } + + rand_legal = rand_legal & max; + for (int i = 0; i <= min_zeros; i++) { + if (prev_val & (1 << (i + _num_bits_obst_pattern - NUM_GRID))){ + rand_legal = rand_legal >> (min_zeros - i); + break; + } + } + rand_legal = prev_val | rand_legal; + print_binary(rand_legal, _num_bits_obst_pattern); + return rand_legal; +} + +static void display_ball(bool jumping) { + if (jumping == NOT_JUMPING) { + watch_set_pixel(0, 21); + watch_set_pixel(1, 21); + watch_set_pixel(0, 20); + watch_set_pixel(1, 20); + watch_clear_pixel(1, 17); + watch_clear_pixel(2, 20); + watch_clear_pixel(2, 21); + } + else { + watch_clear_pixel(0, 21); + watch_clear_pixel(1, 21); + watch_clear_pixel(0, 20); + watch_set_pixel(1, 20); + watch_set_pixel(1, 17); + watch_set_pixel(2, 20); + watch_set_pixel(2, 21); + } +} + +static void display_score(uint8_t score) { + char buf[3]; + if (score > MAX_DISP_SCORE) watch_display_string(" -", 2); + else{ + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); + } +} + +static void display_difficulty(uint16_t difficulty) { + switch (difficulty) + { + case DIFF_EASY: + watch_display_string("E", 9); + break; + case DIFF_HARD: + watch_display_string("H", 9); + break; + case DIFF_NORM: + default: + watch_display_string("n", 9); + break; + } +} + +static void display_title(endless_runner_state_t *state) { + state -> curr_screen = SCREEN_TITLE; + memset(&game_state, 0, sizeof(game_state)); + game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. + if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second + watch_display_string("SC SEL ", 0); + display_score(state -> hi_score); + display_difficulty(state -> difficulty); +} + +static void display_lose_screen(endless_runner_state_t *state) { + movement_request_tick_frequency(1); + state -> curr_screen = SCREEN_LOSE; + state -> curr_score = 0; + watch_display_string(" LOSEr", 4); + if (state -> soundOn) + watch_buzzer_play_note(BUZZER_NOTE_A1, 600); + else + delay_ms(600); +} + +static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { + bool success_jump = false; + switch (grid_loc) + { + case 2: + game_state.loc_2_on = obstacle; + if (obstacle) + watch_set_pixel(0, 20); + else if (game_state.jump_state != NOT_JUMPING) + watch_clear_pixel(0, 20); + break; + case 3: + game_state.loc_3_on = obstacle; + if (obstacle) + watch_set_pixel(1, 21); + else if (game_state.jump_state != NOT_JUMPING) + watch_clear_pixel(1, 21); + break; + + case 1: + if (obstacle) { // If an obstacle is here, it means the ball cleared it + success_jump = true; + if (state -> curr_score < MAX_DISP_SCORE) { + state -> curr_score++; + if (state -> curr_score > state -> hi_score) + state -> hi_score = state -> curr_score; + display_score(state -> curr_score); + } + } + //fall through + case 0: + case 5: + if (obstacle) + watch_set_pixel(0, 18 + grid_loc); + else + watch_clear_pixel(0, 18 + grid_loc); + break; + case 4: + if (obstacle) + watch_set_pixel(1, 22); + else + watch_clear_pixel(1, 22); + break; + case 6: + if (obstacle) + watch_set_pixel(1, 0); + else + watch_clear_pixel(1, 0); + break; + case 7: + case 8: + if (obstacle) + watch_set_pixel(0, grid_loc - 6); + else + watch_clear_pixel(0, grid_loc - 6); + break; + case 9: + case 10: + if (obstacle) + watch_set_pixel(0, grid_loc - 5); + else + watch_clear_pixel(0, grid_loc - 5); + break; + case 11: + if (obstacle) + watch_set_pixel(1, 6); + else + watch_clear_pixel(1, 6); + break; + default: + break; + } + return success_jump; +} + +static bool display_obstacles(endless_runner_state_t *state) { + bool success_jump = false; + for (int i = 0; i < NUM_GRID; i++) { + // Use a bitmask to isolate each bit and shift it to the least significant position + uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); + bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); + if (display_obstacle(obstacle, i, state)) success_jump = true; + + } + game_state.obst_pattern = game_state.obst_pattern << 1; + game_state.obst_indx++; + if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { + game_state.obst_indx = 0; + game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); + } + return success_jump; +} + +void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(endless_runner_state_t)); + memset(*context_ptr, 0, sizeof(endless_runner_state_t)); + } +} + +void endless_runner_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + endless_runner_state_t *state = (endless_runner_state_t *)context; + bool success_jump = false; + + switch (event.event_type) { + case EVENT_ACTIVATE: + state -> curr_screen = SCREEN_TITLE; + if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); + display_title(state); + state -> curr_score = 0; + break; + case EVENT_TICK: + switch (state -> curr_screen) + { + case SCREEN_TITLE: + case SCREEN_LOSE: + break; + default: + if (game_state.sec_before_moves == 0) + success_jump = display_obstacles(state); + else if (event.subsecond == 0) + if(--game_state.sec_before_moves == 0) game_state.obst_pattern = get_random_legal(0, state -> difficulty); + switch (game_state.jump_state) + { + case JUMP: + game_state.jump_state = JUMPING_1; + break; + case JUMPING_1: + game_state.jump_state = JUMPING_2; + break; + case JUMPING_2: + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state); + if (state -> soundOn){ + if (success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + break; + default: + break; + } + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) + display_lose_screen(state); + break; + } + break; + case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_BUTTON_UP: + if (state -> curr_screen == SCREEN_TITLE) { + state -> curr_screen = SCREEN_PLAYING; + movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); + watch_display_string(" ", 4); + display_ball(false); + display_score(state -> curr_score); + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 200); + watch_buzzer_play_note(BUZZER_NOTE_E5, 200); + watch_buzzer_play_note(BUZZER_NOTE_G5, 200); + } + } + else if (state -> curr_screen == SCREEN_LOSE) { + display_title(state); + } + break; + case EVENT_LIGHT_LONG_PRESS: + if (state -> curr_screen == SCREEN_TITLE) { + state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; + display_difficulty(state -> difficulty); + if (state -> soundOn) { + if (state -> difficulty == DIFF_EASY) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + } + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + game_state.jump_state = JUMP; + display_ball(game_state.jump_state); + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state -> curr_screen != SCREEN_PLAYING) + { + state -> soundOn = !state -> soundOn; + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void endless_runner_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h new file mode 100644 index 0000000..b588f35 --- /dev/null +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2024 <#author_name#> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ENDLESS_RUNNER_FACE_H_ +#define ENDLESS_RUNNER_FACE_H_ + +#include "movement.h" + +/* + ENDLESS_RUNNER face + + This is a basic endless-runner, like the [Chrome Dino game](https://en.wikipedia.org/wiki/Dinosaur_Game). + On the title screen, you can select a difficulty by long-pressing LIGHT or toggle sound by long-pressing ALARM. + LED or ALARM are used to jump. + High-score is displayed on the top-right on the title screen. During a game, the current score is displayed. +*/ + +typedef struct { + // These are values that need saving between uses + uint16_t hi_score : 6; + uint16_t curr_score : 6; + uint16_t curr_screen : 2; + uint16_t difficulty : 2; + bool soundOn; + bool unused; +} endless_runner_state_t; + +void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void endless_runner_face_activate(movement_settings_t *settings, void *context); +bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void endless_runner_face_resign(movement_settings_t *settings, void *context); + +#define endless_runner_face ((const watch_face_t){ \ + endless_runner_face_setup, \ + endless_runner_face_activate, \ + endless_runner_face_loop, \ + endless_runner_face_resign, \ + NULL, \ +}) + +#endif // ENDLESS_RUNNER_FACE_H_ + From e2870eb7aff72c05573d78d407a75651e23c7618 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 08:51:45 -0400 Subject: [PATCH 147/220] Removed the binary print debug function --- .../watch_faces/complication/endless_runner_face.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 6b2bb01..2227d27 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -65,18 +65,6 @@ typedef struct { static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; -static void print_binary(uint32_t value, int bits) { - for (int i = bits - 1; i >= 0; i--) { - // Print each bit - printf("%d", (value >> i) & 1); - // Optional: add a space every 4 bits for readability - if (i % 4 == 0 && i != 0) { - printf(" "); - } - } - printf("\r\n"); -} - static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ return rand() % max; @@ -116,7 +104,6 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } } rand_legal = prev_val | rand_legal; - print_binary(rand_legal, _num_bits_obst_pattern); return rand_legal; } From ed3c4d3c3034bdc301b4bdbf7629188b3539a40a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 09:29:19 -0400 Subject: [PATCH 148/220] Fixed the long delays when beginning a game --- .../complication/endless_runner_face.c | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 2227d27..44ba2cd 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -55,9 +55,9 @@ typedef enum { typedef struct { uint32_t obst_pattern; - int16_t obst_indx : 8; - int16_t jump_state : 3; - int16_t sec_before_moves : 3; + uint16_t obst_indx : 8; + uint16_t jump_state : 3; + uint16_t sec_before_moves : 3; bool loc_2_on; bool loc_3_on; } game_state_t; @@ -287,10 +287,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti switch (event.event_type) { case EVENT_ACTIVATE: - state -> curr_screen = SCREEN_TITLE; if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); - state -> curr_score = 0; break; case EVENT_TICK: switch (state -> curr_screen) @@ -299,10 +297,11 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case SCREEN_LOSE: break; default: - if (game_state.sec_before_moves == 0) - success_jump = display_obstacles(state); - else if (event.subsecond == 0) - if(--game_state.sec_before_moves == 0) game_state.obst_pattern = get_random_legal(0, state -> difficulty); + if (game_state.sec_before_moves != 0) { + if (event.subsecond == 0) --game_state.sec_before_moves; + break; + } + success_jump = display_obstacles(state); switch (game_state.jump_state) { case JUMP: @@ -336,6 +335,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); watch_display_string(" ", 4); display_ball(false); + do // Avoid the first array of obstacles being a boring line of 0s + { + game_state.obst_pattern = get_random_legal(0, state -> difficulty); + } while (game_state.obst_pattern == 0); display_score(state -> curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); From abc0bedbde221f303b3960278f827b4a6ff1518a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 16 Jul 2024 22:35:07 -0400 Subject: [PATCH 149/220] Gave an extra jumping frame for non-hard mode; Curr scroll now loops; Title changed to ER --- .../complication/endless_runner_face.c | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 44ba2cd..67a7b54 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -30,23 +30,24 @@ typedef enum { NOT_JUMPING = 0, JUMP, JUMPING_1, - JUMPING_2, + JUMPING_2, + JUMPING_3, JUMP_COUNT -} ScrollingJumpState; +} RunnerJumpState; typedef enum { SCREEN_TITLE = 0, SCREEN_PLAYING, SCREEN_LOSE, SCREEN_COUNT -} ScrollingCurrScreen; +} RunnerCurrScreen; typedef enum { - DIFF_NORM = 0, // 8x speed; 4 0's min; - DIFF_HARD, // 8x speed; 3 0's min; 2 - Easy 4x speed; 4 0's min - DIFF_EASY, // 4x speed; 4 0's min + DIFF_NORM = 0, // 8x speed; 4 0's min; jump is 3 frames + DIFF_HARD, // 8x speed; 3 0's min; jump is 2 frames + DIFF_EASY, // 4x speed; 4 0's min; jump is 3 frames DIFF_COUNT -} ScrollingDifficulty; +} RunnerDifficulty; #define NUM_GRID 12 #define FREQ 8 @@ -158,7 +159,7 @@ static void display_title(endless_runner_state_t *state) { memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second - watch_display_string("SC SEL ", 0); + watch_display_string("ER SEL ", 0); display_score(state -> hi_score); display_difficulty(state -> difficulty); } @@ -167,7 +168,7 @@ static void display_lose_screen(endless_runner_state_t *state) { movement_request_tick_frequency(1); state -> curr_screen = SCREEN_LOSE; state -> curr_score = 0; - watch_display_string(" LOSEr", 4); + watch_display_string(" U LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); else @@ -195,16 +196,19 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - success_jump = true; - if (state -> curr_score < MAX_DISP_SCORE) { - state -> curr_score++; - if (state -> curr_score > state -> hi_score) - state -> hi_score = state -> curr_score; - display_score(state -> curr_score); - } + // Counter will continuously roll over, but high score will max out on the first roll-over + state -> curr_score = (state -> curr_score + 1) % (MAX_DISP_SCORE + 1); + if (state -> curr_score == 0) // This means the counter rolled over + state -> hi_score = MAX_DISP_SCORE + 1; + else if (state -> curr_score > state -> hi_score) + state -> hi_score = state -> curr_score; + display_score(state -> curr_score); } //fall through case 0: + if (obstacle) // If an obstacle is here, it means the ball cleared it + success_jump = true; + //fall through case 5: if (obstacle) watch_set_pixel(0, 18 + grid_loc); @@ -308,9 +312,12 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti game_state.jump_state = JUMPING_1; break; case JUMPING_1: - game_state.jump_state = JUMPING_2; + game_state.jump_state = (state -> difficulty == DIFF_HARD) ? JUMPING_3 : JUMPING_2; break; case JUMPING_2: + game_state.jump_state = JUMPING_3; + break; + case JUMPING_3: game_state.jump_state = NOT_JUMPING; display_ball(game_state.jump_state); if (state -> soundOn){ @@ -323,8 +330,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti default: break; } - if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { + delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. display_lose_screen(state); + } break; } break; @@ -332,7 +341,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_ALARM_BUTTON_UP: if (state -> curr_screen == SCREEN_TITLE) { state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency(state -> difficulty == DIFF_EASY ? FREQ_EASY : FREQ); + movement_request_tick_frequency((state -> difficulty == DIFF_EASY) ? FREQ_EASY : FREQ); watch_display_string(" ", 4); display_ball(false); do // Avoid the first array of obstacles being a boring line of 0s From defd01f9f0c21e79e93bf1321aeca83756fdadf4 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 17 Jul 2024 17:05:48 -0400 Subject: [PATCH 150/220] Added baby mode which used to be easy mode; easy mode is now same speed as normal, but 3 frames to jump and normal is 2 frames. --- .../complication/endless_runner_face.c | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 67a7b54..1e6abae 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -43,15 +43,16 @@ typedef enum { } RunnerCurrScreen; typedef enum { - DIFF_NORM = 0, // 8x speed; 4 0's min; jump is 3 frames - DIFF_HARD, // 8x speed; 3 0's min; jump is 2 frames - DIFF_EASY, // 4x speed; 4 0's min; jump is 3 frames + DIFF_BABY = 0, // 0.5x speed; 4 0's min; jump is 3 frames + DIFF_EASY, // 1x speed; 4 0's min; jump is 3 frames + DIFF_NORM, // 1x speed; 4 0's min; jump is 2 frames + DIFF_HARD, // 1x speed; 3 0's min; jump is 2 frames DIFF_COUNT } RunnerDifficulty; #define NUM_GRID 12 #define FREQ 8 -#define FREQ_EASY 4 +#define FREQ_SLOW 4 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -80,7 +81,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { * @param difficulty To dictate how spread apart the obsticles must be * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val */ - uint8_t min_zeros = difficulty == DIFF_HARD ? 3 : 4; + uint8_t min_zeros = (difficulty == DIFF_HARD) ? 3 : 4; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; uint32_t rand = get_random(max); uint32_t rand_legal = 0; @@ -141,6 +142,9 @@ static void display_score(uint8_t score) { static void display_difficulty(uint16_t difficulty) { switch (difficulty) { + case DIFF_BABY: + watch_display_string("b", 9); + break; case DIFF_EASY: watch_display_string("E", 9); break; @@ -277,6 +281,8 @@ void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(endless_runner_state_t)); memset(*context_ptr, 0, sizeof(endless_runner_state_t)); + endless_runner_state_t *state = (endless_runner_state_t *)*context_ptr; + state->difficulty = DIFF_NORM; } } @@ -312,7 +318,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti game_state.jump_state = JUMPING_1; break; case JUMPING_1: - game_state.jump_state = (state -> difficulty == DIFF_HARD) ? JUMPING_3 : JUMPING_2; + game_state.jump_state = (state -> difficulty >= DIFF_NORM) ? JUMPING_3 : JUMPING_2; break; case JUMPING_2: game_state.jump_state = JUMPING_3; @@ -341,7 +347,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_ALARM_BUTTON_UP: if (state -> curr_screen == SCREEN_TITLE) { state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency((state -> difficulty == DIFF_EASY) ? FREQ_EASY : FREQ); + movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); watch_display_string(" ", 4); display_ball(false); do // Avoid the first array of obstacles being a boring line of 0s @@ -364,7 +370,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; display_difficulty(state -> difficulty); if (state -> soundOn) { - if (state -> difficulty == DIFF_EASY) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); } } From 6f3f09c5babbabfa8471f6f070e442c40df73a5e Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 19 Jul 2024 08:43:15 -0400 Subject: [PATCH 151/220] Reformat to remove some hardocded variables --- .../complication/endless_runner_face.c | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 1e6abae..cd672f6 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -27,12 +27,9 @@ #include "endless_runner_face.h" typedef enum { - NOT_JUMPING = 0, - JUMP, - JUMPING_1, - JUMPING_2, - JUMPING_3, - JUMP_COUNT + JUMPING_FINAL_FRAME = 0, + NOT_JUMPING, + JUMPING_START, } RunnerJumpState; typedef enum { @@ -53,12 +50,16 @@ typedef enum { #define NUM_GRID 12 #define FREQ 8 #define FREQ_SLOW 4 +#define JUMP_FRAMES 2 +#define JUMP_FRAMES_EASY 20 +#define MIN_ZEROES 4 +#define MIN_ZEROES_HARD 3 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { uint32_t obst_pattern; uint16_t obst_indx : 8; - uint16_t jump_state : 3; + uint16_t jump_state : 5; uint16_t sec_before_moves : 3; bool loc_2_on; bool loc_3_on; @@ -81,7 +82,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { * @param difficulty To dictate how spread apart the obsticles must be * @return the new random value, where it's first NUM_GRID MSBs are the same as prev_val */ - uint8_t min_zeros = (difficulty == DIFF_HARD) ? 3 : 4; + uint8_t min_zeros = (difficulty == DIFF_HARD) ? MIN_ZEROES_HARD : MIN_ZEROES; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; uint32_t rand = get_random(max); uint32_t rand_legal = 0; @@ -110,7 +111,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } static void display_ball(bool jumping) { - if (jumping == NOT_JUMPING) { + if (!jumping) { watch_set_pixel(0, 21); watch_set_pixel(1, 21); watch_set_pixel(0, 20); @@ -168,6 +169,24 @@ static void display_title(endless_runner_state_t *state) { display_difficulty(state -> difficulty); } +static void begin_playing(endless_runner_state_t *state) { + state -> curr_screen = SCREEN_PLAYING; + movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); + watch_display_string("ER ", 0); + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + do // Avoid the first array of obstacles being a boring line of 0s + { + game_state.obst_pattern = get_random_legal(0, state -> difficulty); + } while (game_state.obst_pattern == 0); + display_score(state -> curr_score); + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 200); + watch_buzzer_play_note(BUZZER_NOTE_E5, 200); + watch_buzzer_play_note(BUZZER_NOTE_G5, 200); + } +} + static void display_lose_screen(endless_runner_state_t *state) { movement_request_tick_frequency(1); state -> curr_screen = SCREEN_LOSE; @@ -294,6 +313,7 @@ void endless_runner_face_activate(movement_settings_t *settings, void *context) bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; bool success_jump = false; + uint8_t curr_jump_frame = 0; switch (event.event_type) { case EVENT_ACTIVATE: @@ -314,26 +334,25 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti success_jump = display_obstacles(state); switch (game_state.jump_state) { - case JUMP: - game_state.jump_state = JUMPING_1; + case NOT_JUMPING: break; - case JUMPING_1: - game_state.jump_state = (state -> difficulty >= DIFF_NORM) ? JUMPING_3 : JUMPING_2; - break; - case JUMPING_2: - game_state.jump_state = JUMPING_3; - break; - case JUMPING_3: + case JUMPING_FINAL_FRAME: game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state); + display_ball(game_state.jump_state != NOT_JUMPING); if (state -> soundOn){ if (success_jump) watch_buzzer_play_note(BUZZER_NOTE_C5, 60); else watch_buzzer_play_note(BUZZER_NOTE_C3, 60); } - break; + break; default: + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. break; } if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { @@ -345,25 +364,10 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (state -> curr_screen == SCREEN_TITLE) { - state -> curr_screen = SCREEN_PLAYING; - movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); - watch_display_string(" ", 4); - display_ball(false); - do // Avoid the first array of obstacles being a boring line of 0s - { - game_state.obst_pattern = get_random_legal(0, state -> difficulty); - } while (game_state.obst_pattern == 0); - display_score(state -> curr_score); - if (state -> soundOn){ - watch_buzzer_play_note(BUZZER_NOTE_C5, 200); - watch_buzzer_play_note(BUZZER_NOTE_E5, 200); - watch_buzzer_play_note(BUZZER_NOTE_G5, 200); - } - } - else if (state -> curr_screen == SCREEN_LOSE) { + if (state -> curr_screen == SCREEN_TITLE) + begin_playing(state); + else if (state -> curr_screen == SCREEN_LOSE) display_title(state); - } break; case EVENT_LIGHT_LONG_PRESS: if (state -> curr_screen == SCREEN_TITLE) { @@ -378,8 +382,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ - game_state.jump_state = JUMP; - display_ball(game_state.jump_state); + game_state.jump_state = JUMPING_START; + display_ball(game_state.jump_state != NOT_JUMPING); } break; case EVENT_ALARM_LONG_PRESS: From 2d7aaceff7c73ae2438a5c6a11a1749bf0191203 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 06:46:08 -0400 Subject: [PATCH 152/220] hi score resets weekly --- .../watch_faces/complication/endless_runner_face.c | 13 +++++++++++++ .../watch_faces/complication/endless_runner_face.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index cd672f6..6c761df 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -25,6 +25,7 @@ #include #include #include "endless_runner_face.h" +#include "watch_utility.h" typedef enum { JUMPING_FINAL_FRAME = 0, @@ -314,9 +315,21 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti endless_runner_state_t *state = (endless_runner_state_t *)context; bool success_jump = false; uint8_t curr_jump_frame = 0; + watch_date_time date_time; + uint32_t weeknumber; switch (event.event_type) { case EVENT_ACTIVATE: + date_time = watch_rtc_get_date_time(); + weeknumber = watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day); + if ((state -> weeknumber_prev_hi_score != weeknumber) || + (state -> year_prev_hi_score != date_time.unit.year)) + { + // The high score resets itself every new week. + state -> hi_score = 0; + state -> weeknumber_prev_hi_score = weeknumber; + state -> year_prev_hi_score = date_time.unit.year; + } if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index b588f35..03ec418 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -44,6 +44,8 @@ typedef struct { uint16_t difficulty : 2; bool soundOn; bool unused; + uint8_t weeknumber_prev_hi_score; + uint8_t year_prev_hi_score; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 07d2bc91a54b4a817f3deaba92b5218e92e97bf8 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 07:48:50 -0400 Subject: [PATCH 153/220] Modified hi score display to allow for 3 digits in hi-score, it now resets at the beginning of each month --- .../complication/endless_runner_face.c | 88 ++++++++++--------- .../complication/endless_runner_face.h | 12 ++- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 6c761df..3016f88 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -25,7 +25,6 @@ #include #include #include "endless_runner_face.h" -#include "watch_utility.h" typedef enum { JUMPING_FINAL_FRAME = 0, @@ -52,9 +51,10 @@ typedef enum { #define FREQ 8 #define FREQ_SLOW 4 #define JUMP_FRAMES 2 -#define JUMP_FRAMES_EASY 20 +#define JUMP_FRAMES_EASY 3 #define MIN_ZEROES 4 #define MIN_ZEROES_HARD 3 +#define MAX_HI_SCORE 999 #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -62,6 +62,8 @@ typedef struct { uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; + uint16_t curr_score : 10; + uint16_t curr_screen : 4; bool loc_2_on; bool loc_3_on; } game_state_t; @@ -134,44 +136,52 @@ static void display_ball(bool jumping) { static void display_score(uint8_t score) { char buf[3]; - if (score > MAX_DISP_SCORE) watch_display_string(" -", 2); - else{ - sprintf(buf, "%2d", score); - watch_display_string(buf, 2); - } + score %= (MAX_DISP_SCORE + 1); + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); } static void display_difficulty(uint16_t difficulty) { switch (difficulty) { case DIFF_BABY: - watch_display_string("b", 9); + watch_display_string("b", 3); break; case DIFF_EASY: - watch_display_string("E", 9); + watch_display_string("E", 3); break; case DIFF_HARD: - watch_display_string("H", 9); + watch_display_string("H", 3); break; case DIFF_NORM: default: - watch_display_string("n", 9); + watch_display_string("N", 3); break; } } static void display_title(endless_runner_state_t *state) { - state -> curr_screen = SCREEN_TITLE; + char buf[10]; + uint16_t hi_score = state -> hi_score; + uint8_t difficulty = state -> difficulty; + bool sound_on = state -> soundOn; memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. - if (state -> soundOn) game_state.sec_before_moves--; // Start chime is about 1 second - watch_display_string("ER SEL ", 0); - display_score(state -> hi_score); - display_difficulty(state -> difficulty); + if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second + watch_set_colon(); + if (hi_score <= 99) + sprintf(buf, "ER HS%2d ", hi_score); + else if (hi_score <= MAX_HI_SCORE) + sprintf(buf, "ER HS%3d ", hi_score); + else + sprintf(buf, "ER HS-- ", hi_score); + watch_display_string(buf, 0); + display_difficulty(difficulty); } static void begin_playing(endless_runner_state_t *state) { - state -> curr_screen = SCREEN_PLAYING; + game_state.curr_screen = SCREEN_PLAYING; + watch_clear_colon(); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); watch_display_string("ER ", 0); game_state.jump_state = NOT_JUMPING; @@ -180,7 +190,7 @@ static void begin_playing(endless_runner_state_t *state) { { game_state.obst_pattern = get_random_legal(0, state -> difficulty); } while (game_state.obst_pattern == 0); - display_score(state -> curr_score); + display_score( game_state.curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); watch_buzzer_play_note(BUZZER_NOTE_E5, 200); @@ -189,9 +199,8 @@ static void begin_playing(endless_runner_state_t *state) { } static void display_lose_screen(endless_runner_state_t *state) { - movement_request_tick_frequency(1); - state -> curr_screen = SCREEN_LOSE; - state -> curr_score = 0; + game_state.curr_screen = SCREEN_LOSE; + game_state.curr_score = 0; watch_display_string(" U LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); @@ -220,13 +229,12 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - // Counter will continuously roll over, but high score will max out on the first roll-over - state -> curr_score = (state -> curr_score + 1) % (MAX_DISP_SCORE + 1); - if (state -> curr_score == 0) // This means the counter rolled over - state -> hi_score = MAX_DISP_SCORE + 1; - else if (state -> curr_score > state -> hi_score) - state -> hi_score = state -> curr_score; - display_score(state -> curr_score); + if (game_state.curr_score <= MAX_HI_SCORE) { + game_state.curr_score++; + if (game_state.curr_score > state -> hi_score) + state -> hi_score = game_state.curr_score; + } + display_score(game_state.curr_score); } //fall through case 0: @@ -316,25 +324,23 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti bool success_jump = false; uint8_t curr_jump_frame = 0; watch_date_time date_time; - uint32_t weeknumber; switch (event.event_type) { case EVENT_ACTIVATE: date_time = watch_rtc_get_date_time(); - weeknumber = watch_utility_get_weeknumber(date_time.unit.year, date_time.unit.month, date_time.unit.day); - if ((state -> weeknumber_prev_hi_score != weeknumber) || - (state -> year_prev_hi_score != date_time.unit.year)) + if ((state -> year_last_hi_score != date_time.unit.year) || + (state -> month_last_hi_score != date_time.unit.month)) { - // The high score resets itself every new week. + // The high score resets itself every new month. state -> hi_score = 0; - state -> weeknumber_prev_hi_score = weeknumber; - state -> year_prev_hi_score = date_time.unit.year; + state -> year_last_hi_score = date_time.unit.year; + state -> month_last_hi_score = date_time.unit.month; } if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; case EVENT_TICK: - switch (state -> curr_screen) + switch (game_state.curr_screen) { case SCREEN_TITLE: case SCREEN_LOSE: @@ -377,13 +383,13 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_UP: case EVENT_ALARM_BUTTON_UP: - if (state -> curr_screen == SCREEN_TITLE) + if (game_state.curr_screen == SCREEN_TITLE) begin_playing(state); - else if (state -> curr_screen == SCREEN_LOSE) + else if (game_state.curr_screen == SCREEN_LOSE) display_title(state); break; case EVENT_LIGHT_LONG_PRESS: - if (state -> curr_screen == SCREEN_TITLE) { + if (game_state.curr_screen == SCREEN_TITLE) { state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; display_difficulty(state -> difficulty); if (state -> soundOn) { @@ -394,13 +400,13 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: - if (state -> curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ game_state.jump_state = JUMPING_START; display_ball(game_state.jump_state != NOT_JUMPING); } break; case EVENT_ALARM_LONG_PRESS: - if (state -> curr_screen != SCREEN_PLAYING) + if (game_state.curr_screen != SCREEN_PLAYING) { state -> soundOn = !state -> soundOn; if (state -> soundOn){ diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index 03ec418..56d22a0 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -38,14 +38,12 @@ typedef struct { // These are values that need saving between uses - uint16_t hi_score : 6; - uint16_t curr_score : 6; - uint16_t curr_screen : 2; - uint16_t difficulty : 2; + uint32_t hi_score : 10; + uint32_t difficulty : 2; bool soundOn; - bool unused; - uint8_t weeknumber_prev_hi_score; - uint8_t year_prev_hi_score; + uint32_t month_last_hi_score : 4; + uint32_t year_last_hi_score : 6; + uint32_t unused : 9; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 6ec6476d0f6a27137b2955aec82f8979d4c98dbf Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sat, 20 Jul 2024 09:43:49 -0400 Subject: [PATCH 154/220] Refectored the state machine --- .../complication/endless_runner_face.c | 181 ++++++++++-------- 1 file changed, 98 insertions(+), 83 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 3016f88..31507c8 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -40,21 +40,21 @@ typedef enum { } RunnerCurrScreen; typedef enum { - DIFF_BABY = 0, // 0.5x speed; 4 0's min; jump is 3 frames - DIFF_EASY, // 1x speed; 4 0's min; jump is 3 frames - DIFF_NORM, // 1x speed; 4 0's min; jump is 2 frames - DIFF_HARD, // 1x speed; 3 0's min; jump is 2 frames + DIFF_BABY = 0, // FREQ_SLOW FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames + DIFF_EASY, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames + DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames + DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames DIFF_COUNT } RunnerDifficulty; -#define NUM_GRID 12 -#define FREQ 8 -#define FREQ_SLOW 4 -#define JUMP_FRAMES 2 -#define JUMP_FRAMES_EASY 3 -#define MIN_ZEROES 4 -#define MIN_ZEROES_HARD 3 -#define MAX_HI_SCORE 999 +#define NUM_GRID 12 // This the length that the obstacle track can be on +#define FREQ 8 // Frequency request for the game +#define FREQ_SLOW 4 // Frequency request for baby mode +#define JUMP_FRAMES 2 // Wait this many frames on difficulties above EASY before coming down from the jump button pressed +#define JUMP_FRAMES_EASY 3 // Wait this many frames on difficulties at or below EASY before coming down from the jump button pressed +#define MIN_ZEROES 4 // At minimum, we'll have this many spaces between obstacles +#define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode +#define MAX_HI_SCORE 999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { @@ -141,6 +141,19 @@ static void display_score(uint8_t score) { watch_display_string(buf, 2); } +static void check_and_reset_hi_score(endless_runner_state_t *state) { + // Resets the hi scroe at the beginning of each month. + watch_date_time date_time = watch_rtc_get_date_time(); + if ((state -> year_last_hi_score != date_time.unit.year) || + (state -> month_last_hi_score != date_time.unit.month)) + { + // The high score resets itself every new month. + state -> hi_score = 0; + state -> year_last_hi_score = date_time.unit.year; + state -> month_last_hi_score = date_time.unit.month; + } +} + static void display_difficulty(uint16_t difficulty) { switch (difficulty) { @@ -160,8 +173,27 @@ static void display_difficulty(uint16_t difficulty) { } } +static void change_difficulty(endless_runner_state_t *state) { + state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; + display_difficulty(state -> difficulty); + if (state -> soundOn) { + if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); + else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + } +} + +static void toggle_sound(endless_runner_state_t *state) { + state -> soundOn = !state -> soundOn; + if (state -> soundOn){ + watch_buzzer_play_note(BUZZER_NOTE_C5, 30); + watch_set_indicator(WATCH_INDICATOR_BELL); + } + else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } +} + static void display_title(endless_runner_state_t *state) { - char buf[10]; uint16_t hi_score = state -> hi_score; uint8_t difficulty = state -> difficulty; bool sound_on = state -> soundOn; @@ -169,13 +201,17 @@ static void display_title(endless_runner_state_t *state) { game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second watch_set_colon(); - if (hi_score <= 99) - sprintf(buf, "ER HS%2d ", hi_score); - else if (hi_score <= MAX_HI_SCORE) - sprintf(buf, "ER HS%3d ", hi_score); - else - sprintf(buf, "ER HS-- ", hi_score); - watch_display_string(buf, 0); + if (hi_score > MAX_HI_SCORE) { + watch_display_string("ER HS-- ", 0); + } + else { + char buf[14]; + if (hi_score <= 99) + sprintf(buf, "ER HS%2d ", hi_score); + else if (hi_score <= MAX_HI_SCORE) + sprintf(buf, "ER HS%3d ", hi_score); + watch_display_string(buf, 0); + } display_difficulty(difficulty); } @@ -303,6 +339,43 @@ static bool display_obstacles(endless_runner_state_t *state) { return success_jump; } +static void update_game(endless_runner_state_t *state, uint8_t subsecond) { + bool success_jump = false; + uint8_t curr_jump_frame = 0; + if (game_state.sec_before_moves != 0) { + if (subsecond == 0) --game_state.sec_before_moves; + return; + } + success_jump = display_obstacles(state); + switch (game_state.jump_state) + { + case NOT_JUMPING: + break; + case JUMPING_FINAL_FRAME: + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + break; + default: + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. + break; + } + if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { + delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. + display_lose_screen(state); + } +} + void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; @@ -321,21 +394,9 @@ void endless_runner_face_activate(movement_settings_t *settings, void *context) bool endless_runner_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { endless_runner_state_t *state = (endless_runner_state_t *)context; - bool success_jump = false; - uint8_t curr_jump_frame = 0; - watch_date_time date_time; - switch (event.event_type) { case EVENT_ACTIVATE: - date_time = watch_rtc_get_date_time(); - if ((state -> year_last_hi_score != date_time.unit.year) || - (state -> month_last_hi_score != date_time.unit.month)) - { - // The high score resets itself every new month. - state -> hi_score = 0; - state -> year_last_hi_score = date_time.unit.year; - state -> month_last_hi_score = date_time.unit.month; - } + check_and_reset_hi_score(state); if (state -> soundOn) watch_set_indicator(WATCH_INDICATOR_BELL); display_title(state); break; @@ -346,38 +407,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case SCREEN_LOSE: break; default: - if (game_state.sec_before_moves != 0) { - if (event.subsecond == 0) --game_state.sec_before_moves; - break; - } - success_jump = display_obstacles(state); - switch (game_state.jump_state) - { - case NOT_JUMPING: - break; - case JUMPING_FINAL_FRAME: - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } - break; - default: - curr_jump_frame = game_state.jump_state - NOT_JUMPING; - if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) - game_state.jump_state = JUMPING_FINAL_FRAME; - else - game_state.jump_state++; - //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. - break; - } - if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { - delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. - display_lose_screen(state); - } + update_game(state, event.subsecond); break; } break; @@ -389,14 +419,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti display_title(state); break; case EVENT_LIGHT_LONG_PRESS: - if (game_state.curr_screen == SCREEN_TITLE) { - state -> difficulty = (state -> difficulty + 1) % DIFF_COUNT; - display_difficulty(state -> difficulty); - if (state -> soundOn) { - if (state -> difficulty == 0) watch_buzzer_play_note(BUZZER_NOTE_B4, 30); - else watch_buzzer_play_note(BUZZER_NOTE_C5, 30); - } - } + if (game_state.curr_screen == SCREEN_TITLE) + change_difficulty(state); break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: @@ -407,16 +431,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_ALARM_LONG_PRESS: if (game_state.curr_screen != SCREEN_PLAYING) - { - state -> soundOn = !state -> soundOn; - if (state -> soundOn){ - watch_buzzer_play_note(BUZZER_NOTE_C5, 30); - watch_set_indicator(WATCH_INDICATOR_BELL); - } - else { - watch_clear_indicator(WATCH_INDICATOR_BELL); - } - } + toggle_sound(state); break; case EVENT_TIMEOUT: movement_move_to_face(0); From 503fcd6ebcbc9b215c7fe87a10594b92e9e61387 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 23 Jul 2024 17:52:50 -0400 Subject: [PATCH 155/220] Added author in header --- movement/watch_faces/complication/endless_runner_face.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index 56d22a0..b9484dc 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2024 <#author_name#> + * Copyright (c) 2024 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From c027b247b22cf720abe88bd88620542f5e31d813 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 23 Jul 2024 23:31:13 -0400 Subject: [PATCH 156/220] Changed hi score number offset and refactored some code --- .../complication/endless_runner_face.c | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 31507c8..1f35be9 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -54,16 +54,17 @@ typedef enum { #define JUMP_FRAMES_EASY 3 // Wait this many frames on difficulties at or below EASY before coming down from the jump button pressed #define MIN_ZEROES 4 // At minimum, we'll have this many spaces between obstacles #define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode -#define MAX_HI_SCORE 999 // Max hi score to store and display on the title screen. +#define MAX_HI_SCORE 9999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 typedef struct { uint32_t obst_pattern; + uint16_t curr_score; uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; - uint16_t curr_score : 10; - uint16_t curr_screen : 4; + uint8_t curr_screen : 4; + bool success_jump; // Flag used for making a successful jumping sound. bool loc_2_on; bool loc_3_on; } game_state_t; @@ -141,6 +142,16 @@ static void display_score(uint8_t score) { watch_display_string(buf, 2); } +static void add_to_score(endless_runner_state_t *state) { + if (game_state.curr_score <= MAX_HI_SCORE) { + game_state.curr_score++; + if (game_state.curr_score > state -> hi_score) + state -> hi_score = game_state.curr_score; + } + game_state.success_jump = true; + display_score(game_state.curr_score); +} + static void check_and_reset_hi_score(endless_runner_state_t *state) { // Resets the hi scroe at the beginning of each month. watch_date_time date_time = watch_rtc_get_date_time(); @@ -202,14 +213,11 @@ static void display_title(endless_runner_state_t *state) { if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second watch_set_colon(); if (hi_score > MAX_HI_SCORE) { - watch_display_string("ER HS-- ", 0); + watch_display_string("ER HS --", 0); } else { char buf[14]; - if (hi_score <= 99) - sprintf(buf, "ER HS%2d ", hi_score); - else if (hi_score <= MAX_HI_SCORE) - sprintf(buf, "ER HS%3d ", hi_score); + sprintf(buf, "ER HS%4d ", hi_score); watch_display_string(buf, 0); } display_difficulty(difficulty); @@ -244,8 +252,19 @@ static void display_lose_screen(endless_runner_state_t *state) { delay_ms(600); } -static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { - bool success_jump = false; +static void stop_jumping(endless_runner_state_t *state) { + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (game_state.success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + game_state.success_jump = false; +} + +static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { switch (grid_loc) { case 2: @@ -265,18 +284,10 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t case 1: if (obstacle) { // If an obstacle is here, it means the ball cleared it - if (game_state.curr_score <= MAX_HI_SCORE) { - game_state.curr_score++; - if (game_state.curr_score > state -> hi_score) - state -> hi_score = game_state.curr_score; - } - display_score(game_state.curr_score); + add_to_score(state); } //fall through case 0: - if (obstacle) // If an obstacle is here, it means the ball cleared it - success_jump = true; - //fall through case 5: if (obstacle) watch_set_pixel(0, 18 + grid_loc); @@ -318,16 +329,14 @@ static bool display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t default: break; } - return success_jump; } -static bool display_obstacles(endless_runner_state_t *state) { - bool success_jump = false; +static void display_obstacles(endless_runner_state_t *state) { for (int i = 0; i < NUM_GRID; i++) { // Use a bitmask to isolate each bit and shift it to the least significant position uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); - if (display_obstacle(obstacle, i, state)) success_jump = true; + display_obstacle(obstacle, i, state); } game_state.obst_pattern = game_state.obst_pattern << 1; @@ -336,30 +345,21 @@ static bool display_obstacles(endless_runner_state_t *state) { game_state.obst_indx = 0; game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); } - return success_jump; } static void update_game(endless_runner_state_t *state, uint8_t subsecond) { - bool success_jump = false; uint8_t curr_jump_frame = 0; if (game_state.sec_before_moves != 0) { if (subsecond == 0) --game_state.sec_before_moves; return; } - success_jump = display_obstacles(state); + display_obstacles(state); switch (game_state.jump_state) { case NOT_JUMPING: break; case JUMPING_FINAL_FRAME: - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } + stop_jumping(state); break; default: curr_jump_frame = game_state.jump_state - NOT_JUMPING; @@ -434,7 +434,8 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti toggle_sound(state); break; case EVENT_TIMEOUT: - movement_move_to_face(0); + if (game_state.curr_screen != SCREEN_TITLE) + display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: break; From 30363d408e211d8cd1781ad5e8bc4632c9cb66f3 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:03:54 -0400 Subject: [PATCH 157/220] Added fuel mode --- .../complication/endless_runner_face.c | 205 ++++++++++++++---- .../complication/endless_runner_face.h | 4 +- 2 files changed, 166 insertions(+), 43 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 1f35be9..bf8e3ac 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -36,6 +36,7 @@ typedef enum { SCREEN_TITLE = 0, SCREEN_PLAYING, SCREEN_LOSE, + SCREEN_TIME, SCREEN_COUNT } RunnerCurrScreen; @@ -44,6 +45,7 @@ typedef enum { DIFF_EASY, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES_EASY frames DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames + DIFF_FUEL, // Mode where the top-right displays the amoount of fuel that you can be above the ground for, dodging obstacles. When on the ground, your fuel recharges. DIFF_COUNT } RunnerDifficulty; @@ -56,22 +58,45 @@ typedef enum { #define MIN_ZEROES_HARD 3 // At minimum, we'll have this many spaces between obstacles on hard mode #define MAX_HI_SCORE 9999 // Max hi score to store and display on the title screen. #define MAX_DISP_SCORE 39 // The top-right digits can't properly display above 39 +#define JUMP_FRAMES_FUEL 30 // The max fuel that fuel that the fuel mode game will hold +#define JUMP_FRAMES_FUEL_RECHARGE 3 // How much fuel each frame on the ground adds +#define MAX_DISP_SCORE_FUEL 9 // Since the fuel mode displays the score in the weekday slot, two digits will display wonky data typedef struct { uint32_t obst_pattern; - uint16_t curr_score; uint16_t obst_indx : 8; uint16_t jump_state : 5; uint16_t sec_before_moves : 3; - uint8_t curr_screen : 4; - bool success_jump; // Flag used for making a successful jumping sound. + uint16_t curr_score : 10; + uint16_t curr_screen : 4; bool loc_2_on; bool loc_3_on; + bool success_jump; + bool fuel_mode; + uint8_t fuel; } game_state_t; static game_state_t game_state; static const uint8_t _num_bits_obst_pattern = sizeof(game_state.obst_pattern) * 8; +static void print_binary(uint32_t value, int bits) { +#if __EMSCRIPTEN__ + for (int i = bits - 1; i >= 0; i--) { + // Print each bit + printf("%lu", (value >> i) & 1); + // Optional: add a space every 4 bits for readability + if (i % 4 == 0 && i != 0) { + printf(" "); + } + } + printf("\r\n"); +#else + (void) value; + (void) bits; +#endif + return; +} + static uint32_t get_random(uint32_t max) { #if __EMSCRIPTEN__ return rand() % max; @@ -80,6 +105,51 @@ static uint32_t get_random(uint32_t max) { #endif } +static uint32_t get_random_nonzero(uint32_t max) { + uint32_t random; + do + { + random = get_random(max); + } while (random == 0); + return random; +} + +static uint32_t get_random_kinda_nonzero(uint32_t max) { + // Returns a number that's between 1 and max, unless max is 0 or 1, then it returns 0 to max. + if (max == 0) return 0; + else if (max == 1) return get_random(max); + return get_random_nonzero(max); +} + +static uint32_t get_random_fuel(uint32_t prev_val) { + static uint8_t prev_rand_subset = 0; + uint32_t rand; + uint8_t max_ones, subset; + uint32_t rand_legal = 0; + prev_val = prev_val & ~0xFFFF; + + for (int i = 0; i < 2; i++) { + subset = 0; + max_ones = 8; + if (prev_rand_subset > 4) + max_ones -= prev_rand_subset; + rand = get_random_kinda_nonzero(max_ones); + if (rand > 5 && prev_rand_subset) rand = 5; // The gap of one or two is awkward + for (uint32_t j = 0; j < rand; j++) { + subset |= (1 << j); + } + if (prev_rand_subset >= 7) + subset = subset << 1; + subset &= 0xFF; + rand_legal |= subset << (8 * i); + prev_rand_subset = rand; + } + + rand_legal = prev_val | rand_legal; + print_binary(rand_legal, 32); + return rand_legal; +} + static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { /** @brief A legal random number starts with the previous number (which should be the 12 bits on the screen). * @param prev_val The previous value to tack onto. The return will have its first NUM_GRID MSBs be the same as prev_val, and the rest be new @@ -88,7 +158,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { */ uint8_t min_zeros = (difficulty == DIFF_HARD) ? MIN_ZEROES_HARD : MIN_ZEROES; uint32_t max = (1 << (_num_bits_obst_pattern - NUM_GRID)) - 1; - uint32_t rand = get_random(max); + uint32_t rand = get_random_nonzero(max); uint32_t rand_legal = 0; prev_val = prev_val & ~max; @@ -111,6 +181,7 @@ static uint32_t get_random_legal(uint32_t prev_val, uint16_t difficulty) { } } rand_legal = prev_val | rand_legal; + print_binary(rand_legal, 32); return rand_legal; } @@ -137,9 +208,16 @@ static void display_ball(bool jumping) { static void display_score(uint8_t score) { char buf[3]; - score %= (MAX_DISP_SCORE + 1); - sprintf(buf, "%2d", score); - watch_display_string(buf, 2); + if (game_state.fuel_mode) { + score %= (MAX_DISP_SCORE_FUEL + 1); + sprintf(buf, "%1d", score); + watch_display_string(buf, 0); + } + else { + score %= (MAX_DISP_SCORE + 1); + sprintf(buf, "%2d", score); + watch_display_string(buf, 2); + } } static void add_to_score(endless_runner_state_t *state) { @@ -152,8 +230,14 @@ static void add_to_score(endless_runner_state_t *state) { display_score(game_state.curr_score); } +static void display_fuel(uint8_t subsecond, uint8_t difficulty) { + char buf[4]; + sprintf(buf, "%2d", game_state.fuel); + watch_display_string(buf, 2); +} + static void check_and_reset_hi_score(endless_runner_state_t *state) { - // Resets the hi scroe at the beginning of each month. + // Resets the hi score at the beginning of each month. watch_date_time date_time = watch_rtc_get_date_time(); if ((state -> year_last_hi_score != date_time.unit.year) || (state -> month_last_hi_score != date_time.unit.month)) @@ -169,19 +253,23 @@ static void display_difficulty(uint16_t difficulty) { switch (difficulty) { case DIFF_BABY: - watch_display_string("b", 3); + watch_display_string(" b", 2); break; case DIFF_EASY: - watch_display_string("E", 3); + watch_display_string(" E", 2); break; case DIFF_HARD: - watch_display_string("H", 3); + watch_display_string(" H", 2); + break; + case DIFF_FUEL: + watch_display_string(" F", 2); break; case DIFF_NORM: default: - watch_display_string("N", 3); + watch_display_string(" N", 2); break; } + game_state.fuel_mode = difficulty == DIFF_FUEL; } static void change_difficulty(endless_runner_state_t *state) { @@ -217,23 +305,30 @@ static void display_title(endless_runner_state_t *state) { } else { char buf[14]; - sprintf(buf, "ER HS%4d ", hi_score); + sprintf(buf, "ER HS%4d", hi_score); watch_display_string(buf, 0); } display_difficulty(difficulty); } static void begin_playing(endless_runner_state_t *state) { + uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; watch_clear_colon(); movement_request_tick_frequency((state -> difficulty == DIFF_BABY) ? FREQ_SLOW : FREQ); - watch_display_string("ER ", 0); + if (game_state.fuel_mode) { + watch_display_string(" ", 0); + game_state.obst_pattern = get_random_fuel(0); + if ((16 * JUMP_FRAMES_FUEL_RECHARGE) < JUMP_FRAMES_FUEL) // 16 frames of zeros at the start of a level + game_state.fuel = JUMP_FRAMES_FUEL - (16 * JUMP_FRAMES_FUEL_RECHARGE); // Have it below its max to show it counting up when starting. + if (game_state.fuel < JUMP_FRAMES_FUEL_RECHARGE) game_state.fuel = JUMP_FRAMES_FUEL_RECHARGE; + } + else { + watch_display_string(" ", 2); + game_state.obst_pattern = get_random_legal(0, difficulty); + } game_state.jump_state = NOT_JUMPING; display_ball(game_state.jump_state != NOT_JUMPING); - do // Avoid the first array of obstacles being a boring line of 0s - { - game_state.obst_pattern = get_random_legal(0, state -> difficulty); - } while (game_state.obst_pattern == 0); display_score( game_state.curr_score); if (state -> soundOn){ watch_buzzer_play_note(BUZZER_NOTE_C5, 200); @@ -252,27 +347,20 @@ static void display_lose_screen(endless_runner_state_t *state) { delay_ms(600); } -static void stop_jumping(endless_runner_state_t *state) { - game_state.jump_state = NOT_JUMPING; - display_ball(game_state.jump_state != NOT_JUMPING); - if (state -> soundOn){ - if (game_state.success_jump) - watch_buzzer_play_note(BUZZER_NOTE_C5, 60); - else - watch_buzzer_play_note(BUZZER_NOTE_C3, 60); - } - game_state.success_jump = false; -} - static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t *state) { + static bool prev_obst_pos_two = 0; switch (grid_loc) { case 2: game_state.loc_2_on = obstacle; if (obstacle) watch_set_pixel(0, 20); - else if (game_state.jump_state != NOT_JUMPING) + else if (game_state.jump_state != NOT_JUMPING) { watch_clear_pixel(0, 20); + if (game_state.fuel_mode && prev_obst_pos_two) + add_to_score(state); + } + prev_obst_pos_two = obstacle; break; case 3: game_state.loc_3_on = obstacle; @@ -283,9 +371,8 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t break; case 1: - if (obstacle) { // If an obstacle is here, it means the ball cleared it + if (!game_state.fuel_mode && obstacle) // If an obstacle is here, it means the ball cleared it add_to_score(state); - } //fall through case 0: case 5: @@ -331,17 +418,34 @@ static void display_obstacle(bool obstacle, int grid_loc, endless_runner_state_t } } +static void stop_jumping(endless_runner_state_t *state) { + game_state.jump_state = NOT_JUMPING; + display_ball(game_state.jump_state != NOT_JUMPING); + if (state -> soundOn){ + if (game_state.success_jump) + watch_buzzer_play_note(BUZZER_NOTE_C5, 60); + else + watch_buzzer_play_note(BUZZER_NOTE_C3, 60); + } + game_state.success_jump = false; +} + static void display_obstacles(endless_runner_state_t *state) { for (int i = 0; i < NUM_GRID; i++) { // Use a bitmask to isolate each bit and shift it to the least significant position uint32_t mask = 1 << ((_num_bits_obst_pattern - 1) - i); bool obstacle = (game_state.obst_pattern & mask) >> ((_num_bits_obst_pattern - 1) - i); display_obstacle(obstacle, i, state); - } game_state.obst_pattern = game_state.obst_pattern << 1; game_state.obst_indx++; - if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { + if (game_state.fuel_mode) { + if (game_state.obst_indx >= (_num_bits_obst_pattern / 2)) { + game_state.obst_indx = 0; + game_state.obst_pattern = get_random_fuel(game_state.obst_pattern); + } + } + else if (game_state.obst_indx >= _num_bits_obst_pattern - NUM_GRID) { game_state.obst_indx = 0; game_state.obst_pattern = get_random_legal(game_state.obst_pattern, state -> difficulty); } @@ -357,23 +461,41 @@ static void update_game(endless_runner_state_t *state, uint8_t subsecond) { switch (game_state.jump_state) { case NOT_JUMPING: + if (game_state.fuel_mode) { + for (int i = 0; i < JUMP_FRAMES_FUEL_RECHARGE; i++) + { + if(game_state.fuel >= JUMP_FRAMES_FUEL) + break; + game_state.fuel++; + } + } break; case JUMPING_FINAL_FRAME: stop_jumping(state); break; default: - curr_jump_frame = game_state.jump_state - NOT_JUMPING; - if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) - game_state.jump_state = JUMPING_FINAL_FRAME; - else - game_state.jump_state++; - //if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) game_state.jump_state = JUMPING_FINAL_FRAME; // Uncomment to have depressing the buttons cause the ball to drop regardless of its current frame. + if (game_state.fuel_mode) { + if (!game_state.fuel) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.fuel--; + if (!watch_get_pin_level(BTN_ALARM) && !watch_get_pin_level(BTN_LIGHT)) stop_jumping(state); + } + else { + curr_jump_frame = game_state.jump_state - NOT_JUMPING; + if (curr_jump_frame >= JUMP_FRAMES_EASY || (state -> difficulty >= DIFF_NORM && curr_jump_frame >= JUMP_FRAMES)) + game_state.jump_state = JUMPING_FINAL_FRAME; + else + game_state.jump_state++; + } break; } if (game_state.jump_state == NOT_JUMPING && (game_state.loc_2_on || game_state.loc_3_on)) { delay_ms(200); // To show the player jumping onto the obstacle before displaying the lose screen. display_lose_screen(state); } + else if (game_state.fuel_mode) + display_fuel(subsecond, state -> difficulty); } void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { @@ -425,6 +547,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: if (game_state.curr_screen == SCREEN_PLAYING && game_state.jump_state == NOT_JUMPING){ + if (game_state.fuel_mode && !game_state.fuel) break; game_state.jump_state = JUMPING_START; display_ball(game_state.jump_state != NOT_JUMPING); } diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index b9484dc..f9d6409 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -39,11 +39,11 @@ typedef struct { // These are values that need saving between uses uint32_t hi_score : 10; - uint32_t difficulty : 2; + uint32_t difficulty : 3; bool soundOn; uint32_t month_last_hi_score : 4; uint32_t year_last_hi_score : 6; - uint32_t unused : 9; + uint32_t unused : 8; } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From 324942009ef8adaa7d3e3669b8420a3d1b9e35b2 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:08:35 -0400 Subject: [PATCH 158/220] Added second fuel mode where we don't recharge the fuel if it hits zero. --- .../watch_faces/complication/endless_runner_face.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index bf8e3ac..6009015 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -46,6 +46,7 @@ typedef enum { DIFF_NORM, // FREQ FPS; MIN_ZEROES 0's min; Jump is JUMP_FRAMES frames DIFF_HARD, // FREQ FPS; MIN_ZEROES_HARD 0's min; jump is JUMP_FRAMES frames DIFF_FUEL, // Mode where the top-right displays the amoount of fuel that you can be above the ground for, dodging obstacles. When on the ground, your fuel recharges. + DIFF_FUEL_1, // Same as DIFF_FUEL, but if your fuel is 0, then you won't recharge DIFF_COUNT } RunnerDifficulty; @@ -232,6 +233,10 @@ static void add_to_score(endless_runner_state_t *state) { static void display_fuel(uint8_t subsecond, uint8_t difficulty) { char buf[4]; + if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % 4 == 0) { + watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled. + return; + } sprintf(buf, "%2d", game_state.fuel); watch_display_string(buf, 2); } @@ -264,12 +269,15 @@ static void display_difficulty(uint16_t difficulty) { case DIFF_FUEL: watch_display_string(" F", 2); break; + case DIFF_FUEL_1: + watch_display_string("1F", 2); + break; case DIFF_NORM: default: watch_display_string(" N", 2); break; } - game_state.fuel_mode = difficulty == DIFF_FUEL; + game_state.fuel_mode = difficulty >= DIFF_FUEL && difficulty <= DIFF_FUEL_1; } static void change_difficulty(endless_runner_state_t *state) { @@ -464,7 +472,7 @@ static void update_game(endless_runner_state_t *state, uint8_t subsecond) { if (game_state.fuel_mode) { for (int i = 0; i < JUMP_FRAMES_FUEL_RECHARGE; i++) { - if(game_state.fuel >= JUMP_FRAMES_FUEL) + if(game_state.fuel >= JUMP_FRAMES_FUEL || (state -> difficulty == DIFF_FUEL_1 && !game_state.fuel)) break; game_state.fuel++; } From 28b14d36654998963a75bab687f4e02b645f1203 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Wed, 24 Jul 2024 21:12:17 -0400 Subject: [PATCH 159/220] LE mode in the endless runner now displays the current time. --- .../complication/endless_runner_face.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 6009015..96dfa89 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -319,6 +319,37 @@ static void display_title(endless_runner_state_t *state) { display_difficulty(difficulty); } +static void display_time(watch_date_time date_time, bool clock_mode_24h) { + static watch_date_time previous_date_time; + char buf[6 + 1]; + + // If the hour needs updating + if (date_time.unit.hour != previous_date_time.unit.hour) { + uint8_t hour = date_time.unit.hour; + + if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + else { + if (hour >= 12) watch_set_indicator(WATCH_INDICATOR_PM); + hour %= 12; + if (hour == 0) hour = 12; + } + watch_set_colon(); + sprintf( buf, "%2d%02d ", hour, date_time.unit.minute); + watch_display_string(buf, 4); + } + // If both digits of the minute need updating + else if ((date_time.unit.minute / 10) != (previous_date_time.unit.minute / 10)) { + sprintf( buf, "%02d ", date_time.unit.minute); + watch_display_string(buf, 6); + } + // If only the ones-place of the minute needs updating. + else if (date_time.unit.minute != previous_date_time.unit.minute) { + sprintf( buf, "%d ", date_time.unit.minute % 10); + watch_display_string(buf, 7); + } + previous_date_time.reg = date_time.reg; +} + static void begin_playing(endless_runner_state_t *state) { uint8_t difficulty = state -> difficulty; game_state.curr_screen = SCREEN_PLAYING; @@ -569,6 +600,7 @@ bool endless_runner_face_loop(movement_event_t event, movement_settings_t *setti display_title(state); break; case EVENT_LOW_ENERGY_UPDATE: + display_time(watch_rtc_get_date_time(), settings->bit.clock_mode_24h); break; default: return movement_default_loop_handler(event, settings); From e4a5121303c70d46773af8f99174386047ce86dc Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Fri, 26 Jul 2024 06:10:26 -0400 Subject: [PATCH 160/220] bug fix on displaying time in LE mode --- movement/watch_faces/complication/endless_runner_face.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 96dfa89..f24003f 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -304,6 +304,7 @@ static void display_title(endless_runner_state_t *state) { uint16_t hi_score = state -> hi_score; uint8_t difficulty = state -> difficulty; bool sound_on = state -> soundOn; + game_state.curr_screen = SCREEN_TITLE; memset(&game_state, 0, sizeof(game_state)); game_state.sec_before_moves = 1; // The first obstacles will all be 0s, which is about an extra second of delay. if (sound_on) game_state.sec_before_moves--; // Start chime is about 1 second @@ -323,9 +324,10 @@ static void display_time(watch_date_time date_time, bool clock_mode_24h) { static watch_date_time previous_date_time; char buf[6 + 1]; - // If the hour needs updating - if (date_time.unit.hour != previous_date_time.unit.hour) { + // If the hour needs updating or it's the first time displaying the time + if ((game_state.curr_screen != SCREEN_TIME) || (date_time.unit.hour != previous_date_time.unit.hour)) { uint8_t hour = date_time.unit.hour; + game_state.curr_screen = SCREEN_TIME; if (clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); else { From c74ed78d72c98433c5b57f2573da984b2d0a4149 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 27 Aug 2024 13:09:44 -0400 Subject: [PATCH 161/220] Changed U LOSE to LOSE --- movement/watch_faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index f24003f..54cf31e 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -381,7 +381,7 @@ static void begin_playing(endless_runner_state_t *state) { static void display_lose_screen(endless_runner_state_t *state) { game_state.curr_screen = SCREEN_LOSE; game_state.curr_score = 0; - watch_display_string(" U LOSE ", 0); + watch_display_string(" LOSE ", 0); if (state -> soundOn) watch_buzzer_play_note(BUZZER_NOTE_A1, 600); else From 2e46aa0e2c5c3e406e90890e5a581b0257303fa9 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 16:25:05 -0400 Subject: [PATCH 162/220] got rid of hardcoding of half-second zero blink --- movement/watch_faces/complication/endless_runner_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/endless_runner_face.c b/movement/watch_faces/complication/endless_runner_face.c index 54cf31e..3509fc2 100644 --- a/movement/watch_faces/complication/endless_runner_face.c +++ b/movement/watch_faces/complication/endless_runner_face.c @@ -233,7 +233,7 @@ static void add_to_score(endless_runner_state_t *state) { static void display_fuel(uint8_t subsecond, uint8_t difficulty) { char buf[4]; - if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % 4 == 0) { + if (difficulty == DIFF_FUEL_1 && game_state.fuel == 0 && subsecond % (FREQ/2) == 0) { watch_display_string(" ", 2); // Blink the 0 fuel to show it cannot be refilled. return; } From faec45ce245fa45a24a6ef3c67b6e8b1d089583b Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 17:09:52 -0400 Subject: [PATCH 163/220] Include ability to select only the best words as the first choice --- .../watch_faces/complication/wordle_face.h | 1 + .../complication/wordle_face_dict.h | 26 ++++++++++--------- utils/wordle_face/wordle_list.py | 11 ++++++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/movement/watch_faces/complication/wordle_face.h b/movement/watch_faces/complication/wordle_face.h index 1b9a7fb..f390e73 100644 --- a/movement/watch_faces/complication/wordle_face.h +++ b/movement/watch_faces/complication/wordle_face.h @@ -81,6 +81,7 @@ * 0 = Don't allow quickly choosing a random quess * 1 = Allow using a random guess of any value that can be an answer * 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 #include "wordle_face_dict.h" diff --git a/movement/watch_faces/complication/wordle_face_dict.h b/movement/watch_faces/complication/wordle_face_dict.h index 11a89f7..6b82976 100644 --- a/movement/watch_faces/complication/wordle_face_dict.h +++ b/movement/watch_faces/complication/wordle_face_dict.h @@ -14,25 +14,25 @@ static const char _valid_letters[] = {'A', 'C', 'E', 'H', 'I', 'L', 'N', 'O', 'P // From: https://matthewminer.name/projects/calculators/wordle-words-left/ // Number of words found: 432 static const char _valid_words[][WORDLE_LENGTH + 1] = { - "STALE", "TRACE", "CLOSE", "ARISE", "SNIPE", "SHIRE", "LEASH", "SAINT", "CLEAN", - "RELIC", "CHORE", "CRONE", "REACH", "CHAOS", "TAPIR", "CAIRN", "TENOR", "STARE", - "HEART", "SCOPE", "SNARL", "SLEPT", "SINCE", "EPOCH", "SPACE", "SHARE", "SPOIL", + "SLATE", "STARE", "SNARE", "SANER", "CRANE", "STALE", "CRATE", "RAISE", "TRACE", + "SHARE", "ARISE", "SCARE", "SPARE", "CHAOS", "TAPIR", "CAIRN", "TENOR", "CLEAN", + "HEART", "SCOPE", "SNARL", "SLEPT", "SINCE", "EPOCH", "SPACE", "RELIC", "SPOIL", "LITER", "LEAPT", "LANCE", "RANCH", "HORSE", "LEACH", "LATER", "STEAL", "CHEAP", - "SHORT", "ETHIC", "CHANT", "ACTOR", "SPARE", "SEPIA", "ONSET", "SPLAT", "LEANT", + "SHORT", "ETHIC", "CHANT", "ACTOR", "REACH", "SEPIA", "ONSET", "SPLAT", "LEANT", "REACT", "OCTAL", "SPORE", "IRATE", "CORAL", "NICER", "SPILT", "SCENT", "PANIC", "SHIRT", "PECAN", "SLAIN", "SPLIT", "ROACH", "ASCOT", "PHONE", "LITHE", "STOIC", "STRIP", "RENAL", "POISE", "ENACT", "CHEAT", "PITCH", "NOISE", "INLET", "PEARL", - "POLAR", "PEACH", "STOLE", "CASTE", "CREST", "SCARE", "ETHOS", "THEIR", "STONE", - "SLATE", "LATCH", "HASTE", "SNARE", "SPINE", "SLANT", "SPEAR", "SCALE", "CAPER", + "POLAR", "PEACH", "STOLE", "CASTE", "CREST", "CRONE", "ETHOS", "THEIR", "STONE", + "SHIRE", "LATCH", "HASTE", "CLOSE", "SPINE", "SLANT", "SPEAR", "SCALE", "CAPER", "RETCH", "PESTO", "CHIRP", "SPORT", "OPTIC", "SNAIL", "PRICE", "PLANE", "TORCH", "PASTE", "RECAP", "SOLAR", "CRASH", "LINER", "OPINE", "ASHEN", "PALER", "ECLAT", - "SPELT", "TRIAL", "PERIL", "SLICE", "SCANT", "RAISE", "POSIT", "ATONE", "SPIRE", + "SPELT", "TRIAL", "PERIL", "SLICE", "SCANT", "SAINT", "POSIT", "ATONE", "SPIRE", "COAST", "INEPT", "SHOAL", "CLASH", "THORN", "PHASE", "SCORE", "TRICE", "PERCH", - "PORCH", "SHEAR", "CHOIR", "RHINO", "PLANT", "SHONE", "SANER", "LEARN", "ALTER", + "PORCH", "SHEAR", "CHOIR", "RHINO", "PLANT", "SHONE", "CHORE", "LEARN", "ALTER", "CHAIN", "PANEL", "PLIER", "STEIN", "COPSE", "SONIC", "ALIEN", "CHOSE", "ACORN", "ANTIC", "CHEST", "OTHER", "CHINA", "TALON", "SCORN", "PLAIN", "PILOT", "RIPEN", "PATCH", "SPICE", "CLONE", "SCION", "SCONE", "STRAP", "PARSE", "SHALE", "RISEN", - "CANOE", "INTER", "CRATE", "ISLET", "PRINT", "SHINE", "NORTH", "CLEAT", "PLAIT", + "CANOE", "INTER", "LEASH", "ISLET", "PRINT", "SHINE", "NORTH", "CLEAT", "PLAIT", "SCRAP", "CLEAR", "SLOTH", "LAPSE", "CHAIR", "SNORT", "SHARP", "OPERA", "STAIN", "TEACH", "TRAIL", "TRAIN", "LATHE", "PIANO", "PINCH", "PETAL", "STERN", "PRONE", "PROSE", "PLEAT", "TROPE", "PLACE", "POSER", "INERT", "CHASE", "CAROL", "STAIR", @@ -41,7 +41,7 @@ static const char _valid_words[][WORDLE_LENGTH + 1] = { "SONAR", "AISLE", "AROSE", "HATER", "NICHE", "POINT", "EARTH", "PINTO", "THOSE", "CLOTH", "NOTCH", "TOPIC", "RESIN", "SCALP", "HEIST", "HERON", "TRIPE", "TONAL", "TAPER", "SHORN", "TONIC", "HOIST", "SNORE", "STORE", "SLOPE", "OCEAN", "CHART", - "PAINT", "SPENT", "CRANE", "CRISP", "TRASH", "PATIO", "PLATE", "HOTEL", "LEAST", + "PAINT", "SPENT", "SNIPE", "CRISP", "TRASH", "PATIO", "PLATE", "HOTEL", "LEAST", "ALONE", "RALPH", "SPIEL", "SIREN", "RATIO", "STOOP", "TROLL", "ATOLL", "SLASH", "RETRO", "CREEP", "STILT", "SPREE", "TASTE", "CACHE", "CANON", "EATEN", "TEPEE", "SHEET", "SNEER", "ERROR", "NATAL", "SLEEP", "STINT", "TROOP", "SHALL", "STALL", @@ -282,8 +282,10 @@ static const char _possible_words[][WORDLE_LENGTH + 1] = { #endif }; -#if (WORDLE_USE_RANDOM_GUESS == 2) -static const uint16_t _num_random_guess_words = 257; // The _valid_words array begins with this many words where each letter is different. +#if (WORDLE_USE_RANDOM_GUESS == 3) +static const uint16_t _num_random_guess_words = 13; // The valid_words array begins with this many words that are considered the top 3% best options. +#elif (WORDLE_USE_RANDOM_GUESS == 2) +static const uint16_t _num_random_guess_words = 257; // The valid_words array begins with this many words where each letter is different. #elif (WORDLE_USE_RANDOM_GUESS == 1) static const uint16_t _num_random_guess_words = _num_words; #endif diff --git a/utils/wordle_face/wordle_list.py b/utils/wordle_face/wordle_list.py index 39e310c..dac5ad6 100644 --- a/utils/wordle_face/wordle_list.py +++ b/utils/wordle_face/wordle_list.py @@ -1169,11 +1169,16 @@ def print_valid_words(letters=alphabet): print("#endif\n") items_per_row = 9 + top_words_percent = 3 valid_words = list_of_valid_words(letters, valid_list) valid_words = capitalize_all_and_remove_duplicates(valid_words) random.shuffle(valid_words) # Just in case the watch's random function is too pseudo, better to shuffle th elist so it's less likely to always have the same starting letter valid_words, num_uniq = rearrange_words_by_uniqueness(valid_words) + best_words = list(best_first_word(letters=letters, print_result=False).keys()) + num_best_words = round(len(valid_words) * top_words_percent / 100) + for i in range(num_best_words, 0, -1): + valid_words.insert(0, valid_words.pop(valid_words.index(best_words[i-1]))) print("static const char _valid_letters[] = {", end='') letters = sorted(letters) @@ -1209,8 +1214,10 @@ def print_valid_words(letters=alphabet): print("#endif") print("};\n") - print("#if (WORDLE_USE_RANDOM_GUESS == 2)") - print(f"static const uint16_t _num_random_guess_words = {num_uniq}; // The _valid_words array begins with this many words where each letter is different.") + print("#if (WORDLE_USE_RANDOM_GUESS == 3)") + print(f"static const uint16_t _num_random_guess_words = {num_best_words}; // The valid_words array begins with this many words that are considered the top {top_words_percent}% best options.") + print("#elif (WORDLE_USE_RANDOM_GUESS == 2)") + print(f"static const uint16_t _num_random_guess_words = {num_uniq}; // The valid_words array begins with this many words where each letter is different.") print("#elif (WORDLE_USE_RANDOM_GUESS == 1)") print("static const uint16_t _num_random_guess_words = _num_words;") print("#endif") From 118c07a3b63392fc5fb9c5b4b0dbfa052b254c9a Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 3 Sep 2024 17:13:59 -0400 Subject: [PATCH 164/220] Reduced struct memory per code review --- .../watch_faces/complication/endless_runner_face.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/movement/watch_faces/complication/endless_runner_face.h b/movement/watch_faces/complication/endless_runner_face.h index f9d6409..8c8fa21 100644 --- a/movement/watch_faces/complication/endless_runner_face.h +++ b/movement/watch_faces/complication/endless_runner_face.h @@ -37,13 +37,12 @@ */ typedef struct { - // These are values that need saving between uses - uint32_t hi_score : 10; - uint32_t difficulty : 3; - bool soundOn; - uint32_t month_last_hi_score : 4; - uint32_t year_last_hi_score : 6; - uint32_t unused : 8; + uint16_t hi_score : 10; + uint8_t difficulty : 3; + uint8_t month_last_hi_score : 4; + uint8_t year_last_hi_score : 6; + uint8_t soundOn : 1; + /* 24 bits, likely aligned to 32 bits = 4 bytes */ } endless_runner_state_t; void endless_runner_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From b37be89bae9874ca70967580ba55a70f8a6f3dd4 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Wed, 4 Sep 2024 11:46:33 -0400 Subject: [PATCH 165/220] adds a short and long harry potter signal --- movement/movement_custom_signal_tunes.h | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index 0ac527a..2db10fc 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -119,4 +119,60 @@ int8_t signal_tune[] = { }; #endif // SIGNAL_TUNE_LAYLA +#ifdef SIGNAL_TUNE_HARRY_POTTER_SHORT +int8_t signal_tune[] = { + BUZZER_NOTE_B5, 24, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_E6, 12, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_G6, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6SHARP_G6FLAT, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_E6, 16, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_B6, 8, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_A6, 24, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6SHARP_G6FLAT, 24, + 0 +}; +#endif // SIGNAL_TUNE_HARRY_POTTER_SHORT + +#ifdef SIGNAL_TUNE_HARRY_POTTER_LONG +int8_t signal_tune[] = { + BUZZER_NOTE_B5, 24, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_E6, 12, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_G6, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6SHARP_G6FLAT, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_E6, 16, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_B6, 8, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_A6, 24, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6SHARP_G6FLAT, 24, + BUZZER_NOTE_REST, 1, + + BUZZER_NOTE_E6, 12, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_G6, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6SHARP_G6FLAT, 6, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_D6SHARP_E6FLAT, 16, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_F6, 8, + BUZZER_NOTE_REST, 1, + BUZZER_NOTE_B5, 24, + + 0 +}; +#endif // SIGNAL_TUNE_HARRY_POTTER_LONG + #endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_ From fd526ed4014fc5f9cac47dc3ca880e5419f98656 Mon Sep 17 00:00:00 2001 From: Alex Maestas Date: Wed, 4 Sep 2024 20:10:37 +0000 Subject: [PATCH 166/220] bump to the newer upload-artifact github action --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b4fc79..84d179a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: env: COLOR: BLUE -jobs: +jobs: build: container: image: ghcr.io/armmbed/mbed-os-env:latest @@ -29,7 +29,7 @@ jobs: run: make working-directory: 'movement/make' - name: Upload UF2 - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: watch.uf2 path: movement/make/build/watch.uf2 @@ -52,7 +52,7 @@ jobs: cp watch.html index.html tar -czf simulator.tar.gz index.html watch.wasm watch.js - name: Upload simulator build - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: simulator.tar.gz path: movement/make/build-sim/simulator.tar.gz From 48fd4ee90314ed44b0e6680f3e476533b4975c07 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Fri, 6 Sep 2024 10:19:17 -0400 Subject: [PATCH 167/220] slightly shorter first note --- movement/movement_custom_signal_tunes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index 2db10fc..5bf0f27 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -121,7 +121,7 @@ int8_t signal_tune[] = { #ifdef SIGNAL_TUNE_HARRY_POTTER_SHORT int8_t signal_tune[] = { - BUZZER_NOTE_B5, 24, + BUZZER_NOTE_B5, 12, BUZZER_NOTE_REST, 1, BUZZER_NOTE_E6, 12, BUZZER_NOTE_REST, 1, @@ -142,7 +142,7 @@ int8_t signal_tune[] = { #ifdef SIGNAL_TUNE_HARRY_POTTER_LONG int8_t signal_tune[] = { - BUZZER_NOTE_B5, 24, + BUZZER_NOTE_B5, 12, BUZZER_NOTE_REST, 1, BUZZER_NOTE_E6, 12, BUZZER_NOTE_REST, 1, From a79bb46d39ac32add2e0447ce2a100c8dd94dbf1 Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Tue, 3 Sep 2024 21:43:25 -0400 Subject: [PATCH 168/220] add metal gear solid codec tune --- movement/movement_custom_signal_tunes.h | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/movement/movement_custom_signal_tunes.h b/movement/movement_custom_signal_tunes.h index 0ac527a..596df33 100644 --- a/movement/movement_custom_signal_tunes.h +++ b/movement/movement_custom_signal_tunes.h @@ -119,4 +119,31 @@ int8_t signal_tune[] = { }; #endif // SIGNAL_TUNE_LAYLA +#ifdef SIGNAL_TUNE_MGS_CODEC +int8_t signal_tune[] = { + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_REST, 6, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + BUZZER_NOTE_G5SHARP_A5FLAT, 1, + BUZZER_NOTE_C6, 1, + 0 +}; +#endif // SIGNAL_TUNE_MGS_CODEC + #endif // MOVEMENT_CUSTOM_SIGNAL_TUNES_H_ From ddaf3a8324239b4da112ebbc7aaf0c8d2ed7952e Mon Sep 17 00:00:00 2001 From: Joseph Bryant Date: Sat, 24 Aug 2024 12:02:50 +0100 Subject: [PATCH 169/220] Add auto-repeat feature to Countdown watch face --- .../watch_faces/complication/countdown_face.c | 69 ++++++++++++++----- .../watch_faces/complication/countdown_face.h | 2 + 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index b48ef59..f585ebc 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -1,6 +1,7 @@ /* * MIT License * + * Copyright (c) 2024 Joseph Bryant * Copyright (c) 2023 Konrad Rieck * Copyright (c) 2022 Wesley Ellis * @@ -68,17 +69,30 @@ static inline void button_beep(movement_settings_t *settings) { watch_buzzer_play_note(BUZZER_NOTE_C7, 50); } -static void start(countdown_state_t *state, movement_settings_t *settings) { - watch_date_time now = watch_rtc_get_date_time(); +static void schedule_countdown(countdown_state_t *state, movement_settings_t *settings) { - state->mode = cd_running; - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); - state->target_ts = watch_utility_offset_timestamp(state->now_ts, state->hours, state->minutes, state->seconds); + // Calculate the new state->now_ts but don't update it until we've updated the target - + // avoid possible race where the old target is compared to the new time and immediately triggers + uint32_t new_now = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), get_tz_offset(settings)); + state->target_ts = watch_utility_offset_timestamp(new_now, state->hours, state->minutes, state->seconds); + state->now_ts = new_now; watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); - movement_schedule_background_task(target_dt); - watch_set_indicator(WATCH_INDICATOR_BELL); + movement_schedule_background_task_for_face(state->watch_face_index, target_dt); } +static void auto_repeat(countdown_state_t *state, movement_settings_t *settings) { + movement_play_alarm(); + load_countdown(state); + schedule_countdown(state, settings); +} + +static void start(countdown_state_t *state, movement_settings_t *settings) { + state->mode = cd_running; + schedule_countdown(state, settings); +} + + + static void draw(countdown_state_t *state, uint8_t subsecond) { char buf[16]; @@ -100,7 +114,7 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { break; case cd_reset: case cd_paused: - watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); sprintf(buf, "CD %2d%02d%02d", state->hours, state->minutes, state->seconds); break; case cd_setting: @@ -127,13 +141,13 @@ static void draw(countdown_state_t *state, uint8_t subsecond) { static void pause(countdown_state_t *state) { state->mode = cd_paused; - movement_cancel_background_task(); - watch_clear_indicator(WATCH_INDICATOR_BELL); + movement_cancel_background_task_for_face(state->watch_face_index); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); } static void reset(countdown_state_t *state) { state->mode = cd_reset; - movement_cancel_background_task(); + movement_cancel_background_task_for_face(state->watch_face_index); load_countdown(state); } @@ -142,6 +156,15 @@ static void ring(countdown_state_t *state) { reset(state); } +static void times_up(movement_settings_t *settings, countdown_state_t *state) { + if(state->repeat) { + auto_repeat(state, settings); + } + else { + ring(state); + } +} + static void settings_increment(countdown_state_t *state) { switch(state->selection) { case 0: @@ -170,6 +193,7 @@ void countdown_face_setup(movement_settings_t *settings, uint8_t watch_face_inde memset(*context_ptr, 0, sizeof(countdown_state_t)); state->minutes = DEFAULT_MINUTES; state->mode = cd_reset; + state->watch_face_index = watch_face_index; store_countdown(state); } } @@ -180,9 +204,11 @@ void countdown_face_activate(movement_settings_t *settings, void *context) { if(state->mode == cd_running) { watch_date_time now = watch_rtc_get_date_time(); state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); - watch_set_indicator(WATCH_INDICATOR_BELL); + watch_set_indicator(WATCH_INDICATOR_SIGNAL); } watch_set_colon(); + if(state->repeat) + watch_set_indicator(WATCH_INDICATOR_BELL); movement_request_tick_frequency(1); quick_ticks_running = false; @@ -252,6 +278,7 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings, // Only start the timer if we have a valid time. start(state, settings); button_beep(settings); + watch_set_indicator(WATCH_INDICATOR_SIGNAL); } break; case cd_setting: @@ -261,9 +288,19 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings, draw(state, event.subsecond); break; case EVENT_ALARM_LONG_PRESS: - if (state->mode == cd_setting) { - quick_ticks_running = true; - movement_request_tick_frequency(8); + switch(state->mode) { + case cd_setting: + quick_ticks_running = true; + movement_request_tick_frequency(8); + break; + default: + // Toggle auto-repeat + button_beep(settings); + state->repeat = !state->repeat; + if(state->repeat) + watch_set_indicator(WATCH_INDICATOR_BELL); + else + watch_clear_indicator(WATCH_INDICATOR_BELL); } break; case EVENT_LIGHT_LONG_PRESS: @@ -285,7 +322,7 @@ bool countdown_face_loop(movement_event_t event, movement_settings_t *settings, abort_quick_ticks(state); break; case EVENT_BACKGROUND_TASK: - ring(state); + times_up(settings, state); break; case EVENT_TIMEOUT: abort_quick_ticks(state); diff --git a/movement/watch_faces/complication/countdown_face.h b/movement/watch_faces/complication/countdown_face.h index 1fe7c37..0f9cd8d 100644 --- a/movement/watch_faces/complication/countdown_face.h +++ b/movement/watch_faces/complication/countdown_face.h @@ -62,6 +62,8 @@ typedef struct { uint8_t set_seconds; uint8_t selection; countdown_mode_t mode; + bool repeat; + uint8_t watch_face_index; } countdown_state_t; From 07a2a49e72b43d56f570536b08e16a11358221ba Mon Sep 17 00:00:00 2001 From: CarpeNoctem Date: Wed, 17 Jan 2024 22:47:16 +1100 Subject: [PATCH 170/220] french_revolutionary face: fix compiler warning and uninitialized date_time variable --- movement/watch_faces/clock/french_revolutionary_face.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/clock/french_revolutionary_face.c b/movement/watch_faces/clock/french_revolutionary_face.c index 519cfc3..da94fc9 100644 --- a/movement/watch_faces/clock/french_revolutionary_face.c +++ b/movement/watch_faces/clock/french_revolutionary_face.c @@ -28,6 +28,7 @@ void french_revolutionary_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; + (void) watch_face_index; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(french_revolutionary_state_t)); memset(*context_ptr, 0, sizeof(french_revolutionary_state_t)); @@ -64,7 +65,7 @@ bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t break; case EVENT_TICK: case EVENT_LOW_ENERGY_UPDATE: - + date_time = watch_rtc_get_date_time(); decimal_time = get_decimal_time(&date_time); @@ -107,6 +108,7 @@ bool french_revolutionary_face_loop(movement_event_t event, movement_settings_t state->use_am_pm = !state->use_am_pm; if (state->use_am_pm) { watch_clear_indicator(WATCH_INDICATOR_24H); + date_time = watch_rtc_get_date_time(); if (date_time.unit.hour < 12) { watch_clear_indicator(WATCH_INDICATOR_PM); } else { watch_set_indicator(WATCH_INDICATOR_PM); } } else { @@ -240,4 +242,4 @@ char fix_character_one(char digit) { break; } return return_char; -} \ No newline at end of file +} From fea60e615cd1eeffb0b9ff791cb0fd82b67d8801 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 9 Sep 2023 15:40:57 +0200 Subject: [PATCH 171/220] display closest deadline on activation --- movement/watch_faces/complication/deadline_face.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 01ddc12..0796bd4 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -503,6 +503,18 @@ void deadline_face_activate(movement_settings_t *settings, void *context) /* Set display options */ _running_init(settings, state); state->mode = DEADLINE_FACE_RUNNING; + + /* Determine closest deadline */ + watch_date_time now = watch_rtc_get_date_time(); + uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + uint32_t min_ts = UINT32_MAX; + + for (uint8_t i = 0; i < DEADLINE_FACE_DATES; i++) { + if (state->deadlines[i] < now_ts || state->deadlines[i] > min_ts) + continue; + min_ts = state->deadlines[i]; + state->current_index = i; + } } /* Loop face */ From 4cb00ebb4e1cb186b5c67a198255f550cddcc676 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 9 Sep 2023 16:26:11 +0200 Subject: [PATCH 172/220] support for alarm --- .../watch_faces/complication/deadline_face.c | 81 +++++++++++++++---- .../watch_faces/complication/deadline_face.h | 1 + 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 0796bd4..09e3c34 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -196,6 +196,43 @@ static inline void _change_tick_freq(uint8_t freq) } } +/* Determine index of closest deadline */ +static uint8_t _closest_deadline(movement_settings_t *settings, deadline_state_t *state) +{ + watch_date_time now = watch_rtc_get_date_time(); + uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + uint32_t min_ts = UINT32_MAX; + uint8_t min_index = 0; + + for (uint8_t i = 0; i < DEADLINE_FACE_DATES; i++) { + if (state->deadlines[i] < now_ts || state->deadlines[i] > min_ts) + continue; + min_ts = state->deadlines[i]; + min_index = i; + } + + return min_index; +} + +/* Schedule alarm for next deadline */ +static void _schedule_alarm(movement_settings_t *settings, deadline_state_t *state) +{ + /* Cancel current alarm */ + movement_cancel_background_task(); + + /* Determine closest deadline */ + watch_date_time now = watch_rtc_get_date_time(); + uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + uint32_t next_ts = state->deadlines[_closest_deadline(settings, state)]; + watch_date_time next = watch_utility_date_time_from_unix_time(next_ts, _get_tz_offset(settings)); + + /* No suitable deadline */ + if (next_ts < now_ts) + return; + + movement_schedule_background_task(next); +} + /* Reset deadline to tomorrow */ static inline void _reset_deadline(movement_settings_t *settings, deadline_state_t *state) { @@ -259,6 +296,12 @@ static void _running_display(movement_event_t event, movement_settings_t *settin uint8_t i, range[] = { 60, 60, 24, 30, 12, 0 }; char buf[16]; + /* Display indicators */ + if (state->alarm_enabled) + watch_set_indicator(WATCH_INDICATOR_BELL); + else + watch_clear_indicator(WATCH_INDICATOR_BELL); + watch_date_time now = watch_rtc_get_date_time(); uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); @@ -275,8 +318,9 @@ static void _running_display(movement_event_t event, movement_settings_t *settin } /* Get date time structs */ - watch_date_time deadline = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], _get_tz_offset(settings) - ); + watch_date_time deadline = watch_utility_date_time_from_unix_time( + state->deadlines[state->current_index], _get_tz_offset(settings) + ); /* Calculate naive difference of dates */ unit[0] = deadline.unit.second - now.unit.second; @@ -316,8 +360,6 @@ static void _running_display(movement_event_t event, movement_settings_t *settin /* hours:minutes:seconds */ sprintf(buf, "DL%2d%02d%02d%02d", i, unit[2] % 24, unit[1] % 60, unit[0] % 60); } - - //watch_set_indicator(WATCH_INDICATOR_BELL); watch_display_string(buf, 0); } @@ -352,9 +394,20 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, case EVENT_MODE_BUTTON_UP: movement_move_to_next_face(); return false; + case EVENT_LIGHT_BUTTON_DOWN: + break; + case EVENT_LIGHT_LONG_PRESS: + state->alarm_enabled = !state->alarm_enabled; + _running_display(event, settings, state); + break; case EVENT_TIMEOUT: movement_move_to_face(0); break; + case EVENT_BACKGROUND_TASK: + if (state->alarm_enabled) + movement_play_alarm(); + _schedule_alarm(settings, state); + break; case EVENT_LOW_ENERGY_UPDATE: break; default: @@ -467,14 +520,21 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, break; case EVENT_TIMEOUT: _beep_button(settings); + _schedule_alarm(settings, state); _change_tick_freq(1); movement_move_to_face(0); break; case EVENT_MODE_BUTTON_UP: _beep_disable(settings); + _schedule_alarm(settings, state); _running_init(settings, state); state->mode = DEADLINE_FACE_RUNNING; break; + case EVENT_BACKGROUND_TASK: + if (state->alarm_enabled) + movement_play_alarm(); + _schedule_alarm(settings, state); + break; default: return movement_default_loop_handler(event, settings); } @@ -503,18 +563,7 @@ void deadline_face_activate(movement_settings_t *settings, void *context) /* Set display options */ _running_init(settings, state); state->mode = DEADLINE_FACE_RUNNING; - - /* Determine closest deadline */ - watch_date_time now = watch_rtc_get_date_time(); - uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); - uint32_t min_ts = UINT32_MAX; - - for (uint8_t i = 0; i < DEADLINE_FACE_DATES; i++) { - if (state->deadlines[i] < now_ts || state->deadlines[i] > min_ts) - continue; - min_ts = state->deadlines[i]; - state->current_index = i; - } + state->current_index = _closest_deadline(settings, state); } /* Loop face */ diff --git a/movement/watch_faces/complication/deadline_face.h b/movement/watch_faces/complication/deadline_face.h index b560ab2..977ddf3 100644 --- a/movement/watch_faces/complication/deadline_face.h +++ b/movement/watch_faces/complication/deadline_face.h @@ -42,6 +42,7 @@ typedef struct { deadline_mode_t mode:1; uint8_t current_page:3; uint8_t current_index:2; + uint8_t alarm_enabled:1; uint32_t deadlines[DEADLINE_FACE_DATES]; } deadline_state_t; From c102a1016510388e37c55250153cc8dd4b93750e Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 9 Sep 2023 16:35:30 +0200 Subject: [PATCH 173/220] beep on button --- movement/watch_faces/complication/deadline_face.c | 1 + 1 file changed, 1 insertion(+) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 09e3c34..21f4062 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -397,6 +397,7 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, case EVENT_LIGHT_BUTTON_DOWN: break; case EVENT_LIGHT_LONG_PRESS: + _beep_button(settings); state->alarm_enabled = !state->alarm_enabled; _running_display(event, settings, state); break; From fab8c94428e1ea812b77121595e0c2fa08f3cfb3 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 2 Mar 2024 21:11:17 +0100 Subject: [PATCH 174/220] moved tick_freq to deadline_state_t structure. --- .../watch_faces/complication/deadline_face.c | 26 +++++++++---------- .../watch_faces/complication/deadline_face.h | 1 + 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 21f4062..2eeb0f7 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -100,8 +100,6 @@ #define SETTINGS_NUM (5) const char settings_titles[SETTINGS_NUM][3] = { "YR", "MO", "DA", "HR", "M1" }; -static uint8_t tick_freq; - /* Local functions */ static void _running_init(movement_settings_t *settings, deadline_state_t * state); static bool _running_loop(movement_event_t event, movement_settings_t *settings, void *context); @@ -113,7 +111,7 @@ static void _setting_display(movement_event_t event, movement_settings_t *settin /* Utility functions */ static void _increment_date(movement_settings_t *settings, deadline_state_t * state, watch_date_time date_time); static inline int32_t _get_tz_offset(movement_settings_t *settings); -static inline void _change_tick_freq(uint8_t freq); +static inline void _change_tick_freq(uint8_t freq, deadline_state_t * state); static inline bool _is_leap(int16_t y); static inline int _days_in_month(int16_t mpnth, int16_t y); static inline unsigned int _mod(int a, int b); @@ -188,11 +186,11 @@ static inline void _beep_disable(movement_settings_t *settings) } /* Change tick frequency */ -static inline void _change_tick_freq(uint8_t freq) +static inline void _change_tick_freq(uint8_t freq, deadline_state_t *state) { - if (tick_freq != freq) { + if (state->tick_freq != freq) { movement_request_tick_frequency(freq); - tick_freq = freq; + state->tick_freq = freq; } } @@ -416,7 +414,7 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, } /* Slow down frequency after first loop for snappiness */ - _change_tick_freq(1); + _change_tick_freq(1, state); return true; } @@ -469,7 +467,7 @@ static void _setting_display(movement_event_t event, movement_settings_t *settin /* Init setting mode */ static void _setting_init(movement_settings_t *settings, deadline_state_t *state) { - _change_tick_freq(4); + _change_tick_freq(4, state); state->current_page = 0; /* Init fresh deadline to next day */ @@ -489,20 +487,20 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, switch (event.event_type) { case EVENT_TICK: - if (tick_freq == 8) { + if (state->tick_freq == 8) { if (watch_get_pin_level(BTN_ALARM)) { _increment_date(settings, state, date_time); _setting_display(event, settings, state, date_time); } else { - _change_tick_freq(4); + _change_tick_freq(4, state); } } break; case EVENT_ALARM_LONG_PRESS: - _change_tick_freq(8); + _change_tick_freq(8, state); break; case EVENT_ALARM_LONG_UP: - _change_tick_freq(4); + _change_tick_freq(4, state); break; case EVENT_LIGHT_LONG_PRESS: _beep_button(settings); @@ -515,14 +513,14 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, _setting_display(event, settings, state, date_time); break; case EVENT_ALARM_BUTTON_UP: - _change_tick_freq(4); + _change_tick_freq(4, state); _increment_date(settings, state, date_time); _setting_display(event, settings, state, date_time); break; case EVENT_TIMEOUT: _beep_button(settings); _schedule_alarm(settings, state); - _change_tick_freq(1); + _change_tick_freq(1, state); movement_move_to_face(0); break; case EVENT_MODE_BUTTON_UP: diff --git a/movement/watch_faces/complication/deadline_face.h b/movement/watch_faces/complication/deadline_face.h index 977ddf3..3b554b6 100644 --- a/movement/watch_faces/complication/deadline_face.h +++ b/movement/watch_faces/complication/deadline_face.h @@ -43,6 +43,7 @@ typedef struct { uint8_t current_page:3; uint8_t current_index:2; uint8_t alarm_enabled:1; + uint8_t tick_freq; uint32_t deadlines[DEADLINE_FACE_DATES]; } deadline_state_t; From c89316b3ecc11d91d32ec3a0c2b36e8260aa6873 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 2 Mar 2024 21:17:37 +0100 Subject: [PATCH 175/220] removed higher frequency in first loop run. --- movement/watch_faces/complication/deadline_face.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 2eeb0f7..c6b074d 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -413,8 +413,6 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, return movement_default_loop_handler(event, settings); } - /* Slow down frequency after first loop for snappiness */ - _change_tick_freq(1, state); return true; } @@ -467,7 +465,7 @@ static void _setting_display(movement_event_t event, movement_settings_t *settin /* Init setting mode */ static void _setting_init(movement_settings_t *settings, deadline_state_t *state) { - _change_tick_freq(4, state); + _change_tick_freq(1, state); state->current_page = 0; /* Init fresh deadline to next day */ From 27ab799e858ff98e66c4d163dc7a2da869e678e8 Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Fri, 8 Mar 2024 14:50:32 +0100 Subject: [PATCH 176/220] Fixed bugs and improved watchface - A background task is only scheduled if the alarm option is activated. If the option is enabled, an alarm sounds when the next deadline is reached. If the option is disabled, no alarm sounds and the watch can enter low energy sleep. - Fixed tick frequency error. During running mode, the clock ticks at 1Hz. This is set in the init function `_running_init` and thus ensures that we do not run too fast when returning from the setting mode. - Minor corrections to comments and indentations. --- .../watch_faces/complication/deadline_face.c | 63 +++++++++++++------ .../watch_faces/complication/deadline_face.h | 2 +- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index c6b074d..3267940 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Konrad Rieck + * Copyright (c) 2023-2024 Konrad Rieck * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the @@ -65,6 +65,12 @@ * * - A *long press* on the *alarm button* activates settings mode and * enables configuring the currently selected deadline. + * + * - A *long press* on the *light button* activates a deadline alarm. The + * bell icon is displayed, and the alarm will ring upon reaching any of + * the deadlines set. It is important to note that the watch will not + * enter low-energy sleep mode while the alarm is enabled. + * * * ## Settings Mode * @@ -101,24 +107,24 @@ const char settings_titles[SETTINGS_NUM][3] = { "YR", "MO", "DA", "HR", "M1" }; /* Local functions */ -static void _running_init(movement_settings_t *settings, deadline_state_t * state); +static void _running_init(movement_settings_t *settings, deadline_state_t *state); static bool _running_loop(movement_event_t event, movement_settings_t *settings, void *context); -static void _running_display(movement_event_t event, movement_settings_t *settings, deadline_state_t * state); -static void _setting_init(movement_settings_t *settings, deadline_state_t * state); +static void _running_display(movement_event_t event, movement_settings_t *settings, deadline_state_t *state); +static void _setting_init(movement_settings_t *settings, deadline_state_t *state); static bool _setting_loop(movement_event_t event, movement_settings_t *settings, void *context); -static void _setting_display(movement_event_t event, movement_settings_t *settings, deadline_state_t * state, watch_date_time date); +static void _setting_display(movement_event_t event, movement_settings_t *settings, deadline_state_t *state, watch_date_time date); /* Utility functions */ -static void _increment_date(movement_settings_t *settings, deadline_state_t * state, watch_date_time date_time); +static void _increment_date(movement_settings_t *settings, deadline_state_t *state, watch_date_time date_time); static inline int32_t _get_tz_offset(movement_settings_t *settings); -static inline void _change_tick_freq(uint8_t freq, deadline_state_t * state); +static inline void _change_tick_freq(uint8_t freq, deadline_state_t *state); static inline bool _is_leap(int16_t y); static inline int _days_in_month(int16_t mpnth, int16_t y); static inline unsigned int _mod(int a, int b); static inline void _beep_button(movement_settings_t *settings); static inline void _beep_enable(movement_settings_t *settings); static inline void _beep_disable(movement_settings_t *settings); -static inline void _reset_deadline(movement_settings_t *settings, deadline_state_t * state); +static inline void _reset_deadline(movement_settings_t *settings, deadline_state_t *state); /* Check for leap year */ static inline bool _is_leap(int16_t y) @@ -147,7 +153,7 @@ static inline int _days_in_month(int16_t month, int16_t year) } } -/* Calculate time zone offset */ +/* Return time zone offset */ static inline int32_t _get_tz_offset(movement_settings_t *settings) { return movement_timezone_offsets[settings->bit.time_zone] * 60; @@ -156,7 +162,7 @@ static inline int32_t _get_tz_offset(movement_settings_t *settings) /* Beep for a button press*/ static inline void _beep_button(movement_settings_t *settings) { - // play a beep as confirmation for a button press (if applicable) + // Play a beep as confirmation for a button press (if applicable) if (!settings->bit.button_should_sound) return; @@ -289,7 +295,7 @@ static void _running_display(movement_event_t event, movement_settings_t *settin (void) event; (void) settings; - /* seconds, minutes, hours, days, months, years */ + /* Seconds, minutes, hours, days, months, years */ int16_t unit[] = { 0, 0, 0, 0, 0, 0 }; uint8_t i, range[] = { 60, 60, 24, 30, 12, 0 }; char buf[16]; @@ -370,6 +376,9 @@ static void _running_init(movement_settings_t *settings, deadline_state_t *state watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); watch_set_colon(); + + /* Ensure 1Hz updates only */ + _change_tick_freq(1, state); } /* Loop of running mode */ @@ -397,15 +406,21 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, case EVENT_LIGHT_LONG_PRESS: _beep_button(settings); state->alarm_enabled = !state->alarm_enabled; + if (state->alarm_enabled) { + _schedule_alarm(settings, state); + } else { + movement_cancel_background_task(); + } _running_display(event, settings, state); break; case EVENT_TIMEOUT: movement_move_to_face(0); break; case EVENT_BACKGROUND_TASK: - if (state->alarm_enabled) + if (state->alarm_enabled) { movement_play_alarm(); - _schedule_alarm(settings, state); + _schedule_alarm(settings, state); + } break; case EVENT_LOW_ENERGY_UPDATE: break; @@ -465,13 +480,15 @@ static void _setting_display(movement_event_t event, movement_settings_t *settin /* Init setting mode */ static void _setting_init(movement_settings_t *settings, deadline_state_t *state) { - _change_tick_freq(1, state); state->current_page = 0; /* Init fresh deadline to next day */ if (state->deadlines[state->current_index] == 0) { _reset_deadline(settings, state); } + + /* Ensure 1Hz updates only */ + _change_tick_freq(1, state); } /* Loop of setting mode */ @@ -517,20 +534,30 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, break; case EVENT_TIMEOUT: _beep_button(settings); - _schedule_alarm(settings, state); _change_tick_freq(1, state); + + /* Update alarm as deadlines may have changed */ + if (state->alarm_enabled) { + _schedule_alarm(settings, state); + } movement_move_to_face(0); break; case EVENT_MODE_BUTTON_UP: _beep_disable(settings); - _schedule_alarm(settings, state); _running_init(settings, state); + _running_display(event, settings, state); + + /* Update alarm as deadlines may have changed */ + if (state->alarm_enabled) { + _schedule_alarm(settings, state); + } state->mode = DEADLINE_FACE_RUNNING; break; case EVENT_BACKGROUND_TASK: - if (state->alarm_enabled) + if (state->alarm_enabled) { movement_play_alarm(); - _schedule_alarm(settings, state); + _schedule_alarm(settings, state); + } break; default: return movement_default_loop_handler(event, settings); diff --git a/movement/watch_faces/complication/deadline_face.h b/movement/watch_faces/complication/deadline_face.h index 3b554b6..9f175b0 100644 --- a/movement/watch_faces/complication/deadline_face.h +++ b/movement/watch_faces/complication/deadline_face.h @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Konrad Rieck + * Copyright (c) 2023-2024 Konrad Rieck * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the From 93b9ca634168ba2c5bf1c8e16f11f58b51b4e4bf Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Thu, 8 Aug 2024 22:45:07 -0400 Subject: [PATCH 177/220] Made cards go through a deck format. --- .../complication/higher_lower_game_face.c | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index cf5205c..bbf456b 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -43,6 +43,8 @@ #define BOARD_DISPLAY_END 9 #define MIN_CARD_VALUE 2 #define MAX_CARD_VALUE 14 +#define DUPLICATES_OF_CARD 4 +#define DECK_COUNT (DUPLICATES_OF_CARD * (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)) #define FLIP_BOARD_DIRECTION false typedef struct card_t { @@ -73,6 +75,8 @@ static card_t game_board[GAME_BOARD_SIZE] = {0}; static uint8_t guess_position = 0; static uint8_t score = 0; static uint8_t completed_board_count = 0; +static uint8_t _deck[DECK_COUNT]; +static uint8_t _curr_card; static uint8_t generate_random_number(uint8_t num_values) { // Emulator: use rand. Hardware: use arc4random. @@ -83,10 +87,41 @@ static uint8_t generate_random_number(uint8_t num_values) { #endif } +static void stack_deck(uint8_t *array) { + const uint8_t unique_cards = MAX_CARD_VALUE - MIN_CARD_VALUE + 1; + for (uint8_t i = 0; i < unique_cards; i++) + { + for (uint8_t j = 0; j < DUPLICATES_OF_CARD; j++) + array[(i * DUPLICATES_OF_CARD) + j] = MIN_CARD_VALUE + i; + } +} + +static void shuffle_deck(uint8_t *array, uint8_t n) { + // Randomize shuffle with Fisher Yates + uint8_t i, j, tmp; + for (i = n - 1; i > 0; i--) { + j = generate_random_number(0xFF) % (i + 1); + tmp = array[j]; + array[j] = array[i]; + array[i] = tmp; + } +} + +static void reset_deck(void) { + _curr_card = 0; + stack_deck(_deck); + shuffle_deck(_deck, DECK_COUNT); +} + +static uint8_t get_next_card(void) { + if (_curr_card >= DECK_COUNT) reset_deck(); + return _deck[_curr_card++]; +} + static void reset_board(bool first_round) { // First card is random on the first board, and carried over from the last position on subsequent boards const uint8_t first_card_value = first_round - ? generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE + ? get_next_card() : game_board[GAME_BOARD_SIZE - 1].value; game_board[0].value = first_card_value; @@ -95,7 +130,7 @@ static void reset_board(bool first_round) { // Fill remainder of board for (size_t i = 1; i < GAME_BOARD_SIZE; ++i) { game_board[i] = (card_t) { - .value = generate_random_number(MAX_CARD_VALUE - MIN_CARD_VALUE + 1) + MIN_CARD_VALUE, + .value = get_next_card(), .revealed = false }; } @@ -105,6 +140,7 @@ static void init_game(void) { watch_clear_display(); watch_display_string(TITLE_TEXT, BOARD_DISPLAY_START); watch_display_string("GA", STATUS_DISPLAY_START); + reset_deck(); reset_board(true); score = 0; completed_board_count = 0; From 6db0a62bbf6e6de686ea8879f503671064d1840a Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 29 Jul 2023 17:44:54 +0100 Subject: [PATCH 178/220] Hi-lo: Use alternate card faces --- .../complication/higher_lower_game_face.c | 14 ++++---------- .../complication/higher_lower_game_face.h | 7 ++++--- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index aed6eee..cf5205c 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -139,28 +139,22 @@ static void render_board_position(size_t board_position) { const uint8_t value = game_board[board_position].value; switch (value) { - case 14: // A - watch_display_character('H', display_position); - break; - case 13: // K (≡) + case 14: // A (≡) 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 12: // Q (=) + case 13: // K (=) watch_display_character(' ', display_position); set_segment_at_position(A, display_position); set_segment_at_position(D, display_position); break; - case 11: // J (-) + case 12: // Q (-) watch_display_character('-', display_position); break; - case 10: // 10 (0) - watch_display_character('0', display_position); - break; default: { - const char display_char = value + '0'; + const char display_char = (value - MIN_CARD_VALUE) + '0'; watch_display_character(display_char, display_position); } } diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index d093680..efbd394 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -68,13 +68,14 @@ * | 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|-|=|≡|H| + * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| * - * The following may more legible choice: + * A previous alternative can be found in the git history: * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| - * | Display |0|1|2|3|4|5|6|7|8 |9|-|=|≡| + * | Display |2|3|4|5|6|7|8|9| 0|-|=|≡|H| + * * * Future Ideas: * - Add sounds From e2ec468754b5d1b43a4debfc7761041d0b9e0e5b Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 9 Mar 2024 20:48:42 +0100 Subject: [PATCH 179/220] Alarm handled using background task. The alarm for deadlines is now handled via a background task. Instead of a scheduled task that prevents sleep mode, the face checks in the background every minute whether an deadline is due. If this is the case, the face wakes up from sleep mode and starts a scheduled task for the remaining seconds. --- .../watch_faces/complication/deadline_face.c | 107 +++++++++++------- .../watch_faces/complication/deadline_face.h | 4 +- 2 files changed, 72 insertions(+), 39 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 3267940..521c9bb 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -115,6 +115,9 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, static void _setting_display(movement_event_t event, movement_settings_t *settings, deadline_state_t *state, watch_date_time date); /* Utility functions */ +static void _background_alarm_play(movement_settings_t *settings, deadline_state_t *state); +static void _background_alarm_schedule(movement_settings_t *settings, deadline_state_t *state); +static void _background_alarm_cancel(movement_settings_t *settings, deadline_state_t *state); static void _increment_date(movement_settings_t *settings, deadline_state_t *state, watch_date_time date_time); static inline int32_t _get_tz_offset(movement_settings_t *settings); static inline void _change_tick_freq(uint8_t freq, deadline_state_t *state); @@ -218,23 +221,29 @@ static uint8_t _closest_deadline(movement_settings_t *settings, deadline_state_t return min_index; } -/* Schedule alarm for next deadline */ -static void _schedule_alarm(movement_settings_t *settings, deadline_state_t *state) +/* Play background alarm */ +static void _background_alarm_play(movement_settings_t *settings, deadline_state_t *state) { - /* Cancel current alarm */ - movement_cancel_background_task(); + (void) settings; - /* Determine closest deadline */ - watch_date_time now = watch_rtc_get_date_time(); - uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); - uint32_t next_ts = state->deadlines[_closest_deadline(settings, state)]; - watch_date_time next = watch_utility_date_time_from_unix_time(next_ts, _get_tz_offset(settings)); + /* Use the default alarm from movement */ + if (state->alarm_enabled) + movement_play_alarm(); +} - /* No suitable deadline */ - if (next_ts < now_ts) - return; +/* Schedule background alarm */ +static void _background_alarm_schedule(movement_settings_t *settings, deadline_state_t *state) +{ + /* We simply re-use the scheduling in the background task */ + deadline_face_wants_background_task(settings, state); +} - movement_schedule_background_task(next); +/* Cancel background alarm */ +static void _background_alarm_cancel(movement_settings_t *settings, deadline_state_t *state) +{ + (void) settings; + + movement_cancel_background_task_for_face(state->face_idx); } /* Reset deadline to tomorrow */ @@ -322,9 +331,8 @@ static void _running_display(movement_event_t event, movement_settings_t *settin } /* Get date time structs */ - watch_date_time deadline = watch_utility_date_time_from_unix_time( - state->deadlines[state->current_index], _get_tz_offset(settings) - ); + watch_date_time deadline = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], _get_tz_offset(settings) + ); /* Calculate naive difference of dates */ unit[0] = deadline.unit.second - now.unit.second; @@ -385,7 +393,9 @@ static void _running_init(movement_settings_t *settings, deadline_state_t *state static bool _running_loop(movement_event_t event, movement_settings_t *settings, void *context) { deadline_state_t *state = (deadline_state_t *) context; - _running_display(event, settings, state); + + if (event.event_type != EVENT_BACKGROUND_TASK) + _running_display(event, settings, state); switch (event.event_type) { case EVENT_ALARM_BUTTON_UP: @@ -407,9 +417,9 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, _beep_button(settings); state->alarm_enabled = !state->alarm_enabled; if (state->alarm_enabled) { - _schedule_alarm(settings, state); + _background_alarm_schedule(settings, context); } else { - movement_cancel_background_task(); + _background_alarm_cancel(settings, context); } _running_display(event, settings, state); break; @@ -417,10 +427,7 @@ static bool _running_loop(movement_event_t event, movement_settings_t *settings, movement_move_to_face(0); break; case EVENT_BACKGROUND_TASK: - if (state->alarm_enabled) { - movement_play_alarm(); - _schedule_alarm(settings, state); - } + _background_alarm_play(settings, state); break; case EVENT_LOW_ENERGY_UPDATE: break; @@ -498,7 +505,8 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, watch_date_time date_time; date_time = watch_utility_date_time_from_unix_time(state->deadlines[state->current_index], _get_tz_offset(settings)); - _setting_display(event, settings, state, date_time); + if (event.event_type != EVENT_BACKGROUND_TASK) + _setting_display(event, settings, state, date_time); switch (event.event_type) { case EVENT_TICK: @@ -534,30 +542,20 @@ static bool _setting_loop(movement_event_t event, movement_settings_t *settings, break; case EVENT_TIMEOUT: _beep_button(settings); + _background_alarm_schedule(settings, context); _change_tick_freq(1, state); - - /* Update alarm as deadlines may have changed */ - if (state->alarm_enabled) { - _schedule_alarm(settings, state); - } + state->mode = DEADLINE_FACE_RUNNING; movement_move_to_face(0); break; case EVENT_MODE_BUTTON_UP: _beep_disable(settings); + _background_alarm_schedule(settings, context); _running_init(settings, state); _running_display(event, settings, state); - - /* Update alarm as deadlines may have changed */ - if (state->alarm_enabled) { - _schedule_alarm(settings, state); - } state->mode = DEADLINE_FACE_RUNNING; break; case EVENT_BACKGROUND_TASK: - if (state->alarm_enabled) { - movement_play_alarm(); - _schedule_alarm(settings, state); - } + _background_alarm_play(settings, state); break; default: return movement_default_loop_handler(event, settings); @@ -574,8 +572,13 @@ void deadline_face_setup(movement_settings_t *settings, uint8_t watch_face_index if (*context_ptr != NULL) return; /* Skip setup if context available */ + /* Allocate state */ *context_ptr = malloc(sizeof(deadline_state_t)); memset(*context_ptr, 0, sizeof(deadline_state_t)); + + /* Store face index for background tasks */ + deadline_state_t *state = (deadline_state_t *) *context_ptr; + state->face_idx = watch_face_index; } /* Activate face */ @@ -614,3 +617,31 @@ void deadline_face_resign(movement_settings_t *settings, void *context) (void) settings; (void) context; } + +/* Want background task */ +bool deadline_face_wants_background_task(movement_settings_t *settings, void *context) +{ + deadline_state_t *state = (deadline_state_t *) context; + + if (!state->alarm_enabled) + return false; + + /* Determine closest deadline */ + watch_date_time now = watch_rtc_get_date_time(); + uint32_t now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); + uint32_t next_ts = state->deadlines[_closest_deadline(settings, state)]; + + /* No active deadline */ + if (next_ts < now_ts) + return false; + + /* No deadline within next 60 seconds */ + if (next_ts >= now_ts + 60) + return false; + + /* Deadline within next minute. Let's set up an alarm */ + watch_date_time next = watch_utility_date_time_from_unix_time(next_ts, _get_tz_offset(settings)); + movement_request_wake(); + movement_schedule_background_task_for_face(state->face_idx, next); + return false; +} diff --git a/movement/watch_faces/complication/deadline_face.h b/movement/watch_faces/complication/deadline_face.h index 9f175b0..4e4efe0 100644 --- a/movement/watch_faces/complication/deadline_face.h +++ b/movement/watch_faces/complication/deadline_face.h @@ -44,6 +44,7 @@ typedef struct { uint8_t current_index:2; uint8_t alarm_enabled:1; uint8_t tick_freq; + uint8_t face_idx; uint32_t deadlines[DEADLINE_FACE_DATES]; } deadline_state_t; @@ -51,13 +52,14 @@ void deadline_face_setup(movement_settings_t *settings, uint8_t watch_face_index void deadline_face_activate(movement_settings_t *settings, void *context); bool deadline_face_loop(movement_event_t event, movement_settings_t *settings, void *context); void deadline_face_resign(movement_settings_t *settings, void *context); +bool deadline_face_wants_background_task(movement_settings_t *settings, void *context); #define deadline_face ((const watch_face_t){ \ deadline_face_setup, \ deadline_face_activate, \ deadline_face_loop, \ deadline_face_resign, \ - NULL, \ + deadline_face_wants_background_task \ }) #endif // DEADLINE_FACE_H_ From 50cff5483377ac7ff4eae3601ba68207e4533e8f Mon Sep 17 00:00:00 2001 From: Konrad Rieck Date: Sat, 9 Mar 2024 23:50:47 +0100 Subject: [PATCH 180/220] move to deadline face on alarm --- movement/watch_faces/complication/deadline_face.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/movement/watch_faces/complication/deadline_face.c b/movement/watch_faces/complication/deadline_face.c index 521c9bb..ae9cab8 100644 --- a/movement/watch_faces/complication/deadline_face.c +++ b/movement/watch_faces/complication/deadline_face.c @@ -226,9 +226,11 @@ static void _background_alarm_play(movement_settings_t *settings, deadline_state { (void) settings; - /* Use the default alarm from movement */ - if (state->alarm_enabled) + /* Use the default alarm from movement and move to foreground */ + if (state->alarm_enabled) { movement_play_alarm(); + movement_move_to_face(state->face_idx); + } } /* Schedule background alarm */ From 9e1e692511011e9bb105dbab0c2d5426dd7cbb94 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 1 Sep 2024 19:30:31 +0100 Subject: [PATCH 181/220] Hi-lo: Additional code tweaks --- .../complication/higher_lower_game_face.c | 59 ++++++++++--------- .../complication/higher_lower_game_face.h | 2 + 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/movement/watch_faces/complication/higher_lower_game_face.c b/movement/watch_faces/complication/higher_lower_game_face.c index bbf456b..f9dbcd0 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.c +++ b/movement/watch_faces/complication/higher_lower_game_face.c @@ -36,15 +36,16 @@ #define GAME_BOARD_SIZE 6 #define MAX_BOARDS 40 #define GUESSES_PER_SCREEN 5 -#define WIN_SCORE MAX_BOARDS * GUESSES_PER_SCREEN +#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 #define MAX_CARD_VALUE 14 -#define DUPLICATES_OF_CARD 4 -#define DECK_COUNT (DUPLICATES_OF_CARD * (MAX_CARD_VALUE - MIN_CARD_VALUE + 1)) +#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 FLIP_BOARD_DIRECTION false typedef struct card_t { @@ -75,8 +76,8 @@ static card_t game_board[GAME_BOARD_SIZE] = {0}; static uint8_t guess_position = 0; static uint8_t score = 0; static uint8_t completed_board_count = 0; -static uint8_t _deck[DECK_COUNT]; -static uint8_t _curr_card; +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. @@ -87,35 +88,37 @@ static uint8_t generate_random_number(uint8_t num_values) { #endif } -static void stack_deck(uint8_t *array) { - const uint8_t unique_cards = MAX_CARD_VALUE - MIN_CARD_VALUE + 1; - for (uint8_t i = 0; i < unique_cards; i++) - { - for (uint8_t j = 0; j < DUPLICATES_OF_CARD; j++) - array[(i * DUPLICATES_OF_CARD) + j] = MIN_CARD_VALUE + i; +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(uint8_t *array, uint8_t n) { +static void shuffle_deck(void) { // Randomize shuffle with Fisher Yates - uint8_t i, j, tmp; - for (i = n - 1; i > 0; i--) { - j = generate_random_number(0xFF) % (i + 1); - tmp = array[j]; - array[j] = array[i]; - array[i] = tmp; - } + 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) { - _curr_card = 0; - stack_deck(_deck); - shuffle_deck(_deck, DECK_COUNT); + current_card = 0; + stack_deck(); + shuffle_deck(); } static uint8_t get_next_card(void) { - if (_curr_card >= DECK_COUNT) reset_deck(); - return _deck[_curr_card++]; + if (current_card >= DECK_SIZE) + reset_deck(); + return deck[current_card++]; } static void reset_board(bool first_round) { @@ -156,8 +159,8 @@ static void set_segment_at_position(segment_t segment, uint8_t 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; + ? BOARD_DISPLAY_START + board_position + : BOARD_DISPLAY_END - board_position; const bool revealed = game_board[board_position].revealed; //// Current position indicator spot @@ -196,7 +199,7 @@ static void render_board_position(size_t board_position) { } } -static void render_board() { +static void render_board(void) { for (size_t i = 0; i < GAME_BOARD_SIZE; ++i) { render_board_position(i); } @@ -218,7 +221,7 @@ static void render_final_score(void) { watch_display_string(buf, BOARD_DISPLAY_START); } -static guess_t get_answer() { +static guess_t get_answer(void) { if (guess_position < 1 || guess_position > GAME_BOARD_SIZE) return HL_GUESS_EQUAL; // Maybe add an error state, shouldn't ever hit this. diff --git a/movement/watch_faces/complication/higher_lower_game_face.h b/movement/watch_faces/complication/higher_lower_game_face.h index efbd394..13da586 100755 --- a/movement/watch_faces/complication/higher_lower_game_face.h +++ b/movement/watch_faces/complication/higher_lower_game_face.h @@ -65,6 +65,8 @@ * The face tries to remain true to the spirit of using "cards"; to cope with the display limitations I've arrived at * the following mapping of card values to screen display, but am open to better suggestions: * + * Thanks to voloved for adding deck shuffling and drawing! + * * | Cards | | * |---------|--------------------------| * | Value |2|3|4|5|6|7|8|9|10|J|Q|K|A| From 52f9500666a10dfa3d45288749d5edcae1d5b211 Mon Sep 17 00:00:00 2001 From: "R. Alex Barbieri" <> Date: Sat, 27 Apr 2024 13:33:32 -0500 Subject: [PATCH 182/220] add blinking to DST toggle in settings page --- movement/watch_faces/settings/set_time_face.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 16011e3..fb0c806 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -185,6 +185,9 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v case 5: buf[8] = buf[9] = ' '; break; + case 7: + buf[9] = ' '; + break; } } From 5ae88e438d79bcdb6e18f58aa165d4cf9ef6f87f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 10:50:09 -0400 Subject: [PATCH 183/220] Minot cleanup --- movement/movement.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index ebd831f..60e9106 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -457,8 +457,7 @@ uint8_t movement_claim_backup_register(void) { } int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { - if (!movement_state.settings.bit.dst_active) return movement_timezone_offsets[timezone_idx]; - if (dst_occurring(date_time)) + if (movement_state.settings.bit.dst_active && dst_occurring(date_time)) return movement_timezone_dst_offsets[timezone_idx]; return movement_timezone_offsets[timezone_idx]; } From 002f5bc242a7d8a4a2aa371b685994ecb86aa2b7 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Tue, 6 Aug 2024 07:05:19 -0400 Subject: [PATCH 184/220] Updated the tally couter face to allow for presets, negative numbers, fast scrolling, and toggling sound --- .../watch_faces/complication/tally_face.c | 145 ++++++++++++++++-- .../watch_faces/complication/tally_face.h | 21 ++- 2 files changed, 145 insertions(+), 21 deletions(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index 896a54f..ca3ed32 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -27,44 +27,148 @@ #include "tally_face.h" #include "watch.h" +#define TALLY_FACE_MAX 9999 +#define TALLY_FACE_MIN -99 + +static bool _init_val; +static bool _quick_ticks_running; +static const int16_t _tally_default[] = {0, 40, 20}; +static const uint8_t _tally_default_size = sizeof(_tally_default) / sizeof(int16_t); + void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; (void) watch_face_index; if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(tally_state_t)); memset(*context_ptr, 0, sizeof(tally_state_t)); + tally_state_t *state = (tally_state_t *)*context_ptr; + state->tally_default_idx = 0; + state->soundOff = true; + state->tally_idx = _tally_default[state->tally_default_idx]; + _init_val = true; } } void tally_face_activate(movement_settings_t *settings, void *context) { (void) settings; (void) context; + _quick_ticks_running = false; +} + +static void start_quick_cyc(void){ + _quick_ticks_running = true; + movement_request_tick_frequency(8); +} + +static void stop_quick_cyc(void){ + _quick_ticks_running = false; + movement_request_tick_frequency(1); +} + +static void tally_face_increment(tally_state_t *state) { + bool soundOn = !_quick_ticks_running && !state->soundOff; + _init_val = false; + if (state->tally_idx >= TALLY_FACE_MAX){ + if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_E7, 30); + } + else { + state->tally_idx++; + print_tally(state); + if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + } +} + +static void tally_face_decrement(tally_state_t *state) { + bool soundOn = !_quick_ticks_running && !state->soundOff; + _init_val = false; + if (state->tally_idx <= TALLY_FACE_MIN){ + if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_C5SHARP_D5FLAT, 30); + } + else { + state->tally_idx--; + print_tally(state); + if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_C6SHARP_D6FLAT, 30); + } } bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { (void) settings; tally_state_t *state = (tally_state_t *)context; + static bool using_led = false; + + if (using_led) { + if(!watch_get_pin_level(BTN_MODE) && !watch_get_pin_level(BTN_LIGHT) && !watch_get_pin_level(BTN_ALARM)) + using_led = false; + else { + if (event.event_type == EVENT_LIGHT_BUTTON_DOWN || event.event_type == EVENT_ALARM_BUTTON_DOWN) + movement_illuminate_led(); + return true; + } + } switch (event.event_type) { - case EVENT_ALARM_BUTTON_UP: - // increment tally index - state->tally_idx++; - if (state->tally_idx > 999999) { //0-999,999 - //reset tally index and play a reset tune - state->tally_idx = 0; - watch_buzzer_play_note(BUZZER_NOTE_G6, 30); - watch_buzzer_play_note(BUZZER_NOTE_REST, 30); + case EVENT_TICK: + if (_quick_ticks_running) { + bool light_pressed = watch_get_pin_level(BTN_LIGHT); + bool alarm_pressed = watch_get_pin_level(BTN_ALARM); + if (light_pressed && alarm_pressed) stop_quick_cyc(); + else if (light_pressed) tally_face_increment(state); + else if (alarm_pressed) tally_face_decrement(state); + else stop_quick_cyc(); } - print_tally(state); - watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + break; + case EVENT_ALARM_BUTTON_UP: + tally_face_decrement(state); break; case EVENT_ALARM_LONG_PRESS: - state->tally_idx = 0; // reset tally index - //play a reset tune - watch_buzzer_play_note(BUZZER_NOTE_G6, 30); - watch_buzzer_play_note(BUZZER_NOTE_REST, 30); - watch_buzzer_play_note(BUZZER_NOTE_E6, 30); - print_tally(state); + if (_init_val) { + state->soundOff = !state->soundOff; + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + print_tally(state); + } + else{ + tally_face_decrement(state); + start_quick_cyc(); + } + break; + case EVENT_MODE_LONG_PRESS: + if (state->tally_idx == _tally_default[state->tally_default_idx]) { + _init_val = true; + movement_move_to_face(0); + } + else { + state->tally_idx = _tally_default[state->tally_default_idx]; // reset tally index + _init_val = true; + //play a reset tune + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + print_tally(state); + } + break; + case EVENT_LIGHT_BUTTON_UP: + tally_face_increment(state); + break; + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + if (watch_get_pin_level(BTN_MODE)) { + movement_illuminate_led(); + using_led = true; + } + break; + case EVENT_LIGHT_LONG_PRESS: + if (_init_val){ + state->tally_default_idx = (state->tally_default_idx + 1) % _tally_default_size; + state->tally_idx = _tally_default[state->tally_default_idx]; + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); + if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); + print_tally(state); + } + else{ + tally_face_increment(state); + start_quick_cyc(); + } break; case EVENT_ACTIVATE: print_tally(state); @@ -83,7 +187,14 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void // print tally index at the center of display. void print_tally(tally_state_t *state) { char buf[14]; - sprintf(buf, "TA %06d", (int)(state->tally_idx)); // center of LCD display + if (!state->soundOff) + watch_set_indicator(WATCH_INDICATOR_BELL); + else + watch_clear_indicator(WATCH_INDICATOR_BELL); + if (state->tally_idx >= 0) + sprintf(buf, "TA %4d ", (int)(state->tally_idx)); // center of LCD display + else + sprintf(buf, "TA %-3d", (int)(state->tally_idx)); // center of LCD display watch_display_string(buf, 0); } diff --git a/movement/watch_faces/complication/tally_face.h b/movement/watch_faces/complication/tally_face.h index 8096592..727d218 100644 --- a/movement/watch_faces/complication/tally_face.h +++ b/movement/watch_faces/complication/tally_face.h @@ -29,16 +29,29 @@ * TALLY face * * Tally face is designed to act as a tally counter. - * Based on the counter_face watch face by Shogo Okamoto. * - * To advance the counter, press the ALARM button. - * To reset, long press the ALARM button. + * Alarm + * Press: Decrement + * Hold : On initial value: Toggle Sound + * Else: Fast Decrement + * + * Light + * Press: Increment + * Hold : On initial value: Cycles through other initial values. + * Else: Fast Increment + * + * Mode + * Press: Next face + * Hold : On initial value: Go to first face. + * Else: Resets counter */ #include "movement.h" typedef struct { - uint32_t tally_idx; + int16_t tally_idx; + uint8_t tally_default_idx : 7; + bool soundOff; } tally_state_t; From c02ec307ff945e525bac73c19a1c5b8943a881fd Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 11:44:01 -0400 Subject: [PATCH 185/220] Tally face sound now uses the preference's beep so decrementing fast can happen immedietly --- .../watch_faces/complication/tally_face.c | 57 ++++++++----------- .../watch_faces/complication/tally_face.h | 10 ++-- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index ca3ed32..32e3c52 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -43,7 +43,6 @@ void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, v memset(*context_ptr, 0, sizeof(tally_state_t)); tally_state_t *state = (tally_state_t *)*context_ptr; state->tally_default_idx = 0; - state->soundOff = true; state->tally_idx = _tally_default[state->tally_default_idx]; _init_val = true; } @@ -65,34 +64,33 @@ static void stop_quick_cyc(void){ movement_request_tick_frequency(1); } -static void tally_face_increment(tally_state_t *state) { - bool soundOn = !_quick_ticks_running && !state->soundOff; +static void tally_face_increment(tally_state_t *state, bool sound_on) { + bool soundOn = !_quick_ticks_running && sound_on; _init_val = false; if (state->tally_idx >= TALLY_FACE_MAX){ if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_E7, 30); } else { state->tally_idx++; - print_tally(state); + print_tally(state, sound_on); if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); } } -static void tally_face_decrement(tally_state_t *state) { - bool soundOn = !_quick_ticks_running && !state->soundOff; +static void tally_face_decrement(tally_state_t *state, bool sound_on) { + bool soundOn = !_quick_ticks_running && sound_on; _init_val = false; if (state->tally_idx <= TALLY_FACE_MIN){ if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_C5SHARP_D5FLAT, 30); } else { state->tally_idx--; - print_tally(state); + print_tally(state, sound_on); if (soundOn) watch_buzzer_play_note(BUZZER_NOTE_C6SHARP_D6FLAT, 30); } } bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { - (void) settings; tally_state_t *state = (tally_state_t *)context; static bool using_led = false; @@ -112,24 +110,17 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void bool light_pressed = watch_get_pin_level(BTN_LIGHT); bool alarm_pressed = watch_get_pin_level(BTN_ALARM); if (light_pressed && alarm_pressed) stop_quick_cyc(); - else if (light_pressed) tally_face_increment(state); - else if (alarm_pressed) tally_face_decrement(state); + else if (light_pressed) tally_face_increment(state, settings->bit.button_should_sound); + else if (alarm_pressed) tally_face_decrement(state, settings->bit.button_should_sound); else stop_quick_cyc(); } break; case EVENT_ALARM_BUTTON_UP: - tally_face_decrement(state); + tally_face_decrement(state, settings->bit.button_should_sound); break; case EVENT_ALARM_LONG_PRESS: - if (_init_val) { - state->soundOff = !state->soundOff; - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); - print_tally(state); - } - else{ - tally_face_decrement(state); - start_quick_cyc(); - } + tally_face_decrement(state, settings->bit.button_should_sound); + start_quick_cyc(); break; case EVENT_MODE_LONG_PRESS: if (state->tally_idx == _tally_default[state->tally_default_idx]) { @@ -140,14 +131,14 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void state->tally_idx = _tally_default[state->tally_default_idx]; // reset tally index _init_val = true; //play a reset tune - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); - print_tally(state); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + print_tally(state, settings->bit.button_should_sound); } break; case EVENT_LIGHT_BUTTON_UP: - tally_face_increment(state); + tally_face_increment(state, settings->bit.button_should_sound); break; case EVENT_LIGHT_BUTTON_DOWN: case EVENT_ALARM_BUTTON_DOWN: @@ -160,18 +151,18 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void if (_init_val){ state->tally_default_idx = (state->tally_default_idx + 1) % _tally_default_size; state->tally_idx = _tally_default[state->tally_default_idx]; - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); - if (!state->soundOff) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); - print_tally(state); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); + if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_G6, 30); + print_tally(state, settings->bit.button_should_sound); } else{ - tally_face_increment(state); + tally_face_increment(state, settings->bit.button_should_sound); start_quick_cyc(); } break; case EVENT_ACTIVATE: - print_tally(state); + print_tally(state, settings->bit.button_should_sound); break; case EVENT_TIMEOUT: // ignore timeout @@ -185,9 +176,9 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void } // print tally index at the center of display. -void print_tally(tally_state_t *state) { +void print_tally(tally_state_t *state, bool sound_on) { char buf[14]; - if (!state->soundOff) + if (sound_on) watch_set_indicator(WATCH_INDICATOR_BELL); else watch_clear_indicator(WATCH_INDICATOR_BELL); diff --git a/movement/watch_faces/complication/tally_face.h b/movement/watch_faces/complication/tally_face.h index 727d218..286f1c0 100644 --- a/movement/watch_faces/complication/tally_face.h +++ b/movement/watch_faces/complication/tally_face.h @@ -32,8 +32,7 @@ * * Alarm * Press: Decrement - * Hold : On initial value: Toggle Sound - * Else: Fast Decrement + * Hold : Fast Decrement * * Light * Press: Increment @@ -44,14 +43,15 @@ * Press: Next face * Hold : On initial value: Go to first face. * Else: Resets counter + * + * Incrementing or Decrementing the tally will beep if Beeping is set in the global Preferences */ #include "movement.h" typedef struct { int16_t tally_idx; - uint8_t tally_default_idx : 7; - bool soundOff; + uint8_t tally_default_idx; } tally_state_t; @@ -60,7 +60,7 @@ void tally_face_activate(movement_settings_t *settings, void *context); bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void *context); void tally_face_resign(movement_settings_t *settings, void *context); -void print_tally(tally_state_t *state); +void print_tally(tally_state_t *state, bool sound_on); #define tally_face ((const watch_face_t){ \ tally_face_setup, \ From 608199268e5367edd634aedbf7b8712c258689cd Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Sun, 8 Sep 2024 11:49:01 -0400 Subject: [PATCH 186/220] removed break; from EVENT_ACTIVATE -- thanks @voloved --- movement/watch_faces/complication/simple_calculator_face.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/movement/watch_faces/complication/simple_calculator_face.c b/movement/watch_faces/complication/simple_calculator_face.c index 6bfd9e8..53fdd87 100644 --- a/movement/watch_faces/complication/simple_calculator_face.c +++ b/movement/watch_faces/complication/simple_calculator_face.c @@ -273,8 +273,6 @@ bool simple_calculator_face_loop(movement_event_t event, movement_settings_t *se switch (event.event_type) { case EVENT_ACTIVATE: - break; - case EVENT_TICK: switch (state->mode) { case MODE_ENTERING_FIRST_NUM: From c2723ebc8e5db6b42eee51543389bec95b60bc61 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 8 Sep 2024 14:22:26 -0300 Subject: [PATCH 187/220] make: fix missing separator error --- movement/make/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/make/Makefile b/movement/make/Makefile index a47b073..42c3106 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -133,7 +133,7 @@ SRCS += \ ../watch_faces/complication/wordle_face.c \ ../watch_faces/complication/endless_runner_face.c \ ../watch_faces/complication/periodic_face.c \ - ../watch_faces/complication/deadline_face.c + ../watch_faces/complication/deadline_face.c \ ../watch_faces/complication/higher_lower_game_face.c \ ../watch_faces/clock/french_revolutionary_face.c \ ../watch_faces/clock/minimal_clock_face.c \ From 137906e14ac1f37c6d6399db5a30a77a814a6725 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 17:23:53 -0400 Subject: [PATCH 188/220] Added more presets to the tally face --- movement/watch_faces/complication/tally_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index 32e3c52..3893fc7 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -32,7 +32,7 @@ static bool _init_val; static bool _quick_ticks_running; -static const int16_t _tally_default[] = {0, 40, 20}; +static const int16_t _tally_default[] = {0, 100, 40, 20, 10}; static const uint8_t _tally_default_size = sizeof(_tally_default) / sizeof(int16_t); void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { From 531bcfd28593f15476f129056986bccaeea2879b Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 8 Sep 2024 18:38:53 -0300 Subject: [PATCH 189/220] faces/tally: make mtg presets optin via macros Use preprocessor macros to conditionally compile in Magic the Gathering initial value presets for the tally mark, thereby making them opt in. --- movement/watch_faces/complication/tally_face.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index 3893fc7..fb44df2 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -32,7 +32,17 @@ static bool _init_val; static bool _quick_ticks_running; -static const int16_t _tally_default[] = {0, 100, 40, 20, 10}; + +static const int16_t _tally_default[] = { + 0, + +#ifdef TALLY_FACE_PRESETS_MTG + 20, + 40, +#endif /* TALLY_FACE_PRESETS_MTG */ + +}; + static const uint8_t _tally_default_size = sizeof(_tally_default) / sizeof(int16_t); void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { From 6b7ea8bbaa44c7b553cbcc06f65dfcefdd425330 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 8 Sep 2024 18:39:55 -0300 Subject: [PATCH 190/220] faces/tally: add yugioh life point presets Add common Yu-Gi-Oh card game life point preset values for the tally face, opt in by defining a preprocessor macro. --- movement/watch_faces/complication/tally_face.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index fb44df2..4da36cb 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -41,6 +41,11 @@ static const int16_t _tally_default[] = { 40, #endif /* TALLY_FACE_PRESETS_MTG */ +#ifdef TALLY_FACE_PRESETS_YUGIOH + 4000, + 8000, +#endif /* TALLY_FACE_PRESETS_YUGIOH */ + }; static const uint8_t _tally_default_size = sizeof(_tally_default) / sizeof(int16_t); From e4e0b611f33fc99d410fb5049302aafce9ff7a6c Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 8 Sep 2024 18:49:09 -0300 Subject: [PATCH 191/220] faces/tally: avoid resetting counters Don't reset the counters unless there are multiple presets. Move back to the first face directly if there are no presets to cycle through. --- movement/watch_faces/complication/tally_face.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index 4da36cb..b2c553f 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -105,10 +105,15 @@ static void tally_face_decrement(tally_state_t *state, bool sound_on) { } } +static bool tally_face_should_move_back(tally_state_t *state) { + if (_tally_default_size <= 1) { return false; } + return state->tally_idx == _tally_default[state->tally_default_idx]; +} + bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { tally_state_t *state = (tally_state_t *)context; static bool using_led = false; - + if (using_led) { if(!watch_get_pin_level(BTN_MODE) && !watch_get_pin_level(BTN_LIGHT) && !watch_get_pin_level(BTN_ALARM)) using_led = false; @@ -118,7 +123,7 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void return true; } } - + switch (event.event_type) { case EVENT_TICK: if (_quick_ticks_running) { @@ -138,7 +143,7 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void start_quick_cyc(); break; case EVENT_MODE_LONG_PRESS: - if (state->tally_idx == _tally_default[state->tally_default_idx]) { + if (tally_face_should_move_back(state)) { _init_val = true; movement_move_to_face(0); } From 7a4424b2d4ae477b5bfb74ddc35a4708d03c9997 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 8 Sep 2024 18:53:42 -0300 Subject: [PATCH 192/220] faces/tally: convert size into compile time const Creates a macro that returns the size of the presets array, converting it into a compile time constant value that occupies no memory and that compilers can constant fold into other expressions potentially leading to dead code elimination. --- movement/watch_faces/complication/tally_face.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index b2c553f..7267b16 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -48,7 +48,7 @@ static const int16_t _tally_default[] = { }; -static const uint8_t _tally_default_size = sizeof(_tally_default) / sizeof(int16_t); +#define TALLY_FACE_PRESETS_SIZE() (sizeof(_tally_default) / sizeof(int16_t)) void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; @@ -106,7 +106,7 @@ static void tally_face_decrement(tally_state_t *state, bool sound_on) { } static bool tally_face_should_move_back(tally_state_t *state) { - if (_tally_default_size <= 1) { return false; } + if (TALLY_FACE_PRESETS_SIZE() <= 1) { return false; } return state->tally_idx == _tally_default[state->tally_default_idx]; } @@ -169,7 +169,7 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void break; case EVENT_LIGHT_LONG_PRESS: if (_init_val){ - state->tally_default_idx = (state->tally_default_idx + 1) % _tally_default_size; + state->tally_default_idx = (state->tally_default_idx + 1) % TALLY_FACE_PRESETS_SIZE(); state->tally_idx = _tally_default[state->tally_default_idx]; if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_REST, 30); From a539bd7da77d036d4f31f0d009f86bd653187ab6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 18:03:16 -0400 Subject: [PATCH 193/220] Don't try to cycle through presets if the preset count is one --- movement/watch_faces/complication/tally_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/complication/tally_face.c b/movement/watch_faces/complication/tally_face.c index 7267b16..3ea5503 100644 --- a/movement/watch_faces/complication/tally_face.c +++ b/movement/watch_faces/complication/tally_face.c @@ -168,7 +168,7 @@ bool tally_face_loop(movement_event_t event, movement_settings_t *settings, void } break; case EVENT_LIGHT_LONG_PRESS: - if (_init_val){ + if (TALLY_FACE_PRESETS_SIZE() > 1 && _init_val){ state->tally_default_idx = (state->tally_default_idx + 1) % TALLY_FACE_PRESETS_SIZE(); state->tally_idx = _tally_default[state->tally_default_idx]; if (settings->bit.button_should_sound) watch_buzzer_play_note(BUZZER_NOTE_E6, 30); From 22c25f5c239e982507a6ed1e3c527164b7b1d1c6 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 18:09:38 -0400 Subject: [PATCH 194/220] Added commented-out defines for MTG and Yugioh --- movement/watch_faces/complication/tally_face.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/movement/watch_faces/complication/tally_face.h b/movement/watch_faces/complication/tally_face.h index 286f1c0..80623f4 100644 --- a/movement/watch_faces/complication/tally_face.h +++ b/movement/watch_faces/complication/tally_face.h @@ -54,6 +54,9 @@ typedef struct { uint8_t tally_default_idx; } tally_state_t; +//#define TALLY_FACE_PRESETS_MTG +//#define TALLY_FACE_PRESETS_YUGIOH + void tally_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); void tally_face_activate(movement_settings_t *settings, void *context); From f6f427ca7df21b455a9b5f8d4dbf5a05a7590328 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 07:07:44 -0400 Subject: [PATCH 195/220] Daylight offsets now references the timezone of the destination; but it does cause an issue due to DST --- .../watch_faces/complication/sunrise_sunset_face.c | 12 +++++++++--- .../watch_faces/complication/sunrise_sunset_face.h | 7 ++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 4a3c884..29e6404 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -49,20 +49,26 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s double rise, set, minutes, seconds; bool show_next_match = false; movement_location_t movement_location; - if (state->longLatToUse == 0 || _location_count <= 1) + int16_t tz; + if (state->longLatToUse == 0 || _location_count <= 1) { movement_location = (movement_location_t) watch_get_backup_data(1); + tz = movement_timezone_offsets[settings->bit.time_zone]; + } else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; movement_location.bit.longitude = longLatPresets[state->longLatToUse].longitude; + tz = movement_timezone_offsets[longLatPresets[state->longLatToUse].timezone]; } if (movement_location.reg == 0) { + watch_clear_all_indicators(); + watch_clear_colon(); watch_display_string("RI no Loc", 0); return; } watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, state->tz * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -77,7 +83,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s // sunriset returns the rise/set times as signed decimal hours in UTC. // this can mean hours below 0 or above 31, which won't fit into a watch_date_time struct. // to deal with this, we set aside the offset in hours, and add it back before converting it to a watch_date_time. - double hours_from_utc = ((double)state->tz) / 60.0; + double hours_from_utc = ((double)tz) / 60.0; // we loop twice because if it's after sunset today, we need to recalculate to display values for tomorrow. for(int i = 0; i < 2; i++) { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index f216a2f..429253e 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -76,14 +76,15 @@ typedef struct { char name[2]; int16_t latitude; int16_t longitude; + int16_t timezone; // References element in movement_timezone_offsets } long_lat_presets_t; static const long_lat_presets_t longLatPresets[] = { { .name = " "}, // Default, the long and lat get replaced by what's set in the watch -// { .name = "Ny", .latitude = 4072, .longitude = -7401 }, // New York City, NY -// { .name = "LA", .latitude = 3405, .longitude = -11824 }, // Los Angeles, CA -// { .name = "dE", .latitude = 4221, .longitude = -8305 }, // Detroit, MI + { .name = "Ny", .latitude = 4072, .longitude = -7401, .timezone = 35 }, // New York City, NY + { .name = "LA", .latitude = 3405, .longitude = -11824, .timezone = 30 }, // Los Angeles, CA + { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = 35 }, // Detroit, MI }; #endif // SUNRISE_SUNSET_FACE_H_ From 0d16329574a18f7ba8cbf9e8705d9c5b60b8fa3f Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 09:03:43 -0400 Subject: [PATCH 196/220] Sunrise sunset face now warns about using DST --- .../complication/sunrise_sunset_face.c | 15 +++++++++------ .../complication/sunrise_sunset_face.h | 12 +++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 29e6404..a436776 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -57,7 +57,10 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; movement_location.bit.longitude = longLatPresets[state->longLatToUse].longitude; - tz = movement_timezone_offsets[longLatPresets[state->longLatToUse].timezone]; + if (longLatPresets[state->longLatToUse].timezone == SUNRISE_USE_LOCAL_TZ) + tz = movement_timezone_offsets[settings->bit.time_zone]; + else + tz = movement_timezone_offsets[longLatPresets[state->longLatToUse].timezone]; } if (movement_location.reg == 0) { @@ -405,11 +408,11 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_ALARM_LONG_PRESS: if (state->page == 0) { - if (state->longLatToUse != 0) { - state->longLatToUse = 0; - _sunrise_sunset_face_update(settings, state); - break; - } + if (state->longLatToUse != 0) { + state->longLatToUse = 0; + _sunrise_sunset_face_update(settings, state); + break; + } state->page++; state->active_digit = 0; watch_clear_display(); diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index 429253e..537fc51 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -59,6 +59,8 @@ typedef struct { uint8_t longLatToUse; } sunrise_sunset_state_t; +#define SUNRISE_USE_LOCAL_TZ 0xFF + void sunrise_sunset_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); void sunrise_sunset_face_activate(movement_settings_t *settings, void *context); bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context); @@ -76,15 +78,15 @@ typedef struct { char name[2]; int16_t latitude; int16_t longitude; - int16_t timezone; // References element in movement_timezone_offsets + uint8_t timezone; // References element in movement_timezone_offsets; Set to 0xFF to use local timezone } long_lat_presets_t; +// Locations must either use the same timezone as local time, or not observe DST. static const long_lat_presets_t longLatPresets[] = { - { .name = " "}, // Default, the long and lat get replaced by what's set in the watch - { .name = "Ny", .latitude = 4072, .longitude = -7401, .timezone = 35 }, // New York City, NY - { .name = "LA", .latitude = 3405, .longitude = -11824, .timezone = 30 }, // Los Angeles, CA - { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = 35 }, // Detroit, MI + { .name = " "}, // Default, the long, lat, and timezone get replaced by what's set in the watch +// { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = SUNRISE_USE_LOCAL_TZ }, // Detroit, MI; Assumes you live in the Eastern Timezone +// { .name = "To", .latitude = 3567, .longitude = 13965, .timezone = 15 }, // Tokyo, JP }; #endif // SUNRISE_SUNSET_FACE_H_ From 9d1410780c34e990757164976a2e3e3f64643699 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 8 Sep 2024 10:37:36 -0400 Subject: [PATCH 197/220] sunrise sunset fix during DST --- .../watch_faces/complication/sunrise_sunset_face.c | 9 ++++----- .../watch_faces/complication/sunrise_sunset_face.h | 10 ++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index a436776..4c77fe5 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -50,15 +50,16 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s bool show_next_match = false; movement_location_t movement_location; int16_t tz; + watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time if (state->longLatToUse == 0 || _location_count <= 1) { + tz = get_timezone_offset(settings->bit.time_zone, date_time); movement_location = (movement_location_t) watch_get_backup_data(1); - tz = movement_timezone_offsets[settings->bit.time_zone]; } else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; movement_location.bit.longitude = longLatPresets[state->longLatToUse].longitude; - if (longLatPresets[state->longLatToUse].timezone == SUNRISE_USE_LOCAL_TZ) - tz = movement_timezone_offsets[settings->bit.time_zone]; + if (longLatPresets[state->longLatToUse].uses_dst && dst_occurring(date_time)) + tz = movement_timezone_dst_offsets[longLatPresets[state->longLatToUse].timezone]; else tz = movement_timezone_offsets[longLatPresets[state->longLatToUse].timezone]; } @@ -70,7 +71,6 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s return; } - watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -343,7 +343,6 @@ void sunrise_sunset_face_activate(movement_settings_t *settings, void *context) movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1); state->working_latitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.latitude); state->working_longitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.longitude); - state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); } bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index 537fc51..d100dd4 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -52,7 +52,6 @@ typedef struct { uint8_t rise_index; uint8_t active_digit; bool location_changed; - int16_t tz; watch_date_time rise_set_expires; sunrise_sunset_lat_lon_settings_t working_latitude; sunrise_sunset_lat_lon_settings_t working_longitude; @@ -78,15 +77,18 @@ typedef struct { char name[2]; int16_t latitude; int16_t longitude; - uint8_t timezone; // References element in movement_timezone_offsets; Set to 0xFF to use local timezone + uint8_t timezone; // References element in movement_timezone_offsets + bool uses_dst; } long_lat_presets_t; // Locations must either use the same timezone as local time, or not observe DST. static const long_lat_presets_t longLatPresets[] = { { .name = " "}, // Default, the long, lat, and timezone get replaced by what's set in the watch -// { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = SUNRISE_USE_LOCAL_TZ }, // Detroit, MI; Assumes you live in the Eastern Timezone -// { .name = "To", .latitude = 3567, .longitude = 13965, .timezone = 15 }, // Tokyo, JP +// { .name = "Ny", .latitude = 4072, .longitude = -7401, .timezone = 33, .uses_dst = true }, // New York City, NY +// { .name = "LA", .latitude = 3405, .longitude = -11824, .timezone = 30, .uses_dst = true }, // Los Angeles, CA +// { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = 33, .uses_dst = true }, // Detroit, MI +// { .name = "To", .latitude = 3567, .longitude = 13965, .timezone = 15, .uses_dst = false }, // Tokyo, JP }; #endif // SUNRISE_SUNSET_FACE_H_ From 673f90b7242d1f0cdf5b09a3aed473b6ae4d4cf0 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Mon, 9 Sep 2024 23:59:44 -0300 Subject: [PATCH 198/220] make: add beeps face to build PR #386 did not add the C file to the makefile, causing the build to fail at the link stage due to the missing object file and the symbols that were supposed to be provided by it. Adding the file to the build fixes #472. Reported-by: CarpeNoctem Fixed-by: CarpeNoctem Reviewed-by: Matheus Afonso Martins Moreira GitHub-Issue: https://github.com/joeycastillo/Sensor-Watch/issues/472 --- movement/make/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/movement/make/Makefile b/movement/make/Makefile index 42c3106..e347d59 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -140,6 +140,7 @@ SRCS += \ ../watch_faces/complication/simon_face.c \ ../watch_faces/complication/simple_calculator_face.c \ ../watch_faces/sensor/alarm_thermometer_face.c \ + ../watch_faces/demo/beeps_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. From be969c4deb144280b59d0293cc45dc3443b096cf Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Tue, 10 Sep 2024 12:10:48 -0300 Subject: [PATCH 199/220] Revert PR #387 - fixes LE state restoration This commit caused state restored from backup registers to be overwritten. It is thereby reverted until a better solution is found or movement is refactored. This reverts commit 524098b9258232964d7756d4d44f16b006d340bf. Reviewed-by: Matheus Afonso Martins Moreira References: https://github.com/joeycastillo/Sensor-Watch/pull/474 --- movement/movement.c | 34 ---------------------------------- movement/movement.h | 2 -- movement/movement_config.h | 30 +++++++----------------------- 3 files changed, 7 insertions(+), 59 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index ba21a4e..617f79a 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -106,31 +106,6 @@ of debounce time. #define MOVEMENT_DEFAULT_LED_DURATION 1 #endif -// Default to no set location latitude -#ifndef MOVEMENT_DEFAULT_LATITUDE -#define MOVEMENT_DEFAULT_LATITUDE 0 -#endif - -// Default to no set location longitude -#ifndef MOVEMENT_DEFAULT_LONGITUDE -#define MOVEMENT_DEFAULT_LONGITUDE 0 -#endif - -// Default to no set birthdate year -#ifndef MOVEMENT_DEFAULT_BIRTHDATE_YEAR -#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 -#endif - -// Default to no set birthdate month -#ifndef MOVEMENT_DEFAULT_BIRTHDATE_MONTH -#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 -#endif - -// Default to no set birthdate day -#ifndef MOVEMENT_DEFAULT_BIRTHDATE_DAY -#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 -#endif - // Default to having DST get set #ifndef MOVEMENT_DEFAULT_DST_ACTIVE #define MOVEMENT_DEFAULT_DST_ACTIVE true @@ -541,11 +516,6 @@ void app_init(void) { movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL; movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL; movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; - movement_state.location.bit.latitude = MOVEMENT_DEFAULT_LATITUDE; - movement_state.location.bit.longitude = MOVEMENT_DEFAULT_LONGITUDE; - movement_state.birthdate.bit.year = MOVEMENT_DEFAULT_BIRTHDATE_YEAR; - movement_state.birthdate.bit.month = MOVEMENT_DEFAULT_BIRTHDATE_MONTH; - movement_state.birthdate.bit.day = MOVEMENT_DEFAULT_BIRTHDATE_DAY; movement_state.settings.bit.dst_active = MOVEMENT_DEFAULT_DST_ACTIVE; #ifdef MAKEFILE_TIMEZONE @@ -584,14 +554,10 @@ void app_init(void) { void app_wake_from_backup(void) { movement_state.settings.reg = watch_get_backup_data(0); - movement_state.location.reg = watch_get_backup_data(1); - movement_state.birthdate.reg = watch_get_backup_data(2); } void app_setup(void) { watch_store_backup_data(movement_state.settings.reg, 0); - watch_store_backup_data(movement_state.location.reg, 1); - watch_store_backup_data(movement_state.birthdate.reg, 2); static bool is_first_launch = true; diff --git a/movement/movement.h b/movement/movement.h index aeece58..6ca6537 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -245,8 +245,6 @@ typedef struct { typedef struct { // properties stored in BACKUP register movement_settings_t settings; - movement_location_t location; - movement_birthdate_t birthdate; // transient properties int16_t current_face_idx; diff --git a/movement/movement_config.h b/movement/movement_config.h index 566ea3e..46e1d2e 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -76,15 +76,15 @@ const watch_face_t watch_faces[] = { /* Set the timeout before switching to low energy mode * Valid values are: * 0: Never - * 1: 10 mins - * 2: 1 hour - * 3: 2 hours - * 4: 6 hours - * 5: 12 hours - * 6: 1 day + * 1: 1 hour + * 2: 2 hours + * 3: 6 hours + * 4: 12 hours + * 5: 1 day + * 6: 2 days * 7: 7 days */ -#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 2 +#define MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL 1 /* Set the led duration * Valid values are: @@ -95,22 +95,6 @@ const watch_face_t watch_faces[] = { */ #define MOVEMENT_DEFAULT_LED_DURATION 1 -/* The latitude and longitude used for the wearers location - * Set signed values in 1/100ths of a degree - */ -#define MOVEMENT_DEFAULT_LATITUDE 0 -#define MOVEMENT_DEFAULT_LONGITUDE 0 - -/* The wearers birthdate - * Valid values: - * Year: 1 - 4095 - * Month: 1 - 12 - * Day: 1 - 31 - */ -#define MOVEMENT_DEFAULT_BIRTHDATE_YEAR 0 -#define MOVEMENT_DEFAULT_BIRTHDATE_MONTH 0 -#define MOVEMENT_DEFAULT_BIRTHDATE_DAY 0 - /* Set if using DST * Valid values are: * false: Don't allow the watch to use DST From 1462d183764ff577723a509d95861b197bd74e28 Mon Sep 17 00:00:00 2001 From: David Volovskiy Date: Sun, 15 Sep 2024 19:13:38 -0400 Subject: [PATCH 200/220] Fixed the tz that the world clock references --- .../watch_faces/clock/world_clock2_face.c | 20 ++++++++++++------- .../watch_faces/clock/world_clock2_face.h | 1 + movement/watch_faces/clock/world_clock_face.c | 7 ++++--- movement/watch_faces/clock/world_clock_face.h | 1 + 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index cf37a40..845ec37 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -184,8 +184,8 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, /* Determine current time at time zone and store date/time */ date_time = watch_rtc_get_date_time(); - timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60); + timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz_curr * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -234,14 +234,16 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, break; case EVENT_ALARM_BUTTON_UP: state->current_zone = find_selected_zone(state, FORWARD); - state->previous_date_time = REFRESH_TIME; + state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); + state->previous_date_time = REFRESH_TIME; break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing. */ break; case EVENT_LIGHT_BUTTON_UP: state->current_zone = find_selected_zone(state, BACKWARD); - state->previous_date_time = REFRESH_TIME; + state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); + state->previous_date_time = REFRESH_TIME; break; case EVENT_LIGHT_LONG_PRESS: movement_illuminate_led(); @@ -285,7 +287,7 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, watch_clear_indicator(WATCH_INDICATOR_PM); refresh_face = false; } - result = div(state->tz, 60); + result = div(state->tz_curr, 60); hours = result.quot; minutes = result.rem; @@ -314,17 +316,21 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, break; case EVENT_ALARM_BUTTON_UP: state->current_zone = mod(state->current_zone + FORWARD, NUM_TIME_ZONES); + state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); break; case EVENT_LIGHT_BUTTON_UP: state->current_zone = mod(state->current_zone + BACKWARD, NUM_TIME_ZONES); + state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing */ break; case EVENT_ALARM_LONG_PRESS: /* Find next selected zone */ - if (!state->zones[state->current_zone].selected) - state->current_zone = find_selected_zone(state, FORWARD); + if (!state->zones[state->current_zone].selected) { + state->current_zone = find_selected_zone(state, FORWARD); + state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); + } /* Switch to display mode */ state->current_mode = WORLD_CLOCK2_MODE_DISPLAY; diff --git a/movement/watch_faces/clock/world_clock2_face.h b/movement/watch_faces/clock/world_clock2_face.h index efc107e..5d8310f 100644 --- a/movement/watch_faces/clock/world_clock2_face.h +++ b/movement/watch_faces/clock/world_clock2_face.h @@ -105,6 +105,7 @@ typedef struct { uint8_t current_zone; uint32_t previous_date_time; int16_t tz; + int16_t tz_curr; } world_clock2_state_t; void world_clock2_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index e7f393e..b47d1f7 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -69,7 +69,7 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz_curr * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -158,6 +158,7 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ case 3: state->settings.bit.timezone_index++; if (state->settings.bit.timezone_index > 40) state->settings.bit.timezone_index = 0; + state->tz_curr = get_timezone_offset(state->settings.bit.timezone_index, watch_rtc_get_date_time()); break; } break; @@ -172,8 +173,8 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ sprintf(buf, "%c%c %3d%02d ", movement_valid_position_0_chars[state->settings.bit.char_0], movement_valid_position_1_chars[state->settings.bit.char_1], - (int8_t) (state->tz / 60), - (int8_t) (state->tz % 60) * (state->tz < 0 ? -1 : 1)); + (int8_t) (state->tz_curr / 60), + (int8_t) (state->tz_curr % 60) * (state->tz_curr < 0 ? -1 : 1)); watch_set_colon(); watch_clear_indicator(WATCH_INDICATOR_PM); diff --git a/movement/watch_faces/clock/world_clock_face.h b/movement/watch_faces/clock/world_clock_face.h index e76d85e..2de3ced 100644 --- a/movement/watch_faces/clock/world_clock_face.h +++ b/movement/watch_faces/clock/world_clock_face.h @@ -63,6 +63,7 @@ typedef struct { uint8_t current_screen; uint32_t previous_date_time; int16_t tz; + int16_t tz_curr; } world_clock_state_t; void world_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); From c9cbb8216365b07b9d8d8a9aa7fd521639fc155b Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 15 Sep 2024 21:00:22 -0300 Subject: [PATCH 201/220] Revert merge of PR #437 - debouncing logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a715265af61ecab41eaefe0c5a7e7c806d0db41b, reversing changes made to 9c093f954084e61614cba1c259e4581a87420813. Insidious issues were found in the course of long term testing by the community, and further reviews of the code were not enough to pinpoint the issue and fix it. So for now the appropriate action is to revert these changes while development continues, and possibly merge them back in once they have been stabilized. Tested-on-hardware-by: David Volovskiy Tested-on-hardware-by: CarpeNoctem Tested-on-hardware-by: Krzysztof Gałka <@kshysztof@Discord> --- movement/movement.c | 76 +++++++-------------------------------------- movement/movement.h | 4 --- 2 files changed, 11 insertions(+), 69 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 617f79a..a8a22bd 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,16 +23,6 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 -#define DEBOUNCE_TICKS_DOWN 0 -#define DEBOUNCE_TICKS_UP 0 -/* -DEBOUNCE_TICKS_DOWN and DEBOUNCE_TICKS_UP are in terms of fast_cb ticks after a button is pressed. -The logic is that pressed of a button are ignored until the cb_fast_tick function runs this variable amount of times. -Without modifying the code, the cb_fast_tick frequency is 128Hz, or 7.8125ms. -It is not suggested to set this value to one for debouncing, as the callback occurs asynchronously of the button's press, -meaning that if a button was pressed and 7ms passed since th elast time cb_fast_tick was called, then there will be only 812.5us -of debounce time. -*/ #include #include @@ -243,9 +233,6 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; - movement_state.debounce_ticks_light = 0; - movement_state.debounce_ticks_alarm = 0; - movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -254,7 +241,6 @@ static inline void _movement_enable_fast_tick_if_needed(void) { static inline void _movement_disable_fast_tick_if_possible(void) { if ((movement_state.light_ticks == -1) && (movement_state.alarm_ticks == -1) && - ((movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm) == 0) && ((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) { movement_state.fast_tick_enabled = false; watch_rtc_disable_periodic_callback(128); @@ -610,7 +596,6 @@ void app_wake_from_standby(void) { static void _sleep_mode_app_loop(void) { movement_state.needs_wake = false; - movement_state.ignore_alarm_btn_after_sleep = true; // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep. while (movement_state.le_mode_ticks == -1) { // we also have to handle background tasks here in the mini-runloop @@ -785,66 +770,29 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -static movement_event_type_t btn_action(bool pin_level, int code, uint16_t *timestamp) { - _movement_reset_inactivity_countdown(); - return _figure_out_button_event(pin_level, code, timestamp); -} - -static void light_btn_action(bool pin_level) { - event.event_type = btn_action(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); -} - -static void mode_btn_action(bool pin_level) { - event.event_type = btn_action(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); -} - -static void alarm_btn_action(bool pin_level) { - uint8_t event_type = btn_action(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); - if (movement_state.ignore_alarm_btn_after_sleep){ - if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; - return; - } - event.event_type = event_type; -} - -static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { - if (*debounce_ticks == 0) { - bool pin_level = watch_get_pin_level(pin); - function(pin_level); - *debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP; - if (*debounce_ticks != 0) _movement_enable_fast_tick_if_needed(); - } - else - *down_timestamp = 0; -} - -static void disable_if_needed(uint8_t *ticks) { - if (*ticks > 0 && --*ticks == 0) - _movement_disable_fast_tick_if_possible(); -} - -static void movement_disable_if_debounce_complete(void) { - disable_if_needed(&movement_state.debounce_ticks_light); - disable_if_needed(&movement_state.debounce_ticks_alarm); - disable_if_needed(&movement_state.debounce_ticks_mode); -} - void cb_light_btn_interrupt(void) { - debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); + bool pin_level = watch_get_pin_level(BTN_LIGHT); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } void cb_mode_btn_interrupt(void) { - debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); + bool pin_level = watch_get_pin_level(BTN_MODE); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } void cb_alarm_btn_interrupt(void) { - debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); + bool pin_level = watch_get_pin_level(BTN_ALARM); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); } void cb_alarm_btn_extwake(void) { @@ -857,9 +805,7 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - movement_disable_if_debounce_complete(); - if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) - movement_state.fast_ticks++; + movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; // check timestamps and auto-fire the long-press events diff --git a/movement/movement.h b/movement/movement.h index 6ca6537..46be32f 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -273,10 +273,6 @@ typedef struct { // low energy mode countdown int32_t le_mode_ticks; - uint8_t debounce_ticks_light; - uint8_t debounce_ticks_alarm; - uint8_t debounce_ticks_mode; - bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) int16_t timeout_ticks; From 035fff52da00ec7801b29925dce57965ee22625a Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Sun, 15 Sep 2024 21:00:22 -0300 Subject: [PATCH 202/220] Revert merge of PR #437 - debouncing logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a715265af61ecab41eaefe0c5a7e7c806d0db41b, reversing changes made to 9c093f954084e61614cba1c259e4581a87420813. Insidious issues were found in the course of long term testing by the community, and further reviews of the code were not enough to pinpoint the issue and fix it. So for now the appropriate action is to revert these changes while development continues, and possibly merge them back in once they have been stabilized. Tested-on-hardware-by: David Volovskiy Tested-on-hardware-by: CarpeNoctem Tested-on-hardware-by: Krzysztof Gałka <@kshysztof@Discord> --- movement/movement.c | 76 +++++++-------------------------------------- movement/movement.h | 4 --- 2 files changed, 11 insertions(+), 69 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 0f7be19..bed1199 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -23,16 +23,6 @@ */ #define MOVEMENT_LONG_PRESS_TICKS 64 -#define DEBOUNCE_TICKS_DOWN 0 -#define DEBOUNCE_TICKS_UP 0 -/* -DEBOUNCE_TICKS_DOWN and DEBOUNCE_TICKS_UP are in terms of fast_cb ticks after a button is pressed. -The logic is that pressed of a button are ignored until the cb_fast_tick function runs this variable amount of times. -Without modifying the code, the cb_fast_tick frequency is 128Hz, or 7.8125ms. -It is not suggested to set this value to one for debouncing, as the callback occurs asynchronously of the button's press, -meaning that if a button was pressed and 7ms passed since th elast time cb_fast_tick was called, then there will be only 812.5us -of debounce time. -*/ #include #include @@ -204,9 +194,6 @@ static inline void _movement_reset_inactivity_countdown(void) { static inline void _movement_enable_fast_tick_if_needed(void) { if (!movement_state.fast_tick_enabled) { movement_state.fast_ticks = 0; - movement_state.debounce_ticks_light = 0; - movement_state.debounce_ticks_alarm = 0; - movement_state.debounce_ticks_mode = 0; watch_rtc_register_periodic_callback(cb_fast_tick, 128); movement_state.fast_tick_enabled = true; } @@ -215,7 +202,6 @@ static inline void _movement_enable_fast_tick_if_needed(void) { static inline void _movement_disable_fast_tick_if_possible(void) { if ((movement_state.light_ticks == -1) && (movement_state.alarm_ticks == -1) && - ((movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm) == 0) && ((movement_state.light_down_timestamp + movement_state.mode_down_timestamp + movement_state.alarm_down_timestamp) == 0)) { movement_state.fast_tick_enabled = false; watch_rtc_disable_periodic_callback(128); @@ -517,7 +503,6 @@ void app_wake_from_standby(void) { static void _sleep_mode_app_loop(void) { movement_state.needs_wake = false; - movement_state.ignore_alarm_btn_after_sleep = true; // as long as le_mode_ticks is -1 (i.e. we are in low energy mode), we wake up here, update the screen, and go right back to sleep. while (movement_state.le_mode_ticks == -1) { // we also have to handle background tasks here in the mini-runloop @@ -683,66 +668,29 @@ static movement_event_type_t _figure_out_button_event(bool pin_level, movement_e // now that that's out of the way, handle falling edge uint16_t diff = movement_state.fast_ticks - *down_timestamp; *down_timestamp = 0; + _movement_disable_fast_tick_if_possible(); // any press over a half second is considered a long press. Fire the long-up event if (diff > MOVEMENT_LONG_PRESS_TICKS) return button_down_event_type + 3; else return button_down_event_type + 1; } } -static movement_event_type_t btn_action(bool pin_level, int code, uint16_t *timestamp) { - _movement_reset_inactivity_countdown(); - return _figure_out_button_event(pin_level, code, timestamp); -} - -static void light_btn_action(bool pin_level) { - event.event_type = btn_action(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); -} - -static void mode_btn_action(bool pin_level) { - event.event_type = btn_action(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); -} - -static void alarm_btn_action(bool pin_level) { - uint8_t event_type = btn_action(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); - if (movement_state.ignore_alarm_btn_after_sleep){ - if (event_type == EVENT_ALARM_BUTTON_UP || event_type == EVENT_ALARM_LONG_UP) movement_state.ignore_alarm_btn_after_sleep = false; - return; - } - event.event_type = event_type; -} - -static void debounce_btn_press(uint8_t pin, uint8_t *debounce_ticks, uint16_t *down_timestamp, void (*function)(bool)) { - if (*debounce_ticks == 0) { - bool pin_level = watch_get_pin_level(pin); - function(pin_level); - *debounce_ticks = pin_level ? DEBOUNCE_TICKS_DOWN : DEBOUNCE_TICKS_UP; - if (*debounce_ticks != 0) _movement_enable_fast_tick_if_needed(); - } - else - *down_timestamp = 0; -} - -static void disable_if_needed(uint8_t *ticks) { - if (*ticks > 0 && --*ticks == 0) - _movement_disable_fast_tick_if_possible(); -} - -static void movement_disable_if_debounce_complete(void) { - disable_if_needed(&movement_state.debounce_ticks_light); - disable_if_needed(&movement_state.debounce_ticks_alarm); - disable_if_needed(&movement_state.debounce_ticks_mode); -} - void cb_light_btn_interrupt(void) { - debounce_btn_press(BTN_LIGHT, &movement_state.debounce_ticks_light, &movement_state.light_down_timestamp, light_btn_action); + bool pin_level = watch_get_pin_level(BTN_LIGHT); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_LIGHT_BUTTON_DOWN, &movement_state.light_down_timestamp); } void cb_mode_btn_interrupt(void) { - debounce_btn_press(BTN_MODE, &movement_state.debounce_ticks_mode, &movement_state.mode_down_timestamp, mode_btn_action); + bool pin_level = watch_get_pin_level(BTN_MODE); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_MODE_BUTTON_DOWN, &movement_state.mode_down_timestamp); } void cb_alarm_btn_interrupt(void) { - debounce_btn_press(BTN_ALARM, &movement_state.debounce_ticks_alarm, &movement_state.alarm_down_timestamp, alarm_btn_action); + bool pin_level = watch_get_pin_level(BTN_ALARM); + _movement_reset_inactivity_countdown(); + event.event_type = _figure_out_button_event(pin_level, EVENT_ALARM_BUTTON_DOWN, &movement_state.alarm_down_timestamp); } void cb_alarm_btn_extwake(void) { @@ -755,9 +703,7 @@ void cb_alarm_fired(void) { } void cb_fast_tick(void) { - movement_disable_if_debounce_complete(); - if (movement_state.debounce_ticks_light + movement_state.debounce_ticks_mode + movement_state.debounce_ticks_alarm == 0) - movement_state.fast_ticks++; + movement_state.fast_ticks++; if (movement_state.light_ticks > 0) movement_state.light_ticks--; if (movement_state.alarm_ticks > 0) movement_state.alarm_ticks--; // check timestamps and auto-fire the long-press events diff --git a/movement/movement.h b/movement/movement.h index 57067e9..3560009 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -272,10 +272,6 @@ typedef struct { // low energy mode countdown int32_t le_mode_ticks; - uint8_t debounce_ticks_light; - uint8_t debounce_ticks_alarm; - uint8_t debounce_ticks_mode; - bool ignore_alarm_btn_after_sleep; // app resignation countdown (TODO: consolidate with LE countdown?) int16_t timeout_ticks; From 5926e81d25d0429ad9ea62d83618807565f56e14 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Mon, 16 Sep 2024 14:42:43 -0300 Subject: [PATCH 203/220] faces/alarm: fix 24h indication in 024h mode The watch was not indicating to the user that it was in 24h mode when set to the leading zero 024h time format. This could lead to ambiguity and confusion, so make sure to indicate 24h mode. Closes #476. Reported-by: CarpeNoctem GitHub-Issue: https://github.com/joeycastillo/Sensor-Watch/issues/476 --- movement/watch_faces/complication/alarm_face.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index c733c20..fcf9c11 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -82,11 +82,16 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state watch_clear_indicator(WATCH_INDICATOR_PM); } if (h == 0) h = 12; - } else if (!settings->bit.clock_24h_leading_zero) { + } else { watch_set_indicator(WATCH_INDICATOR_24H); - } else if (h < 10) { - set_leading_zero = true; + + if (settings->bit.clock_24h_leading_zero) { + if (h < 10) { + set_leading_zero = true; + } + } } + sprintf(buf, "%c%c%2d%2d%02d ", _dow_strings[i][0], _dow_strings[i][1], (state->alarm_idx + 1), From 831aadddeabfec167c2ebfdec07e25c08757f61f Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Mon, 16 Sep 2024 14:46:06 -0300 Subject: [PATCH 204/220] faces/alarm: use snprintf formats for 24h and 024h This ensures that the display is always in a consistent state. Reported-by: CarpeNoctem GitHub-Issue: https://github.com/joeycastillo/Sensor-Watch/issues/476 --- movement/watch_faces/complication/alarm_face.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/movement/watch_faces/complication/alarm_face.c b/movement/watch_faces/complication/alarm_face.c index fcf9c11..d71ea20 100644 --- a/movement/watch_faces/complication/alarm_face.c +++ b/movement/watch_faces/complication/alarm_face.c @@ -92,7 +92,7 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state } } - sprintf(buf, "%c%c%2d%2d%02d ", + sprintf(buf, set_leading_zero? "%c%c%2d%02d%02d " : "%c%c%2d%2d%02d ", _dow_strings[i][0], _dow_strings[i][1], (state->alarm_idx + 1), h, @@ -102,9 +102,7 @@ static void _alarm_face_draw(movement_settings_t *settings, alarm_state_t *state buf[_blink_idx[state->setting_state]] = buf[_blink_idx2[state->setting_state]] = ' '; } watch_display_string(buf, 0); - if (set_leading_zero) - watch_display_string("0", 4); - + if (state->is_setting) { // draw pitch level indicator if ((subsecond % 2) == 0 || (state->setting_state != alarm_setting_idx_pitch)) { From f2479bee4f0634fc7325f73858350b5f2778d1fe Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Mon, 16 Sep 2024 14:50:42 -0300 Subject: [PATCH 205/220] faces/simple_clock: clear segments if not in 024h There was an issue where the simple clock's display would remain in 024h mode even after switching back to 12h/24h mode because it only took into account the leading zero bit, whose value is meaningless unless the 24h mode bit is also set. The issue is fixed by taking both bits into account. Closes #476. Reported-by: CarpeNoctem GitHub-Issue: https://github.com/joeycastillo/Sensor-Watch/issues/476 --- movement/watch_faces/clock/simple_clock_face.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/clock/simple_clock_face.c b/movement/watch_faces/clock/simple_clock_face.c index fe46077..68e1ccd 100644 --- a/movement/watch_faces/clock/simple_clock_face.c +++ b/movement/watch_faces/clock/simple_clock_face.c @@ -124,7 +124,7 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting } #endif - if (settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { + if (settings->bit.clock_mode_24h && settings->bit.clock_24h_leading_zero && date_time.unit.hour < 10) { set_leading_zero = true; } @@ -137,8 +137,10 @@ bool simple_clock_face_loop(movement_event_t event, movement_settings_t *setting } } watch_display_string(buf, pos); + if (set_leading_zero) watch_display_string("0", 4); + // handle alarm indicator if (state->alarm_enabled != settings->bit.alarm_enabled) _update_alarm_indicator(settings->bit.alarm_enabled, state); break; From ea5efb4d828bd5ef588baa5969400ba59d9e548c Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Mon, 16 Sep 2024 15:34:19 -0300 Subject: [PATCH 206/220] faces/clock: clear segments if not in 024h mode There was an issue where the clock's display would remain in 024h mode even after switching back to 12h/24h mode because it only took into account the leading zero bit, whose value is meaningless unless the 24h mode bit is also set. The issue is fixed by taking both bits into account. Closes #476. Reported-by: CarpeNoctem GitHub-Issue: https://github.com/joeycastillo/Sensor-Watch/issues/476 --- movement/watch_faces/clock/clock_face.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/movement/watch_faces/clock/clock_face.c b/movement/watch_faces/clock/clock_face.c index 21d790f..d517897 100644 --- a/movement/watch_faces/clock/clock_face.c +++ b/movement/watch_faces/clock/clock_face.c @@ -60,6 +60,10 @@ static bool clock_is_in_24h_mode(movement_settings_t *settings) { #endif } +static bool clock_should_set_leading_zero(movement_settings_t *settings) { + return clock_is_in_24h_mode(settings) && settings->bit.clock_24h_leading_zero; +} + static void clock_indicate(WatchIndicatorSegment indicator, bool on) { if (on) { watch_set_indicator(indicator); @@ -180,7 +184,7 @@ static void clock_display_clock(movement_settings_t *settings, clock_state_t *cl clock_indicate_pm(settings, current); current = clock_24h_to_12h(current); } - clock_display_all(current, settings->bit.clock_24h_leading_zero); + clock_display_all(current, clock_should_set_leading_zero(settings)); } } From 683032219edc7ac725723136a22e07775205cd66 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Tue, 17 Sep 2024 17:02:16 -0300 Subject: [PATCH 207/220] Revert PR #471 - add DST to sunrise/sunset The DST code has not yet been fully tested, the upcoming movement refactor is upon us and it will integrate with the micro timezone library anyway. Revert it so that next can be merged into main. This reverts commit 0cc28b9811e76de3dc2cc2919d5309cfa48edfeb, reversing changes made to 337864eb543c9b5cd83667ab0eafb476f63af54c. --- .../complication/sunrise_sunset_face.c | 28 +++++++------------ .../complication/sunrise_sunset_face.h | 15 ++++------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 4c77fe5..4a3c884 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -49,29 +49,20 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s double rise, set, minutes, seconds; bool show_next_match = false; movement_location_t movement_location; - int16_t tz; - watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - if (state->longLatToUse == 0 || _location_count <= 1) { - tz = get_timezone_offset(settings->bit.time_zone, date_time); + if (state->longLatToUse == 0 || _location_count <= 1) movement_location = (movement_location_t) watch_get_backup_data(1); - } else{ movement_location.bit.latitude = longLatPresets[state->longLatToUse].latitude; movement_location.bit.longitude = longLatPresets[state->longLatToUse].longitude; - if (longLatPresets[state->longLatToUse].uses_dst && dst_occurring(date_time)) - tz = movement_timezone_dst_offsets[longLatPresets[state->longLatToUse].timezone]; - else - tz = movement_timezone_offsets[longLatPresets[state->longLatToUse].timezone]; } if (movement_location.reg == 0) { - watch_clear_all_indicators(); - watch_clear_colon(); watch_display_string("RI no Loc", 0); return; } - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC + watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, state->tz * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -86,7 +77,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s // sunriset returns the rise/set times as signed decimal hours in UTC. // this can mean hours below 0 or above 31, which won't fit into a watch_date_time struct. // to deal with this, we set aside the offset in hours, and add it back before converting it to a watch_date_time. - double hours_from_utc = ((double)tz) / 60.0; + double hours_from_utc = ((double)state->tz) / 60.0; // we loop twice because if it's after sunset today, we need to recalculate to display values for tomorrow. for(int i = 0; i < 2; i++) { @@ -343,6 +334,7 @@ void sunrise_sunset_face_activate(movement_settings_t *settings, void *context) movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1); state->working_latitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.latitude); state->working_longitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.longitude); + state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); } bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { @@ -407,11 +399,11 @@ bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *setti break; case EVENT_ALARM_LONG_PRESS: if (state->page == 0) { - if (state->longLatToUse != 0) { - state->longLatToUse = 0; - _sunrise_sunset_face_update(settings, state); - break; - } + if (state->longLatToUse != 0) { + state->longLatToUse = 0; + _sunrise_sunset_face_update(settings, state); + break; + } state->page++; state->active_digit = 0; watch_clear_display(); diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index d100dd4..f216a2f 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -52,14 +52,13 @@ typedef struct { uint8_t rise_index; uint8_t active_digit; bool location_changed; + int16_t tz; watch_date_time rise_set_expires; sunrise_sunset_lat_lon_settings_t working_latitude; sunrise_sunset_lat_lon_settings_t working_longitude; uint8_t longLatToUse; } sunrise_sunset_state_t; -#define SUNRISE_USE_LOCAL_TZ 0xFF - void sunrise_sunset_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); void sunrise_sunset_face_activate(movement_settings_t *settings, void *context); bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context); @@ -77,18 +76,14 @@ typedef struct { char name[2]; int16_t latitude; int16_t longitude; - uint8_t timezone; // References element in movement_timezone_offsets - bool uses_dst; } long_lat_presets_t; -// Locations must either use the same timezone as local time, or not observe DST. static const long_lat_presets_t longLatPresets[] = { - { .name = " "}, // Default, the long, lat, and timezone get replaced by what's set in the watch -// { .name = "Ny", .latitude = 4072, .longitude = -7401, .timezone = 33, .uses_dst = true }, // New York City, NY -// { .name = "LA", .latitude = 3405, .longitude = -11824, .timezone = 30, .uses_dst = true }, // Los Angeles, CA -// { .name = "dE", .latitude = 4221, .longitude = -8305, .timezone = 33, .uses_dst = true }, // Detroit, MI -// { .name = "To", .latitude = 3567, .longitude = 13965, .timezone = 15, .uses_dst = false }, // Tokyo, JP + { .name = " "}, // Default, the long and lat get replaced by what's set in the watch +// { .name = "Ny", .latitude = 4072, .longitude = -7401 }, // New York City, NY +// { .name = "LA", .latitude = 3405, .longitude = -11824 }, // Los Angeles, CA +// { .name = "dE", .latitude = 4221, .longitude = -8305 }, // Detroit, MI }; #endif // SUNRISE_SUNSET_FACE_H_ From a9d503b807c59985cd87627d4cefa14cc2c81694 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Tue, 17 Sep 2024 17:08:32 -0300 Subject: [PATCH 208/220] Revert PR #470 - implement automatic DST toggling The DST code has not yet been fully tested, the upcoming movement refactor is upon us and it will integrate with the micro timezone library anyway. Revert it so that next can be merged into main. This reverts commit ac5bf8cfce67cdb5662aeea618c2eb9511f0d190, reversing changes made to 5a8a49a8c77d6d5ba0f46f0e5b51dec2daba46db. --- movement/movement.c | 186 +++++++++--------- movement/movement.h | 4 +- movement/movement_config.h | 7 - movement/watch_faces/clock/beats_face.c | 4 +- .../clock/day_night_percentage_face.c | 5 +- movement/watch_faces/clock/mars_time_face.c | 2 +- .../watch_faces/clock/world_clock2_face.c | 7 +- .../watch_faces/clock/world_clock2_face.h | 2 - movement/watch_faces/clock/world_clock_face.c | 11 +- movement/watch_faces/clock/world_clock_face.h | 2 - .../watch_faces/complication/astronomy_face.c | 2 +- .../watch_faces/complication/countdown_face.c | 14 +- .../complication/moon_phase_face.c | 6 +- .../watch_faces/complication/orrery_face.c | 2 +- .../complication/planetary_hours_face.c | 7 +- .../complication/planetary_time_face.c | 20 +- .../watch_faces/complication/sailing_face.c | 15 +- .../watch_faces/complication/solstice_face.c | 3 +- .../complication/sunrise_sunset_face.c | 5 +- .../complication/sunrise_sunset_face.h | 1 - .../watch_faces/complication/timer_face.c | 11 +- .../watch_faces/complication/tomato_face.c | 11 +- movement/watch_faces/complication/totp_face.c | 5 +- .../watch_faces/complication/totp_face_lfs.c | 3 +- .../accelerometer_data_acquisition_face.c | 2 +- movement/watch_faces/settings/set_time_face.c | 19 +- .../settings/set_time_hackwatch_face.c | 15 +- watch-library/hardware/watch/watch_rtc.c | 14 -- watch-library/shared/watch/watch_rtc.h | 6 - watch-library/shared/watch/watch_utility.c | 38 ---- watch-library/shared/watch/watch_utility.h | 18 -- watch-library/simulator/watch/watch_rtc.c | 14 -- 32 files changed, 165 insertions(+), 296 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index a8a22bd..2ea43fa 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -34,7 +34,6 @@ #include "filesystem.h" #include "movement.h" #include "shell.h" -#include "watch_utility.h" #ifndef MOVEMENT_FIRMWARE #include "movement_config.h" @@ -96,11 +95,6 @@ #define MOVEMENT_DEFAULT_LED_DURATION 1 #endif -// Default to having DST get set -#ifndef MOVEMENT_DEFAULT_DST_ACTIVE -#define MOVEMENT_DEFAULT_DST_ACTIVE true -#endif - #if __EMSCRIPTEN__ #include #endif @@ -112,8 +106,7 @@ const int32_t movement_le_inactivity_deadlines[8] = {INT_MAX, 600, 3600, 7200, 2 const int16_t movement_timeout_inactivity_deadlines[4] = {60, 120, 300, 1800}; movement_event_t event; -#define NUM_TIME_ZONES 41 -const int16_t movement_timezone_offsets[NUM_TIME_ZONES] = { +const int16_t movement_timezone_offsets[] = { 0, // 0 : 0:00:00 (UTC) 60, // 1 : 1:00:00 (Central European Time) 120, // 2 : 2:00:00 (South African Standard Time) @@ -170,50 +163,94 @@ const int16_t movement_timezone_offsets[NUM_TIME_ZONES] = { * having to separately change the hour and timezone info * in the time set face. */ -const int16_t movement_timezone_dst_offsets[NUM_TIME_ZONES] = { - 60, // 0 UTC + 1 = CET - 120, // 1 CET + 1 = SAST - 189, // 2 SAST + 1 = AST - 240, // 3 AST + 1 = GST - 270, // 4 IST + 1 = AT - 300, // 5 GST + 1 = PST - 330, // 6 AT + 1 = IST - 360, // 7 PST + 1 = KT - 390, // 8 IST + 1 = MT - 345, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway - 420, // 10 KT + 1 = TST - 390, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway - 480, // 12 TST + 1 = CST - 540, // 13 CST + 1 = JST - 525, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway - 600, // 15 JST + 1 = AEST - 630, // 16 ACST + 1 = LHST - 660, // 17 AEST + 1 = SIT - 630, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway - 720, // 19 SIT + 1 = NZST - 780, // 20 NZST + 1 = TT - 825, // 21 CST + 1 = CDT - 840, // 22 TT + 1 = LIT - 825, // 23 CDT is already a daylight timezone - 840, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway - -660, // 25 BIT + 1 = NT - -600, // 26 NT + 1 = HAST - -540, // 27 HAST + 1 = AST - -570, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway - -480, // 29 AST + 1 = PST - -420, // 30 PST + 1 = MST - -360, // 31 MST + 1 = CST - -300, // 32 CST + 1 = EST - -240, // 33 EST + 1 = AST - -210, // 34 VST + 1 = NST - -180, // 35 AST + 1 = BT - -150, // 36 NST + 1 = NDT - -120, // 37 BT + 1 = 39 - -150, // 38 NDT is already a daylight timezone - -60, // 39 FNT + 1 = AST +const uint8_t movement_dst_jump_table[] = { + 1, // 0 UTC + 1 = CET + 2, // 1 CET + 1 = SAST + 3, // 2 SAST + 1 = AST + 5, // 3 AST + 1 = GST + 6, // 4 IST + 1 = AT + 7, // 5 GST + 1 = PST + 8, // 6 AT + 1 = IST + 10, // 7 PST + 1 = KT + 11, // 8 IST + 1 = MT + 9, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway + 12, // 10 KT + 1 = TST + 11, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway + 13, // 12 TST + 1 = CST + 15, // 13 CST + 1 = JST + 14, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway + 17, // 15 JST + 1 = AEST + 18, // 16 ACST + 1 = LHST + 19, // 17 AEST + 1 = SIT + 18, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway + 20, // 19 SIT + 1 = NZST + 22, // 20 NZST + 1 = TT + 23, // 21 CST + 1 = CDT + 24, // 22 TT + 1 = LIT + 23, // 23 CDT is already a daylight timezone + 24, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway + 26, // 25 BIT + 1 = NT + 27, // 26 NT + 1 = HAST + 29, // 27 HAST + 1 = AST + 28, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway + 30, // 29 AST + 1 = PST + 31, // 30 PST + 1 = MST + 32, // 31 MST + 1 = CST + 33, // 32 CST + 1 = EST + 35, // 33 EST + 1 = AST + 36, // 34 VST + 1 = NST + 37, // 35 AST + 1 = BT + 38, // 36 NST + 1 = NDT + 39, // 37 BT + 1 = 39 + 38, // 38 NDT is already a daylight timezone + 40, // 39 FNT + 1 = AST 0 // 40 AST + 1 = UTC }; +const uint8_t movement_dst_inverse_jump_table[] = { + 40, // 0 + 0, // 1 + 1, // 2 + 2, // 3 + 4, // 4 + 3, // 5 + 4, // 6 + 5, // 7 + 6, // 8 + 9, // 9 + 7, // 10 + 8, // 11 + 10, // 12 + 12, // 13 + 14, // 14 + 13, // 15 + 16, // 16 + 15, // 17 + 16, // 18 + 17, // 19 + 19, // 20 + 21, // 21 + 20, // 22 + 21, // 23 + 24, // 24 + 25, // 25 + 25, // 26 + 26, // 27 + 28, // 28 + 27, // 29 + 29, // 30 + 30, // 31 + 31, // 32 + 32, // 33 + 34, // 34 + 33, // 35 + 34, // 36 + 35, // 37 + 36, // 38 + 37, // 39 + 39 // 40 +}; + const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789"; const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378"; @@ -247,31 +284,6 @@ static inline void _movement_disable_fast_tick_if_possible(void) { } } -static bool _check_and_act_on_daylight_savings(void) { - if (!movement_state.settings.bit.dst_active) return false; - watch_date_time date_time = watch_rtc_get_date_time(); - // No need for all of the unix time calculations for times not at the beginning or end of the hour - if (date_time.unit.minute > 1 && date_time.unit.minute < 59) return false; - uint8_t dst_result = get_dst_status(date_time); - bool dst_skip_rolling_back = get_dst_skip_rolling_back(); - - if (dst_skip_rolling_back && (dst_result == DST_ENDED)) { - clear_dst_skip_rolling_back(); - } - else if (dst_result == DST_ENDING && !dst_skip_rolling_back) { - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - watch_rtc_set_date_time(date_time); - set_dst_skip_rolling_back(); - return true; - } - else if (dst_result == DST_STARTING) { - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - watch_rtc_set_date_time(date_time); - return true; - } - return false; -} - static void _movement_handle_background_tasks(void) { for(uint8_t i = 0; i < MOVEMENT_NUM_FACES; i++) { // For each face, if the watch face wants a background task... @@ -281,7 +293,6 @@ static void _movement_handle_background_tasks(void) { watch_faces[i].loop(background_event, &movement_state.settings, watch_face_contexts[i]); } } - _check_and_act_on_daylight_savings(); movement_state.needs_background_tasks_handled = false; } @@ -479,12 +490,6 @@ uint8_t movement_claim_backup_register(void) { return movement_state.next_available_backup_register++; } -int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time) { - if (movement_state.settings.bit.dst_active && dst_occurring(date_time)) - return movement_timezone_dst_offsets[timezone_idx]; - return movement_timezone_offsets[timezone_idx]; -} - void app_init(void) { #if defined(NO_FREQCORR) watch_rtc_freqcorr_write(0, 0); @@ -502,19 +507,6 @@ void app_init(void) { movement_state.settings.bit.to_interval = MOVEMENT_DEFAULT_TIMEOUT_INTERVAL; movement_state.settings.bit.le_interval = MOVEMENT_DEFAULT_LOW_ENERGY_INTERVAL; movement_state.settings.bit.led_duration = MOVEMENT_DEFAULT_LED_DURATION; - movement_state.settings.bit.dst_active = MOVEMENT_DEFAULT_DST_ACTIVE; - -#ifdef MAKEFILE_TIMEZONE - timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets; - for (int i = 0; i < NUM_TIME_ZONES; i++) { - if (timezone_offsets[i] == MAKEFILE_TIMEZONE) { - movement_state.settings.bit.time_zone = i; - break; - } - } -#else - movement_state.settings.bit.time_zone = 35; // Atlantic Time as default -#endif movement_state.light_ticks = -1; movement_state.alarm_ticks = -1; @@ -524,13 +516,11 @@ void app_init(void) { filesystem_init(); #if __EMSCRIPTEN__ - const int16_t* timezone_offsets; int32_t time_zone_offset = EM_ASM_INT({ return -new Date().getTimezoneOffset(); }); - timezone_offsets = dst_occurring(watch_rtc_get_date_time()) ? movement_timezone_dst_offsets : movement_timezone_offsets; - for (int i = 0; i < NUM_TIME_ZONES; i++) { - if (timezone_offsets[i] == time_zone_offset) { + for (int i = 0, count = sizeof(movement_timezone_offsets) / sizeof(movement_timezone_offsets[0]); i < count; i++) { + if (movement_timezone_offsets[i] == time_zone_offset) { movement_state.settings.bit.time_zone = i; break; } diff --git a/movement/movement.h b/movement/movement.h index 46be32f..ecf5d8b 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -130,7 +130,8 @@ typedef struct { } movement_event_t; extern const int16_t movement_timezone_offsets[]; -extern const int16_t movement_timezone_dst_offsets[]; +extern const uint8_t movement_dst_jump_table[]; +extern const uint8_t movement_dst_inverse_jump_table[]; extern const char movement_valid_position_0_chars[]; extern const char movement_valid_position_1_chars[]; @@ -314,6 +315,5 @@ void movement_play_alarm(void); void movement_play_alarm_beeps(uint8_t rounds, BuzzerNote alarm_note); uint8_t movement_claim_backup_register(void); -int16_t get_timezone_offset(uint8_t timezone_idx, watch_date_time date_time); #endif // MOVEMENT_H_ diff --git a/movement/movement_config.h b/movement/movement_config.h index 46e1d2e..10a30af 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -95,11 +95,4 @@ const watch_face_t watch_faces[] = { */ #define MOVEMENT_DEFAULT_LED_DURATION 1 -/* Set if using DST - * Valid values are: - * false: Don't allow the watch to use DST - * true: Allow the watch to use DST - */ -#define MOVEMENT_DEFAULT_DST_ACTIVE true - #endif // MOVEMENT_CONFIG_H_ diff --git a/movement/watch_faces/clock/beats_face.c b/movement/watch_faces/clock/beats_face.c index 6954396..85bcbe0 100644 --- a/movement/watch_faces/clock/beats_face.c +++ b/movement/watch_faces/clock/beats_face.c @@ -61,7 +61,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void case EVENT_ACTIVATE: case EVENT_TICK: date_time = watch_rtc_get_date_time(); - centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time)); + centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]); if (centibeats == state->last_centibeat_displayed) { // we missed this update, try again next subsecond state->next_subsecond_update = (event.subsecond + 1) % BEAT_REFRESH_FREQUENCY; @@ -76,7 +76,7 @@ bool beats_face_loop(movement_event_t event, movement_settings_t *settings, void case EVENT_LOW_ENERGY_UPDATE: if (!watch_tick_animation_is_running()) watch_start_tick_animation(432); date_time = watch_rtc_get_date_time(); - centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, get_timezone_offset(settings->bit.time_zone, date_time)); + centibeats = clock2beats(date_time.unit.hour, date_time.unit.minute, date_time.unit.second, event.subsecond, movement_timezone_offsets[settings->bit.time_zone]); sprintf(buf, "bt %4lu ", centibeats / 100); watch_display_string(buf, 0); diff --git a/movement/watch_faces/clock/day_night_percentage_face.c b/movement/watch_faces/clock/day_night_percentage_face.c index 4041c05..86f07f9 100644 --- a/movement/watch_faces/clock/day_night_percentage_face.c +++ b/movement/watch_faces/clock/day_night_percentage_face.c @@ -62,8 +62,7 @@ void day_night_percentage_face_setup(movement_settings_t *settings, uint8_t watc if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(day_night_percentage_state_t)); day_night_percentage_state_t *state = (day_night_percentage_state_t *)*context_ptr; - watch_date_time date_time = watch_rtc_get_date_time(); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); + watch_date_time utc_now = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0); recalculate(utc_now, state); } } @@ -78,7 +77,7 @@ bool day_night_percentage_face_loop(movement_event_t event, movement_settings_t char buf[12]; watch_date_time date_time = watch_rtc_get_date_time(); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); switch (event.event_type) { case EVENT_ACTIVATE: diff --git a/movement/watch_faces/clock/mars_time_face.c b/movement/watch_faces/clock/mars_time_face.c index 304c615..30f6a34 100644 --- a/movement/watch_faces/clock/mars_time_face.c +++ b/movement/watch_faces/clock/mars_time_face.c @@ -70,7 +70,7 @@ static void _h_to_hms(mars_clock_hms_t *date_time, double h) { static void _update(movement_settings_t *settings, mars_time_state_t *state) { char buf[11]; watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t now = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); + uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); // TODO: I'm skipping over some steps here. // https://www.giss.nasa.gov/tools/mars24/help/algorithm.html double jdut = 2440587.5 + ((double)now / 86400.0); diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 09bda42..4d50552 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -154,7 +154,6 @@ void world_clock2_face_activate(movement_settings_t *settings, void *context) movement_request_tick_frequency(4); break; } - state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); refresh_face = true; } @@ -184,8 +183,8 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, /* Determine current time at time zone and store date/time */ date_time = watch_rtc_get_date_time(); - timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz_curr * 60); + timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->current_zone] * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -292,7 +291,7 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, watch_clear_indicator(WATCH_INDICATOR_PM); refresh_face = false; } - result = div(state->tz_curr, 60); + result = div(movement_timezone_offsets[state->current_zone], 60); hours = result.quot; minutes = result.rem; diff --git a/movement/watch_faces/clock/world_clock2_face.h b/movement/watch_faces/clock/world_clock2_face.h index 5d8310f..0baac21 100644 --- a/movement/watch_faces/clock/world_clock2_face.h +++ b/movement/watch_faces/clock/world_clock2_face.h @@ -104,8 +104,6 @@ typedef struct { world_clock2_mode_t current_mode; uint8_t current_zone; uint32_t previous_date_time; - int16_t tz; - int16_t tz_curr; } world_clock2_state_t; void world_clock2_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void **context_ptr); diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index 80f4ebf..c324b5f 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -60,16 +60,15 @@ static bool world_clock_face_do_display_mode(movement_event_t event, movement_se watch_date_time date_time; switch (event.event_type) { case EVENT_ACTIVATE: - state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); - if (settings->bit.clock_mode_24h) watch_set_indicator(WATCH_INDICATOR_24H); + if (settings->bit.clock_mode_24h && !settings->bit.clock_24h_leading_zero) watch_set_indicator(WATCH_INDICATOR_24H); watch_set_colon(); state->previous_date_time = 0xFFFFFFFF; // fall through case EVENT_TICK: case EVENT_LOW_ENERGY_UPDATE: date_time = watch_rtc_get_date_time(); - timestamp = watch_utility_date_time_to_unix_time(date_time, state->tz * 60); - date_time = watch_utility_date_time_from_unix_time(timestamp, state->tz_curr * 60); + timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + date_time = watch_utility_date_time_from_unix_time(timestamp, movement_timezone_offsets[state->settings.bit.timezone_index] * 60); previous_date_time = state->previous_date_time; state->previous_date_time = date_time.reg; @@ -178,8 +177,8 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ sprintf(buf, "%c%c %3d%02d ", movement_valid_position_0_chars[state->settings.bit.char_0], movement_valid_position_1_chars[state->settings.bit.char_1], - (int8_t) (state->tz_curr / 60), - (int8_t) (state->tz_curr % 60) * (state->tz_curr < 0 ? -1 : 1)); + (int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] / 60), + (int8_t) (movement_timezone_offsets[state->settings.bit.timezone_index] % 60) * (movement_timezone_offsets[state->settings.bit.timezone_index] < 0 ? -1 : 1)); watch_set_colon(); watch_clear_indicator(WATCH_INDICATOR_PM); diff --git a/movement/watch_faces/clock/world_clock_face.h b/movement/watch_faces/clock/world_clock_face.h index 2de3ced..92e91a6 100644 --- a/movement/watch_faces/clock/world_clock_face.h +++ b/movement/watch_faces/clock/world_clock_face.h @@ -62,8 +62,6 @@ typedef struct { uint8_t backup_register; uint8_t current_screen; uint32_t previous_date_time; - int16_t tz; - int16_t tz_curr; } world_clock_state_t; void world_clock_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); diff --git a/movement/watch_faces/complication/astronomy_face.c b/movement/watch_faces/complication/astronomy_face.c index 8db8760..204a0a3 100644 --- a/movement/watch_faces/complication/astronomy_face.c +++ b/movement/watch_faces/complication/astronomy_face.c @@ -80,7 +80,7 @@ static void _astronomy_face_recalculate(movement_settings_t *settings, astronomy #endif watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); + uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); date_time = watch_utility_date_time_from_unix_time(timestamp, 0); double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); diff --git a/movement/watch_faces/complication/countdown_face.c b/movement/watch_faces/complication/countdown_face.c index 825595e..f585ebc 100644 --- a/movement/watch_faces/complication/countdown_face.c +++ b/movement/watch_faces/complication/countdown_face.c @@ -45,8 +45,8 @@ static void abort_quick_ticks(countdown_state_t *state) { } } -static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { - return get_timezone_offset(settings->bit.time_zone, date_time) * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; } static inline void store_countdown(countdown_state_t *state) { @@ -70,15 +70,13 @@ static inline void button_beep(movement_settings_t *settings) { } static void schedule_countdown(countdown_state_t *state, movement_settings_t *settings) { - watch_date_time now = watch_rtc_get_date_time(); - int16_t tz = get_tz_offset(settings, now); - // Calculate the new state->now_ts but don't update it until we've updated the target - + // Calculate the new state->now_ts but don't update it until we've updated the target - // avoid possible race where the old target is compared to the new time and immediately triggers - uint32_t new_now = watch_utility_date_time_to_unix_time(now, tz); + uint32_t new_now = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), get_tz_offset(settings)); state->target_ts = watch_utility_offset_timestamp(new_now, state->hours, state->minutes, state->seconds); state->now_ts = new_now; - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); movement_schedule_background_task_for_face(state->watch_face_index, target_dt); } @@ -205,7 +203,7 @@ void countdown_face_activate(movement_settings_t *settings, void *context) { countdown_state_t *state = (countdown_state_t *)context; if(state->mode == cd_running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); watch_set_indicator(WATCH_INDICATOR_SIGNAL); } watch_set_colon(); diff --git a/movement/watch_faces/complication/moon_phase_face.c b/movement/watch_faces/complication/moon_phase_face.c index c571843..f74de64 100644 --- a/movement/watch_faces/complication/moon_phase_face.c +++ b/movement/watch_faces/complication/moon_phase_face.c @@ -59,9 +59,8 @@ static void _update(movement_settings_t *settings, moon_phase_state_t *state, ui (void)state; char buf[11]; watch_date_time date_time = watch_rtc_get_date_time(); - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); - uint32_t now = watch_utility_date_time_to_unix_time(date_time, tz * 60) + offset; - date_time = watch_utility_date_time_from_unix_time(now, tz * 60); + uint32_t now = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60) + offset; + date_time = watch_utility_date_time_from_unix_time(now, movement_timezone_offsets[settings->bit.time_zone] * 60); double currentfrac = fmod(now - FIRST_MOON, LUNAR_SECONDS) / LUNAR_SECONDS; double currentday = currentfrac * LUNAR_DAYS; uint8_t phase_index = 0; @@ -139,7 +138,6 @@ bool moon_phase_face_loop(movement_event_t event, movement_settings_t *settings, switch (event.event_type) { case EVENT_ACTIVATE: - _update(settings, state, state->offset); break; case EVENT_TICK: diff --git a/movement/watch_faces/complication/orrery_face.c b/movement/watch_faces/complication/orrery_face.c index 0570dd4..42fdf81 100644 --- a/movement/watch_faces/complication/orrery_face.c +++ b/movement/watch_faces/complication/orrery_face.c @@ -48,7 +48,7 @@ static const char orrery_celestial_body_names[NUM_AVAILABLE_BODIES][3] = { static void _orrery_face_recalculate(movement_settings_t *settings, orrery_state_t *state) { watch_date_time date_time = watch_rtc_get_date_time(); - uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); + uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); date_time = watch_utility_date_time_from_unix_time(timestamp, 0); double jd = astro_convert_date_to_julian_date(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second); double et = astro_convert_jd_to_julian_millenia_since_j2000(jd); diff --git a/movement/watch_faces/complication/planetary_hours_face.c b/movement/watch_faces/complication/planetary_hours_face.c index e0c0a15..e13466b 100644 --- a/movement/watch_faces/complication/planetary_hours_face.c +++ b/movement/watch_faces/complication/planetary_hours_face.c @@ -134,8 +134,7 @@ static void _planetary_solar_phases(movement_settings_t *settings, planetary_hou state->no_location = false; watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times watch_date_time midnight; scratch_time.reg = midnight.reg = utc_now.reg; @@ -148,7 +147,7 @@ static void _planetary_solar_phases(movement_settings_t *settings, planetary_hou double lon = (double)lon_centi / 100.0; // save UTC offset - state->utc_offset = ((double)tz) / 60.0; + state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; // calculate sunrise and sunset of current day in decimal hours after midnight sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset); @@ -239,7 +238,7 @@ static void _planetary_hours(movement_settings_t *settings, planetary_hours_stat // get current time watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC current_hour_epoch = watch_utility_date_time_to_unix_time(utc_now, 0); // set the current planetary hour as default screen diff --git a/movement/watch_faces/complication/planetary_time_face.c b/movement/watch_faces/complication/planetary_time_face.c index 1245cc6..7e7ac6c 100644 --- a/movement/watch_faces/complication/planetary_time_face.c +++ b/movement/watch_faces/complication/planetary_time_face.c @@ -37,7 +37,7 @@ // STATIC FUNCTIONS AND CONSTANTS ///////////////////////////////////////////// /** @brief Planetary rulers in the Chaldean order from slowest to fastest - * @details Planetary rulers in the Chaldean order from slowest to fastest: + * @details Planetary rulers in the Chaldean order from slowest to fastest: * Jupiter, Mars, Sun, Venus, Mercury, Moon */ static const char planets[7][3] = {"Sa", "Ju", "Ma", "So", "Ve", "Me", "Lu"}; // Latin @@ -129,8 +129,7 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time state->no_location = false; watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, tz * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times watch_date_time midnight; scratch_time.reg = midnight.reg = utc_now.reg; @@ -143,7 +142,7 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time double lon = (double)lon_centi / 100.0; // save UTC offset - state->utc_offset = ((double)tz) / 60.0; + state->utc_offset = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; // get UNIX epoch time now_epoch = watch_utility_date_time_to_unix_time(utc_now, 0); @@ -151,7 +150,7 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time // calculate sunrise and sunset of current day in decimal hours after midnight sun_rise_set(scratch_time.unit.year + WATCH_RTC_REFERENCE_YEAR, scratch_time.unit.month, scratch_time.unit.day, lon, lat, &sunrise, &sunset); - + // calculate sunrise and sunset UNIX timestamps sunrise_epoch = midnight_epoch + sunrise * 3600; sunset_epoch = midnight_epoch + sunset * 3600; @@ -192,7 +191,7 @@ static void _planetary_solar_phase(movement_settings_t *settings, planetary_time state->phase_end = sunrise_epoch; } - // calculate the duration of a planetary second during this solar phase + // calculate the duration of a planetary second during this solar phase // and convert to Hertz so we can call a faster tick rate state->freq = (1 / ((double)( state->phase_end - state->phase_start ) / 43200)); } @@ -208,12 +207,11 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting uint8_t weekday, planet, planetary_hour; double hour_duration, current_hour, current_minute, current_second; bool set_leading_zero = false; - watch_date_time date_time = watch_rtc_get_date_time(); watch_set_colon(); // get current time and convert to UTC - state->scratch = watch_utility_date_time_convert_zone(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60, 0); + state->scratch = watch_utility_date_time_convert_zone(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // when current phase ends calculate the next phase if ( watch_utility_date_time_to_unix_time(state->scratch, 0) >= state->phase_end ) { @@ -263,11 +261,11 @@ static void _planetary_time(movement_event_t event, movement_settings_t *setting if ( state->ruler == 0 ) strncpy(ruler, planets[planet], 3); if ( state->ruler == 1 ) strncpy(ruler, planetes[planet], 3); if ( state->ruler == 2 ) strncpy(ruler, " ", 3); - + // display planetary time with ruler of the hour or ruler of the day if ( state->day_ruler ) sprintf(buf, "%s d%2d%02d%02d", ruler, state->scratch.unit.hour, state->scratch.unit.minute, state->scratch.unit.second); else sprintf(buf, "%s h%2d%02d%02d", ruler, state->scratch.unit.hour, state->scratch.unit.minute, state->scratch.unit.second); - + watch_display_string(buf, 0); if (set_leading_zero) watch_display_string("0", 4); @@ -303,7 +301,7 @@ void planetary_time_face_activate(movement_settings_t *settings, void *context) #endif planetary_time_state_t *state = (planetary_time_state_t *)context; - + // calculate phase _planetary_solar_phase(settings, state); } diff --git a/movement/watch_faces/complication/sailing_face.c b/movement/watch_faces/complication/sailing_face.c index 16d60c9..a6c13fe 100644 --- a/movement/watch_faces/complication/sailing_face.c +++ b/movement/watch_faces/complication/sailing_face.c @@ -33,8 +33,8 @@ #define sl_SELECTIONS 6 #define DEFAULT_MINUTES { 5,4,1,0,0,0 } -static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { - return get_timezone_offset(settings->bit.time_zone, date_time) * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; } static int lap = 0; @@ -165,8 +165,7 @@ static void ring(sailing_state_t *state, movement_settings_t *settings) { return; } state->nextbeep_ts = state->target_ts - beepseconds[beepflag+1]; - watch_date_time now = watch_rtc_get_date_time(); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->nextbeep_ts, get_tz_offset(settings, now)); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->nextbeep_ts, get_tz_offset(settings)); movement_schedule_background_task_for_face(state->watch_face_index, target_dt); //background task is set, now we have time to play the tune. If this is cancelled accidentally, the next alarm will still ring. Sound is implemented non-blocking, so that neither buttons nor display output are compromised. for (int i = 0; i < 5; i++) { @@ -195,7 +194,7 @@ static void start(sailing_state_t *state, movement_settings_t *settings) {//gets } if (state->index > 5 || state->minutes[state->index] == 0) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); state->target_ts = state->now_ts; if (alarmflag != 0){ watch_buzzer_play_sequence(long_beep, NULL); @@ -206,7 +205,7 @@ static void start(sailing_state_t *state, movement_settings_t *settings) {//gets movement_request_tick_frequency(1); //synchronises tick with the moment the button was pressed. Solves 1s offset between sound and display, solves up to +-0.5s offset between button action and display. state->mode = sl_running; watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, state->minutes[state->index], 0); ring(state, settings); } @@ -254,11 +253,11 @@ void sailing_face_activate(movement_settings_t *settings, void *context) { sailing_state_t *state = (sailing_state_t *)context; if(state->mode == sl_running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); } if(state->mode == sl_counting) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); watch_set_indicator(WATCH_INDICATOR_LAP); } switch (alarmflag) { diff --git a/movement/watch_faces/complication/solstice_face.c b/movement/watch_faces/complication/solstice_face.c index 76d0d26..e74f878 100644 --- a/movement/watch_faces/complication/solstice_face.c +++ b/movement/watch_faces/complication/solstice_face.c @@ -123,10 +123,9 @@ static watch_date_time jde_to_date_time(double JDE) { } static void calculate_datetimes(solstice_state_t *state, movement_settings_t *settings) { - watch_date_time date_time = watch_rtc_get_date_time(); for (int i = 0; i < 4; i++) { // TODO: handle DST changes - state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (get_timezone_offset(settings->bit.time_zone, date_time) / (60.0*24.0))); + state->datetimes[i] = jde_to_date_time(calculate_solstice_equinox(2020 + state->year, i) + (movement_timezone_offsets[settings->bit.time_zone] / (60.0*24.0))); } } diff --git a/movement/watch_faces/complication/sunrise_sunset_face.c b/movement/watch_faces/complication/sunrise_sunset_face.c index 4a3c884..8747bd8 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.c +++ b/movement/watch_faces/complication/sunrise_sunset_face.c @@ -62,7 +62,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s } watch_date_time date_time = watch_rtc_get_date_time(); // the current local date / time - watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, state->tz * 60, 0); // the current date / time in UTC + watch_date_time utc_now = watch_utility_date_time_convert_zone(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60, 0); // the current date / time in UTC watch_date_time scratch_time; // scratchpad, contains different values at different times scratch_time.reg = utc_now.reg; @@ -77,7 +77,7 @@ static void _sunrise_sunset_face_update(movement_settings_t *settings, sunrise_s // sunriset returns the rise/set times as signed decimal hours in UTC. // this can mean hours below 0 or above 31, which won't fit into a watch_date_time struct. // to deal with this, we set aside the offset in hours, and add it back before converting it to a watch_date_time. - double hours_from_utc = ((double)state->tz) / 60.0; + double hours_from_utc = ((double)movement_timezone_offsets[settings->bit.time_zone]) / 60.0; // we loop twice because if it's after sunset today, we need to recalculate to display values for tomorrow. for(int i = 0; i < 2; i++) { @@ -334,7 +334,6 @@ void sunrise_sunset_face_activate(movement_settings_t *settings, void *context) movement_location_t movement_location = (movement_location_t) watch_get_backup_data(1); state->working_latitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.latitude); state->working_longitude = _sunrise_sunset_face_struct_from_latlon(movement_location.bit.longitude); - state->tz = get_timezone_offset(settings->bit.time_zone, watch_rtc_get_date_time()); } bool sunrise_sunset_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { diff --git a/movement/watch_faces/complication/sunrise_sunset_face.h b/movement/watch_faces/complication/sunrise_sunset_face.h index f216a2f..df5fda5 100644 --- a/movement/watch_faces/complication/sunrise_sunset_face.h +++ b/movement/watch_faces/complication/sunrise_sunset_face.h @@ -52,7 +52,6 @@ typedef struct { uint8_t rise_index; uint8_t active_digit; bool location_changed; - int16_t tz; watch_date_time rise_set_expires; sunrise_sunset_lat_lon_settings_t working_latitude; sunrise_sunset_lat_lon_settings_t working_longitude; diff --git a/movement/watch_faces/complication/timer_face.c b/movement/watch_faces/complication/timer_face.c index 5ee2967..29392d6 100644 --- a/movement/watch_faces/complication/timer_face.c +++ b/movement/watch_faces/complication/timer_face.c @@ -36,8 +36,8 @@ static const int8_t _sound_seq_start[] = {BUZZER_NOTE_C8, 2, 0}; static uint8_t _beeps_to_play; // temporary counter for ring signals playing -static inline int32_t _get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { - return get_timezone_offset(settings->bit.time_zone, date_time) * 60; +static inline int32_t _get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; } static void _signal_callback() { @@ -50,8 +50,7 @@ static void _signal_callback() { static void _start(timer_state_t *state, movement_settings_t *settings, bool with_beep) { if (state->timers[state->current_timer].value == 0) return; watch_date_time now = watch_rtc_get_date_time(); - int32_t tz = _get_tz_offset(settings, now); - state->now_ts = watch_utility_date_time_to_unix_time(now, tz); + state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); if (state->mode == pausing) state->target_ts = state->now_ts + state->paused_left; else @@ -59,7 +58,7 @@ static void _start(timer_state_t *state, movement_settings_t *settings, bool wit state->timers[state->current_timer].unit.hours, state->timers[state->current_timer].unit.minutes, state->timers[state->current_timer].unit.seconds); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, _get_tz_offset(settings)); state->mode = running; movement_schedule_background_task_for_face(state->watch_face_index, target_dt); watch_set_indicator(WATCH_INDICATOR_BELL); @@ -211,7 +210,7 @@ void timer_face_activate(movement_settings_t *settings, void *context) { watch_set_colon(); if(state->mode == running) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, _get_tz_offset(settings)); watch_set_indicator(WATCH_INDICATOR_BELL); } else { state->pausing_seconds = 1; diff --git a/movement/watch_faces/complication/tomato_face.c b/movement/watch_faces/complication/tomato_face.c index 15b67c6..3d46ba9 100644 --- a/movement/watch_faces/complication/tomato_face.c +++ b/movement/watch_faces/complication/tomato_face.c @@ -30,8 +30,8 @@ static uint8_t focus_min = 25; static uint8_t break_min = 5; -static inline int32_t get_tz_offset(movement_settings_t *settings, watch_date_time date_time) { - return get_timezone_offset(settings->bit.time_zone, date_time) * 60; +static inline int32_t get_tz_offset(movement_settings_t *settings) { + return movement_timezone_offsets[settings->bit.time_zone] * 60; } static uint8_t get_length(tomato_state_t *state) { @@ -47,13 +47,12 @@ static uint8_t get_length(tomato_state_t *state) { static void tomato_start(tomato_state_t *state, movement_settings_t *settings) { watch_date_time now = watch_rtc_get_date_time(); - int32_t tz = get_tz_offset(settings, now); int8_t length = (int8_t) get_length(state); state->mode = tomato_run; - state->now_ts = watch_utility_date_time_to_unix_time(now, tz); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); state->target_ts = watch_utility_offset_timestamp(state->now_ts, 0, length, 0); - watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, tz); + watch_date_time target_dt = watch_utility_date_time_from_unix_time(state->target_ts, get_tz_offset(settings)); movement_schedule_background_task(target_dt); watch_set_indicator(WATCH_INDICATOR_BELL); } @@ -127,7 +126,7 @@ void tomato_face_activate(movement_settings_t *settings, void *context) { tomato_state_t *state = (tomato_state_t *)context; if (state->mode == tomato_run) { watch_date_time now = watch_rtc_get_date_time(); - state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings, now)); + state->now_ts = watch_utility_date_time_to_unix_time(now, get_tz_offset(settings)); watch_set_indicator(WATCH_INDICATOR_BELL); } watch_set_colon(); diff --git a/movement/watch_faces/complication/totp_face.c b/movement/watch_faces/complication/totp_face.c index c5b26f2..fdb4109 100644 --- a/movement/watch_faces/complication/totp_face.c +++ b/movement/watch_faces/complication/totp_face.c @@ -157,9 +157,8 @@ static void totp_generate_and_display(totp_state_t *totp_state) { totp_display(totp_state); } -static uint32_t totp_compute_base_timestamp(movement_settings_t *settings) { - watch_date_time date_time = watch_rtc_get_date_time(); - return watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); +static inline uint32_t totp_compute_base_timestamp(movement_settings_t *settings) { + return watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60); } void totp_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { diff --git a/movement/watch_faces/complication/totp_face_lfs.c b/movement/watch_faces/complication/totp_face_lfs.c index 1319d47..5d7defe 100644 --- a/movement/watch_faces/complication/totp_face_lfs.c +++ b/movement/watch_faces/complication/totp_face_lfs.c @@ -254,8 +254,7 @@ void totp_face_lfs_activate(movement_settings_t *settings, void *context) { } #endif - watch_date_time date_time = watch_rtc_get_date_time(); - totp_state->timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); + totp_state->timestamp = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), movement_timezone_offsets[settings->bit.time_zone] * 60); totp_face_set_record(totp_state, 0); } diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c index 9cadf67..6d174d5 100644 --- a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c @@ -442,7 +442,7 @@ static void start_reading(accelerometer_data_acquisition_state_t *state, movemen accelerometer_data_acquisition_record_t record; watch_date_time date_time = watch_rtc_get_date_time(); - state->starting_timestamp = watch_utility_date_time_to_unix_time(date_time, get_timezone_offset(settings->bit.time_zone, date_time) * 60); + state->starting_timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); record.header.info.record_type = ACCELEROMETER_DATA_ACQUISITION_HEADER; record.header.info.range = ACCELEROMETER_RANGE; record.header.info.temperature = lis2dw_get_temperature(); diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index 0b70575..d50d24b 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -60,6 +60,13 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; case 7: // daylight savings time + if (settings->bit.dst_active) { // deactivate DST + date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; + settings->bit.time_zone = movement_dst_inverse_jump_table[settings->bit.time_zone]; + } else { // activate DST + date_time.unit.hour = (date_time.unit.hour + 1) % 24; + settings->bit.time_zone = movement_dst_jump_table[settings->bit.time_zone]; + } settings->bit.dst_active = !settings->bit.dst_active; break; } @@ -128,9 +135,8 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v return movement_default_loop_handler(event, settings); } - char buf[13]; + char buf[11]; bool set_leading_zero = false; - if (current_page < 3) { watch_set_colon(); if (settings->bit.clock_mode_24h) { @@ -150,18 +156,17 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_clear_indicator(WATCH_INDICATOR_PM); sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); } else if (current_page < 7) { // zone - char dst_char = (settings->bit.dst_active && dst_occurring(watch_rtc_get_date_time())) ? 'd' : ' '; if (event.subsecond % 2) { watch_clear_colon(); - sprintf(buf, "%s %c", set_time_face_titles[current_page], dst_char); + sprintf(buf, "%s ", set_time_face_titles[current_page]); } else { - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time); watch_set_colon(); - sprintf(buf, "%s %3d%02d %c", set_time_face_titles[current_page], (int8_t) (tz / 60), (int8_t) (tz % 60) * (tz < 0 ? -1 : 1), dst_char); + sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (movement_timezone_offsets[settings->bit.time_zone] / 60), (int8_t) (movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); } } else { // daylight savings watch_clear_colon(); - sprintf(buf, "%s dsT %c", set_time_face_titles[current_page], settings->bit.dst_active ? 'y' : 'n'); + if (settings->bit.dst_active) sprintf(buf, "%s dsT y", set_time_face_titles[current_page]); + else sprintf(buf, "%s dsT n", set_time_face_titles[current_page]); } // blink up the parameter we're setting diff --git a/movement/watch_faces/settings/set_time_hackwatch_face.c b/movement/watch_faces/settings/set_time_hackwatch_face.c index 6e67808..75bc7bb 100644 --- a/movement/watch_faces/settings/set_time_hackwatch_face.c +++ b/movement/watch_faces/settings/set_time_hackwatch_face.c @@ -132,9 +132,8 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s } break; } - if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously + if (current_page != 2) // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); - } break; case EVENT_ALARM_LONG_UP://Setting seconds on long release @@ -172,16 +171,11 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; } - if (date_time_settings.unit.day > days_in_month(date_time_settings.unit.month, date_time_settings.unit.year + WATCH_RTC_REFERENCE_YEAR)) date_time_settings.unit.day = 1; - - if (current_page != 2) { // Do not set time when we are at seconds, it was already set previously + if (current_page != 2) // Do not set time when we are at seconds, it was already set previously watch_rtc_set_date_time(date_time_settings); - } - //TODO: Do not update whole RTC, just what we are changing - break; case EVENT_TIMEOUT: movement_move_to_face(0); @@ -237,13 +231,12 @@ bool set_time_hackwatch_face_loop(movement_event_t event, movement_settings_t *s watch_clear_colon(); sprintf(buf, "%s ", set_time_hackwatch_face_titles[current_page]); } else { - int16_t tz = get_timezone_offset(settings->bit.time_zone, date_time_settings); watch_set_colon(); sprintf(buf, "%s %3d%02d ", set_time_hackwatch_face_titles[current_page], - (int8_t)(tz / 60), - (int8_t)(tz % 60) * (tz < 0 ? -1 : 1)); + (int8_t)(movement_timezone_offsets[settings->bit.time_zone] / 60), + (int8_t)(movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); } } diff --git a/watch-library/hardware/watch/watch_rtc.c b/watch-library/hardware/watch/watch_rtc.c index 67ecb53..93cb9f1 100644 --- a/watch-library/hardware/watch/watch_rtc.c +++ b/watch-library/hardware/watch/watch_rtc.c @@ -30,19 +30,6 @@ ext_irq_cb_t btn_alarm_callback; ext_irq_cb_t a2_callback; ext_irq_cb_t a4_callback; -static bool dst_skip_rolling_back; -bool get_dst_skip_rolling_back(void) { - return dst_skip_rolling_back; -} - -void set_dst_skip_rolling_back(void) { - dst_skip_rolling_back = true; -} - -void clear_dst_skip_rolling_back(void) { - dst_skip_rolling_back = false; -} - bool _watch_rtc_is_enabled(void) { return RTC->MODE2.CTRLA.bit.ENABLE; } @@ -73,7 +60,6 @@ void watch_rtc_set_date_time(watch_date_time date_time) { _sync_rtc(); // Double sync as without it at high Hz faces setting time is unrealiable (specifically, set_time_hackwatch) RTC->MODE2.CLOCK.reg = date_time.reg; _sync_rtc(); - clear_dst_skip_rolling_back(); } watch_date_time watch_rtc_get_date_time(void) { diff --git a/watch-library/shared/watch/watch_rtc.h b/watch-library/shared/watch/watch_rtc.h index a9134eb..3e63bb5 100644 --- a/watch-library/shared/watch/watch_rtc.h +++ b/watch-library/shared/watch/watch_rtc.h @@ -157,11 +157,5 @@ void watch_rtc_enable(bool en); */ void watch_rtc_freqcorr_write(int16_t value, int16_t sign); -/** @brief Returns if we're currently at a point where the we rolled back for DST and need to ignore the next DST segment - */ -bool get_dst_skip_rolling_back(void); -void set_dst_skip_rolling_back(void); -void clear_dst_skip_rolling_back(void); - /// @} #endif diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c index a7db658..c00791e 100644 --- a/watch-library/shared/watch/watch_utility.c +++ b/watch-library/shared/watch/watch_utility.c @@ -83,44 +83,6 @@ uint8_t is_leap(uint16_t y) return !(y%4) && ((y%100) || !(y%400)); } -uint8_t get_dst_status(watch_date_time date_time) { - watch_date_time dst_start_time; - watch_date_time dst_end_time; - uint32_t unix_dst_start_time; - uint32_t unix_dst_end_time; - uint32_t unix_curr_time; - - dst_start_time.unit.year = date_time.unit.year; - dst_start_time.unit.month = 3; - dst_start_time.unit.hour = 2; - dst_start_time.unit.minute = 0; - dst_start_time.unit.second = 0; - dst_start_time.unit.day = 15 - watch_utility_get_iso8601_weekday_number(dst_start_time.unit.year + WATCH_RTC_REFERENCE_YEAR, dst_start_time.unit.month, 1); - unix_dst_start_time = watch_utility_date_time_to_unix_time(dst_start_time, 0); - - dst_end_time.unit.year = date_time.unit.year; - dst_end_time.unit.month = 11; - dst_end_time.unit.hour = 2; - dst_end_time.unit.minute = 0; - dst_end_time.unit.second = 0; - dst_end_time.unit.day = 15 - watch_utility_get_iso8601_weekday_number(dst_end_time.unit.year + WATCH_RTC_REFERENCE_YEAR, dst_end_time.unit.month, 1); - unix_dst_end_time = watch_utility_date_time_to_unix_time(dst_end_time, 0); - - unix_curr_time = watch_utility_date_time_to_unix_time(date_time, 0); - unix_curr_time -= date_time.unit.second; - if (date_time.unit.second > 45) // In emu, it's been seen that we may trigger at 59sec rather than exactly 0 each time - unix_curr_time += 60; - - if (unix_curr_time == unix_dst_start_time) return DST_STARTING; - if (unix_curr_time == unix_dst_end_time) return DST_ENDING; - if (unix_curr_time > unix_dst_end_time || unix_curr_time < unix_dst_start_time) return DST_ENDED; - return DST_OCCURRING; -} - -bool dst_occurring(watch_date_time date_time) { - return get_dst_status(date_time) <= DST_OCCURRING; -} - uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t day) { uint16_t DAYS_SO_FAR[] = { 0, // Jan diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h index 12ee8d2..5533e19 100644 --- a/watch-library/shared/watch/watch_utility.h +++ b/watch-library/shared/watch/watch_utility.h @@ -45,13 +45,6 @@ typedef struct { uint32_t days; // 0-4294967295 } watch_duration_t; -typedef enum { - DST_STARTING, - DST_OCCURRING, - DST_ENDING, - DST_ENDED -} dst_t; - /** @brief Returns a two-letter weekday for the given timestamp, suitable for display * in positions 0-1 of the watch face * @param date_time The watch_date_time whose weekday you want. @@ -85,17 +78,6 @@ uint16_t watch_utility_days_since_new_year(uint16_t year, uint8_t month, uint8_t */ uint8_t is_leap(uint16_t year); -/** @brief Returns off of dst_t based off if DST is occurring, srted, ended, or none of those. - * @param date_time The watch_date_time that you wish to convert. - * @return DST_OCCURRING, DST_HAPPENING, DST_ENDING, DST_ENDED - */ -uint8_t get_dst_status(watch_date_time date_time); - -/** @brief Returns true if it's DST and false otherwise. - * @param date_time The watch_date_time that you wish to convert. - */ -bool dst_occurring(watch_date_time date_time); - /** @brief Returns the UNIX time (seconds since 1970) for a given date/time in UTC. * @param date_time The watch_date_time that you wish to convert. * @param year The year of the date you wish to convert. diff --git a/watch-library/simulator/watch/watch_rtc.c b/watch-library/simulator/watch/watch_rtc.c index 9fe9e29..2bb6074 100644 --- a/watch-library/simulator/watch/watch_rtc.c +++ b/watch-library/simulator/watch/watch_rtc.c @@ -39,19 +39,6 @@ ext_irq_cb_t btn_alarm_callback; ext_irq_cb_t a2_callback; ext_irq_cb_t a4_callback; -static bool dst_skip_rolling_back; -bool get_dst_skip_rolling_back(void) { - return dst_skip_rolling_back; -} - -void set_dst_skip_rolling_back(void) { - dst_skip_rolling_back = true; -} - -void clear_dst_skip_rolling_back(void) { - dst_skip_rolling_back = false; -} - bool _watch_rtc_is_enabled(void) { return true; } @@ -70,7 +57,6 @@ void watch_rtc_set_date_time(watch_date_time date_time) { const date = new Date(year, month - 1, day, hour, minute, second); return date - Date.now(); }, date_time.reg); - clear_dst_skip_rolling_back(); } watch_date_time watch_rtc_get_date_time(void) { From 30267dfc0cec2f06058c9c76e44ac9bf8b549ba4 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Tue, 17 Sep 2024 17:19:05 -0300 Subject: [PATCH 209/220] Revert PR #470 - fixes world_clock2 face The DST code has not yet been fully tested, the upcoming movement refactor is upon us and it will integrate with the micro timezone library anyway. Revert it so that next can be merged into main. This reverts commit 3c86a42aa83356735be2bd84b7027b156e8d3a2c, reversing changes made to be969c4deb144280b59d0293cc45dc3443b096cf. --- movement/watch_faces/clock/world_clock2_face.c | 14 ++++---------- movement/watch_faces/clock/world_clock_face.c | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/movement/watch_faces/clock/world_clock2_face.c b/movement/watch_faces/clock/world_clock2_face.c index 4d50552..5b3f8a9 100644 --- a/movement/watch_faces/clock/world_clock2_face.c +++ b/movement/watch_faces/clock/world_clock2_face.c @@ -238,16 +238,14 @@ static bool mode_display(movement_event_t event, movement_settings_t *settings, break; case EVENT_ALARM_BUTTON_UP: state->current_zone = find_selected_zone(state, FORWARD); - state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); - state->previous_date_time = REFRESH_TIME; + state->previous_date_time = REFRESH_TIME; break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing. */ break; case EVENT_LIGHT_BUTTON_UP: state->current_zone = find_selected_zone(state, BACKWARD); - state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); - state->previous_date_time = REFRESH_TIME; + state->previous_date_time = REFRESH_TIME; break; case EVENT_LIGHT_LONG_PRESS: movement_illuminate_led(); @@ -320,21 +318,17 @@ static bool mode_settings(movement_event_t event, movement_settings_t *settings, break; case EVENT_ALARM_BUTTON_UP: state->current_zone = mod(state->current_zone + FORWARD, NUM_TIME_ZONES); - state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); break; case EVENT_LIGHT_BUTTON_UP: state->current_zone = mod(state->current_zone + BACKWARD, NUM_TIME_ZONES); - state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); break; case EVENT_LIGHT_BUTTON_DOWN: /* Do nothing */ break; case EVENT_ALARM_LONG_PRESS: /* Find next selected zone */ - if (!state->zones[state->current_zone].selected) { - state->current_zone = find_selected_zone(state, FORWARD); - state->tz_curr = get_timezone_offset(state->current_zone, watch_rtc_get_date_time()); - } + if (!state->zones[state->current_zone].selected) + state->current_zone = find_selected_zone(state, FORWARD); /* Switch to display mode */ state->current_mode = WORLD_CLOCK2_MODE_DISPLAY; diff --git a/movement/watch_faces/clock/world_clock_face.c b/movement/watch_faces/clock/world_clock_face.c index c324b5f..6b731e8 100644 --- a/movement/watch_faces/clock/world_clock_face.c +++ b/movement/watch_faces/clock/world_clock_face.c @@ -162,7 +162,6 @@ static bool _world_clock_face_do_settings_mode(movement_event_t event, movement_ case 3: state->settings.bit.timezone_index++; if (state->settings.bit.timezone_index > 40) state->settings.bit.timezone_index = 0; - state->tz_curr = get_timezone_offset(state->settings.bit.timezone_index, watch_rtc_get_date_time()); break; } break; From fe9a0a693d4a9f721c589dad5d7499c408aea198 Mon Sep 17 00:00:00 2001 From: Matheus Afonso Martins Moreira Date: Tue, 17 Sep 2024 17:09:02 -0300 Subject: [PATCH 210/220] Revert PR #268 - add daylight savings time toggle The DST code has not yet been fully tested, the upcoming movement refactor is upon us and it will integrate with the micro timezone library anyway. Revert it so that next can be merged into main. This reverts commit 5a8a49a8c77d6d5ba0f46f0e5b51dec2daba46db, reversing changes made to bfadb81e82dfe47c4445ce7978d9dbc3b4706808. --- movement/movement.c | 101 ------------------ movement/movement.h | 5 +- movement/watch_faces/settings/set_time_face.c | 23 +--- 3 files changed, 4 insertions(+), 125 deletions(-) diff --git a/movement/movement.c b/movement/movement.c index 2ea43fa..fe6f9d1 100644 --- a/movement/movement.c +++ b/movement/movement.c @@ -150,107 +150,6 @@ const int16_t movement_timezone_offsets[] = { -60, // 40 : -1:00:00 (Azores Standard Time) }; -/* These are approximate equivalent DST timezones for each - * timezone in the offset table. Unlike the full tzinfo file, - * the time-offsets used above are incomplete, so there are - * cases below where an approximate DST timezone is proposed - * for a timezone where no one observes DST, and cases - * where we can't propose an equivaent DST timezone since - * there isn't an appropriate one in the offset table. - * - * However, this should be good enough for anyone living in - * a DST-observing region to manually toggle DST without - * having to separately change the hour and timezone info - * in the time set face. - */ -const uint8_t movement_dst_jump_table[] = { - 1, // 0 UTC + 1 = CET - 2, // 1 CET + 1 = SAST - 3, // 2 SAST + 1 = AST - 5, // 3 AST + 1 = GST - 6, // 4 IST + 1 = AT - 7, // 5 GST + 1 = PST - 8, // 6 AT + 1 = IST - 10, // 7 PST + 1 = KT - 11, // 8 IST + 1 = MT - 9, // 9 Nepal has no equivalent DST timezone, but they don't observe DST anyway - 12, // 10 KT + 1 = TST - 11, // 11 Myanmar has no equivalent DST timezone, but they don't observe DST anyway - 13, // 12 TST + 1 = CST - 15, // 13 CST + 1 = JST - 14, // 14 ACWST has no equivalent DST timezone, but they don't observe DST anyway - 17, // 15 JST + 1 = AEST - 18, // 16 ACST + 1 = LHST - 19, // 17 AEST + 1 = SIT - 18, // 18 LHST has no equivalent DST timezone, but they don't observe DST anyway - 20, // 19 SIT + 1 = NZST - 22, // 20 NZST + 1 = TT - 23, // 21 CST + 1 = CDT - 24, // 22 TT + 1 = LIT - 23, // 23 CDT is already a daylight timezone - 24, // 24 LIT has no equivalent DST timezone, but they don't observe DST anyway - 26, // 25 BIT + 1 = NT - 27, // 26 NT + 1 = HAST - 29, // 27 HAST + 1 = AST - 28, // 28 MIT has no equivalent DST timezone, but they don't observe DST anyway - 30, // 29 AST + 1 = PST - 31, // 30 PST + 1 = MST - 32, // 31 MST + 1 = CST - 33, // 32 CST + 1 = EST - 35, // 33 EST + 1 = AST - 36, // 34 VST + 1 = NST - 37, // 35 AST + 1 = BT - 38, // 36 NST + 1 = NDT - 39, // 37 BT + 1 = 39 - 38, // 38 NDT is already a daylight timezone - 40, // 39 FNT + 1 = AST - 0 // 40 AST + 1 = UTC -}; - -const uint8_t movement_dst_inverse_jump_table[] = { - 40, // 0 - 0, // 1 - 1, // 2 - 2, // 3 - 4, // 4 - 3, // 5 - 4, // 6 - 5, // 7 - 6, // 8 - 9, // 9 - 7, // 10 - 8, // 11 - 10, // 12 - 12, // 13 - 14, // 14 - 13, // 15 - 16, // 16 - 15, // 17 - 16, // 18 - 17, // 19 - 19, // 20 - 21, // 21 - 20, // 22 - 21, // 23 - 24, // 24 - 25, // 25 - 25, // 26 - 26, // 27 - 28, // 28 - 27, // 29 - 29, // 30 - 30, // 31 - 31, // 32 - 32, // 33 - 34, // 34 - 33, // 35 - 34, // 36 - 35, // 37 - 36, // 38 - 37, // 39 - 39 // 40 -}; - const char movement_valid_position_0_chars[] = " AaBbCcDdEeFGgHhIiJKLMNnOoPQrSTtUuWXYZ-='+\\/0123456789"; const char movement_valid_position_1_chars[] = " ABCDEFHlJLNORTtUX-='01378"; diff --git a/movement/movement.h b/movement/movement.h index ecf5d8b..6747ff6 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -63,8 +63,7 @@ typedef union { bool clock_24h_leading_zero : 1; // indicates whether clock should leading zero to indicate 24 hour mode. bool use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial. bool alarm_enabled : 1; // indicates whether there is at least one alarm enabled. - bool dst_active : 1; // indicates whether daylight savings time is active - uint8_t reserved : 4; // room for more preferences if needed. + uint8_t reserved : 5; // room for more preferences if needed. } bit; uint32_t reg; } movement_settings_t; @@ -130,8 +129,6 @@ typedef struct { } movement_event_t; extern const int16_t movement_timezone_offsets[]; -extern const uint8_t movement_dst_jump_table[]; -extern const uint8_t movement_dst_inverse_jump_table[]; extern const char movement_valid_position_0_chars[]; extern const char movement_valid_position_1_chars[]; diff --git a/movement/watch_faces/settings/set_time_face.c b/movement/watch_faces/settings/set_time_face.c index d50d24b..605849f 100644 --- a/movement/watch_faces/settings/set_time_face.c +++ b/movement/watch_faces/settings/set_time_face.c @@ -27,8 +27,8 @@ #include "watch.h" #include "watch_utility.h" -#define SET_TIME_FACE_NUM_SETTINGS (8) -const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO", "DS"}; +#define SET_TIME_FACE_NUM_SETTINGS (7) +const char set_time_face_titles[SET_TIME_FACE_NUM_SETTINGS][3] = {"HR", "M1", "SE", "YR", "MO", "DA", "ZO"}; static bool _quick_ticks_running; @@ -59,16 +59,6 @@ static void _handle_alarm_button(movement_settings_t *settings, watch_date_time settings->bit.time_zone++; if (settings->bit.time_zone > 40) settings->bit.time_zone = 0; break; - case 7: // daylight savings time - if (settings->bit.dst_active) { // deactivate DST - date_time.unit.hour = (date_time.unit.hour + 24 - 1) % 24; - settings->bit.time_zone = movement_dst_inverse_jump_table[settings->bit.time_zone]; - } else { // activate DST - date_time.unit.hour = (date_time.unit.hour + 1) % 24; - settings->bit.time_zone = movement_dst_jump_table[settings->bit.time_zone]; - } - settings->bit.dst_active = !settings->bit.dst_active; - break; } if (date_time.unit.day > days_in_month(date_time.unit.month, date_time.unit.year + WATCH_RTC_REFERENCE_YEAR)) date_time.unit.day = 1; @@ -155,7 +145,7 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_clear_indicator(WATCH_INDICATOR_24H); watch_clear_indicator(WATCH_INDICATOR_PM); sprintf(buf, "%s %2d%02d%02d", set_time_face_titles[current_page], date_time.unit.year + 20, date_time.unit.month, date_time.unit.day); - } else if (current_page < 7) { // zone + } else { if (event.subsecond % 2) { watch_clear_colon(); sprintf(buf, "%s ", set_time_face_titles[current_page]); @@ -163,10 +153,6 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v watch_set_colon(); sprintf(buf, "%s %3d%02d ", set_time_face_titles[current_page], (int8_t) (movement_timezone_offsets[settings->bit.time_zone] / 60), (int8_t) (movement_timezone_offsets[settings->bit.time_zone] % 60) * (movement_timezone_offsets[settings->bit.time_zone] < 0 ? -1 : 1)); } - } else { // daylight savings - watch_clear_colon(); - if (settings->bit.dst_active) sprintf(buf, "%s dsT y", set_time_face_titles[current_page]); - else sprintf(buf, "%s dsT n", set_time_face_titles[current_page]); } // blink up the parameter we're setting @@ -184,9 +170,6 @@ bool set_time_face_loop(movement_event_t event, movement_settings_t *settings, v case 5: buf[8] = buf[9] = ' '; break; - case 7: - buf[9] = ' '; - break; } } From fc2f9c5130485607f89aebf459911cb707465d56 Mon Sep 17 00:00:00 2001 From: Jose Castillo Date: Tue, 17 Sep 2024 20:38:09 -0400 Subject: [PATCH 211/220] add accelerometer interrupt counter (#452) --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/demo/lis2dw_logging_face.c | 5 +- .../sensor/accel_interrupt_count_face.c | 162 ++++++++++++++++++ .../sensor/accel_interrupt_count_face.h | 58 +++++++ 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 movement/watch_faces/sensor/accel_interrupt_count_face.c create mode 100644 movement/watch_faces/sensor/accel_interrupt_count_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index e347d59..38f684d 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -141,6 +141,7 @@ SRCS += \ ../watch_faces/complication/simple_calculator_face.c \ ../watch_faces/sensor/alarm_thermometer_face.c \ ../watch_faces/demo/beeps_face.c \ + ../watch_faces/sensor/accel_interrupt_count_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index d1dbe44..5a74df8 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -116,6 +116,7 @@ #include "simple_calculator_face.h" #include "alarm_thermometer_face.h" #include "beeps_face.h" +#include "accel_interrupt_count_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/demo/lis2dw_logging_face.c b/movement/watch_faces/demo/lis2dw_logging_face.c index cbae54b..3b4827a 100644 --- a/movement/watch_faces/demo/lis2dw_logging_face.c +++ b/movement/watch_faces/demo/lis2dw_logging_face.c @@ -94,6 +94,7 @@ static void _lis2dw_logging_face_update_display(movement_settings_t *settings, l watch_display_string(buf, 0); if (set_leading_zero) watch_display_string("0", 4); + printf("%s\n", buf); } static void _lis2dw_logging_face_log_data(lis2dw_logger_state_t *logger_state) { @@ -142,7 +143,7 @@ void lis2dw_logging_face_activate(movement_settings_t *settings, void *context) logger_state->display_index = 0; logger_state->log_ticks = 0; - watch_enable_digital_input(A0); + watch_enable_digital_input(A4); } bool lis2dw_logging_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { @@ -196,7 +197,7 @@ bool lis2dw_logging_face_loop(movement_event_t event, movement_settings_t *setti void lis2dw_logging_face_resign(movement_settings_t *settings, void *context) { (void) settings; (void) context; - watch_disable_digital_input(A0); + watch_disable_digital_input(A4); } bool lis2dw_logging_face_wants_background_task(movement_settings_t *settings, void *context) { diff --git a/movement/watch_faces/sensor/accel_interrupt_count_face.c b/movement/watch_faces/sensor/accel_interrupt_count_face.c new file mode 100644 index 0000000..6473988 --- /dev/null +++ b/movement/watch_faces/sensor/accel_interrupt_count_face.c @@ -0,0 +1,162 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "accel_interrupt_count_face.h" +#include "lis2dw.h" +#include "watch.h" + +// hacky hacky! +uint32_t *ptr_to_count = 0; + +void accel_interrupt_handler(void); +void accel_interrupt_handler(void) { + (*ptr_to_count)++; +} + +static void _accel_interrupt_count_face_update_display(accel_interrupt_count_state_t *state) { + char buf[11]; + + if (state->running) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } else { + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + } + + // "AC"celerometer "IN"terrupts + snprintf(buf, 11, "AC1N%6ld", state->count); + watch_display_string(buf, 0); + printf("%s\n", buf); +} + +static void _accel_interrupt_count_face_configure_threshold(uint8_t threshold) { + lis2dw_configure_wakeup_int1(threshold, false, true); +} + +void accel_interrupt_count_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(accel_interrupt_count_state_t)); + memset(*context_ptr, 0, sizeof(accel_interrupt_count_state_t)); + ptr_to_count = &((accel_interrupt_count_state_t *)*context_ptr)->count; + watch_enable_i2c(); + lis2dw_begin(); + lis2dw_set_low_power_mode(LIS2DW_LP_MODE_2); // lowest power 14-bit mode, 25 Hz is 3.5 µA @ 1.8V w/ low noise, 3µA without + lis2dw_set_low_noise_mode(true); // consumes a little more power + lis2dw_set_range(LIS2DW_CTRL6_VAL_RANGE_4G); + lis2dw_set_data_rate(LIS2DW_DATA_RATE_25_HZ); // is this enough? + + // threshold is 1/64th of full scale, so for a FS of ±4G this is 1.25G + ((accel_interrupt_count_state_t *)*context_ptr)->threshold = 10; + _accel_interrupt_count_face_configure_threshold(((accel_interrupt_count_state_t *)*context_ptr)->threshold); + } +} + +void accel_interrupt_count_face_activate(movement_settings_t *settings, void *context) { + accel_interrupt_count_state_t *state = (accel_interrupt_count_state_t *)context; + + // never in settings mode at the start + state->is_setting = false; + + // force LE interval to never sleep + settings->bit.le_interval = 0; +} + +bool accel_interrupt_count_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + accel_interrupt_count_state_t *state = (accel_interrupt_count_state_t *)context; + + if (state->is_setting) { + switch (event.event_type) { + case EVENT_LIGHT_BUTTON_DOWN: + state->new_threshold = (state->new_threshold + 1) % 64; + // fall through + case EVENT_TICK: + { + char buf[11]; + snprintf(buf, 11, "TH %4d ", state->new_threshold); + watch_display_string(buf, 0); + printf("%s\n", buf); + } + break; + case EVENT_ALARM_BUTTON_UP: + lis2dw_configure_wakeup_int1(state->threshold, false, true); + state->threshold = state->new_threshold; + state->is_setting = false; + break; + default: + movement_default_loop_handler(event, settings); + break; + } + } else { + switch (event.event_type) { + case EVENT_LIGHT_BUTTON_DOWN: + movement_illuminate_led(); + + // if stopped, reset the count + if (!state->running) { + state->count = 0; + } + _accel_interrupt_count_face_update_display(state); + break; + case EVENT_ALARM_BUTTON_UP: + if (state->running) { + state->running = false; + watch_register_interrupt_callback(A4, NULL, INTERRUPT_TRIGGER_RISING); + } else { + state->running = true; + watch_register_interrupt_callback(A4, accel_interrupt_handler, INTERRUPT_TRIGGER_RISING); + } + _accel_interrupt_count_face_update_display(state); + break; + case EVENT_ACTIVATE: + case EVENT_TICK: + _accel_interrupt_count_face_update_display(state); + break; + case EVENT_ALARM_LONG_PRESS: + if (!state->running) { + state->new_threshold = state->threshold; + state->is_setting = true; + } + return false; + default: + movement_default_loop_handler(event, settings); + break; + } + } + + return true; +} + +void accel_interrupt_count_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +bool accel_interrupt_count_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + return false; +} diff --git a/movement/watch_faces/sensor/accel_interrupt_count_face.h b/movement/watch_faces/sensor/accel_interrupt_count_face.h new file mode 100644 index 0000000..3308b01 --- /dev/null +++ b/movement/watch_faces/sensor/accel_interrupt_count_face.h @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * 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. + */ + +#pragma once + +/* + * Accelerometer Interrupt Counter + * + * This is an experimental watch face for counting the number of interrupts that + * the Sensor Watch Motion acceleromoeter board fires. I expect it will be removed + * once we integrate accelerometer functionality more deeply into Movement. + */ + +#include "movement.h" +#include "watch.h" + +typedef struct { + uint32_t count; + uint8_t new_threshold; + uint8_t threshold; + bool running; + bool is_setting; +} accel_interrupt_count_state_t; + +void accel_interrupt_count_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void accel_interrupt_count_face_activate(movement_settings_t *settings, void *context); +bool accel_interrupt_count_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void accel_interrupt_count_face_resign(movement_settings_t *settings, void *context); +bool accel_interrupt_count_face_wants_background_task(movement_settings_t *settings, void *context); + +#define accel_interrupt_count_face ((const watch_face_t){ \ + accel_interrupt_count_face_setup, \ + accel_interrupt_count_face_activate, \ + accel_interrupt_count_face_loop, \ + accel_interrupt_count_face_resign, \ + accel_interrupt_count_face_wants_background_task, \ +}) From 3634460a02dedc62e0eaced0d4e7c27761a78075 Mon Sep 17 00:00:00 2001 From: joeycastillo Date: Tue, 17 Sep 2024 20:46:00 -0400 Subject: [PATCH 212/220] silence warning in beeps_face --- movement/watch_faces/demo/beeps_face.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movement/watch_faces/demo/beeps_face.c b/movement/watch_faces/demo/beeps_face.c index 07df627..5deeb62 100644 --- a/movement/watch_faces/demo/beeps_face.c +++ b/movement/watch_faces/demo/beeps_face.c @@ -38,7 +38,7 @@ void beeps_face_setup(movement_settings_t *settings, uint8_t watch_face_index, v void beeps_face_activate(movement_settings_t *settings, void *context) { (void) settings; - beeps_state_t *state = (beeps_state_t *)context; + (void) context; } static void _beep_face_update_lcd(beeps_state_t *state) { From 0f5defe78942726687927329e0f2383b9f051490 Mon Sep 17 00:00:00 2001 From: jokomo24 <45131638+jokomo24@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:55:50 +0200 Subject: [PATCH 213/220] Face for tracking the menstrual cycle (#250) Authored-by: jokomo --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/menstrual_cycle_face.c | 472 ++++++++++++++++++ .../complication/menstrual_cycle_face.h | 80 +++ 4 files changed, 554 insertions(+) create mode 100644 movement/watch_faces/complication/menstrual_cycle_face.c create mode 100644 movement/watch_faces/complication/menstrual_cycle_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 38f684d..3990f9f 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -119,6 +119,7 @@ SRCS += \ ../watch_faces/complication/toss_up_face.c \ ../watch_faces/complication/geomancy_face.c \ ../watch_faces/clock/simple_clock_bin_led_face.c \ + ../watch_faces/complication/menstrual_cycle_face.c \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 5a74df8..7c28f23 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -94,6 +94,7 @@ #include "geomancy_face.h" #include "dual_timer_face.h" #include "simple_clock_bin_led_face.h" +#include "menstrual_cycle_face.h" #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" diff --git a/movement/watch_faces/complication/menstrual_cycle_face.c b/movement/watch_faces/complication/menstrual_cycle_face.c new file mode 100644 index 0000000..812de59 --- /dev/null +++ b/movement/watch_faces/complication/menstrual_cycle_face.c @@ -0,0 +1,472 @@ +/* + * MIT License + * + * Copyright (c) 2023 Joseph Borne Komosa | @jokomo24 + * + * 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. + * + * + * Menstrual Cycle Face + * + * Background: + * + * I discovered the Casio F-91W through my partner, appreciated the retro aesthetic of the watch, + * and got one for myself. Soon afterward I discovered the Sensor Watch project and ordered two boards! + * I introduced the Sensor Watch to my partner who inquired whether she could track her menstrual cycle. + * So I decided to implement a menstrual cycle watch face that also calculates the peak fertility window + * using The Calendar Method. While this information may be useful when attempting to achieve or avoid + * pregnancy, it is important to understand that these are rough estimates at best. + * + * How to use: + * + * 1. To begin tracking, go to 'Last Period' page and toggle the alarm button to the number of days since + * the last, most recent, period and hold the alarm button to enter. This will perform the following actions: + * - Store the corresponding date as the 'first' period in order to calculate the total_days_tracked. + * - Turn on the Signal Indicator to signify that tracking has been activated. + * - Deactivate this page and instead show the ticking animation. + * - Adjust the days left in the 'Period in Days' page accordingly. + * - Activate the 'Period Is Here' page and no longer display 'NA'. To prevent accidental user entry, + * the page will display the ticking animation until ten days have passed since the date of the last + * period entered. + * - Activate the 'Peak Fertility' page to begin showing the estimated window, + * as well as display the Alarm Indicator, on this page and on the main 'Period in Days' page, + * whenever the current date falls within the Peak Fertility Window. + * + * 2. Toggle and enter 'y' in the 'Period Is Here' page on the day of every sequential period afterward. + * DO NOT FORGET TO DO SO! + * - If forgotten, the data will become inaccurate and tracking will need to be reset! -> (FIXME, allow one to enter a 'missed' period using the 'Last Period' page). + * This will perform the following actions: + * - Calculate this completed cycle's length and reevaluate the shortest and longest cycle variables. + * - Increment total_cycles by one. + * - Recalculate and save the average cycle for 'Average Cycle' page. + */ + +#include +#include +#include "menstrual_cycle_face.h" +#include "watch.h" +#include "watch_utility.h" + +#define TYPICAL_AVG_CYC 28 +#define SECONDS_PER_DAY 86400 + +#define MENSTRUAL_CYCLE_FACE_NUM_PAGES (6) +enum { + period_in_num_days, + average_cycle, + peak_fertility_window, + period_is_here, + first_period, + reset, +} page_titles_e; +const char menstrual_cycle_face_titles[MENSTRUAL_CYCLE_FACE_NUM_PAGES][11] = { + "Prin day", // Period In Days: Estimated days till the next period occurs + "Av cycle ", // Average Cycle: The average number of days estimated per cycle + "Peak Fert ", // Peak Fertility Window: The first and last day of month (displayed top & bottom right, respectively, once tracking) for the estimated window of fertility + "Prishere ", // Period Is Here: Toggle and enter 'y' on the day the actual period occurs to improve Avg and Fert estimations + "Last Per ", // Last Period: Enter the number of days since the last period to begin tracking from that corresponding date by storing it as the 'first' + " Reset ", // Reset: Toggle and enter 'y' to reset tracking data +}; + +/* Beep function */ +static inline void beep(movement_settings_t *settings) { + if (settings->bit.button_should_sound) + watch_buzzer_play_note(BUZZER_NOTE_E8, 75); +} + +// Calculate the total number of days for which menstrual cycle tracking has been active +static inline uint32_t total_days_tracked(menstrual_cycle_state_t *state) { + + // If tracking has not yet been activated, return 0 + if (!(state->dates.reg)) + return 0; + + // Otherwise, set the start date to the first day of the first tracked cycle + watch_date_time date_time_start; + date_time_start.unit.second = 0; + date_time_start.unit.minute = 0; + date_time_start.unit.hour = 0; + date_time_start.unit.day = state->dates.bit.first_day; + date_time_start.unit.month = state->dates.bit.first_month; + date_time_start.unit.year = state->dates.bit.first_year; + + // Get the current date and time + watch_date_time date_time_now = watch_rtc_get_date_time(); + + // Convert the start date and current date to Unix time + uint32_t unix_start = watch_utility_date_time_to_unix_time(date_time_start, state->utc_offset); + uint32_t unix_now = watch_utility_date_time_to_unix_time(date_time_now, state->utc_offset); + + // Calculate the total number of days and return it + return (unix_now - unix_start) / SECONDS_PER_DAY; +} + +// Calculate the number of days until the next menstrual period +static inline int8_t days_till_period(menstrual_cycle_state_t *state) { + + // Calculate the number of days left until the next period based on the average cycle length and the number of cycles tracked + int8_t days_left = (state->cycles.bit.average_cycle * (state->cycles.bit.total_cycles + 1)) - total_days_tracked(state); + + // If the result is negative, return 0 (i.e., the period is expected to start today or has already started) + return (days_left < 0) ? 0 : days_left; +} + +static inline void reset_tracking(menstrual_cycle_state_t *state) { + + state->dates.bit.first_day = 0; + state->dates.bit.first_month = 0; + state->dates.bit.first_year = 0; + + state->dates.bit.prev_day = 0; + state->dates.bit.prev_month = 0; + state->dates.bit.prev_year = 0; + + state->cycles.bit.shortest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.longest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.average_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.total_cycles = 0; + + state->dates.bit.reserved = 0; + state->cycles.bit.reserved = 0; + + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); +} + +/* +Fertility Window based on "The Calendar Method" +Source: https://www.womenshealth.gov/pregnancy/you-get-pregnant/trying-conceive + +The Calendar Method has several steps: + +Step 1: Track the menstrual cycle for 8–12 months. One cycle is from the first day of one + period until the first day of the next period. The average cycle is 28 days, but + it may be as short as 24 days or as long as 38 days. +Step 2: Subtract 18 from the number of days in the shortest menstrual cycle. +Step 3: Subtract 11 from the number of days in the longest menstrual cycle. +Step 4: Using a calendar, mark down the start of the next period (using previous instead). Count ahead by the number + of days calculated in step 2. This is when peak fertility begins. Peak fertility ends + at the number of days calculated in step 3. +NOTE: Right now, the fertility window face displays its estimated window as soon as tracking is activated, although + it is important to keep in mind that The Calendar Method states that peak accuracy of the window will be + reached only after at least 8 months of tracking the menstrual cycle (can make it so that it only displays + after total_days_tracked >= 8 months...but the info is interesting and should already be taken with the understanding that, + in general, it is a rough estimation at best). +*/ +typedef enum Fertile_Window {first_day, last_day} fertile_window; +// Calculate the predicted starting or ending day of peak fertility +static inline uint32_t get_day_pk_fert(menstrual_cycle_state_t *state, fertile_window which_day) { + + // Get the date of the previous period + watch_date_time date_prev_period; + date_prev_period.unit.second = 0; + date_prev_period.unit.minute = 0; + date_prev_period.unit.hour = 0; + date_prev_period.unit.day = state->dates.bit.prev_day; + date_prev_period.unit.month = state->dates.bit.prev_month; + date_prev_period.unit.year = state->dates.bit.prev_year; + + // Convert the previous period date to Unix time + uint32_t unix_prev_period = watch_utility_date_time_to_unix_time(date_prev_period, state->utc_offset); + + // Calculate the Unix time of the predicted peak fertility day based on the length of the shortest/longest cycle + uint32_t unix_pk_date; + switch(which_day) { + case first_day: + unix_pk_date = unix_prev_period + ((state->cycles.bit.shortest_cycle - 18) * SECONDS_PER_DAY); + break; + case last_day: + unix_pk_date = unix_prev_period + ((state->cycles.bit.longest_cycle - 11) * SECONDS_PER_DAY); + break; + } + + // Convert the Unix time of the predicted peak fertility day to a date/time and return the day of the month + return watch_utility_date_time_from_unix_time(unix_pk_date, state->utc_offset).unit.day; +} + +// Determine if today falls within the predicted peak fertility window +static inline bool inside_fert_window(menstrual_cycle_state_t *state) { + + // If tracking has not yet been activated, return false + if (!(state->dates.reg)) + return false; + + // Get the current date/time + watch_date_time date_time_now = watch_rtc_get_date_time(); + + // Check if the current day falls between the first and last predicted peak fertility days + if (get_day_pk_fert(state, first_day) > get_day_pk_fert(state, last_day)) { // We are crossing over the end of the month + if (date_time_now.unit.day >= get_day_pk_fert(state, first_day) || + date_time_now.unit.day <= get_day_pk_fert(state, last_day)) + return true; + } + else if (date_time_now.unit.day >= get_day_pk_fert(state, first_day) && + date_time_now.unit.day <= get_day_pk_fert(state, last_day)) + return true; + // If the current day does not fall within the predicted peak fertility window, return false + return false; +} + +// Update the shortest and longest menstrual cycles based on the previous menstrual cycle +static inline void update_shortest_longest_cycle(menstrual_cycle_state_t *state) { + + // Get the date of the previous menstrual cycle + watch_date_time date_prev_period; + date_prev_period.unit.second = 0; + date_prev_period.unit.minute = 0; + date_prev_period.unit.hour = 0; + date_prev_period.unit.day = state->dates.bit.prev_day; + date_prev_period.unit.month = state->dates.bit.prev_month; + date_prev_period.unit.year = state->dates.bit.prev_year; + + // Convert the date of the previous menstrual cycle to UNIX time + uint32_t unix_prev_period = watch_utility_date_time_to_unix_time(date_prev_period, state->utc_offset); + + // Calculate the length of the current menstrual cycle + uint8_t cycle_length = total_days_tracked(state) - (unix_prev_period / SECONDS_PER_DAY); + + // Update the shortest or longest cycle length if necessary + if (cycle_length < state->cycles.bit.shortest_cycle) + state->cycles.bit.shortest_cycle = cycle_length; + else if (cycle_length > state->cycles.bit.longest_cycle) + state->cycles.bit.longest_cycle = cycle_length; +} + +void menstrual_cycle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + (void) settings; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(menstrual_cycle_state_t)); + memset(*context_ptr, 0, sizeof(menstrual_cycle_state_t)); + menstrual_cycle_state_t *state = ((menstrual_cycle_state_t *)*context_ptr); + + state->dates.bit.first_day = 0; + state->dates.bit.first_month = 0; + state->dates.bit.first_year = 0; + + state->dates.bit.prev_day = 0; + state->dates.bit.prev_month = 0; + state->dates.bit.prev_year = 0; + + state->cycles.bit.shortest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.longest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.average_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.total_cycles = 0; + + state->dates.bit.reserved = 0; + state->cycles.bit.reserved = 0; + + state->backup_register_dt = 0; + state->backup_register_cy = 0; + } + + menstrual_cycle_state_t *state = ((menstrual_cycle_state_t *)*context_ptr); + if (!(state->backup_register_dt && state->backup_register_cy)) { + state->backup_register_dt = movement_claim_backup_register(); + state->backup_register_cy = movement_claim_backup_register(); + + if (state->backup_register_dt && state->backup_register_cy) { + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + } + } + else { + state->dates.reg = watch_get_backup_data(state->backup_register_dt); + state->cycles.reg = watch_get_backup_data(state->backup_register_cy); + } +} + +void menstrual_cycle_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + menstrual_cycle_state_t *state = (menstrual_cycle_state_t *)context; + state->period_today = 0; + state->current_page = 0; + state->reset_tracking = 0; + state->utc_offset = movement_timezone_offsets[settings->bit.time_zone] * 60; + movement_request_tick_frequency(4); // we need to manually blink some pixels +} + +bool menstrual_cycle_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + menstrual_cycle_state_t *state = (menstrual_cycle_state_t *)context; + watch_date_time date_period; + uint8_t current_page = state->current_page; + uint8_t first_day_fert; + uint8_t last_day_fert; + uint32_t unix_now; + uint32_t unix_prev_period; + switch (event.event_type) { + case EVENT_TICK: + case EVENT_ACTIVATE: + // Do nothing; handled below. + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + return false; + case EVENT_LIGHT_BUTTON_DOWN: + current_page = (current_page + 1) % MENSTRUAL_CYCLE_FACE_NUM_PAGES; + state->current_page = current_page; + state->days_prev_period = 0; + watch_clear_indicator(WATCH_INDICATOR_BELL); + if (watch_tick_animation_is_running()) + watch_stop_tick_animation(); + break; + case EVENT_ALARM_LONG_PRESS: + switch (current_page) { + case period_in_num_days: + break; + case average_cycle: + break; + case peak_fertility_window: + break; + case period_is_here: + if (state->period_today && total_days_tracked(state)) { + // Calculate before updating date of last period + update_shortest_longest_cycle(state); + // Update the date of last period after calulating the, now previous, cycle length + date_period = watch_rtc_get_date_time(); + state->dates.bit.prev_day = date_period.unit.day; + state->dates.bit.prev_month = date_period.unit.month; + state->dates.bit.prev_year = date_period.unit.year; + // Calculate new cycle average + state->cycles.bit.total_cycles += 1; + state->cycles.bit.average_cycle = total_days_tracked(state) / state->cycles.bit.total_cycles; + // Store the new data + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + state->period_today = !(state->period_today); + beep(settings); + } + break; + case first_period: + // If tracking has not yet been activated + if (!(state->dates.reg)) { + unix_now = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), state->utc_offset); + unix_prev_period = unix_now - (state->days_prev_period * SECONDS_PER_DAY); + date_period = watch_utility_date_time_from_unix_time(unix_prev_period, state->utc_offset); + state->dates.bit.first_day = date_period.unit.day; + state->dates.bit.first_month = date_period.unit.month; + state->dates.bit.first_year = date_period.unit.year; + state->dates.bit.prev_day = date_period.unit.day; + state->dates.bit.prev_month = date_period.unit.month; + state->dates.bit.prev_year = date_period.unit.year; + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + beep(settings); + } + break; + case reset: + if (state->reset_tracking) { + reset_tracking(state); + state->reset_tracking = !(state->reset_tracking); + beep(settings); + } + break; + } + break; + case EVENT_ALARM_BUTTON_UP: + switch (current_page) { + case period_in_num_days: + break; + case average_cycle: + break; + case peak_fertility_window: + break; + case period_is_here: + if (total_days_tracked(state)) + state->period_today = !(state->period_today); + break; + case first_period: + if (!(state->dates.reg)) + state->days_prev_period = (state->days_prev_period > 99) ? 0 : state->days_prev_period + 1; // Cycle through pages to quickly reset to 0 + break; + case reset: + state->reset_tracking = !(state->reset_tracking); + break; + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + default: + return movement_default_loop_handler(event, settings); + } + + watch_display_string((char *)menstrual_cycle_face_titles[current_page], 0); + if (state->dates.reg) + watch_set_indicator(WATCH_INDICATOR_SIGNAL); // signal that we are now in a tracking state + + char buf[13]; + switch (current_page) { + case period_in_num_days: + sprintf(buf, "%2d", days_till_period(state)); + if (inside_fert_window(state)) + watch_set_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 4); + break; + case average_cycle: + sprintf(buf, "%2d", state->cycles.bit.average_cycle); + watch_display_string(buf, 2); + break; + case peak_fertility_window: + if (event.subsecond % 5 && state->dates.reg) { // blink active for 3 quarter-seconds + first_day_fert = get_day_pk_fert(state, first_day); + last_day_fert = get_day_pk_fert(state, last_day); + sprintf(buf, "Fr%2d To %2d", first_day_fert, last_day_fert); // From: first day | To: last day + if (inside_fert_window(state)) + watch_set_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 0); + } + break; + case period_is_here: + if (event.subsecond % 5) { // blink active for 3 quarter-seconds + if (!(state->dates.reg)) + watch_display_string("NA", 8); // Not Applicable: Do not allow period entry until tracking is activated... + else if (state->period_today) + watch_display_string("y", 9); + else + watch_display_string("n", 9); + } + break; + case first_period: + if (state->dates.reg) { + if (!watch_tick_animation_is_running()) + watch_start_tick_animation(500); // Tracking activated + } + else if (event.subsecond % 5) { // blink active for 3 quarter-seconds + sprintf(buf, "%2d", state->days_prev_period); + watch_display_string(buf, 8); + } + break; + case reset: + // blink active for 3 quarter-seconds + if (event.subsecond % 5 && state->reset_tracking) + watch_display_string("y", 9); + else if (event.subsecond % 5) + watch_display_string("n", 9); + break; + } + return true; +} + +void menstrual_cycle_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} \ No newline at end of file diff --git a/movement/watch_faces/complication/menstrual_cycle_face.h b/movement/watch_faces/complication/menstrual_cycle_face.h new file mode 100644 index 0000000..73adaf8 --- /dev/null +++ b/movement/watch_faces/complication/menstrual_cycle_face.h @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2023 Joseph Borne Komosa | @jokomo24 + * + * 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 MENSTRUAL_CYCLE_FACE_H_ +#define MENSTRUAL_CYCLE_FACE_H_ + +#include "movement.h" + +typedef struct { + // Store the date of the 'first' and the total cycles since to calulate and store the average menstrual cycle. + // Store the date of the previous, most recent, period to calculate the cycle length. + // Store the shortest and longest cycle to calculate the fertility window for The Calender Method. + // NOTE: Not thrilled about using two registers, but could not find a way to perform The Calender Method + // without requiring both the 'first' and 'prev' dates. + union { + struct { + uint8_t first_day : 5; + uint8_t first_month : 4; + uint8_t first_year : 6; // 0-63 (representing 2020-2083) + uint8_t prev_day : 5; + uint8_t prev_month : 4; + uint8_t prev_year : 6; // 0-63 (representing 2020-2083) + uint8_t reserved : 2; // left over bit space + } bit; + uint32_t reg; // Tracking's been activated if > 0 + } dates; + union { + struct { + uint8_t shortest_cycle : 6; // For step 2 of The Calender Method + uint8_t longest_cycle : 6; // For step 3 of The Calender Method + uint8_t average_cycle : 6; // The average menstrual cycle lasts 28 days, but normal cycles can vary from 21 to 35 days + uint16_t total_cycles : 11; // The total cycles (periods) entered since the start of tracking + uint8_t reserved : 3; // left over bit space + } bit; + uint32_t reg; + } cycles; + uint8_t backup_register_dt; + uint8_t backup_register_cy; + uint8_t current_page; + uint8_t days_prev_period; + int32_t utc_offset; + bool period_today; + bool reset_tracking; +} menstrual_cycle_state_t; + +void menstrual_cycle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void menstrual_cycle_face_activate(movement_settings_t *settings, void *context); +bool menstrual_cycle_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void menstrual_cycle_face_resign(movement_settings_t *settings, void *context); + +#define menstrual_cycle_face ((const watch_face_t){ \ + menstrual_cycle_face_setup, \ + menstrual_cycle_face_activate, \ + menstrual_cycle_face_loop, \ + menstrual_cycle_face_resign, \ + NULL, \ +}) + +#endif // MENSTRUAL_CYCLE_FACE_H_ \ No newline at end of file From b2d313e0e75e9ee7d3903393450fb6ee2ad0ed8d Mon Sep 17 00:00:00 2001 From: Austoria Date: Tue, 17 Sep 2024 21:46:20 -0400 Subject: [PATCH 214/220] Metronome Complication (#303) * Metronome Complication A simple metronome complication that allows user to set BPM, toggle sound, and set counts per measure. * silence warnings in metronome_face * avoid mode button in metronome settings, other tweaks --------- Co-authored-by: joeycastillo --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/metronome_face.c | 263 ++++++++++++++++++ .../watch_faces/complication/metronome_face.h | 86 ++++++ 4 files changed, 351 insertions(+) create mode 100644 movement/watch_faces/complication/metronome_face.c create mode 100644 movement/watch_faces/complication/metronome_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 3990f9f..eedb7f9 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -143,6 +143,7 @@ SRCS += \ ../watch_faces/sensor/alarm_thermometer_face.c \ ../watch_faces/demo/beeps_face.c \ ../watch_faces/sensor/accel_interrupt_count_face.c \ + ../watch_faces/complication/metronome_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 7c28f23..1eb5671 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -118,6 +118,7 @@ #include "alarm_thermometer_face.h" #include "beeps_face.h" #include "accel_interrupt_count_face.h" +#include "metronome_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/metronome_face.c b/movement/watch_faces/complication/metronome_face.c new file mode 100644 index 0000000..ad53c6c --- /dev/null +++ b/movement/watch_faces/complication/metronome_face.c @@ -0,0 +1,263 @@ +/* + * MIT License + * + * Copyright (c) 2023 Austin Teets + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "metronome_face.h" +#include "watch.h" + +static const int8_t _sound_seq_start[] = {BUZZER_NOTE_C8, 2, 0}; +static const int8_t _sound_seq_beat[] = {BUZZER_NOTE_C6, 2, 0}; + +void metronome_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(metronome_state_t)); + memset(*context_ptr, 0, sizeof(metronome_state_t)); + } +} + +void metronome_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + metronome_state_t *state = (metronome_state_t *)context; + movement_request_tick_frequency(2); + if (state->bpm == 0) { + state->count = 4; + state->bpm = 120; + state->soundOn = true; + } + state->mode = metWait; + state->correction = 0; + state->setCur = hundred; +} + +static void _metronome_face_update_lcd(metronome_state_t *state) { + char buf[11]; + if (state->soundOn) { + watch_set_indicator(WATCH_INDICATOR_BELL); + } else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } + sprintf(buf, "MN %d %03d%s", state->count, state->bpm, "bp"); + watch_display_string(buf, 0); +} + +static void _metronome_start_stop(metronome_state_t *state) { + if (state->mode != metRun) { + movement_request_tick_frequency(64); + state->mode = metRun; + watch_clear_display(); + double ticks = 3840.0 / (double)state->bpm; + state->tick = (int) ticks; + state->curTick = (int) ticks; + state->halfBeat = (int)(state->tick/2); + state->curCorrection = ticks - state->tick; + state->correction = ticks - state->tick; + state->curBeat = 1; + } else { + state->mode = metWait; + movement_request_tick_frequency(2); + _metronome_face_update_lcd(state); + } +} + +static void _metronome_tick_beat(metronome_state_t *state) { + char buf[11]; + if (state->soundOn) { + if (state->curBeat == 1) { + watch_buzzer_play_sequence((int8_t *)_sound_seq_start, NULL); + } else { + watch_buzzer_play_sequence((int8_t *)_sound_seq_beat, NULL); + } + } + sprintf(buf, "MN %d %03d%s", state->count, state->bpm, "bp"); + watch_display_string(buf, 0); +} + +static void _metronome_event_tick(uint8_t subsecond, metronome_state_t *state) { + (void) subsecond; + + if (state->curCorrection >= 1) { + state->curCorrection -= 1; + state->curTick -= 1; + } + int diff = state->curTick - state->tick; + if(diff == 0) { + _metronome_tick_beat(state); + state->curTick = 0; + state->curCorrection += state->correction; + if (state->curBeat < state->count ) { + state->curBeat += 1; + } else { + state->curBeat = 1; + } + } else { + if (state->curTick == state->halfBeat) { + watch_clear_display(); + } + state->curTick += 1; + } +} + +static void _metronome_setting_tick(uint8_t subsecond, metronome_state_t *state) { + char buf[13]; + sprintf(buf, "MN %d %03d%s", state->count, state->bpm, "bp"); + if (subsecond%2 == 0) { + switch (state->setCur) { + case hundred: + buf[5] = ' '; + break; + case ten: + buf[6] = ' '; + break; + case one: + buf[7] = ' '; + break; + case count: + buf[3] = ' '; + break; + case alarm: + break; + } + } + if (state->setCur == alarm) { + sprintf(buf, "MN 8eep%s", state->soundOn ? "On" : " -"); + } + if (state->soundOn) { + watch_set_indicator(WATCH_INDICATOR_BELL); + } else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } + watch_display_string(buf, 0); +} + +static void _metronome_update_setting(metronome_state_t *state) { + char buf[13]; + switch (state->setCur) { + case hundred: + if (state->bpm < 100) { + state->bpm += 100; + } else { + state->bpm -= 100; + } + break; + case ten: + if ((state->bpm / 10) % 10 < 9) { + state->bpm += 10; + } else { + state->bpm -= 90; + } + break; + case one: + if (state->bpm%10 < 9) { + state->bpm += 1; + } else { + state->bpm -= 9; + } + break; + case count: + if (state->count < 9) { + state->count += 1; + } else { + state->count = 2; + } + break; + case alarm: + state->soundOn = !state->soundOn; + break; + } + sprintf(buf, "MN %d %03d%s", state->count % 10, state->bpm, "bp"); + if (state->setCur == alarm) { + sprintf(buf, "MN 8eep%s", state->soundOn ? "On" : " -"); + } + if (state->soundOn) { + watch_set_indicator(WATCH_INDICATOR_BELL); + } else { + watch_clear_indicator(WATCH_INDICATOR_BELL); + } + watch_display_string(buf, 0); +} + +bool metronome_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + metronome_state_t *state = (metronome_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + _metronome_face_update_lcd(state); + break; + case EVENT_TICK: + if (state->mode == metRun){ + _metronome_event_tick(event.subsecond, state); + } else if (state->mode == setMenu) { + _metronome_setting_tick(event.subsecond, state); + } + break; + case EVENT_ALARM_BUTTON_UP: + if (state->mode == setMenu) { + _metronome_update_setting(state); + } else { + _metronome_start_stop(state); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + if (state->mode == setMenu) { + if (state->setCur < alarm) { + state->setCur += 1; + } else { + state->setCur = hundred; + } + } + break; + case EVENT_ALARM_LONG_PRESS: + if (state->mode != metRun && state->mode != setMenu) { + movement_request_tick_frequency(2); + state->mode = setMenu; + _metronome_face_update_lcd(state); + } else if (state->mode == setMenu) { + state->mode = metWait; + _metronome_face_update_lcd(state); + } + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + case EVENT_TIMEOUT: + if (state->mode != metRun) { + movement_move_to_face(0); + } + break; + case EVENT_LOW_ENERGY_UPDATE: + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + +void metronome_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/complication/metronome_face.h b/movement/watch_faces/complication/metronome_face.h new file mode 100644 index 0000000..7c8573f --- /dev/null +++ b/movement/watch_faces/complication/metronome_face.h @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2023 Austin Teets + * + * 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 METRONOME_FACE_H_ +#define METRONOME_FACE_H_ + +#include "movement.h" + +/* + * A Metronome watch complication + * Allows the user to set the BPM, counts per measure, beep sound on/off + * Screen flashes on on the beat and off on the half beat (1/8th note) + * Beep will sound high for downbeat and low for subsequent beats in measure + * USE: + * Press Alarm to start/stop metronome_face + * Hold Alarm to enter settings menu + * Short Light press will move through options + * Short Alarm press will increment/toggle options + * Long alarm press will exit options + */ + +typedef enum { + metWait, + metRun, + setMenu +} metronome_mode_t; + +typedef enum { + hundred, + ten, + one, + count, + alarm +} setting_cursor_t; + +typedef struct { + // Anything you need to keep track of, put it here! + uint8_t bpm; + double correction; + double curCorrection; + int count; + int tick; + int curTick; + int curBeat; + int halfBeat; + metronome_mode_t mode : 3; + setting_cursor_t setCur : 4; + bool soundOn; +} metronome_state_t; + +void metronome_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void metronome_face_activate(movement_settings_t *settings, void *context); +bool metronome_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void metronome_face_resign(movement_settings_t *settings, void *context); + +#define metronome_face ((const watch_face_t){ \ + metronome_face_setup, \ + metronome_face_activate, \ + metronome_face_loop, \ + metronome_face_resign, \ + NULL, \ +}) + +#endif // METRONOME_FACE_H_ + From 7af562614732f324ff96ac79453bb2f9b5064b79 Mon Sep 17 00:00:00 2001 From: MarkBlyth Date: Wed, 18 Sep 2024 02:54:33 +0100 Subject: [PATCH 215/220] Add min/max temperature watch face (#335) --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + movement/watch_faces/sensor/minmax_face.c | 154 ++++++++++++++++++++++ movement/watch_faces/sensor/minmax_face.h | 69 ++++++++++ 4 files changed, 225 insertions(+) create mode 100644 movement/watch_faces/sensor/minmax_face.c create mode 100644 movement/watch_faces/sensor/minmax_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index eedb7f9..582092f 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -130,6 +130,7 @@ SRCS += \ ../watch_faces/complication/couch_to_5k_face.c \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ + ../watch_faces/sensor/minmax_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ ../watch_faces/complication/wordle_face.c \ ../watch_faces/complication/endless_runner_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 1eb5671..f405a21 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -105,6 +105,7 @@ #include "couch_to_5k_face.h" #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" +#include "minmax_face.h" #include "kitchen_conversions_face.h" #include "wordle_face.h" #include "endless_runner_face.h" diff --git a/movement/watch_faces/sensor/minmax_face.c b/movement/watch_faces/sensor/minmax_face.c new file mode 100644 index 0000000..82f3b7e --- /dev/null +++ b/movement/watch_faces/sensor/minmax_face.c @@ -0,0 +1,154 @@ +/* + * MIT License + * + * Copyright (c) 2023 Mark Blyth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "minmax_face.h" +#include "thermistor_driver.h" +#include "watch.h" + + +static float _get_displayed_temperature_c(minmax_state_t *state){ + float min_temp = 1000; + float max_temp = -1000; + for(int i = 0; i < LOGGING_DATA_POINTS; i++){ + if(state->hourly_maxs[i] > max_temp){ + max_temp = state->hourly_maxs[i]; + } + if(state->hourly_mins[i] < min_temp){ + min_temp = state->hourly_mins[i]; + } + } + if(state->show_min) return min_temp; + return max_temp; +} + + +static void _minmax_face_log_data(minmax_state_t *logger_state) { + thermistor_driver_enable(); + size_t pos = (size_t) watch_rtc_get_date_time().unit.hour; + float temp_c = thermistor_driver_get_temperature(); + // If no data yet, initialise with current temperature + if(!logger_state->have_logged){ + logger_state->have_logged = true; + for(int i=0; ihourly_mins[i] = temp_c; + logger_state->hourly_maxs[i] = temp_c; + } + } + // On new hour, update lists to current temperature + else if(watch_rtc_get_date_time().unit.minute < 2){ + logger_state->hourly_mins[pos] = temp_c; + logger_state->hourly_maxs[pos] = temp_c; + } + // Log hourly highs and lows + else if(logger_state->hourly_mins[pos] > temp_c){ + logger_state->hourly_mins[pos] = temp_c; + } + else if(logger_state->hourly_maxs[pos] < temp_c){ + logger_state->hourly_maxs[pos] = temp_c; + } + thermistor_driver_disable(); +} + +static void _minmax_face_update_display(float temperature_c, bool in_fahrenheit) { + char buf[14]; + if (in_fahrenheit) { + sprintf(buf, "%4.0f#F", temperature_c * 1.8 + 32.0); + } else { + sprintf(buf, "%4.0f#C", temperature_c); + } + watch_display_string(buf, 4); +} + + +void minmax_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(minmax_state_t)); + memset(*context_ptr, 0, sizeof(minmax_state_t)); + } +} + + +void minmax_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + minmax_state_t *state = (minmax_state_t *)context; + state->show_min = true; + watch_display_string("MN", 0); // Start with minimum temp +} + +bool minmax_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + minmax_state_t *state = (minmax_state_t *)context; + float temp_c; + + switch (event.event_type) { + case EVENT_ACTIVATE: + temp_c = _get_displayed_temperature_c(state); + _minmax_face_update_display(temp_c, settings->bit.use_imperial_units); + break; + + case EVENT_LIGHT_LONG_PRESS: + settings->bit.use_imperial_units = !settings->bit.use_imperial_units; + temp_c = _get_displayed_temperature_c(state); + _minmax_face_update_display(temp_c, settings->bit.use_imperial_units); + break; + + case EVENT_ALARM_BUTTON_UP: + state->show_min = !state->show_min; + if(state->show_min){ + watch_display_string("MN", 0); + } else { + watch_display_string("MX", 0); + } + temp_c = _get_displayed_temperature_c(state); + _minmax_face_update_display(temp_c, settings->bit.use_imperial_units); + break; + + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + case EVENT_BACKGROUND_TASK: + _minmax_face_log_data(state); + break; + default: + return movement_default_loop_handler(event, settings); + } + return true; +} + + +void minmax_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + + +bool minmax_face_wants_background_task(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + // this will get called at the top of each minute; always request bg task + return true; +} diff --git a/movement/watch_faces/sensor/minmax_face.h b/movement/watch_faces/sensor/minmax_face.h new file mode 100644 index 0000000..1fd3c48 --- /dev/null +++ b/movement/watch_faces/sensor/minmax_face.h @@ -0,0 +1,69 @@ +/* + * MIT License + * + * Copyright (c) 2023 Mark Blyth + * + * 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 MINMAX_FACE_H_ +#define MINMAX_FACE_H_ + +#include "movement.h" +#include "watch.h" + +#define LOGGING_DATA_POINTS (24) + +/* + * Log for the min. and max. temperature over the last 24h. + * + * Temperature is logged once a minute, every minute. Results are + * stored, noting the highest and lowest temperatures observed within + * any given hour. The watch face then displays the minimum or maximum + * temperature recorded over the last 24h. + * + * A long press of the light button changes units between Celsius and + * Fahrenheit. Pressing the alarm button switches between displaying the + * minimum and maximum observed temperatures. If no buttons are pressed, + * the watch face will eventually time out and return home. + */ + +typedef struct { + bool show_min; + bool have_logged; + float hourly_mins[LOGGING_DATA_POINTS]; + float hourly_maxs[LOGGING_DATA_POINTS]; +} minmax_state_t; + +void minmax_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void minmax_face_activate(movement_settings_t *settings, void *context); +bool minmax_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void minmax_face_resign(movement_settings_t *settings, void *context); +bool minmax_face_wants_background_task(movement_settings_t *settings, void *context); + +#define minmax_face ((const watch_face_t){ \ + minmax_face_setup, \ + minmax_face_activate, \ + minmax_face_loop, \ + minmax_face_resign, \ + minmax_face_wants_background_task, \ +}) + +#endif // MINMAX_FACE_H_ + From 52c3d5b7961ccb3a8626d103b9749f09ead073d6 Mon Sep 17 00:00:00 2001 From: kbc-yam <66929688+kbc-yam@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:00:44 +0900 Subject: [PATCH 216/220] add wareki_face for japanese user (#351) --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../watch_faces/complication/wareki_face.c | 230 ++++++++++++++++++ .../watch_faces/complication/wareki_face.h | 34 +++ 4 files changed, 266 insertions(+) create mode 100644 movement/watch_faces/complication/wareki_face.c create mode 100644 movement/watch_faces/complication/wareki_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 582092f..9bafa6d 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -132,6 +132,7 @@ SRCS += \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/sensor/minmax_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/wareki_face.c \ ../watch_faces/complication/wordle_face.c \ ../watch_faces/complication/endless_runner_face.c \ ../watch_faces/complication/periodic_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index f405a21..c0bcc75 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -107,6 +107,7 @@ #include "tuning_tones_face.h" #include "minmax_face.h" #include "kitchen_conversions_face.h" +#include "wareki_face.h" #include "wordle_face.h" #include "endless_runner_face.h" #include "periodic_face.h" diff --git a/movement/watch_faces/complication/wareki_face.c b/movement/watch_faces/complication/wareki_face.c new file mode 100644 index 0000000..8b3cf5d --- /dev/null +++ b/movement/watch_faces/complication/wareki_face.c @@ -0,0 +1,230 @@ +#include +#include +#include "wareki_face.h" +#include "filesystem.h" +#include "watch_utility.h" + + +//Long press status flag +static bool _alarm_button_press; +static bool _light_button_press; + + +void wareki_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + + //printf("wareki_setup() \n"); + (void) settings; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(wareki_state_t)); + memset(*context_ptr, 0, sizeof(wareki_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + + //debug code + // watch_date_time datetime = watch_rtc_get_date_time(); + // datetime.unit.year = 2022 - WATCH_RTC_REFERENCE_YEAR; + // datetime.unit.month = 12; + // datetime.unit.day = 31; + // datetime.unit.hour = 23; + // datetime.unit.minute= 59; + // datetime.unit.second= 30; + // watch_rtc_set_date_time(datetime); + // settings->bit.clock_mode_24h = true; //24時間表記 + // settings->bit.to_interval = 1;//0=60sec 1=2m 2=5m 3=30m + // watch_store_backup_data(settings->reg, 0); + } + +} + + +// splash view +static void draw_wareki_splash(wareki_state_t *state) { + char buf[11]; + + watch_clear_colon(); + + sprintf(buf, "%s","wa ------"); + + watch_display_string(buf, 0); +} + + +//draw year and Japanese wareki +static void draw_year_and_wareki(wareki_state_t *state) { + char buf[11]; + + if(state->disp_year < REIWA_GANNEN){ + //Heisei + sprintf(buf, " h%2d%4d ",state->disp_year - HEISEI_GANNEN + 1,state->disp_year); + } + else{ + //Reiwa + sprintf(buf, " r%2d%4d ",state->disp_year - REIWA_GANNEN + 1 ,state->disp_year); + } + watch_display_string(buf, 0); +} + + +void wareki_activate(movement_settings_t *settings, void *context) { + + //printf("wareki_activate() \n"); + + (void) settings; + wareki_state_t *state = (wareki_state_t *)context; + + if (watch_tick_animation_is_running()) watch_stop_tick_animation(); + + state->active = false; + + _alarm_button_press = false; + _light_button_press = false; + + state->real_year = watch_rtc_get_date_time().unit.year + WATCH_RTC_REFERENCE_YEAR; + state->start_year = state->real_year; + state->disp_year = state->real_year; + + movement_request_tick_frequency(1); +} + + +void addYear(wareki_state_t* state,int count){ + + state->disp_year = state->disp_year + count; + + if(state->disp_year > REIWA_LIMIT ){ + state->disp_year = REIWA_LIMIT; + } + else{ + //watch_buzzer_play_note(BUZZER_NOTE_C8, 30); + } +} + + +void subYear(wareki_state_t* state,int count){ + + state->disp_year = state->disp_year - count; + + if(state->disp_year < 1989 ){ + state->disp_year = 1989; + } + else{ + //watch_buzzer_play_note(BUZZER_NOTE_C7, 30); + } +} + + +bool wareki_loop(movement_event_t event, movement_settings_t *settings, void *context) { + wareki_state_t *state = (wareki_state_t *)context; + + state->real_year = watch_rtc_get_date_time().unit.year + WATCH_RTC_REFERENCE_YEAR; + + if( state->real_year != state->start_year ){ + state->start_year = state->real_year; + state->disp_year = state->real_year; + } + + + switch (event.event_type) { + case EVENT_ACTIVATE: + draw_wareki_splash(state); + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + + case EVENT_LOW_ENERGY_UPDATE: + case EVENT_TICK: + + //printf("tick %d\n",state->disp_year ); + + if (_alarm_button_press && watch_get_pin_level(BTN_ALARM)){ + //printf("ALARM ON\n"); + } + else{ + //printf("ALARM OFF\n"); + _alarm_button_press = false; + } + + if (_light_button_press && watch_get_pin_level(BTN_LIGHT)){ + //printf("LIGHT ON\n"); + } + else{ + //printf("LIGHT OFF\n"); + _light_button_press = false; + } + + if (_alarm_button_press) { + addYear(state,1); + } + if (_light_button_press) { + subYear(state,1); + } + + draw_year_and_wareki(state); + + break; + + case EVENT_LIGHT_BUTTON_DOWN: + //printf("LIGHT DOWN\n"); + subYear(state,1); + break; + case EVENT_LIGHT_LONG_PRESS: + //printf("LIGHTPRESS \n"); + _light_button_press = true; + movement_request_tick_frequency(8); + break; + case EVENT_LIGHT_LONG_UP: + //printf("LIGHTPRESS UP\n"); + _light_button_press = false; + movement_request_tick_frequency(4); + case EVENT_LIGHT_BUTTON_UP: + //printf("LIGHT UP\n"); + _light_button_press = false; + movement_request_tick_frequency(4); + break; + case EVENT_ALARM_BUTTON_DOWN: + //printf("ALARM DOWN\n"); + addYear(state,1); + break; + case EVENT_ALARM_LONG_PRESS: + //printf("LONGPRESS \n"); + _alarm_button_press = true; + movement_request_tick_frequency(8); + break; + case EVENT_ALARM_LONG_UP: + //printf("LONGPRESS UP\n"); + _alarm_button_press = false; + movement_request_tick_frequency(4); + case EVENT_ALARM_BUTTON_UP: + //printf("ALARM UP\n"); + movement_request_tick_frequency(4); + break; + + case EVENT_TIMEOUT: + //printf("time out ! \n"); + movement_move_to_face(0); + + + break; + //case EVENT_LOW_ENERGY_UPDATE: + // If you did not resign in EVENT_TIMEOUT, you can use this event to update the display once a minute. + // Avoid displaying fast-updating values like seconds, since the display won't update again for 60 seconds. + // You should also consider starting the tick animation, to show the wearer that this is sleep mode: + // watch_start_tick_animation(500); + //break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + return true; +} + +void wareki_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + diff --git a/movement/watch_faces/complication/wareki_face.h b/movement/watch_faces/complication/wareki_face.h new file mode 100644 index 0000000..a07f3f6 --- /dev/null +++ b/movement/watch_faces/complication/wareki_face.h @@ -0,0 +1,34 @@ +#ifndef WAREKI_FACE_H_ +#define WAREKI_FACE_H_ + +#include "movement.h" + +#define REIWA_LIMIT 2018 + 31 +#define REIWA_GANNEN 2019 +#define HEISEI_GANNEN 1989 + +typedef struct { + bool active; + uint32_t disp_year; //Current displayed year + uint32_t start_year; //Year when this screen was launched + uint32_t real_year; //The actual current year +} wareki_state_t; + + +void wareki_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void wareki_activate(movement_settings_t *settings, void *context); +bool wareki_loop(movement_event_t event, movement_settings_t *settings, void *context); +void wareki_resign(movement_settings_t *settings, void *context); +void addYear(wareki_state_t* state,int count); +void subYear(wareki_state_t* state,int count); + +#define wareki_face ((const watch_face_t){ \ + wareki_setup, \ + wareki_activate, \ + wareki_loop, \ + wareki_resign, \ + NULL, \ +}) + +#endif // WAREKI_FACE_H_ + From e8ba59713139b7d720df616007a16521a9d4487c Mon Sep 17 00:00:00 2001 From: Hugo Chargois Date: Wed, 18 Sep 2024 04:04:00 +0200 Subject: [PATCH 217/220] Add Butterfly game face (#338) --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/butterfly_game_face.c | 462 ++++++++++++++++++ .../complication/butterfly_game_face.h | 125 +++++ 4 files changed, 589 insertions(+) create mode 100644 movement/watch_faces/complication/butterfly_game_face.c create mode 100644 movement/watch_faces/complication/butterfly_game_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 9bafa6d..8d045bf 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -132,6 +132,7 @@ SRCS += \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/sensor/minmax_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/butterfly_game_face.c \ ../watch_faces/complication/wareki_face.c \ ../watch_faces/complication/wordle_face.c \ ../watch_faces/complication/endless_runner_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index c0bcc75..1364ca9 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -107,6 +107,7 @@ #include "tuning_tones_face.h" #include "minmax_face.h" #include "kitchen_conversions_face.h" +#include "butterfly_game_face.h" #include "wareki_face.h" #include "wordle_face.h" #include "endless_runner_face.h" diff --git a/movement/watch_faces/complication/butterfly_game_face.c b/movement/watch_faces/complication/butterfly_game_face.c new file mode 100644 index 0000000..3c498a9 --- /dev/null +++ b/movement/watch_faces/complication/butterfly_game_face.c @@ -0,0 +1,462 @@ +/* + * MIT License + * + * Copyright (c) 2023 Hugo Chargois + * + * 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 "butterfly_game_face.h" + +static char butterfly_shapes[][3] = { + "[]", "][", "25", "52", "9e", "e9", "6a", "a6", "3E", "E3", "00", "HH", "88" +}; + +static int8_t single_beep[] = {BUZZER_NOTE_A7, 4, 0}; +static int8_t round_win_melody[] = { + BUZZER_NOTE_C6, 4, + BUZZER_NOTE_E6, 4, + BUZZER_NOTE_G6, 4, + BUZZER_NOTE_C7, 12, + 0}; +static int8_t round_lose_melody[] = { + BUZZER_NOTE_E6, 4, + BUZZER_NOTE_F6, 4, + BUZZER_NOTE_D6SHARP_E6FLAT, 4, + BUZZER_NOTE_C6, 12, + 0}; +static int8_t game_win_melody[] = { + BUZZER_NOTE_G6, 4, + BUZZER_NOTE_A6, 4, + BUZZER_NOTE_B6, 4, + BUZZER_NOTE_C7, 12, + BUZZER_NOTE_D7, 4, + BUZZER_NOTE_E7, 4, + BUZZER_NOTE_D7, 4, + BUZZER_NOTE_C7, 12, + BUZZER_NOTE_B6, 4, + BUZZER_NOTE_C7, 4, + BUZZER_NOTE_D7, 4, + BUZZER_NOTE_G7, 24, + 0}; + +#define NUM_SHAPES (sizeof(butterfly_shapes) / sizeof(butterfly_shapes[0])) + +#define POS_LEFT 4 +#define POS_CENTER 6 +#define POS_RIGHT 8 + +#define TICK_FREQ 8 +#define TICKS_PER_SHAPE 8 + +#define PLAYER_1 0 +#define PLAYER_2 1 + + +// returns a random integer r with 0 <= r < max +static inline uint8_t _get_rand(uint8_t max) { +#if __EMSCRIPTEN__ + return rand() % max; +#else + return arc4random_uniform(max); +#endif +} + +/* + * The game is built with a simple state machine where each state is called a + * "screen". Each screen can draw on the display and handles events, including + * the "activate" event, which is repurposed and sent whenever we move from one + * screen to another via the _transition_to function. Basically it's a mini + * movement inside movement. + */ +typedef bool (*screen_fn_t)(movement_event_t, butterfly_game_state_t*); + +static screen_fn_t cur_screen_fn; + +static bool _transition_to(screen_fn_t sf, butterfly_game_state_t *state) { + movement_event_t ev = {EVENT_ACTIVATE, 0}; + cur_screen_fn = sf; + return sf(ev, state); +} + +static uint8_t _pick_wrong_shape(butterfly_game_state_t *state, bool skip_wrong_shape) { + if (!skip_wrong_shape) { + // easy case, we only need to skip over 1 shape: the correct shape + uint8_t r = _get_rand(NUM_SHAPES-1); + if (r >= state->correct_shape) { + r++; + } + return r; + } else { + // a bit more complex, we need to skip over 2 shapes: the correct one + // and the current wrong one + uint8_t r = _get_rand(NUM_SHAPES-2); + uint8_t i1, i2; // the 2 indices to skip over, with i1 < i2 + if (state->correct_shape < state->current_shape) { + i1 = state->correct_shape; + i2 = state->current_shape; + } else { + i1 = state->current_shape; + i2 = state->correct_shape; + } + + if (r >= i1) { + r++; + } + if (r >= i2) { + r++; + } + + return r; + } +} + +static void _display_shape(uint8_t shape, uint8_t pos) { + watch_display_string(butterfly_shapes[shape], pos); +} + +static void _display_scores(butterfly_game_state_t *state) { + char buf[] = " "; + buf[0] = '0' + state->score_p1; + watch_display_string(buf, 0); + buf[0] = '0' + state->score_p2; + watch_display_string(buf, 3); +} + +static void _play_sound(butterfly_game_state_t *state, int8_t *seq) { + if (state->sound) watch_buzzer_play_sequence(seq, NULL); +} + +static bool _round_start_screen(movement_event_t event, butterfly_game_state_t *state); +static bool _reset_screen(movement_event_t event, butterfly_game_state_t *state); + +static bool _game_win_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = 4 * TICK_FREQ; + watch_clear_display(); + + if (state->score_p1 >= state->goal_score) { + watch_display_string("pl1 wins", 0); + } else { + watch_display_string("pl2 wins", 0); + } + _play_sound(state, game_win_melody); + + break; + case EVENT_TICK: + state->ctr--; + if (state->ctr == 0) { + return _transition_to(_reset_screen, state); + } + break; + } + return true; +} + +static bool _round_win_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = TICK_FREQ; + + if (state->round_winner == PLAYER_1) { + state->score_p1++; + } else { + state->score_p2++; + } + + watch_clear_display(); + _display_scores(state); + _display_shape(state->correct_shape, state->round_winner == PLAYER_1 ? POS_LEFT : POS_RIGHT); + _play_sound(state, round_win_melody); + break; + case EVENT_TICK: + state->ctr--; + if (state->ctr == 0) { + if (state->score_p1 >= state->goal_score || state->score_p2 >= state->goal_score) { + return _transition_to(_game_win_screen, state); + } + return _transition_to(_round_start_screen, state); + } + break; + } + return true; +} + +static bool _round_lose_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = TICK_FREQ; + + if (state->round_winner == PLAYER_1) { + if (state->score_p2 > 0) state->score_p2--; + } else { + if (state->score_p1 > 0) state->score_p1--; + } + + _display_shape(state->correct_shape, POS_CENTER); + _play_sound(state, round_lose_melody); + break; + case EVENT_TICK: + if (--state->ctr == 0) { + return _transition_to(_round_start_screen, state); + } + _display_shape(state->ctr%2 ? state->correct_shape : state->current_shape, POS_CENTER); + break; + + } + return true; +} + +static bool _correct_shape_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + _display_shape(state->correct_shape, POS_CENTER); + _play_sound(state, single_beep); + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->round_winner = PLAYER_1; + return _transition_to(_round_win_screen, state); + case EVENT_ALARM_BUTTON_DOWN: + state->round_winner = PLAYER_2; + return _transition_to(_round_win_screen, state); + } + return true; +} + +static bool _wrong_shape_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = TICKS_PER_SHAPE; + state->current_shape = _pick_wrong_shape(state, true); + _display_shape(state->current_shape, POS_CENTER); + _play_sound(state, single_beep); + break; + case EVENT_TICK: + if (--state->ctr == 0) { + if (--state->show_correct_shape_after == 0) { + return _transition_to(_correct_shape_screen, state); + } + return _transition_to(_wrong_shape_screen, state); + } + break; + case EVENT_LIGHT_BUTTON_DOWN: + state->round_winner = PLAYER_2; + return _transition_to(_round_lose_screen, state); + case EVENT_ALARM_BUTTON_DOWN: + state->round_winner = PLAYER_1; + return _transition_to(_round_lose_screen, state); + } + return true; +} + +static bool _first_wrong_shape_screen(movement_event_t event, butterfly_game_state_t *state) { + // the first of the wrong shape screens is a bit different than the next + // ones, for 2 reasons: + // * we can pick any shape except one (the correct shape); whereas in the + // subsequent wrong shape screens, we also must not pick the same wrong + // shape as the last + // * we don't act on the light/alarm button events; they would normally be + // a fail in a wrong shape screen, but in this case it may just be that + // the 2 players acknowledge the picked shape (in the previous screen) in + // quick succession, and we don't want the second player to immediately + // fail. + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = TICKS_PER_SHAPE; + state->current_shape = _pick_wrong_shape(state, false); + _display_shape(state->current_shape, POS_CENTER); + _play_sound(state, single_beep); + break; + case EVENT_TICK: + if (--state->ctr == 0) { + return _transition_to(_wrong_shape_screen, state); + } + break; + } + return true; +} + +static bool _round_start_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->correct_shape = _get_rand(NUM_SHAPES); + state->show_correct_shape_after = _get_rand(10) + 1; + watch_display_string(" - -", 0); + _display_scores(state); + _display_shape(state->correct_shape, POS_CENTER); + break; + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + watch_display_string(" ", 4); + return _transition_to(_first_wrong_shape_screen, state); + } + return true; +} + +static bool _goal_select_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + watch_clear_display(); + state->goal_score = 6; + break; + case EVENT_LIGHT_BUTTON_DOWN: + return _transition_to(_round_start_screen, state); + case EVENT_ALARM_BUTTON_DOWN: + state->goal_score += 3; + if (state->goal_score > 9) state->goal_score = 3; + break; + } + + char buf[] = "GOaL "; + buf[5] = '0' + state->goal_score; + watch_display_string(buf, 4); + return true; +} + +static bool _reset_screen(movement_event_t event, butterfly_game_state_t *state) { + state->score_p1 = 0; + state->score_p2 = 0; + + return _transition_to(_goal_select_screen, state); +} + +static bool _continue_select_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + watch_clear_display(); + + // no game in progress, start a new game + if (state->score_p1 == 0 && state->score_p2 == 0) { + return _transition_to(_goal_select_screen, state); + } + + state->cont = false; + break; + case EVENT_LIGHT_BUTTON_DOWN: + if (state->cont) { + return _transition_to(_round_start_screen, state); + } + return _transition_to(_reset_screen, state); + case EVENT_ALARM_BUTTON_DOWN: + state->cont = !state->cont; + break; + } + + if (state->cont) { + watch_display_string("Cont y", 4); + } else { + watch_display_string("Cont n", 4); + } + return true; +} + +static bool _sound_select_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + watch_clear_display(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + return _transition_to(_continue_select_screen, state); + case EVENT_ALARM_BUTTON_DOWN: + state->sound = !state->sound; + break; + } + + if (state->sound) { + watch_display_string("snd y", 5); + } else { + watch_display_string("snd n", 5); + } + return true; +} + +static bool _splash_screen(movement_event_t event, butterfly_game_state_t *state) { + switch (event.event_type) { + case EVENT_ACTIVATE: + state->ctr = TICK_FREQ; + + watch_clear_display(); + watch_display_string("Btrfly", 4); + break; + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + return _transition_to(_sound_select_screen, state); + case EVENT_TICK: + if (--state->ctr == 0) { + return _transition_to(_sound_select_screen, state); + } + break; + } + return true; +} + +void butterfly_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(butterfly_game_state_t)); + memset(*context_ptr, 0, sizeof(butterfly_game_state_t)); + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +#if __EMSCRIPTEN__ + // simulator only: seed the random number generator + time_t t; + srand((unsigned) time(&t)); +#endif +} + +void butterfly_game_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + + movement_request_tick_frequency(TICK_FREQ); +} + +bool butterfly_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + butterfly_game_state_t *state = (butterfly_game_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + return _transition_to(_splash_screen, state); + case EVENT_TICK: + case EVENT_LIGHT_BUTTON_DOWN: + case EVENT_ALARM_BUTTON_DOWN: + return (*cur_screen_fn)(event, state); + case EVENT_TIMEOUT: + movement_move_to_face(0); + return true; + default: + return movement_default_loop_handler(event, settings); + } +} + +void butterfly_game_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/complication/butterfly_game_face.h b/movement/watch_faces/complication/butterfly_game_face.h new file mode 100644 index 0000000..d02636e --- /dev/null +++ b/movement/watch_faces/complication/butterfly_game_face.h @@ -0,0 +1,125 @@ +/* + * MIT License + * + * Copyright (c) 2023 Hugo Chargois + * + * 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 BUTTERFLY_GAME_FACE_H_ +#define BUTTERFLY_GAME_FACE_H_ + +#include "movement.h" + +/* + * BUTTERFLY + * + * A GAME OF SHAPE RECOGNITION AND QUICK REFLEXES FOR 2 PLAYERS + * + * Setup + * ===== + * + * The game is played by 2 players, each using a distinct button: + * - player 1 plays with the LIGHT (upper left) button + * - player 2 plays with the ALARM (lower right) button + * + * To play, both players need a firm grip on the watch. A suggested method is to + * face each other, remove the watch from the wrist, and position it sideways + * between you. Hold one side of the strap in your preferred hand (right or + * left) and use your thumb to play. + * + * Start of the game + * ================= + * + * After the splash screen (BtrFly) is shown, the game proceeds through a couple + * configuration screens. Use ALARM to cycle through the possible values, and + * LIGHT to validate and move to the next screen. + * + * The configuration options are: + * + * - snd y/n Toggle sound effects on or off + * - goal 3/6/9 Choose to play a game of 3, 6 or 9 points + * - cont y/n Decide to continue an unfinished game or start a new one + * (this option appears only if a game is in progress) + * + * Rules + * ===== + * + * Prior to each round, a symmetrical shape composed of 2 characters is shown in + * the center of the screen. This shape, representing a butterfly's wings, is + * randomly chosen from a set of a dozen or so possible shapes. For example: + * + * ][ + * + * Memorize this shape! Your objective in the round will be to "catch" this + * "butterfly" by pressing your button before your opponent does. + * + * Once you believe you've memorized the shape, press your button. The round + * officially begins as soon as either player presses their button. + * + * Various "butterflies" will then appear on the screen, one after the other. + * The fastest player to press their button when the correct butterfly is shown + * wins the round. However, if a player presses their button when an incorrect + * butterfly is shown, they immediately lose the round. + * + * Scoring + * ======= + * + * The scores are displayed at the top of the screen at all times. + * + * When a round is won by a player, their score increases by one. When a round + * is lost by a player, their score decreases by one; unless they have a score + * of 0, in which case it remains unchanged. + * + * The game ends when a player reaches the set point goal (3, 6 or 9 points). + * + */ + +typedef struct { + bool cont : 1; // continue + bool sound : 1; + uint8_t goal_score : 4; + + // a generic ctr used by multiple states to display themselves for multiple frames + uint8_t ctr : 6; + + uint8_t correct_shape : 5; + uint8_t current_shape : 5; + uint8_t show_correct_shape_after : 5; + uint8_t round_winner : 1; + + uint8_t score_p1 : 5; + uint8_t score_p2 : 5; +} butterfly_game_state_t; + +void butterfly_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void butterfly_game_face_activate(movement_settings_t *settings, void *context); +bool butterfly_game_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void butterfly_game_face_resign(movement_settings_t *settings, void *context); + +#define butterfly_game_face ((const watch_face_t){ \ + butterfly_game_face_setup, \ + butterfly_game_face_activate, \ + butterfly_game_face_loop, \ + butterfly_game_face_resign, \ + NULL, \ +}) + +#endif // BUTTERFLY_GAME_FACE_H_ + From 88338dc0bae02305d8cbb2a282dc1eb40fd8ad79 Mon Sep 17 00:00:00 2001 From: joeycastillo Date: Tue, 17 Sep 2024 22:19:22 -0400 Subject: [PATCH 218/220] silence warnings in wareki_face --- movement/watch_faces/complication/wareki_face.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/movement/watch_faces/complication/wareki_face.c b/movement/watch_faces/complication/wareki_face.c index 8b3cf5d..620ef12 100644 --- a/movement/watch_faces/complication/wareki_face.c +++ b/movement/watch_faces/complication/wareki_face.c @@ -11,6 +11,7 @@ static bool _light_button_press; void wareki_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; //printf("wareki_setup() \n"); (void) settings; @@ -38,6 +39,7 @@ void wareki_setup(movement_settings_t *settings, uint8_t watch_face_index, void // splash view static void draw_wareki_splash(wareki_state_t *state) { + (void) state; char buf[11]; watch_clear_colon(); @@ -50,15 +52,15 @@ static void draw_wareki_splash(wareki_state_t *state) { //draw year and Japanese wareki static void draw_year_and_wareki(wareki_state_t *state) { - char buf[11]; + char buf[27]; if(state->disp_year < REIWA_GANNEN){ //Heisei - sprintf(buf, " h%2d%4d ",state->disp_year - HEISEI_GANNEN + 1,state->disp_year); + sprintf(buf, " h%2d%4d ", (int)state->disp_year - HEISEI_GANNEN + 1, (int)state->disp_year); } else{ //Reiwa - sprintf(buf, " r%2d%4d ",state->disp_year - REIWA_GANNEN + 1 ,state->disp_year); + sprintf(buf, " r%2d%4d ", (int)state->disp_year - REIWA_GANNEN + 1 , (int)state->disp_year); } watch_display_string(buf, 0); } @@ -176,6 +178,7 @@ bool wareki_loop(movement_event_t event, movement_settings_t *settings, void *co //printf("LIGHTPRESS UP\n"); _light_button_press = false; movement_request_tick_frequency(4); + break; case EVENT_LIGHT_BUTTON_UP: //printf("LIGHT UP\n"); _light_button_press = false; @@ -194,6 +197,7 @@ bool wareki_loop(movement_event_t event, movement_settings_t *settings, void *co //printf("LONGPRESS UP\n"); _alarm_button_press = false; movement_request_tick_frequency(4); + break; case EVENT_ALARM_BUTTON_UP: //printf("ALARM UP\n"); movement_request_tick_frequency(4); From c2103d9eaa4d2d38b9aa724dea72b4edb713870d Mon Sep 17 00:00:00 2001 From: joeycastillo Date: Tue, 17 Sep 2024 22:27:43 -0400 Subject: [PATCH 219/220] silence warnings in butterfly_game_face --- movement/watch_faces/complication/butterfly_game_face.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/movement/watch_faces/complication/butterfly_game_face.c b/movement/watch_faces/complication/butterfly_game_face.c index 3c498a9..fb13b97 100644 --- a/movement/watch_faces/complication/butterfly_game_face.c +++ b/movement/watch_faces/complication/butterfly_game_face.c @@ -338,6 +338,8 @@ static bool _goal_select_screen(movement_event_t event, butterfly_game_state_t * } static bool _reset_screen(movement_event_t event, butterfly_game_state_t *state) { + (void) event; + state->score_p1 = 0; state->score_p2 = 0; @@ -416,6 +418,8 @@ static bool _splash_screen(movement_event_t event, butterfly_game_state_t *state void butterfly_game_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { *context_ptr = malloc(sizeof(butterfly_game_state_t)); memset(*context_ptr, 0, sizeof(butterfly_game_state_t)); @@ -431,6 +435,7 @@ void butterfly_game_face_setup(movement_settings_t *settings, uint8_t watch_face void butterfly_game_face_activate(movement_settings_t *settings, void *context) { (void) settings; + (void) context; movement_request_tick_frequency(TICK_FREQ); } From e8f31beb70d0574b7c402f50009fc01643b463e6 Mon Sep 17 00:00:00 2001 From: Jeremy O'Brien Date: Tue, 17 Sep 2024 22:36:34 -0400 Subject: [PATCH 220/220] Smallchess face (#272) * smallchess face * use correct game-state modifying board move function * make show last work after undo * use SCL_Game->ply instead of board[ply_byte] * beep when cpu is done computing a move * increase engine strength to ply 3 * match ply type and use the local variable where available * fix warnings * add doc to smallchess face * smallchess: fix compile warnings * smallchess: move smallchesslib.h to movement/lib --- movement/lib/smallchesslib/smallchesslib.h | 3704 +++++++++++++++++ movement/make/Makefile | 2 + movement/movement_faces.h | 1 + .../complication/smallchess_face.c | 504 +++ .../complication/smallchess_face.h | 90 + 5 files changed, 4301 insertions(+) create mode 100644 movement/lib/smallchesslib/smallchesslib.h create mode 100644 movement/watch_faces/complication/smallchess_face.c create mode 100644 movement/watch_faces/complication/smallchess_face.h diff --git a/movement/lib/smallchesslib/smallchesslib.h b/movement/lib/smallchesslib/smallchesslib.h new file mode 100644 index 0000000..7ac8542 --- /dev/null +++ b/movement/lib/smallchesslib/smallchesslib.h @@ -0,0 +1,3704 @@ +#ifndef SMALLCHESSLIB_H +#define SMALLCHESSLIB_H + +/** + @file smallchesslib.h + + Small and simple single header C99 public domain chess library and engine. + + author: Miloslav Ciz (drummyfish) + license: CC0 1.0 (public domain) + found at https://creativecommons.org/publicdomain/zero/1.0/ + + additional waiver of all IP + version: 0.8d + + Default notation format for this library is a coordinate one, i.e. + + squarefrom squareto [promotedpiece] + + e.g.: e2e4 or A2A1q + + This work's goal is to never be encumbered by any exclusive intellectual + property rights. The work is therefore provided under CC0 1.0 + additional + WAIVER OF ALL INTELLECTUAL PROPERTY RIGHTS that waives the rest of + intellectual property rights not already waived by CC0 1.0. The WAIVER OF ALL + INTELLECTUAL PROPERTY RGHTS is as follows: + + Each contributor to this work agrees that they waive any exclusive rights, + including but not limited to copyright, patents, trademark, trade dress, + industrial design, plant varieties and trade secrets, to any and all ideas, + concepts, processes, discoveries, improvements and inventions conceived, + discovered, made, designed, researched or developed by the contributor either + solely or jointly with others, which relate to this work or result from this + work. Should any waiver of such right be judged legally invalid or + ineffective under applicable law, the contributor hereby grants to each + affected person a royalty-free, non transferable, non sublicensable, non + exclusive, irrevocable and unconditional license to this right. +*/ + +#include + +#ifndef SCL_DEBUG_AI +/** AI will print out a Newick-like tree of searched moves. */ + #define SCL_DEBUG_AI 0 +#endif + +/** + Maximum number of moves a chess piece can have (a queen in the middle of the + board). +*/ +#define SCL_CHESS_PIECE_MAX_MOVES 25 +#define SCL_BOARD_SQUARES 64 + +typedef uint8_t (*SCL_RandomFunction)(void); + +#if SCL_COUNT_EVALUATED_POSITIONS + uint32_t SCL_positionsEvaluated = 0; /**< If enabled by + SCL_COUNT_EVALUATED_POSITIONS, this + will increment with every + dynamically evaluated position (e.g. + when AI computes its move). */ +#endif + +#ifndef SCL_CALL_WDT_RESET + #define SCL_CALL_WDT_RESET 0 /**< Option that should be enabled on some + Arduinos. If 1, call to watchdog timer + reset will be performed during dynamic + evaluation (without it if AI takes long the + program will reset). */ +#endif + +/** + Returns a pseudorandom byte. This function has a period 256 and returns each + possible byte value exactly once in the period. +*/ +uint8_t SCL_randomSimple(void); +void SCL_randomSimpleSeed(uint8_t seed); + +/** + Like SCL_randomSimple, but internally uses a 16 bit value, so the period is + 65536. +*/ +uint8_t SCL_randomBetter(void); +void SCL_randomBetterSeed(uint16_t seed); + +#ifndef SCL_EVALUATION_FUNCTION + /** + If defined, AI will always use the static evaluation function with this + name. This helps avoid pointers to functions and can be faster but the + function can't be changed at runtime. + */ + #define SCL_EVALUATION_FUNCTION + #undef SCL_EVALUATION_FUNCTION +#endif + +#ifndef SCL_960_CASTLING + /** + If set, chess 960 (Fisher random) castling will be considered by the library + rather than normal castling. 960 castling is slightly different (e.g. + requires the inital rook positions to be stored in board state). The + castling move is performed as "capturing own rook". + */ + #define SCL_960_CASTLING 0 +#endif + +#ifndef SCL_ALPHA_BETA + /** + Turns alpha-beta pruning (AI optimization) on or off. This can gain + performance and should normally be turned on. AI behavior should not + change at all. + */ + #define SCL_ALPHA_BETA 1 +#endif + +/** + A set of game squares as a bit array, each bit representing one game square. + Useful for representing e.g. possible moves. To easily iterate over the set + use provided macros (SCL_SQUARE_SET_ITERATE, ...). +*/ +typedef uint8_t SCL_SquareSet[8]; + +#define SCL_SQUARE_SET_EMPTY {0, 0, 0, 0, 0, 0, 0, 0} + +void SCL_squareSetClear(SCL_SquareSet squareSet); +void SCL_squareSetAdd(SCL_SquareSet squareSet, uint8_t square); +uint8_t SCL_squareSetContains(const SCL_SquareSet squareSet, uint8_t square); +uint8_t SCL_squareSetSize(const SCL_SquareSet squareSet); +uint8_t SCL_squareSetEmpty(const SCL_SquareSet squareSet); + +/** + Returns a random square from a square set. +*/ +uint8_t SCL_squareSetGetRandom(const SCL_SquareSet squareSet, + SCL_RandomFunction randFunc); + +#define SCL_SQUARE_SET_ITERATE_BEGIN(squareSet) \ + { uint8_t iteratedSquare = 0;\ + uint8_t iterationEnd = 0;\ + for (int8_t _i = 0; _i < 8 && !iterationEnd; ++_i) {\ + uint8_t _row = squareSet[_i];\ + if (_row == 0) { iteratedSquare += 8; continue; }\ + \ + for (uint8_t _j = 0; _j < 8 && !iterationEnd; ++_j) {\ + if (_row & 0x01) { +/* + Between SCL_SQUARE_SET_ITERATE_BEGIN and _END iteratedSquare variable + represents the next square contained in the set. To break out of the + iteration set iterationEnd to 1. +*/ + +#define SCL_SQUARE_SET_ITERATE_END }\ + _row >>= 1;\ + iteratedSquare++;}\ + } /*for*/ } + +#define SCL_SQUARE_SET_ITERATE(squareSet,command)\ + SCL_SQUARE_SET_ITERATE_BEGIN(squareSet)\ + { command }\ + SCL_SQUARE_SET_ITERATE_END + +#define SCL_BOARD_STATE_SIZE 69 + +/** + Represents chess board state as a string in this format: + - First 64 characters represent the chess board (A1, B1, ... H8), each field + can be either a piece (PRNBKQprnbkq) or empty ('.'). I.e. the state looks + like this: + + 0 (A1) RNBQKBNR + PPPPPPPP + ........ + ........ + ........ + ........ + pppppppp + rnbqkbnr 63 (H8) + + - After this more bytes follow to represent global state, these are: + - 64: bits holding en-passant and castling related information: + - bits 0-3 (lsb): Column of the pawn that can, in current turn, be + taken by en-passant (0xF means no pawn can be taken this way). + - bit 4: Whether white is not prevented from short castling by previous + king or rook movement. + - bit 5: Same as 4, but for long castling. + - bit 6: Same as 4, but for black. + - bit 7: Same as 4, but for black and long castling. + - 65: Number saying the number of ply (half-moves) that have already been + played, also determining whose turn it currently is. + - 66: Move counter used in the 50 move rule, says the number of ply since + the last pawn move or capture. + - 67: Extra byte, left for storing additional info in variants. For normal + chess this byte should always be 0. + - 68: The last byte is always 0 to properly terminate the string in case + someone tries to print it. + - The state is designed so as to be simple and also print-friendly, i.e. you + can simply print it with line break after 8 characters to get a human + readable representation of the board. + + NOTE: there is a much more compact representation which however trades some + access speed which would affect the AI performance and isn't print friendly, + so we don't use it. In it each square takes 4 bits, using 15 out of 16 + possible values (empty square and W and B pieces including 2 types of pawns, + one "en-passant takeable"). Then only one extra byte needed is for castling + info (4 bits) and ply count (4 bits). +*/ +typedef char SCL_Board[SCL_BOARD_STATE_SIZE]; + +#define SCL_BOARD_ENPASSANT_CASTLE_BYTE 64 +#define SCL_BOARD_PLY_BYTE 65 +#define SCL_BOARD_MOVE_COUNT_BYTE 66 +#define SCL_BOARD_EXTRA_BYTE 67 + +#if SCL_960_CASTLING + #define _SCL_EXTRA_BYTE_VALUE (0 | (7 << 3)) // rooks on classic positions +#else + #define _SCL_EXTRA_BYTE_VALUE 0 +#endif + +#define SCL_BOARD_START_STATE \ + {82, 78, 66, 81, 75, 66, 78, 82,\ + 80, 80, 80, 80, 80, 80, 80, 80,\ + 46, 46, 46, 46, 46, 46, 46, 46,\ + 46, 46, 46, 46, 46, 46, 46, 46,\ + 46, 46, 46, 46, 46, 46, 46, 46,\ + 46, 46, 46, 46, 46, 46, 46, 46,\ + 112,112,112,112,112,112,112,112,\ + 114,110,98, 113,107,98, 110,114,\ + (char) 0xff,0,0,_SCL_EXTRA_BYTE_VALUE,0} + +#define SCL_FEN_START \ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + +#define SCL_FEN_HORDE \ + "ppp2ppp/pppppppp/pppppppp/pppppppp/3pp3/8/PPPPPPPP/RNBQKBNR w KQ - 0 1" + +#define SCL_FEN_UPSIDE_DOWN \ + "RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1" + +#define SCL_FEN_PEASANT_REVOLT \ + "1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1" + +#define SCL_FEN_ENDGAME \ + "4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1" + +#define SCL_FEN_KNIGHTS \ + "N6n/1N4n1/2N2n2/3Nn3/k2nN2K/2n2N2/1n4N1/n6N w - - 0 1" + +/** + Holds an info required to undo a single move. +*/ +typedef struct +{ + uint8_t squareFrom; ///< start square + uint8_t squareTo; ///< target square + char enPassantCastle; ///< previous en passant/castle byte + char moveCount; ///< previous values of the move counter byte + uint8_t other; /**< lowest 7 bits: previous value of target square, + highest bit: if 1 then the move was promotion or + en passant */ +} SCL_MoveUndo; + +#define SCL_GAME_STATE_PLAYING 0x00 +#define SCL_GAME_STATE_WHITE_WIN 0x01 +#define SCL_GAME_STATE_BLACK_WIN 0x02 +#define SCL_GAME_STATE_DRAW 0x10 ///< further unspecified draw +#define SCL_GAME_STATE_DRAW_STALEMATE 0x11 ///< draw by stalemate +#define SCL_GAME_STATE_DRAW_REPETITION 0x12 ///< draw by repetition +#define SCL_GAME_STATE_DRAW_50 0x13 ///< draw by 50 move rule +#define SCL_GAME_STATE_DRAW_DEAD 0x14 ///< draw by dead position +#define SCL_GAME_STATE_END 0xff ///< end without known result + +/** + Converts square in common notation (e.g. 'c' 8) to square number. Only accepts + lowercase column. +*/ +#define SCL_SQUARE(colChar,rowInt) (((rowInt) - 1) * 8 + ((colChar) - 'a')) +#define SCL_S(c,r) SCL_SQUARE(c,r) + +void SCL_boardInit(SCL_Board board); +void SCL_boardCopy(const SCL_Board boardFrom, SCL_Board boardTo); + +/** + Initializes given chess 960 (Fisher random) position. If SCL_960_CASTLING + is not set, castling will be disabled by this function. +*/ +void SCL_boardInit960(SCL_Board board, uint16_t positionNumber); + +void SCL_boardDisableCastling(SCL_Board board); + +uint32_t SCL_boardHash32(const SCL_Board board); + +#define SCL_PHASE_OPENING 0 +#define SCL_PHASE_MIDGAME 1 +#define SCL_PHASE_ENDGAME 2 + +/** + Estimates the game phase: opening, midgame or endgame. +*/ +uint8_t SCL_boardEstimatePhase(SCL_Board board); + +/** + Sets the board position. The input string should be 64 characters long zero + terminated C string representing the board as squares A1, A2, ..., H8 with + each char being either a piece (RKBKQPrkbkqp) or an empty square ('.'). +*/ +void SCL_boardSetPosition(SCL_Board board, const char *pieces, + uint8_t castlingEnPassant, uint8_t moveCount, uint8_t ply); + +uint8_t SCL_boardsDiffer(SCL_Board b1, SCL_Board b2); + +/** + Function that prints out a single character. This is passed to printing + functions. +*/ +typedef void (*SCL_PutCharFunction)(char); + +/** + Gets a random move on given board for the player whose move it is. +*/ +void SCL_boardRandomMove(SCL_Board board, SCL_RandomFunction randFunc, + uint8_t *squareFrom, uint8_t *squareTo, char *resultProm); + +#define SCL_FEN_MAX_LENGTH 90 + +/** + Converts a position to FEN (Forsyth–Edwards Notation) string. The string has + to have at least SCL_FEN_MAX_LENGTH bytes allocated to guarantee the + function won't write to unallocated memory. The string will be terminated by + 0 (this is included in SCL_FEN_MAX_LENGTH). The number of bytes written + (including the terminating 0) is returned. +*/ +uint8_t SCL_boardToFEN(SCL_Board board, char *string); + +/** + Loads a board from FEN (Forsyth–Edwards Notation) string. Returns 1 on + success, 0 otherwise. XFEN isn't supported fully but a start position in + chess960 can be loaded with this function. +*/ +uint8_t SCL_boardFromFEN(SCL_Board board, const char *string); + +/** + Returns an approximate/heuristic board rating as a number, 0 meaning equal + chances for both players, positive favoring white, negative favoring black. +*/ +typedef int16_t (*SCL_StaticEvaluationFunction)(SCL_Board); + +/* + NOTE: int8_t as a return value was tried for evaluation function, which would + be simpler, but it fails to capture important non-material position + differences counted in fractions of pawn values, hence we have to use int16_t. +*/ + +/** + Basic static evaluation function. WARNING: this function supposes a standard + chess game, for non-standard positions it may either not work well or even + crash the program. You should use a different function for non-standard games. +*/ +int16_t SCL_boardEvaluateStatic(SCL_Board board); + +/** + Dynamic evaluation function (search), i.e. unlike SCL_boardEvaluateStatic, + this one performs a recursive search for deeper positions to get a more + accurate score. Of course, this is much slower and hugely dependent on + baseDepth (you mostly want to keep this under 5). +*/ +int16_t SCL_boardEvaluateDynamic(SCL_Board board, uint8_t baseDepth, + uint8_t extensionExtraDepth, SCL_StaticEvaluationFunction evalFunction); + +#define SCL_EVALUATION_MAX_SCORE 32600 // don't increase this, we need a margin + +/** + Checks if the board position is dead, i.e. mate is impossible (e.g. due to + insufficient material), which by the rules results in a draw. WARNING: This + function may fail to detect some dead positions as this is a non-trivial task. +*/ +uint8_t SCL_boardDead(SCL_Board board); + +/** + Tests whether given player is in check. +*/ +uint8_t SCL_boardCheck(SCL_Board board, uint8_t white); + +/** + Checks whether given move resets the move counter (used in the 50 move rule). +*/ +uint8_t SCL_boardMoveResetsCount(SCL_Board board, + uint8_t squareFrom, uint8_t squareTo); + +uint8_t SCL_boardMate(SCL_Board board); + +/** + Performs a move on a board WITHOUT checking if the move is legal. Returns an + info with which the move can be undone. +*/ +SCL_MoveUndo SCL_boardMakeMove(SCL_Board board, uint8_t squareFrom, uint8_t squareTo, + char promotePiece); + +void SCL_boardUndoMove(SCL_Board board, SCL_MoveUndo moveUndo); + +/** + Checks if the game is over, i.e. the current player to move has no legal + moves, the game is in dead position etc. +*/ +uint8_t SCL_boardGameOver(SCL_Board board); + +/** + Checks if given move is legal. +*/ +uint8_t SCL_boardMoveIsLegal(SCL_Board board, uint8_t squareFrom, + uint8_t squareTo); + +/** + Checks if the player to move has at least one legal move. +*/ +uint8_t SCL_boardMovePossible(SCL_Board board); + +#define SCL_POSITION_NORMAL 0x00 +#define SCL_POSITION_CHECK 0x01 +#define SCL_POSITION_MATE 0x02 +#define SCL_POSITION_STALEMATE 0x03 +#define SCL_POSITION_DEAD 0x04 + +uint8_t SCL_boardGetPosition(SCL_Board board); + +/** + Returns 1 if the square is attacked by player of given color. This is used to + examine checks, so for performance reasons the functions only checks whether + or not the square is attacked (not the number of attackers). +*/ +uint8_t SCL_boardSquareAttacked(SCL_Board board, uint8_t square, + uint8_t byWhite); + +/** + Gets pseudo moves of a piece: all possible moves WITHOUT eliminating moves + that lead to own check. To get only legal moves use SCL_boardGetMoves. +*/ +void SCL_boardGetPseudoMoves( + SCL_Board board, + uint8_t pieceSquare, + uint8_t checkCastling, + SCL_SquareSet result); + +/** + Gets all legal moves of given piece. +*/ +void SCL_boardGetMoves( + SCL_Board board, + uint8_t pieceSquare, + SCL_SquareSet result); + +void _SCL_board960RememberRookPositions(SCL_Board board); +void _SCL_boardPlaceOnNthAvailable(SCL_Board board, uint8_t pos, char piece); +void _SCL_handleRookActivity(SCL_Board board, uint8_t rookSquare); +void SCL_printSquareSet(SCL_SquareSet set, SCL_PutCharFunction putCharFunc); +int16_t _SCL_rateKingEndgamePosition(uint8_t position); +int16_t _SCL_boardEvaluateDynamic(SCL_Board board, int8_t depth, int16_t alphaBeta, int8_t takenSquare); + +static inline uint8_t SCL_boardWhitesTurn(SCL_Board board); + +static inline uint8_t SCL_pieceIsWhite(char piece); +static inline uint8_t SCL_squareIsWhite(uint8_t square); +char SCL_pieceToColor(uint8_t piece, uint8_t toWhite); + +/** + Converts square coordinates to square number. Each coordinate must be a number + <1,8>. Validity of the coordinates is NOT checked. +*/ +static inline uint8_t SCL_coordsToSquare(uint8_t row, uint8_t column); + +#ifndef SCL_VALUE_PAWN + #define SCL_VALUE_PAWN 256 +#endif + +#ifndef SCL_VALUE_KNIGHT + #define SCL_VALUE_KNIGHT 768 +#endif + +#ifndef SCL_VALUE_BISHOP + #define SCL_VALUE_BISHOP 800 +#endif + +#ifndef SCL_VALUE_ROOK + #define SCL_VALUE_ROOK 1280 +#endif + +#ifndef SCL_VALUE_QUEEN + #define SCL_VALUE_QUEEN 2304 +#endif + +#ifndef SCL_VALUE_KING + #define SCL_VALUE_KING 0 +#endif + +#define SCL_ENDGAME_MATERIAL_LIMIT \ + (2 * (SCL_VALUE_PAWN * 4 + SCL_VALUE_QUEEN + \ + SCL_VALUE_KING + SCL_VALUE_ROOK + SCL_VALUE_KNIGHT)) + +#define SCL_START_MATERIAL \ + (16 * SCL_VALUE_PAWN + 4 * SCL_VALUE_ROOK + 4 * SCL_VALUE_KNIGHT + \ + 4 * SCL_VALUE_BISHOP + 2 * SCL_VALUE_QUEEN + 2 * SCL_VALUE_KING) + +#ifndef SCL_RECORD_MAX_LENGTH + #define SCL_RECORD_MAX_LENGTH 256 +#endif + +#define SCL_RECORD_MAX_SIZE (SCL_RECORD_MAX_LENGTH * 2) + +/** + Records a single chess game. The format is following: + + Each record item consists of 2 bytes which record a single move (ply): + + abxxxxxx cdyyyyyy + + xxxxxx Start square of the move, counted as A0, A1, ... + yyyyyy End square of the move in the same format as the start square. + ab 00 means this move isn't the last move of the game, other possible + values are 01: white wins, 10: black wins, 11: draw or end for + other reasons. + cd In case of pawn promotion move this encodes the promoted piece as + 00: queen, 01: rook, 10: bishop, 11: knight (pawn isn't allowed by + chess rules). + + Every record should be ended by an ending move (ab != 00), empty record should + have one move where xxxxxx == yyyyyy == 0 and ab == 11. +*/ +typedef uint8_t SCL_Record[SCL_RECORD_MAX_SIZE]; + +#define SCL_RECORD_CONT 0x00 +#define SCL_RECORD_W_WIN 0x40 +#define SCL_RECORD_B_WIN 0x80 +#define SCL_RECORD_END 0xc0 + +#define SCL_RECORD_PROM_Q 0x00 +#define SCL_RECORD_PROM_R 0x40 +#define SCL_RECORD_PROM_B 0x80 +#define SCL_RECORD_PROM_N 0xc0 + +#define SCL_RECORD_ITEM(s0,s1,p,e) ((e) | (s0)),((p) | (s1)) + +void SCL_recordInit(SCL_Record r); + +void SCL_recordCopy(SCL_Record recordFrom, SCL_Record recordTo); + +/** + Represents a complete game of chess (or a variant with different staring + position). This struct along with associated functions allows to easily + implement a chess game that allows undoing moves, detecting draws, recording + the moves etc. On platforms with extremely little RAM one can reduce + SCL_RECORD_MAX_LENGTH to reduce the size of this struct (which will however + possibly limit how many moves can be undone). +*/ +typedef struct +{ + SCL_Board board; + SCL_Record record; /**< Holds the game record. This record is here + firstly because games are usually recorded and + secondly this allows undoing moves up to the + beginning of the game. This infinite undoing will + only work as long as the record is able to hold + the whole game; if the record is full, undoing is + no longet possible. */ + uint16_t state; + uint16_t ply; ///< ply count (board ply counter is only 8 bit) + + uint32_t prevMoves[14]; ///< stores last moves, for repetition detection + + const char *startState; /**< Optional pointer to the starting board state. + If this is null, standard chess start position is + assumed. This is needed for undoing moves with + game record. */ +} SCL_Game; + +/** + Initializes a new chess game. The startState parameter is optional and allows + for setting up chess variants that differ by starting positions, setting this + to 0 will assume traditional starting position. WARNING: if startState is + provided, the pointed to board mustn't be deallocated afterwards, the string + is not internally copied (for memory saving reasons). +*/ +void SCL_gameInit(SCL_Game *game, const SCL_Board startState); + +void SCL_gameMakeMove(SCL_Game *game, uint8_t squareFrom, uint8_t squareTo, + char promoteTo); + +uint8_t SCL_gameUndoMove(SCL_Game *game); + +/** + Gets a move which if played now would cause a draw by repetition. Returns 1 + if such move exists, 0 otherwise. The results parameters can be set to 0 in + which case they will be ignored and only the existence of a draw move will be + tested. +*/ +uint8_t SCL_gameGetRepetiotionMove(SCL_Game *game, + uint8_t *squareFrom, uint8_t *squareTo); + +/** + Leads a game record from PGN string. The function will probably not strictly + adhere to the PGN input format, but should accept most sanely written PGN + strings. +*/ +void SCL_recordFromPGN(SCL_Record r, const char *pgn); + +uint16_t SCL_recordLength(const SCL_Record r); + +/** + Gets the move out of a game record, returns the end state of the move + (SCL_RECORD_CONT, SCL_RECORD_END etc.) +*/ +uint8_t SCL_recordGetMove(const SCL_Record r, uint16_t index, + uint8_t *squareFrom, uint8_t *squareTo, char *promotedPiece); + +/** + Adds another move to the game record. Terminating the record is handled so + that the last move is always marked with end flag, endState is here to only + indicate possible game result (otherwise pass SCL_RECORD_CONT). Returns 1 if + the item was added, otherwise 0 (replay was already of maximum size). +*/ +uint8_t SCL_recordAdd(SCL_Record r, uint8_t squareFrom, + uint8_t squareTo, char promotePiece, uint8_t endState); + +/** + Removes the last move from the record, returns 1 if the replay is non-empty + after the removal, otherwise 0. +*/ +uint8_t SCL_recordRemoveLast(SCL_Record r); + +/** + Applies given number of half-moves (ply) to a given board (the board is + automatically initialized at the beginning). +*/ +void SCL_recordApply(const SCL_Record r, SCL_Board b, uint16_t moves); + +int16_t SCL_pieceValue(char piece); +int16_t SCL_pieceValuePositive(char piece); + +#define SCL_PRINT_FORMAT_NONE 0 +#define SCL_PRINT_FORMAT_NORMAL 1 +#define SCL_PRINT_FORMAT_COMPACT 2 +#define SCL_PRINT_FORMAT_UTF8 3 +#define SCL_PRINT_FORMAT_COMPACT_UTF8 4 + +/** + Gets the best move for the currently moving player as computed by AI. The + return value is the value of the move (with the same semantics as the value + of an evaluation function). baseDepth is depth in plys to which all moves will + be checked. If baseDepth 0 is passed, the function makes a random move and + returns the evaluation of the board. extensionExtraDepth is extra depth for + checking specific situations like exchanges and checks. endgameExtraDepth is + extra depth which is added to baseDepth in the endgame. If the randomness + function is 0, AI will always make the first best move it finds, if it is + not 0 and randomness is 0, AI will randomly pick between the equally best + moves, if it is not 0 and randomness is positive, AI will randomly choose + between best moves with some bias (may not pick the best rated move). +*/ +int16_t SCL_getAIMove( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + uint8_t endgameExtraDepth, + SCL_StaticEvaluationFunction evalFunc, + SCL_RandomFunction randFunc, + uint8_t randomness, + uint8_t repetitionMoveFrom, + uint8_t repetitionMoveTo, + uint8_t *resultFrom, + uint8_t *resultTo, + char *resultProm); + +/** + Prints given chessboard using given format and an abstract printing function. +*/ +void SCL_printBoard( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + SCL_SquareSet highlightSquares, + uint8_t selectSquare, + uint8_t format, + uint8_t offset, + uint8_t labels, + uint8_t blackDown); + +void SCL_printBoardSimple( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + uint8_t selectSquare, + uint8_t format); + +void SCL_printSquareUTF8(uint8_t square, SCL_PutCharFunction putCharFunc); +void SCL_printPGN(SCL_Record r, SCL_PutCharFunction putCharFunc, + SCL_Board initialState); + +/** + Reads a move from string (the notation format is described at the top of this + file). The function is safe as long as the string is 0 terminated. Returns 1 + on success or 0 on fail (invalid move string). +*/ +uint8_t SCL_stringToMove(const char *moveString, uint8_t *resultFrom, + uint8_t *resultTo, char *resultPromotion); + +char *SCL_moveToString(SCL_Board board, uint8_t s0, uint8_t s1, + char promotion, char *string); + +/** + Function used in drawing, it is called to draw the next pixel. The first + parameter is the pixel color, the second one if the sequential number of the + pixel. +*/ +typedef void (*SCL_PutPixelFunction)(uint8_t, uint16_t); + +#define SCL_BOARD_PICTURE_WIDTH 64 + +/** + Draws a simple 1bit 64x64 pixels board using a provided abstract function for + drawing pixels. The function renders from top left to bottom right, i.e. no + frame buffer is required. +*/ +void SCL_drawBoard( + SCL_Board board, + SCL_PutPixelFunction putPixel, + uint8_t selectedSquare, + SCL_SquareSet highlightSquares, + uint8_t blackDown); + +/** + Converts square number to string representation (e.g. "d2"). This function + will modify exactly the first two bytes of the provided string. +*/ +static inline char *SCL_squareToString(uint8_t square, char *string); + +/** + Converts a string, such as "A1" or "b4", to square number. The string must + start with a letter (lower or upper case) and be followed by a number <1,8>. + Validity of the string is NOT checked. +*/ +uint8_t SCL_stringToSquare(const char *square); + +//============================================================================= +// privates: + +#define SCL_UNUSED(v) (void)(v) + +uint8_t SCL_currentRandom8 = 0; + +uint16_t SCL_currentRandom16 = 0; + +void SCL_randomSimpleSeed(uint8_t seed) +{ + SCL_currentRandom8 = seed; +} + +uint8_t SCL_randomSimple(void) +{ + SCL_currentRandom8 *= 13; + SCL_currentRandom8 += 7; + return SCL_currentRandom8; +} + +uint8_t SCL_randomBetter(void) +{ + SCL_currentRandom16 *= 13; + SCL_currentRandom16 += 7; + return (SCL_currentRandom16 % 256) ^ (SCL_currentRandom16 / 256); +} + +void SCL_randomBetterSeed(uint16_t seed) +{ + SCL_currentRandom16 = seed; +} + +void SCL_squareSetClear(SCL_SquareSet squareSet) +{ + for (uint8_t i = 0; i < 8; ++i) + squareSet[i] = 0; +} + +uint8_t SCL_stringToSquare(const char *square) +{ + return (square[1] - '1') * 8 + + (square[0] - ((square[0] >= 'A' && square[0] <= 'Z') ? 'A' : 'a')); +} + +char *SCL_moveToString(SCL_Board board, uint8_t s0, uint8_t s1, + char promotion, char *string) +{ + char *result = string; + + SCL_squareToString(s0,string); + string += 2; + string = SCL_squareToString(s1,string); + string += 2; + + char c = board[s0]; + + if (c == 'p' || c == 'P') + { + uint8_t rank = s1 / 8; + + if (rank == 0 || rank == 7) + { + *string = promotion; + string++; + } + } + + *string = 0; + + return result; +} + +uint8_t SCL_boardWhitesTurn(SCL_Board board) +{ + return (board[SCL_BOARD_PLY_BYTE] % 2) == 0; +} + +uint8_t SCL_coordsToSquare(uint8_t row, uint8_t column) +{ + return row * 8 + column; +} + +uint8_t SCL_pieceIsWhite(char piece) +{ + return piece < 'a'; +} + +char *SCL_squareToString(uint8_t square, char *string) +{ + string[0] = 'a' + square % 8; + string[1] = '1' + square / 8; + + return string; +} + +uint8_t SCL_squareIsWhite(uint8_t square) +{ + return (square % 2) != ((square / 8) % 2); +} + +char SCL_pieceToColor(uint8_t piece, uint8_t toWhite) +{ + return (SCL_pieceIsWhite(piece) == toWhite) ? + piece : (piece + (toWhite ? -32 : 32)); +} + +/** + Records the rook starting positions in the board state. This is required in + chess 960 in order to be able to correctly perform castling (castling rights + knowledge isn't enough as one rook might have moved to the other side and we + wouldn't know which one can castle and which not). +*/ +void _SCL_board960RememberRookPositions(SCL_Board board) +{ + uint8_t pos = 0; + uint8_t rooks = 2; + + while (pos < 8 && rooks != 0) + { + if (board[pos] == 'R') + { + board[SCL_BOARD_EXTRA_BYTE] = rooks == 2 ? pos : + (board[SCL_BOARD_EXTRA_BYTE] | (pos << 3)); + + rooks--; + } + + pos++; + } +} + +void SCL_boardInit(SCL_Board board) +{ + /* + We might use SCL_BOARD_START_STATE and copy it to the board, but that might + waste RAM on Arduino, so we init the board by code. + */ + + char *b = board; + + *b = 'R'; b++; *b = 'N'; b++; + *b = 'B'; b++; *b = 'Q'; b++; + *b = 'K'; b++; *b = 'B'; b++; + *b = 'N'; b++; *b = 'R'; b++; + + char *b2 = board + 48; + + for (uint8_t i = 0; i < 8; ++i, b++, b2++) + { + *b = 'P'; + *b2 = 'p'; + } + + for (uint8_t i = 0; i < 32; ++i, b++) + *b = '.'; + + b += 8; + + *b = 'r'; b++; *b = 'n'; b++; + *b = 'b'; b++; *b = 'q'; b++; + *b = 'k'; b++; *b = 'b'; b++; + *b = 'n'; b++; *b = 'r'; b++; + + for (uint8_t i = 0; i < SCL_BOARD_STATE_SIZE - SCL_BOARD_SQUARES; ++i, ++b) + *b = 0; + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = (char) 0xff; + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#endif +} + +void _SCL_boardPlaceOnNthAvailable(SCL_Board board, uint8_t pos, char piece) +{ + char *c = board; + + while (1) + { + if (*c == '.') + { + if (pos == 0) + break; + + pos--; + } + + c++; + } + + *c = piece; +} + +void SCL_boardInit960(SCL_Board board, uint16_t positionNumber) +{ + SCL_Board b; + + SCL_boardInit(b); + + for (uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) + board[i] = ((i >= 8 && i < 56) || i >= 64) ? b[i] : '.'; + + uint8_t helper = positionNumber % 16; + + board[(helper / 4) * 2] = 'B'; + board[1 + (helper % 4) * 2] = 'B'; + + helper = positionNumber / 16; + + // maybe there's a simpler way :) + + _SCL_boardPlaceOnNthAvailable(board,helper % 6,'Q'); + _SCL_boardPlaceOnNthAvailable(board,0,helper <= 23 ? 'N' : 'R'); + + _SCL_boardPlaceOnNthAvailable(board,0, + (helper >= 7 && helper <= 23) ? 'R' : + (helper > 41 ? 'K' : 'N' )); + + _SCL_boardPlaceOnNthAvailable(board,0, + (helper <= 5 || helper >= 54) ? 'R' : + (((helper >= 12 && helper <= 23) || + (helper >= 30 && helper <= 41)) ? 'K' : 'N')); + + _SCL_boardPlaceOnNthAvailable(board,0, + (helper <= 11 || (helper <= 29 && helper >= 24)) ? 'K' : + ( + ( + (helper >= 18 && helper <= 23) || + (helper >= 36 && helper <= 41) || + (helper >= 48 && helper <= 53) + ) ? 'R' : 'N' + ) + ); + + uint8_t rooks = 0; + + for (uint8_t i = 0; i < 8; ++i) + if (board[i] == 'R') + rooks++; + + _SCL_boardPlaceOnNthAvailable(board,0,rooks == 2 ? 'N' : 'R'); + + for (uint8_t i = 0; i < 8; ++i) + board[56 + i] = SCL_pieceToColor(board[i],0); + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#else + SCL_boardDisableCastling(board); +#endif +} + +uint8_t SCL_boardsDiffer(SCL_Board b1, SCL_Board b2) +{ + const char *p1 = b1, *p2 = b2; + + while (p1 < b1 + SCL_BOARD_STATE_SIZE) + { + if (*p1 != *p2) + return 1; + + p1++; + p2++; + } + + return 0; +} + +void SCL_recordInit(SCL_Record r) +{ + r[0] = 0 | SCL_RECORD_END; + r[1] = 0; +} + +void SCL_recordFromPGN(SCL_Record r, const char *pgn) +{ + SCL_Board board; + + SCL_boardInit(board); + + SCL_recordInit(r); + + uint8_t state = 0; + uint8_t evenMove = 0; + + while (*pgn != 0) + { + switch (state) + { + case 0: // skipping tags and spaces, outside [] + if (*pgn == '1') + state = 2; + else if (*pgn == '[') + state = 1; + + break; + + case 1: // skipping tags and spaces, inside [] + if (*pgn == ']') + state = 0; + + break; + + case 2: // reading move number + if (*pgn == '{') + state = 3; + else if ((*pgn >= 'a' && *pgn <= 'h') || (*pgn >= 'A' && *pgn <= 'Z')) + { + state = 4; + pgn--; + } + + break; + + case 3: // initial comment + if (*pgn == '}') + state = 2; + + break; + + case 4: // reading move + { + char piece = 'p'; + char promoteTo = 'q'; + uint8_t castle = 0; + uint8_t promotion = 0; + + int8_t coords[4]; + + uint8_t ranks = 0, files = 0; + + for (uint8_t i = 0; i < 4; ++i) + coords[i] = -1; + + while (*pgn != ' ' && *pgn != '\n' && + *pgn != '\t' && *pgn != '{' && *pgn != 0) + { + if (*pgn == '=') + promotion = 1; + if (*pgn == 'O' || *pgn == '0') + castle++; + if (*pgn >= 'A' && *pgn <= 'Z') + { + if (promotion) + promoteTo = *pgn; + else + piece = *pgn; + } + else if (*pgn >= 'a' && *pgn <= 'h') + { + coords[files * 2] = *pgn - 'a'; + files++; + } + else if (*pgn >= '1' && *pgn <= '8') + { + coords[1 + ranks * 2] = *pgn - '1'; + ranks++; + } + + pgn++; + } + + if (castle) + { + piece = 'K'; + + coords[0] = 4; + coords[1] = 0; + coords[2] = castle < 3 ? 6 : 2; + coords[3] = 0; + + if (evenMove) + { + coords[1] = 7; + coords[3] = 7; + } + } + + piece = SCL_pieceToColor(piece,evenMove == 0); + + if (coords[2] < 0) + { + coords[2] = coords[0]; + coords[0] = -1; + } + + if (coords[3] < 0) + { + coords[3] = coords[1]; + coords[1] = -1; + } + + uint8_t squareTo = coords[3] * 8 + coords[2]; + + if (coords[0] < 0 || coords[1] < 0) + { + // without complete starting coords we have to find the piece + + for (int i = 0; i < SCL_BOARD_SQUARES; ++i) + if (board[i] == piece) + { + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_boardGetMoves(board,i,s); + + if (SCL_squareSetContains(s,squareTo) && + (coords[0] < 0 || coords[0] == i % 8) && + (coords[1] < 0 || coords[1] == i / 8)) + { + coords[0] = i % 8; + coords[1] = i / 8; + break; + } + } + } + + uint8_t squareFrom = coords[1] * 8 + coords[0]; + + SCL_boardMakeMove(board,squareFrom,squareTo,promoteTo); + +// for some reason tcc bugs here, the above line sets squareFrom to 0 lol +// can be fixed with doing "squareFrom = coords[1] * 8 + coords[0];" again + + SCL_recordAdd(r,squareFrom,squareTo,promoteTo,SCL_RECORD_CONT); + + while (*pgn == ' ' || *pgn == '\n' || *pgn == '\t' || *pgn == '{') + { + if (*pgn == '{') + while (*pgn != '}') + pgn++; + + pgn++; + } + + if (*pgn == 0) + return; + + pgn--; + + if (evenMove) + state = 2; + + evenMove = !evenMove; + + break; + } + + default: break; + } + + pgn++; + } +} + +uint16_t SCL_recordLength(const SCL_Record r) +{ + if ((r[0] & 0x3f) == (r[1] & 0x3f)) // empty record that's only terminator + return 0; + + uint16_t result = 0; + + while ((r[result] & 0xc0) == 0) + result += 2; + + return (result / 2) + 1; +} + +uint8_t SCL_recordGetMove(const SCL_Record r, uint16_t index, + uint8_t *squareFrom, uint8_t *squareTo, char *promotedPiece) +{ + index *= 2; + + uint8_t b = r[index]; + + *squareFrom = b & 0x3f; + uint8_t result = b & 0xc0; + + index++; + + b = r[index]; + + *squareTo = b & 0x3f; + + b &= 0xc0; + + switch (b) + { + case SCL_RECORD_PROM_Q: *promotedPiece = 'q'; break; + case SCL_RECORD_PROM_R: *promotedPiece = 'r'; break; + case SCL_RECORD_PROM_B: *promotedPiece = 'b'; break; + case SCL_RECORD_PROM_N: + default: *promotedPiece = 'n'; break; + } + + return result; +} + +uint8_t SCL_recordAdd(SCL_Record r, uint8_t squareFrom, + uint8_t squareTo, char promotePiece, uint8_t endState) +{ + uint16_t l = SCL_recordLength(r); + + if (l >= SCL_RECORD_MAX_LENGTH) + return 0; + + l *= 2; + + if (l != 0) + r[l - 2] &= 0x3f; // remove the end flag from previous item + + if (endState == SCL_RECORD_CONT) + endState = SCL_RECORD_END; + + r[l] = squareFrom | endState; + + uint8_t p; + + switch (promotePiece) + { + case 'n': case 'N': p = SCL_RECORD_PROM_N; break; + case 'b': case 'B': p = SCL_RECORD_PROM_B; break; + case 'r': case 'R': p = SCL_RECORD_PROM_R; break; + case 'q': case 'Q': + default: p = SCL_RECORD_PROM_Q; break; + } + + l++; + + r[l] = squareTo | p; + + return 1; +} + +uint8_t SCL_recordRemoveLast(SCL_Record r) +{ + uint16_t l = SCL_recordLength(r); + + if (l == 0) + return 0; + + if (l == 1) + SCL_recordInit(r); + else + { + l = (l - 2) * 2; + + r[l] = (r[l] & 0x3f) | SCL_RECORD_END; + } + + return 1; +} + +void SCL_recordApply(const SCL_Record r, SCL_Board b, uint16_t moves) +{ + SCL_boardInit(b); + + uint16_t l = SCL_recordLength(r); + + if (moves > l) + moves = l; + + for (uint16_t i = 0; i < moves; ++i) + { + uint8_t s0, s1; + char p; + + SCL_recordGetMove(r,i,&s0,&s1,&p); + SCL_boardMakeMove(b,s0,s1,p); + } +} + +void SCL_boardUndoMove(SCL_Board board, SCL_MoveUndo moveUndo) +{ +#if SCL_960_CASTLING + char squareToNow = board[moveUndo.squareTo]; +#endif + + board[moveUndo.squareFrom] = board[moveUndo.squareTo]; + board[moveUndo.squareTo] = moveUndo.other & 0x7f; + board[SCL_BOARD_PLY_BYTE]--; + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = moveUndo.enPassantCastle; + board[SCL_BOARD_MOVE_COUNT_BYTE] = moveUndo.moveCount; + + if (moveUndo.other & 0x80) + { + moveUndo.squareTo /= 8; + + if (moveUndo.squareTo == 0 || moveUndo.squareTo == 7) + board[moveUndo.squareFrom] = SCL_pieceIsWhite(board[moveUndo.squareFrom]) + ? 'P' : 'p'; + // ^ was promotion + else + board[(moveUndo.squareFrom / 8) * 8 + (moveUndo.enPassantCastle & 0x0f)] = + (board[moveUndo.squareFrom] == 'P') ? 'p' : 'P'; // was en passant + } +#if !SCL_960_CASTLING + else if (board[moveUndo.squareFrom] == 'k' && // black castling + moveUndo.squareFrom == 60) + { + if (moveUndo.squareTo == 58) + { + board[59] = '.'; + board[56] = 'r'; + } + else if (moveUndo.squareTo == 62) + { + board[61] = '.'; + board[63] = 'r'; + } + } + else if (board[moveUndo.squareFrom] == 'K' && // white castling + moveUndo.squareFrom == 4) + { + if (moveUndo.squareTo == 2) + { + board[3] = '.'; + board[0] = 'R'; + } + else if (moveUndo.squareTo == 6) + { + board[5] = '.'; + board[7] = 'R'; + } + } +#else // 960 castling + else if (((moveUndo.other & 0x7f) == 'r') && // black castling + (squareToNow == '.' || !SCL_pieceIsWhite(squareToNow))) + { + board[moveUndo.squareTo < moveUndo.squareFrom ? 59 : 61] = '.'; + board[moveUndo.squareTo < moveUndo.squareFrom ? 58 : 62] = '.'; + + board[moveUndo.squareFrom] = 'k'; + board[moveUndo.squareTo] = 'r'; + } + else if (((moveUndo.other & 0x7f) == 'R') && // white castling + (squareToNow == '.' || SCL_pieceIsWhite(squareToNow))) + { + board[moveUndo.squareTo < moveUndo.squareFrom ? 3 : 5] = '.'; + board[moveUndo.squareTo < moveUndo.squareFrom ? 2 : 6] = '.'; + + board[moveUndo.squareFrom] = 'K'; + board[moveUndo.squareTo] = 'R'; + } +#endif +} + +/** + Potentially disables castling rights according to whether something moved from + or to a square with a rook. +*/ +void _SCL_handleRookActivity(SCL_Board board, uint8_t rookSquare) +{ +#if !SCL_960_CASTLING + switch (rookSquare) + { + case 0: board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x20; break; + case 7: board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x10; break; + case 56: board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x80; break; + case 63: board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x40; break; + default: break; + } +#else // 960 castling + if (rookSquare == (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x20; + else if (rookSquare == (board[SCL_BOARD_EXTRA_BYTE] >> 3)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x10; + else if (rookSquare == 56 + (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x80; + else if (rookSquare == 56 + (board[SCL_BOARD_EXTRA_BYTE] >> 3)) + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= (uint8_t) ~0x40; +#endif +} + +SCL_MoveUndo SCL_boardMakeMove(SCL_Board board, uint8_t squareFrom, uint8_t squareTo, + char promotePiece) +{ + char s = board[squareFrom]; + + SCL_MoveUndo moveUndo; + + moveUndo.squareFrom = squareFrom; + moveUndo.squareTo = squareTo; + moveUndo.moveCount = board[SCL_BOARD_MOVE_COUNT_BYTE]; + moveUndo.enPassantCastle = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE]; + moveUndo.other = board[squareTo]; + + // reset the en-passant state + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] |= 0x0f; + + if (SCL_boardMoveResetsCount(board,squareFrom,squareTo)) + board[SCL_BOARD_MOVE_COUNT_BYTE] = 0; + else + board[SCL_BOARD_MOVE_COUNT_BYTE]++; + +#if SCL_960_CASTLING + uint8_t castled = 0; +#endif + + if ((s == 'k') || (s == 'K')) + { +#if !SCL_960_CASTLING + if ((squareFrom == 4) || (squareFrom == 60)) // check castling + { + int8_t difference = squareTo - squareFrom; + + char rook = SCL_pieceToColor('r',SCL_pieceIsWhite(s)); + + if (difference == 2) // short + { + board[squareTo - 1] = rook; + board[squareTo + 1] = '.'; + } + else if (difference == -2) // long + { + board[squareTo - 2] = '.'; + board[squareTo + 1] = rook; + } + } +#else // 960 castling + uint8_t isWhite = SCL_pieceIsWhite(s); + char rook = SCL_pieceToColor('r',isWhite); + + if (board[squareTo] == rook) + { + castled = 1; + + board[squareFrom] = '.'; + board[squareTo] = '.'; + + if (squareTo > squareFrom) // short + { + board[isWhite ? 6 : (56 + 6)] = s; + board[isWhite ? 5 : (56 + 5)] = rook; + } + else // long + { + board[isWhite ? 2 : (56 + 2)] = s; + board[isWhite ? 3 : (56 + 3)] = rook; + } + } +#endif + + // after king move disable castling + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= ~(0x03 << ((s == 'K') ? 4 : 6)); + } + else if ((s == 'p') || (s == 'P')) + { + uint8_t row = squareTo / 8; + + int8_t rowDiff = squareFrom / 8 - row; + + if (rowDiff == 2 || rowDiff == -2) // record en passant column + { + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = + (board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0xf0) | (squareFrom % 8); + } + + if (row == 0 || row == 7) + { + // promotion + s = SCL_pieceToColor(promotePiece,SCL_pieceIsWhite(s)); + + moveUndo.other |= 0x80; + } + else + { + // check en passant move + + int8_t columnDiff = (squareTo % 8) - (squareFrom % 8); + + if ((columnDiff != 0) && (board[squareTo] == '.')) + { + board[squareFrom + columnDiff] = '.'; + moveUndo.other |= 0x80; + } + } + } + else if ((s == 'r') || (s == 'R')) + _SCL_handleRookActivity(board,squareFrom); + + char taken = board[squareTo]; + + // taking a rook may also disable castling: + + if (taken == 'R' || taken == 'r') + _SCL_handleRookActivity(board,squareTo); + +#if SCL_960_CASTLING + if (!castled) +#endif + { + board[squareTo] = s; + board[squareFrom] = '.'; + } + + board[SCL_BOARD_PLY_BYTE]++; // increase ply count + + return moveUndo; +} + +void SCL_boardSetPosition(SCL_Board board, const char *pieces, + uint8_t castlingEnPassant, uint8_t moveCount, uint8_t ply) +{ + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, pieces++) + if (*pieces != 0) + board[i] = *pieces; + else + break; + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = castlingEnPassant; + board[SCL_BOARD_PLY_BYTE] = ply; + board[SCL_BOARD_MOVE_COUNT_BYTE] = moveCount; + board[SCL_BOARD_STATE_SIZE - 1] = 0; +} + +void SCL_squareSetAdd(SCL_SquareSet squareSet, uint8_t square) +{ + squareSet[square / 8] |= 0x01 << (square % 8); +} + +uint8_t SCL_squareSetContains(const SCL_SquareSet squareSet, uint8_t square) +{ + return squareSet[square / 8] & (0x01 << (square % 8)); +} + +uint8_t SCL_squareSetSize(const SCL_SquareSet squareSet) +{ + uint8_t result = 0; + + for (uint8_t i = 0; i < 8; ++i) + { + uint8_t byte = squareSet[i]; + + for (uint8_t j = 0; j < 8; ++j) + { + result += byte & 0x01; + byte >>= 1; + } + } + + return result; +} + +uint8_t SCL_squareSetEmpty(const SCL_SquareSet squareSet) +{ + for (uint8_t i = 0; i < 8; ++i) + if (squareSet[i] != 0) + return 0; + + return 1; +} + +uint8_t SCL_squareSetGetRandom( + const SCL_SquareSet squareSet, SCL_RandomFunction randFunc) +{ + uint8_t size = SCL_squareSetSize(squareSet); + + if (size == 0) + return 0; + + uint8_t n = (randFunc() % size) + 1; + uint8_t i = 0; + + while (i < SCL_BOARD_SQUARES) + { + if (SCL_squareSetContains(squareSet,i)) + { + n--; + + if (n == 0) + break; + } + + ++i; + } + + return i; +} + +void SCL_boardCopy(const SCL_Board boardFrom, SCL_Board boardTo) +{ + for (uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) + boardTo[i] = boardFrom[i]; +} + +uint8_t SCL_boardSquareAttacked( + SCL_Board board, + uint8_t square, + uint8_t byWhite) +{ + const char *currentSquare = board; + + /* We need to place a temporary piece on the tested square in order to test if + the square is attacked (consider testing if attacked by a pawn). */ + + char previous = board[square]; + + board[square] = SCL_pieceToColor('r',!byWhite); + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++currentSquare) + { + char s = *currentSquare; + + if ((s == '.') || (SCL_pieceIsWhite(s) != byWhite)) + continue; + + SCL_SquareSet moves; + SCL_boardGetPseudoMoves(board,i,0,moves); + + if (SCL_squareSetContains(moves,square)) + { + board[square] = previous; + return 1; + } + } + + board[square] = previous; + return 0; +} + +uint8_t SCL_boardCheck(SCL_Board board,uint8_t white) +{ + const char *square = board; + char kingChar = white ? 'K' : 'k'; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++square) + if ((*square == kingChar && + SCL_boardSquareAttacked(board,i,!white))) + return 1; + + return 0; +} + +uint8_t SCL_boardGameOver(SCL_Board board) +{ + uint8_t position = SCL_boardGetPosition(board); + + return (position == SCL_POSITION_MATE) || + (position == SCL_POSITION_STALEMATE) || + (position == SCL_POSITION_DEAD); +} + +uint8_t SCL_boardMovePossible(SCL_Board board) +{ + uint8_t white = SCL_boardWhitesTurn(board); + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + { + char s = board[i]; + + if ((s != '.') && (SCL_pieceIsWhite(s) == white)) + { + SCL_SquareSet moves; + + SCL_boardGetMoves(board,i,moves); + + if (SCL_squareSetSize(moves) != 0) + return 1; + } + } + + return 0; +} + +uint8_t SCL_boardMate(SCL_Board board) +{ + return SCL_boardGetPosition(board) == SCL_POSITION_MATE; +} + +void SCL_boardGetPseudoMoves( + SCL_Board board, + uint8_t pieceSquare, + uint8_t checkCastling, + SCL_SquareSet result) +{ + char piece = board[pieceSquare]; + + SCL_squareSetClear(result); + + uint8_t isWhite = SCL_pieceIsWhite(piece); + int8_t horizontalPosition = pieceSquare % 8; + int8_t pawnOffset = -8; + + switch (piece) + { + case 'P': + pawnOffset = 8; + // intentional fallthrough + case 'p': + { + uint8_t square = pieceSquare + pawnOffset; + uint8_t verticalPosition = pieceSquare / 8; + + if (board[square] == '.') // forward move + { + SCL_squareSetAdd(result,square); + + if (verticalPosition == (1 + (piece == 'p') * 5)) // start position? + { + uint8_t square2 = square + pawnOffset; + + if (board[square2] == '.') + SCL_squareSetAdd(result,square2); + } + } + + #define checkDiagonal(hor,add) \ + if (horizontalPosition != hor) \ + { \ + uint8_t square2 = square + add; \ + char c = board[square2]; \ + if (c != '.' && SCL_pieceIsWhite(c) != isWhite) \ + SCL_squareSetAdd(result,square2); \ + } + + // diagonal moves + checkDiagonal(0,-1) + checkDiagonal(7,1) + + uint8_t enPassantRow = 4; + uint8_t enemyPawn = 'p'; + + if (piece == 'p') + { + enPassantRow = 3; + enemyPawn = 'P'; + } + + // en-passant moves + if (verticalPosition == enPassantRow) + { + uint8_t enPassantColumn = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f; + uint8_t column = pieceSquare % 8; + + for (int8_t offset = -1; offset < 2; offset += 2) + if ((enPassantColumn == column + offset) && + (board[pieceSquare + offset] == enemyPawn)) + { + SCL_squareSetAdd(result,pieceSquare + pawnOffset + offset); + break; + } + } + + #undef checkDiagonal + } + break; + + case 'r': // rook + case 'R': + case 'b': // bishop + case 'B': + case 'q': // queen + case 'Q': + { + const int8_t offsets[8] = {-8, 1, 8, -1, -7, 9, -9, 7}; + const int8_t columnDirs[8] = { 0, 1, 0, -1, 1, 1, -1,-1}; + + uint8_t from = (piece == 'b' || piece == 'B') * 4; + uint8_t to = 4 + (piece != 'r' && piece != 'R') * 4; + + for (uint8_t i = from; i < to; ++i) + { + int8_t offset = offsets[i]; + int8_t columnDir = columnDirs[i]; + int8_t square = pieceSquare; + int8_t col = horizontalPosition; + + while (1) + { + square += offset; + col += columnDir; + + if (square < 0 || square > 63 || col < 0 || col > 7) + break; + + char squareC = board[square]; + + if (squareC == '.') + SCL_squareSetAdd(result,square); + else + { + if (SCL_pieceIsWhite(squareC) != isWhite) + SCL_squareSetAdd(result,square); + + break; + } + } + } + } + break; + + case 'n': // knight + case 'N': + { + const int8_t offsets[4] = {6, 10, 15, 17}; + const int8_t columnsMinus[4] = {2,-2,1,-1}; + const int8_t columnsPlus[4] = {-2,2,-1,1}; + const int8_t *off, *col; + + #define checkOffsets(op,comp,limit,dir)\ + off = offsets;\ + col = columns ## dir;\ + for (uint8_t i = 0; i < 4; ++i, ++off, ++col)\ + {\ + int8_t square = pieceSquare op (*off);\ + if (square comp limit) /* out of board? */\ + break;\ + int8_t horizontalCheck = horizontalPosition + (*col);\ + if (horizontalCheck < 0 || horizontalCheck >= 8)\ + continue;\ + char squareC = board[square];\ + if ((squareC == '.') || (SCL_pieceIsWhite(squareC) != isWhite))\ + SCL_squareSetAdd(result,square);\ + } + + checkOffsets(-,<,0,Minus) + checkOffsets(+,>=,SCL_BOARD_SQUARES,Plus) + + #undef checkOffsets + } + break; + + case 'k': // king + case 'K': + { + uint8_t verticalPosition = pieceSquare / 8; + + uint8_t + u = verticalPosition != 0, + d = verticalPosition != 7, + l = horizontalPosition != 0, + r = horizontalPosition != 7; + + uint8_t square2 = pieceSquare - 9; + + #define checkSquare(cond,add) \ + if (cond && ((board[square2] == '.') || \ + (SCL_pieceIsWhite(board[square2])) != isWhite))\ + SCL_squareSetAdd(result,square2);\ + square2 += add; + + checkSquare(l && u,1) + checkSquare(u,1) + checkSquare(r && u,6) + checkSquare(l,2) + checkSquare(r,6) + checkSquare(l && d,1) + checkSquare(d,1) + checkSquare(r && d,0) + + #undef checkSquare + + // castling: + + if (checkCastling) + { + uint8_t bitShift = 4 + 2 * (!isWhite); + + if ((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x03 << bitShift)) && + !SCL_boardSquareAttacked(board,pieceSquare,!isWhite)) // no check? + { +#if !SCL_960_CASTLING + // short castle: + pieceSquare++; + + if ((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x01 << bitShift)) && + (board[pieceSquare] == '.') && + (board[pieceSquare + 1] == '.') && + (board[pieceSquare + 2] == SCL_pieceToColor('r',isWhite)) && + !SCL_boardSquareAttacked(board,pieceSquare,!isWhite)) + SCL_squareSetAdd(result,pieceSquare + 1); + + /* note: don't check the final square for check, it will potentially + be removed later (can't end up in check) */ + + // long castle: + pieceSquare -= 2; + + if ((board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & (0x02 << bitShift)) && + (board[pieceSquare] == '.') && + (board[pieceSquare - 1] == '.') && + (board[pieceSquare - 2] == '.') && + (board[pieceSquare - 3] == SCL_pieceToColor('r',isWhite)) && + !SCL_boardSquareAttacked(board,pieceSquare,!isWhite)) + SCL_squareSetAdd(result,pieceSquare - 1); +#else // 960 castling + for (int i = 0; i < 2; ++i) // short and long + if (board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & ((i + 1) << bitShift)) + { + uint8_t + rookPos = board[SCL_BOARD_EXTRA_BYTE] >> 3, + targetPos = 5; + + if (i == 1) + { + rookPos = board[SCL_BOARD_EXTRA_BYTE] & 0x07, + targetPos = 3; + } + + if (!isWhite) + { + rookPos += 56; + targetPos += 56; + } + + uint8_t ok = board[rookPos] == SCL_pieceToColor('r',isWhite); + + if (!ok) + continue; + + int8_t inc = 1 - 2 * (targetPos > rookPos); + + while (targetPos != rookPos) // check vacant squares for the rook + { + if (board[targetPos] != '.' && + targetPos != pieceSquare) + { + ok = 0; + break; + } + + targetPos += inc; + } + + if (!ok) + continue; + + targetPos = i == 0 ? 6 : 2; + + if (!isWhite) + targetPos += 56; + + inc = 1 - 2 * (targetPos > pieceSquare); + + while (targetPos != pieceSquare) // check squares for the king + { + if ((board[targetPos] != '.' && + targetPos != rookPos) || + SCL_boardSquareAttacked(board,targetPos,!isWhite)) + { + ok = 0; + break; + } + + targetPos += inc; + } + + if (ok) + SCL_squareSetAdd(result,rookPos); + } +#endif + } + } + } + break; + + default: + break; + } +} + +void SCL_printSquareSet(SCL_SquareSet set, SCL_PutCharFunction putCharFunc) +{ + uint8_t first = 1; + + putCharFunc('('); + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + { + if (!SCL_squareSetContains(set, i)) + continue; + + if (!first) + putCharFunc(','); + else + first = 0; + + putCharFunc('A' + i % 8); + putCharFunc('1' + i / 8); + } + + putCharFunc(')'); +} + +void SCL_printSquareUTF8(uint8_t square, SCL_PutCharFunction putCharFunc) +{ + uint32_t val = 0; + + switch (square) + { + case 'r': val = 0x9c99e200; break; + case 'n': val = 0x9e99e200; break; + case 'b': val = 0x9d99e200; break; + case 'q': val = 0x9b99e200; break; + case 'k': val = 0x9a99e200; break; + case 'p': val = 0x9f99e200; break; + case 'R': val = 0x9699e200; break; + case 'N': val = 0x9899e200; break; + case 'B': val = 0x9799e200; break; + case 'Q': val = 0x9599e200; break; + case 'K': val = 0x9499e200; break; + case 'P': val = 0x9999e200; break; + case '.': val = 0x9296e200; break; + case ',': val = 0x9196e200; break; + default: putCharFunc(square); return; break; + } + + uint8_t count = 4; + + while ((val % 256 == 0) && (count > 0)) + { + val /= 256; + count--; + } + + while (count > 0) + { + putCharFunc(val % 256); + val /= 256; + count--; + } +} + +void SCL_boardGetMoves( + SCL_Board board, + uint8_t pieceSquare, + SCL_SquareSet result) +{ + SCL_SquareSet allMoves; + + SCL_squareSetClear(allMoves); + + for (uint8_t i = 0; i < 8; ++i) + result[i] = 0; + + SCL_boardGetPseudoMoves(board,pieceSquare,1,allMoves); + + // Now only keep moves that don't lead to one's check: + + SCL_SQUARE_SET_ITERATE_BEGIN(allMoves) + + SCL_MoveUndo undo = SCL_boardMakeMove(board,pieceSquare,iteratedSquare,'q'); + + if (!SCL_boardCheck(board,!SCL_boardWhitesTurn(board))) + SCL_squareSetAdd(result,iteratedSquare); + + SCL_boardUndoMove(board,undo); + + SCL_SQUARE_SET_ITERATE_END +} + +uint8_t SCL_boardDead(SCL_Board board) +{ + /* + This byte represents material by bits: + + MSB _ _ _ _ _ _ _ _ LSB + | | | | | \_ white knight + | | | | \__ white bishop on white + | | | \____ white bishop on black + | | \________ black knight + | \__________ black bishop on white + \____________ black bishop on black + */ + uint8_t material = 0; + + const char *p = board; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + { + char c = *p; + + switch (c) + { + case 'n': material |= 0x01; break; + case 'N': material |= 0x10; break; + case 'b': material |= (0x02 << (!SCL_squareIsWhite(i))); break; + case 'B': material |= (0x20 << (!SCL_squareIsWhite(i))); break; + case 'p': + case 'P': + case 'r': + case 'R': + case 'q': + case 'Q': + return 0; // REMOVE later if more complex check are performed + break; + + default: break; + } + + p++; + } + + // TODO: add other checks than only insufficient material + + // possible combinations of insufficient material: + + return + (material == 0x00) || // king vs king + (material == 0x01) || // king and knight vs king + (material == 0x10) || // king and knight vs king + (material == 0x02) || // king and bishop vs king + (material == 0x20) || // king and bishop vs king + (material == 0x04) || // king and bishop vs king + (material == 0x40) || // king and bishop vs king + (material == 0x22) || // king and bishop vs king and bishop (same color) + (material == 0x44); // king and bishop vs king and bishop (same color) +} + +uint8_t SCL_boardGetPosition(SCL_Board board) +{ + uint8_t check = SCL_boardCheck(board,SCL_boardWhitesTurn(board)); + uint8_t moves = SCL_boardMovePossible(board); + + if (check) + return moves ? SCL_POSITION_CHECK : SCL_POSITION_MATE; + else if (!moves) + return SCL_POSITION_STALEMATE; + + if (SCL_boardDead(board)) + return SCL_POSITION_DEAD; + + return SCL_POSITION_NORMAL; +} + +uint8_t SCL_stringToMove(const char *moveString, uint8_t *resultFrom, + uint8_t *resultTo, char *resultPromotion) +{ + char c; + + uint8_t *dst = resultFrom; + + for (uint8_t i = 0; i < 2; ++i) + { + c = *moveString; + + *dst = (c >= 'a') ? (c - 'a') : (c - 'A'); + + if (*dst > 7) + return 0; + + moveString++; + c = *moveString; + + *dst += 8 * (c - '1'); + + if (*dst > 63) + return 0; + + moveString++; + + dst = resultTo; + } + + c = *moveString; + + if (c < 'A') + c = c - 'A' + 'a'; + + switch (c) + { + case 'N': case 'n': *resultPromotion = 'n'; break; + case 'B': case 'b': *resultPromotion = 'b'; break; + case 'R': case 'r': *resultPromotion = 'r'; break; + case 'Q': case 'q': + default: *resultPromotion = 'q'; break; + } + + return 1; +} + +void SCL_printBoard( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + SCL_SquareSet highlightSquares, + uint8_t selectSquare, + uint8_t format, + uint8_t offset, + uint8_t labels, + uint8_t blackDown) +{ + if (labels) + { + for (uint8_t i = 0; i < offset + 2; ++i) + putCharFunc(' '); + + for (uint8_t i = 0; i < 8; ++i) + { + if ((format != SCL_PRINT_FORMAT_COMPACT) && + (format != SCL_PRINT_FORMAT_COMPACT_UTF8)) + putCharFunc(' '); + + putCharFunc(blackDown ? ('H' - i) : ('A' + i)); + } + + putCharFunc('\n'); + } + + int8_t i = 7; + int8_t add = 1; + + if (!blackDown) + { + i = 56; + add = -1; + } + + for (int8_t row = 0; row < 8; ++row) + { + for (uint8_t j = 0; j < offset; ++j) + putCharFunc(' '); + + if (labels) + { + putCharFunc(!blackDown ? ('8' - row) : ('1' + row)); + putCharFunc(' '); + } + + const char *square = board + i; + + for (int8_t col = 0; col < 8; ++col) + { + switch (format) + { + case SCL_PRINT_FORMAT_COMPACT: + putCharFunc( + (*square == '.') ? ( + ((i != selectSquare) ? + (!SCL_squareSetContains(highlightSquares,i) ? *square : '*') + : '#')) : *square); + break; + + case SCL_PRINT_FORMAT_UTF8: + { + char squareChar = SCL_squareIsWhite(i) ? '.' : ','; + char pieceChar = (*square == '.') ? squareChar : *square; + + if (i == selectSquare) + { + putCharFunc('('); + + if (*square == '.') + putCharFunc(')'); + else + SCL_printSquareUTF8(pieceChar,putCharFunc); + } + else if (!SCL_squareSetContains(highlightSquares,i)) + { + SCL_printSquareUTF8(squareChar,putCharFunc); + SCL_printSquareUTF8(pieceChar,putCharFunc); + } + else + { + putCharFunc('['); + + if (*square == '.') + putCharFunc(']'); + else + SCL_printSquareUTF8(*square,putCharFunc); + } + + break; + } + + case SCL_PRINT_FORMAT_COMPACT_UTF8: + SCL_printSquareUTF8( + (*square == '.') ? ( + SCL_squareSetContains(highlightSquares,i) ? '*' : + (i == selectSquare ? '#' : ((SCL_squareIsWhite(i) ? '.' : ','))) + ) : *square,putCharFunc); + break; + + case SCL_PRINT_FORMAT_NORMAL: + default: + { + uint8_t c = *square; + + char squareColor = SCL_squareIsWhite(i) ? ' ' : ':'; + + putCharFunc((i != selectSquare) ? + (!SCL_squareSetContains(highlightSquares,i) ? + squareColor : '#') : '@'); + + putCharFunc(c == '.' ? squareColor : *square); + break; + } + } + + i -= add; + square -= add; + } + + putCharFunc('\n'); + + i += add * 16; + } // for rows +} + +int16_t SCL_pieceValuePositive(char piece) +{ + switch (piece) + { + case 'p': + case 'P': return SCL_VALUE_PAWN; break; + case 'n': + case 'N': return SCL_VALUE_KNIGHT; break; + case 'b': + case 'B': return SCL_VALUE_BISHOP; break; + case 'r': + case 'R': return SCL_VALUE_ROOK; break; + case 'q': + case 'Q': return SCL_VALUE_QUEEN; break; + case 'k': + case 'K': return SCL_VALUE_KING; break; + default: break; + } + + return 0; +} + +int16_t SCL_pieceValue(char piece) +{ + switch (piece) + { + case 'P': return SCL_VALUE_PAWN; break; + case 'N': return SCL_VALUE_KNIGHT; break; + case 'B': return SCL_VALUE_BISHOP; break; + case 'R': return SCL_VALUE_ROOK; break; + case 'Q': return SCL_VALUE_QUEEN; break; + case 'K': return SCL_VALUE_KING; break; + case 'p': return -1 * SCL_VALUE_PAWN; break; + case 'n': return -1 * SCL_VALUE_KNIGHT; break; + case 'b': return -1 * SCL_VALUE_BISHOP; break; + case 'r': return -1 * SCL_VALUE_ROOK; break; + case 'q': return -1 * SCL_VALUE_QUEEN; break; + case 'k': return -1 * SCL_VALUE_KING; break; + default: break; + } + + return 0; +} + +#define ATTACK_BONUS 3 +#define MOBILITY_BONUS 10 +#define CENTER_BONUS 7 +#define CHECK_BONUS 5 +#define KING_CASTLED_BONUS 30 +#define KING_BACK_BONUS 15 +#define KING_NOT_CENTER_BONUS 15 +#define PAWN_NON_DOUBLE_BONUS 3 +#define PAWN_PAIR_BONUS 3 +#define KING_CENTERNESS 10 + +int16_t _SCL_rateKingEndgamePosition(uint8_t position) +{ + int16_t result = 0; + uint8_t rank = position / 8; + position %= 8; + + if (position > 1 && position < 6) + result += KING_CENTERNESS; + + if (rank > 1 && rank < 6) + result += KING_CENTERNESS; + + return result; +} + +int16_t SCL_boardEvaluateStatic(SCL_Board board) +{ + uint8_t position = SCL_boardGetPosition(board); + + int16_t total = 0; + + switch (position) + { + case SCL_POSITION_MATE: + return SCL_boardWhitesTurn(board) ? + -1 * SCL_EVALUATION_MAX_SCORE : SCL_EVALUATION_MAX_SCORE; + break; + + case SCL_POSITION_STALEMATE: + case SCL_POSITION_DEAD: + return 0; + break; + + /* + main points are assigned as follows: + - points for material as a sum of all material on board + - for playing side: if a piece attacks piece of greater value, a fraction + of the value difference is gained (we suppose exchange), this is only + gained once per every attacking piece (maximum gain is taken), we only + take fraction so that actually taking the piece is favored + - ATTACK_BONUS points for any attacked piece + + other points are assigned as follows (in total these shouldn't be more + than the value of one pawn) + - mobility: MOBILITY_BONUS points for each piece with at least 4 possible + moves + - center control: CENTER_BONUS points for a piece on a center square + - CHECK_BONUS points for check + - king: + - safety (non endgame): KING_BACK_BONUS points for king on staring rank, + additional KING_CASTLED_BONUS if the kind if on castled square or + closer to the edge, additional KING_NOT_CENTER_BONUS for king not on + its start neighbouring center square + - center closeness (endgame): up to 2 * KING_CENTERNESS points for + being closer to center + - non-doubled pawns: PAWN_NON_DOUBLE_BONUS points for each pawn without + same color pawn directly in front of it + - pawn structure: PAWN_PAIR_BONUS points for each pawn guarding own pawn + - advancing pawns: 1 point for each pawn's rank in its move + direction + */ + + case SCL_POSITION_CHECK: + total += SCL_boardWhitesTurn(board) ? -1 * CHECK_BONUS : CHECK_BONUS; + // intentional fallthrough + case SCL_POSITION_NORMAL: + default: + { + SCL_SquareSet moves; + + const char *p = board; + + int16_t positiveMaterial = 0; + uint8_t endgame = 0; + + // first count material to see if this is endgame or not + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++p) + { + char s = *p; + + if (s != '.') + { + positiveMaterial += SCL_pieceValuePositive(s); + total += SCL_pieceValue(s); + } + } + + endgame = positiveMaterial <= SCL_ENDGAME_MATERIAL_LIMIT; + + p = board; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++p) + { + char s = *p; + + if (s != '.') + { + uint8_t white = SCL_pieceIsWhite(s); + + switch (s) + { + case 'k': // king safety + if (endgame) + total -= _SCL_rateKingEndgamePosition(i); + else if (i >= 56) + { + total -= KING_BACK_BONUS; + + if (i != 59) + { + total -= KING_NOT_CENTER_BONUS; + + if (i >= 62 || i <= 58) + total -= KING_CASTLED_BONUS; + } + } + break; + + case 'K': + if (endgame) + total += _SCL_rateKingEndgamePosition(i); + else if (i <= 7) + { + total += KING_BACK_BONUS; + + if (i != 3) + { + total += KING_NOT_CENTER_BONUS; + + if (i <= 2 || i >= 6) + total += KING_CASTLED_BONUS; + } + } + break; + + case 'P': // pawns + case 'p': + { + int8_t rank = i / 8; + + if (rank != 0 && rank != 7) + { + if (s == 'P') + { + total += rank; + + char *tmp = board + i + 8; + + if (*tmp != 'P') + total += PAWN_NON_DOUBLE_BONUS; + + if (i % 8 != 7) + { + tmp++; + + if (*tmp == 'P') + total += PAWN_PAIR_BONUS; + + if (*(tmp - 16) == 'P') + total += PAWN_PAIR_BONUS; + } + } + else + { + total -= 7 - rank; + + char *tmp = board + i - 8; + + if (*tmp != 'p') + total -= PAWN_NON_DOUBLE_BONUS; + + if (i % 8 != 7) + { + tmp += 17; + + if (*tmp == 'p') + total -= PAWN_PAIR_BONUS; + + if (*(tmp - 16) == 'p') + total -= PAWN_PAIR_BONUS; + } + } + } + + break; + } + + default: break; + } + + if (i >= 27 && i <= 36 && (i >= 35 || i <= 28)) // center control + total += white ? CENTER_BONUS : (-1 * CENTER_BONUS); + + // for performance we only take pseudo moves + SCL_boardGetPseudoMoves(board,i,0,moves); + + if (SCL_squareSetSize(moves) >= 4) // mobility + total += white ? + MOBILITY_BONUS : (-1 * MOBILITY_BONUS); + + int16_t exchangeBonus = 0; + + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + if (board[iteratedSquare] != '.') + { + total += white ? + ATTACK_BONUS : (- 1 * ATTACK_BONUS); + + if (SCL_boardWhitesTurn(board) == white) + { + int16_t valueDiff = + SCL_pieceValuePositive(board[iteratedSquare]) - + SCL_pieceValuePositive(s); + + valueDiff /= 4; // only take a fraction to favor taking + + if (valueDiff > exchangeBonus) + exchangeBonus = valueDiff; + } + } + + SCL_SQUARE_SET_ITERATE_END + + if (exchangeBonus != 0) + total += white ? exchangeBonus : -1 * exchangeBonus; + } + } // for each square + + return total; + + break; + + } // normal position + } // switch + + return 0; +} + +#undef ATTACK_BONUS +#undef MOBILITY_BONUS +#undef CENTER_BONUS +#undef CHECK_BONUS +#undef KING_CASTLED_BONUS +#undef KING_BACK_BONUS +#undef PAWN_NON_DOUBLE_BONUS +#undef PAWN_PAIR_BONUS +#undef KING_CENTERNESS + +SCL_StaticEvaluationFunction _SCL_staticEvaluationFunction; +int16_t _SCL_currentEval; +int8_t _SCL_depthHardLimit; + +/** + Inner recursive function for SCL_boardEvaluateDynamic. It is passed a square + (or -1) at which last capture happened, to implement capture extension. +*/ +int16_t _SCL_boardEvaluateDynamic(SCL_Board board, int8_t depth, + int16_t alphaBeta, int8_t takenSquare) +{ +#if SCL_COUNT_EVALUATED_POSITIONS + SCL_positionsEvaluated++; +#endif + +#if SCL_CALL_WDT_RESET + wdt_reset(); +#endif + + uint8_t whitesTurn = SCL_boardWhitesTurn(board); + int8_t valueMultiply = whitesTurn ? 1 : -1; + int16_t bestMoveValue = -1 * SCL_EVALUATION_MAX_SCORE; + uint8_t shouldCompute = depth > 0; + uint8_t extended = 0; + uint8_t positionType = SCL_boardGetPosition(board); + + if (!shouldCompute) + { + /* here we do two extensions (deeper search): taking on a same square + (exchanges) and checks (good for mating and preventing mates): */ + extended = + (depth > _SCL_depthHardLimit) && + (takenSquare >= 0 || + (SCL_boardGetPosition(board) == SCL_POSITION_CHECK)); + + shouldCompute = extended; + } + +#if SCL_DEBUG_AI + char moveStr[8]; + uint8_t debugFirst = 1; +#endif + + if (shouldCompute && + (positionType == SCL_POSITION_NORMAL || positionType == SCL_POSITION_CHECK)) + { +#if SCL_DEBUG_AI + putchar('('); +#endif + + alphaBeta *= valueMultiply; + uint8_t end = 0; + const char *b = board; + + depth--; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++b) + { + char s = *b; + + if (s != '.' && SCL_pieceIsWhite(s) == whitesTurn) + { + SCL_SquareSet moves; + + SCL_squareSetClear(moves); + + SCL_boardGetMoves(board,i,moves); + + if (!SCL_squareSetEmpty(moves)) + { + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + int8_t captureExtension = -1; + + if (board[iteratedSquare] != '.' && // takes a piece + (takenSquare == -1 || // extend on first taken sq. + (extended && takenSquare != -1) || // ignore check extension + (iteratedSquare == takenSquare))) // extend on same sq. taken + captureExtension = iteratedSquare; + + SCL_MoveUndo undo = SCL_boardMakeMove(board,i,iteratedSquare,'q'); + + uint8_t s0Dummy, s1Dummy; + char pDummy; + + SCL_UNUSED(s0Dummy); + SCL_UNUSED(s1Dummy); + SCL_UNUSED(pDummy); + +#if SCL_DEBUG_AI + if (debugFirst) + debugFirst = 0; + else + putchar(','); + + if (extended) + putchar('*'); + + printf("%s ",SCL_moveToString(board,i,iteratedSquare,'q',moveStr)); +#endif + + int16_t value = _SCL_boardEvaluateDynamic( + board, + depth, // this is depth - 1, we decremented it +#if SCL_ALPHA_BETA + valueMultiply * bestMoveValue, +#else + 0, +#endif + captureExtension + ) * valueMultiply; + + SCL_boardUndoMove(board,undo); + + if (value > bestMoveValue) + { + bestMoveValue = value; + +#if SCL_ALPHA_BETA + // alpha-beta pruning: + + if (value > alphaBeta) // no, >= can't be here + { + end = 1; + iterationEnd = 1; + } +#endif + } + + SCL_SQUARE_SET_ITERATE_END + } // !squre set empty? + } // valid piece? + + if (end) + break; + + } // for each square + +#if SCL_DEBUG_AI + putchar(')'); +#endif + } + else // don't dive recursively, evaluate statically + { + bestMoveValue = valueMultiply * + #ifndef SCL_EVALUATION_FUNCTION + _SCL_staticEvaluationFunction(board); + #else + SCL_EVALUATION_FUNCTION(board); + #endif + + /* For stalemate return the opposite value of the board, i.e. if the + position is good for white, then stalemate is good for black and vice + versa. */ + if (positionType == SCL_POSITION_STALEMATE) + bestMoveValue *= -1; + } + + /* Here we either improve (if the move worsens the situation) or devalve (if + it improves the situation) the result: this needs to be done so that good + moves far away are seen as worse compared to equally good moves achieved + in fewer moves. Without this an AI in winning situation may just repeat + random moves and draw by repetition even if it has mate in 1 (it sees all + moves as leading to mate). */ + bestMoveValue += bestMoveValue > _SCL_currentEval * valueMultiply ? -1 : 1; + +#if SCL_DEBUG_AI + printf("%d",bestMoveValue * valueMultiply); +#endif + + return bestMoveValue * valueMultiply; +} + +int16_t SCL_boardEvaluateDynamic(SCL_Board board, uint8_t baseDepth, + uint8_t extensionExtraDepth, SCL_StaticEvaluationFunction evalFunction) +{ + _SCL_staticEvaluationFunction = evalFunction; + _SCL_currentEval = evalFunction(board); + _SCL_depthHardLimit = 0; + _SCL_depthHardLimit -= extensionExtraDepth; + + return _SCL_boardEvaluateDynamic( + board, + baseDepth, + SCL_boardWhitesTurn(board) ? + SCL_EVALUATION_MAX_SCORE : (-1 * SCL_EVALUATION_MAX_SCORE),-1); +} + +void SCL_boardRandomMove(SCL_Board board, SCL_RandomFunction randFunc, + uint8_t *squareFrom, uint8_t *squareTo, char *resultProm) +{ + *resultProm = (randFunc() < 128) ? + ((randFunc() < 128) ? 'r' : 'n') : + ((randFunc() < 128) ? 'b' : 'q'); + + SCL_SquareSet set; + uint8_t white = SCL_boardWhitesTurn(board); + const char *s = board; + + SCL_squareSetClear(set); + + // find squares with pieces that have legal moves + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++s) + { + char c = *s; + + if (c != '.' && SCL_pieceIsWhite(c) == white) + { + SCL_SquareSet moves; + + SCL_boardGetMoves(board,i,moves); + + if (SCL_squareSetSize(moves) != 0) + SCL_squareSetAdd(set,i); + } + } + + *squareFrom = SCL_squareSetGetRandom(set,randFunc); + + SCL_boardGetMoves(board,*squareFrom,set); + + *squareTo = SCL_squareSetGetRandom(set,randFunc); +} + +void SCL_printBoardSimple( + SCL_Board board, + SCL_PutCharFunction putCharFunc, + uint8_t selectSquare, + uint8_t format) +{ + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_printBoard(board,putCharFunc,s,selectSquare,format,1,1,0); +} + +int16_t SCL_getAIMove( + SCL_Board board, + uint8_t baseDepth, + uint8_t extensionExtraDepth, + uint8_t endgameExtraDepth, + SCL_StaticEvaluationFunction evalFunc, + SCL_RandomFunction randFunc, + uint8_t randomness, + uint8_t repetitionMoveFrom, + uint8_t repetitionMoveTo, + uint8_t *resultFrom, + uint8_t *resultTo, + char *resultProm) +{ +#if SCL_DEBUG_AI + puts("===== AI debug ====="); + putchar('('); + unsigned char debugFirst = 1; + char moveStr[8]; +#endif + + if (baseDepth == 0) + { + SCL_boardRandomMove(board,randFunc,resultFrom,resultTo,resultProm); +#ifndef SCL_EVALUATION_FUNCTION + return evalFunc(board); +#else + return SCL_EVALUATION_FUNCTION(board); +#endif + } + + if (SCL_boardEstimatePhase(board) == SCL_PHASE_ENDGAME) + baseDepth += endgameExtraDepth; + + *resultFrom = 0; + *resultTo = 0; + *resultProm = 'q'; + + int16_t bestScore = + SCL_boardWhitesTurn(board) ? + -1 * SCL_EVALUATION_MAX_SCORE - 1 : (SCL_EVALUATION_MAX_SCORE + 1); + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + if (board[i] != '.' && + SCL_boardWhitesTurn(board) == SCL_pieceIsWhite(board[i])) + { + SCL_SquareSet moves; + + SCL_squareSetClear(moves); + + SCL_boardGetMoves(board,i,moves); + + SCL_SQUARE_SET_ITERATE_BEGIN(moves) + + int16_t score = 0; + +#if SCL_DEBUG_AI + if (debugFirst) + debugFirst = 0; + else + putchar(','); + +printf("%s ",SCL_moveToString( +board,i,iteratedSquare,'q',moveStr)); + +#endif + + if (i != repetitionMoveFrom || iteratedSquare != repetitionMoveTo) + { + SCL_MoveUndo undo = SCL_boardMakeMove(board,i,iteratedSquare,'q'); + + score = SCL_boardEvaluateDynamic(board,baseDepth - 1, + extensionExtraDepth,evalFunc); + + SCL_boardUndoMove(board,undo); + } + + if (randFunc != 0 && + randomness > 1 && + score < 16000 && + score > -16000) + { + /*^ We limit randomizing by about half the max score for two reasons: + to prevent over/under flows and secondly we don't want to alter + the highest values for checkmate -- these are modified by tiny + values depending on their depth so as to prevent endless loops in + which most moves are winning, biasing such values would completely + kill that algorithm */ + + int16_t bias = randFunc(); + bias = (bias - 128) / 2; + bias *= randomness - 1; + score += bias; + } + + uint8_t comparison = + score == bestScore; + + if ((comparison != 1) && + ( + (SCL_boardWhitesTurn(board) && score > bestScore) || + (!SCL_boardWhitesTurn(board) && score < bestScore) + )) + comparison = 2; + + uint8_t replace = 0; + + if (randFunc == 0) + replace = comparison == 2; + else + replace = (comparison == 2) || + ((comparison == 1) && (randFunc() < 160)); // not uniform distr. but simple + + if (replace) + { + *resultFrom = i; + *resultTo = iteratedSquare; + bestScore = score; + } + + SCL_SQUARE_SET_ITERATE_END + } + +#if SCL_DEBUG_AI + printf(")%d %s\n",bestScore,SCL_moveToString(board,*resultFrom,*resultTo,'q',moveStr)); + puts("===== AI debug end ===== "); +#endif + + return bestScore; +} + +uint8_t SCL_boardToFEN(SCL_Board board, char *string) +{ + uint8_t square = 56; + uint8_t spaces = 0; + uint8_t result = 0; + + #define put(c) { *string = (c); string++; result++; } + + while (1) // pieces + { + char s = board[square]; + + if (s == '.') + { + spaces++; + } + else + { + if (spaces != 0) + { + put('0' + spaces) + spaces = 0; + } + + put(s) + } + + square++; + + if (square % 8 == 0) + { + if (spaces != 0) + { + put('0' + spaces) + spaces = 0; + } + + if (square == 8) + break; + + put('/'); + + square -= 16; + } + } + + put(' '); + put(SCL_boardWhitesTurn(board) ? 'w' : 'b'); + put(' '); + + uint8_t b = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0xf0; + + if (b != 0) // castling + { + if (b & 0x10) put('K'); + if (b & 0x20) put('Q'); + if (b & 0x40) put('k'); + if (b & 0x80) put('q'); + } + else + put('-'); + + put(' '); + + b = board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f; + + if (b < 8) + { + put('a' + b); + put(SCL_boardWhitesTurn(board) ? '6' : '3'); + } + else + put('-'); + + for (uint8_t i = 0; i < 2; ++i) + { + put(' '); + + uint8_t moves = i == 0 ? + ((uint8_t) board[SCL_BOARD_MOVE_COUNT_BYTE]) : + (((uint8_t) board[SCL_BOARD_PLY_BYTE]) / 2 + 1); + + uint8_t hundreds = moves / 100; + uint8_t tens = (moves % 100) / 10; + + if (hundreds != 0) + { + put('0' + hundreds); + put('0' + tens); + } + else if (tens != 0) + put('0' + tens); + + put('0' + moves % 10); + + } + + *string = 0; // terminate the string + + return result + 1; + + #undef put +} + +uint8_t SCL_boardFromFEN(SCL_Board board, const char *string) +{ + uint8_t square = 56; + + while (1) + { + char c = *string; + + if (c == 0) + return 0; + + if (c != '/' && c != ' ') // ignore line separators + { + if (c < '9') // empty square sequence + { + while (c > '0') + { + board[square] = '.'; + square++; + c--; + } + } + else // piece + { + board[square] = c; + square++; + } + } + else + { + if (square == 8) + break; + + square -= 16; + } + + string++; + } + +#define nextChar string++; if (*string == 0) return 0; + + nextChar // space + + board[SCL_BOARD_PLY_BYTE] = *string == 'b'; + nextChar + + nextChar // space + + uint8_t castleEnPassant = 0x0; + + while (*string != ' ') + { + switch (*string) + { + case 'K': castleEnPassant |= 0x10; break; + case 'Q': castleEnPassant |= 0x20; break; + case 'k': castleEnPassant |= 0x40; break; + case 'q': castleEnPassant |= 0x80; break; + default: castleEnPassant |= 0xf0; break; // for partial XFEN compat. + } + + nextChar + } + + nextChar // space + + if (*string != '-') + { + castleEnPassant |= *string - 'a'; + nextChar + } + else + castleEnPassant |= 0x0f; + + nextChar + + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] = castleEnPassant; + + for (uint8_t i = 0; i < 2; ++i) + { + nextChar // space + + uint8_t ply = 0; + + while (1) + { + char c = *string; + + if (c < '0' || c > '9') + break; + + ply = ply * 10 + (c - '0'); + + string++; + } + + if (i == 0 && *string == 0) + return 0; + + if (i == 0) + board[SCL_BOARD_MOVE_COUNT_BYTE] = ply; + else + board[SCL_BOARD_PLY_BYTE] += (ply - 1) * 2; + } + +#if SCL_960_CASTLING + _SCL_board960RememberRookPositions(board); +#endif + + return 1; +#undef nextChar +} + +uint8_t SCL_boardEstimatePhase(SCL_Board board) +{ + uint16_t totalMaterial = 0; + + uint8_t ply = board[SCL_BOARD_PLY_BYTE]; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i) + { + char s = *board; + + if (s != '.') + { + int16_t v = SCL_pieceValue(s); + + if (!SCL_pieceIsWhite(s)) + v *= -1; + + totalMaterial += v; + } + + board++; + } + + if (totalMaterial < SCL_ENDGAME_MATERIAL_LIMIT) + return SCL_PHASE_ENDGAME; + + if (ply <= 10 && (totalMaterial >= SCL_START_MATERIAL - 3 * SCL_VALUE_PAWN)) + return SCL_PHASE_OPENING; + + return SCL_PHASE_MIDGAME; +} + +#define SCL_IMAGE_COUNT 12 + +static const uint8_t SCL_images[8 * SCL_IMAGE_COUNT] = +{ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0x81,0xff,0xff,0xff,0xff,0xff,0x81,0xff,0xff,0xff,0xff, + 0xff,0x81,0xe7,0xf7,0xf7,0xaa,0xff,0xbd,0xe7,0xf7,0xf7,0xaa, + 0xff,0xc3,0xc3,0xe3,0xc1,0x80,0xff,0x99,0xdb,0xeb,0xc9,0x94, + 0xe7,0xc3,0x81,0xc1,0x94,0x80,0xe7,0xdb,0xbd,0xdd,0xbe,0xbe, + 0xc3,0xc3,0x91,0xe3,0x80,0x80,0xdb,0x99,0x8d,0xeb,0xaa,0xbe, + 0xc3,0x81,0xe1,0xc1,0xc1,0xc1,0xdb,0xbd,0xdd,0xe3,0xdd,0xdd, + 0x81,0x81,0xc1,0x9c,0xc1,0xc1,0x81,0x81,0xc1,0x9c,0xc1,0xc1 +}; + +void SCL_drawBoard( + SCL_Board board, + SCL_PutPixelFunction putPixel, + uint8_t selectedSquare, + SCL_SquareSet highlightSquares, + uint8_t blackDown) +{ + uint8_t row = 0; + uint8_t col = 0; + uint8_t x = 0; + uint8_t y = 0; + uint16_t n = 0; + uint8_t s = 0; + + uint8_t pictureLine = 0; + uint8_t loadLine = 1; + + while (row < 8) + { + if (loadLine) + { + s = blackDown ? (row * 8 + (7 - col)) : ((7 - row) * 8 + col); + + char piece = board[s]; + + if (piece == '.') + pictureLine = (y == 4) ? 0xef : 0xff; + else + { + uint8_t offset = SCL_pieceIsWhite(piece) ? 6 : 0; + piece = SCL_pieceToColor(piece,1); + + switch (piece) + { + case 'R': offset += 1; break; + case 'N': offset += 2; break; + case 'B': offset += 3; break; + case 'K': offset += 4; break; + case 'Q': offset += 5; break; + default: break; + } + + pictureLine = SCL_images[y * SCL_IMAGE_COUNT + offset]; + } + + if (SCL_squareSetContains(highlightSquares,s)) + pictureLine &= (y % 2) ? 0xaa : 0x55; + + if (s == selectedSquare) + pictureLine &= (y == 0 || y == 7) ? 0x00 : ~0x81; + + loadLine = 0; + } + + putPixel(pictureLine & 0x80,n); + pictureLine <<= 1; + + n++; + x++; + + if (x == 8) + { + col++; + loadLine = 1; + x = 0; + } + + if (col == 8) + { + y++; + col = 0; + x = 0; + } + + if (y == 8) + { + row++; + y = 0; + } + } +} + +uint32_t SCL_boardHash32(const SCL_Board board) +{ + uint32_t result = (board[SCL_BOARD_PLY_BYTE] & 0x01) + + (((uint32_t) ((uint8_t) board[SCL_BOARD_ENPASSANT_CASTLE_BYTE])) << 24) + + board[SCL_BOARD_MOVE_COUNT_BYTE]; + + const char *b = board; + + for (uint8_t i = 0; i < SCL_BOARD_SQUARES; ++i, ++b) + { + switch (*b) + { +#define C(p,n) case p: result ^= (i + 1) * n; break; + // the below number are primes + C('P',4003) + C('R',84673) + C('N',93911) + C('B',999331) + C('Q',909091) + C('K',2796203) + C('p',4793) + C('r',19391) + C('n',391939) + C('b',108301) + C('q',174763) + C('k',2474431) +#undef C + default: break; + } + } + + // for extra spread of values we swap the low/high parts: + result = (result >> 16) | (result << 16); + + return result; +} + +void SCL_boardDisableCastling(SCL_Board board) +{ + board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] &= 0x0f; +} + +uint8_t SCL_boardMoveResetsCount(SCL_Board board, + uint8_t squareFrom, uint8_t squareTo) +{ + return board[squareFrom] == 'P' || board[squareFrom] == 'p' || + board[squareTo] != '.'; +} + +void SCL_printPGN(SCL_Record r, SCL_PutCharFunction putCharFunc, + SCL_Board initialState) +{ + if (SCL_recordLength(r) == 0) + return; + + uint16_t pos = 0; + + SCL_Board board; + + if (initialState != 0) + for (uint8_t i = 0; i < SCL_BOARD_STATE_SIZE; ++i) + board[i] = initialState[i]; + else + SCL_boardInit(board); + + while (1) + { + uint8_t s0, s1; + char p; + + uint8_t state = SCL_recordGetMove(r,pos,&s0,&s1,&p); + + pos++; + + if (pos % 2) + { + uint8_t move = pos / 2 + 1; + + if (move / 100 != 0) + putCharFunc('0' + move / 100); + + if (move / 10 != 0 || move / 100 != 0) + putCharFunc('0' + (move % 100) / 10); + + putCharFunc('0' + move % 10); + + putCharFunc('.'); + putCharFunc(' '); + } + +#if !SCL_960_CASTLING + if ((board[s0] == 'K' && s0 == 4 && (s1 == 2 || s1 == 6)) || + (board[s0] == 'k' && s0 == 60 && (s1 == 62 || s1 == 58))) +#else + if ((board[s0] == 'K' && board[s1] == 'R') || + (board[s0] == 'k' && board[s1] == 'r')) +#endif + { + putCharFunc('O'); + putCharFunc('-'); + putCharFunc('O'); + +#if !SCL_960_CASTLING + if (s1 == 58 || s1 == 2) +#else + if ((s1 == (board[SCL_BOARD_EXTRA_BYTE] & 0x07)) || + (s1 == 56 + (board[SCL_BOARD_EXTRA_BYTE] & 0x07))) +#endif + { + putCharFunc('-'); + putCharFunc('O'); + } + } + else + { + uint8_t pawn = board[s0] == 'P' || board[s0] == 'p'; + + if (!pawn) + { + putCharFunc(SCL_pieceToColor(board[s0],1)); + + // disambiguation: + + uint8_t specify = 0; + + for (int i = 0; i < SCL_BOARD_SQUARES; ++i) + if (i != s0 && board[i] == board[s0]) + { + SCL_SquareSet s; + + SCL_squareSetClear(s); + + SCL_boardGetMoves(board,i,s); + + if (SCL_squareSetContains(s,s1)) + specify |= (s0 % 8 != s1 % 8) ? 1 : 2; + } + + if (specify & 0x01) + putCharFunc('a' + s0 % 8); + + if (specify & 0x02) + putCharFunc('1' + s0 / 8); + } + + if (board[s1] != '.' || + (pawn && s0 % 8 != s1 % 8 && board[s1] == '.')) // capture? + { + if (pawn) + putCharFunc('a' + s0 % 8); + + putCharFunc('x'); + } + + putCharFunc('a' + s1 % 8); + putCharFunc('1' + s1 / 8); + + if (pawn && (s1 >= 56 || s1 <= 7)) // promotion? + { + putCharFunc('='); + putCharFunc(SCL_pieceToColor(p,1)); + } + } + + SCL_boardMakeMove(board,s0,s1,p); + + uint8_t position = SCL_boardGetPosition(board); + + if (position == SCL_POSITION_CHECK) + putCharFunc('+'); + + if (position == SCL_POSITION_MATE) + { + putCharFunc('#'); + break; + } + else if (state != SCL_RECORD_CONT) + { + putCharFunc('*'); + break; + } + + putCharFunc(' '); + } +} + +void SCL_recordCopy(SCL_Record recordFrom, SCL_Record recordTo) +{ + for (uint16_t i = 0; i < SCL_RECORD_MAX_SIZE; ++i) + recordTo[i] = recordFrom[i]; +} + +void SCL_gameInit(SCL_Game *game, const SCL_Board startState) +{ + game->startState = startState; + + if (startState != 0) + SCL_boardCopy(startState,game->board); + else + SCL_boardInit(game->board); + + SCL_recordInit(game->record); + + for (uint8_t i = 0; i < 14; ++i) + game->prevMoves[i] = 0; + + game->state = SCL_GAME_STATE_PLAYING; + game->ply = 0; + + SCL_recordInit(game->record); +} + +uint8_t SCL_gameGetRepetiotionMove(SCL_Game *game, + uint8_t *squareFrom, uint8_t *squareTo) +{ + if (squareFrom != 0 && squareTo != 0) + { + *squareFrom = 0; + *squareTo = 0; + } + + /* pos. 1st 2nd 3rd + | | | + v v v + 01 23 45 67 89 AB CD EF + move ab cd ba dc ab cd ba dc */ + + if (game->ply >= 7 && + game->prevMoves[0] == game->prevMoves[5] && + game->prevMoves[0] == game->prevMoves[8] && + game->prevMoves[0] == game->prevMoves[13] && + + game->prevMoves[1] == game->prevMoves[4] && + game->prevMoves[1] == game->prevMoves[9] && + game->prevMoves[1] == game->prevMoves[12] && + + game->prevMoves[2] == game->prevMoves[7] && + game->prevMoves[2] == game->prevMoves[10] && + + game->prevMoves[3] == game->prevMoves[6] && + game->prevMoves[3] == game->prevMoves[11] + ) + { + if (squareFrom != 0 && squareTo != 0) + { + *squareFrom = game->prevMoves[3]; + *squareTo = game->prevMoves[2]; + } + + return 1; + } + + return 0; +} + +void SCL_gameMakeMove(SCL_Game *game, uint8_t squareFrom, uint8_t squareTo, + char promoteTo) +{ + uint8_t repetitionS0, repetitionS1; + + SCL_gameGetRepetiotionMove(game,&repetitionS0,&repetitionS1); + SCL_boardMakeMove(game->board,squareFrom,squareTo,promoteTo); + SCL_recordAdd(game->record,squareFrom,squareTo,promoteTo,SCL_RECORD_CONT); + // ^ TODO: SCL_RECORD_CONT + + game->ply++; + + for (uint8_t i = 0; i < 14 - 2; ++i) + game->prevMoves[i] = game->prevMoves[i + 2]; + + game->prevMoves[12] = squareFrom; + game->prevMoves[13] = squareTo; + + if (squareFrom == repetitionS0 && squareTo == repetitionS1) + game->state = SCL_GAME_STATE_DRAW_REPETITION; + else if (game->board[SCL_BOARD_MOVE_COUNT_BYTE] >= 50) + game->state = SCL_GAME_STATE_DRAW_50; + else + { + uint8_t position = SCL_boardGetPosition(game->board); + + switch (position) + { + case SCL_POSITION_MATE: + game->state = SCL_boardWhitesTurn(game->board) ? + SCL_GAME_STATE_BLACK_WIN : SCL_GAME_STATE_WHITE_WIN; + break; + + case SCL_POSITION_STALEMATE: + game->state = SCL_GAME_STATE_DRAW_STALEMATE; + break; + + case SCL_POSITION_DEAD: + game->state = SCL_GAME_STATE_DRAW_DEAD; + break; + + default: break; + } + } +} + +uint8_t SCL_gameUndoMove(SCL_Game *game) +{ + if (game->ply == 0) + return 0; + + if ((game->ply - 1) > SCL_recordLength(game->record)) + return 0; // can't undo, lacking record + + SCL_Record r; + + SCL_recordCopy(game->record,r); + + uint16_t applyMoves = game->ply - 1; + + SCL_gameInit(game,game->startState); + + for (uint16_t i = 0; i < applyMoves; ++i) + { + uint8_t s0, s1; + char p; + + SCL_recordGetMove(r,i,&s0,&s1,&p); + SCL_gameMakeMove(game,s0,s1,p); + } + + return 1; +} + +uint8_t SCL_boardMoveIsLegal(SCL_Board board, uint8_t squareFrom, + uint8_t squareTo) +{ + if (squareFrom >= SCL_BOARD_SQUARES || squareTo >= SCL_BOARD_SQUARES) + return 0; + + char piece = board[squareFrom]; + + if ((piece == '.') || + (SCL_boardWhitesTurn(board) != SCL_pieceIsWhite(piece))) + return 0; + + SCL_SquareSet moves; + + SCL_boardGetMoves(board,squareFrom,moves); + + return SCL_squareSetContains(moves,squareTo); +} + +#endif // guard diff --git a/movement/make/Makefile b/movement/make/Makefile index 8d045bf..2116eba 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -24,6 +24,7 @@ INCLUDES += \ -I../lib/vsop87/ \ -I../lib/astrolib/ \ -I../lib/morsecalc/ \ + -I../lib/smallchesslib/ \ # If you add any other source files you wish to compile, add them after ../app.c # Note that you will need to add a backslash at the end of any line you wish to continue, i.e. @@ -147,6 +148,7 @@ SRCS += \ ../watch_faces/demo/beeps_face.c \ ../watch_faces/sensor/accel_interrupt_count_face.c \ ../watch_faces/complication/metronome_face.c \ + ../watch_faces/complication/smallchess_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 1364ca9..630f264 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -122,6 +122,7 @@ #include "beeps_face.h" #include "accel_interrupt_count_face.h" #include "metronome_face.h" +#include "smallchess_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/smallchess_face.c b/movement/watch_faces/complication/smallchess_face.c new file mode 100644 index 0000000..df70064 --- /dev/null +++ b/movement/watch_faces/complication/smallchess_face.c @@ -0,0 +1,504 @@ +/* + * MIT License + * + * Copyright (c) 2023 Jeremy O'Brien + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include "smallchesslib.h" + +#include "smallchess_face.h" +#include "watch.h" + +#define PIECE_LIST_END_MARKER 0xff + +int8_t cpu_done_beep[] = {BUZZER_NOTE_C5, 5, BUZZER_NOTE_C6, 5, BUZZER_NOTE_C7, 5, 0}; + +static void smallchess_init_board(smallchess_face_state_t *state) { + SCL_gameInit((SCL_Game *)state->game, 0); + memset(state->moveable_pieces, 0xff, sizeof(state->moveable_pieces)); + memset(state->moveable_dests, 0xff, sizeof(state->moveable_dests)); +} + +void smallchess_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(smallchess_face_state_t)); + memset(*context_ptr, 0, sizeof(smallchess_face_state_t)); + + /* now alloc/init the game board */ + smallchess_face_state_t *state = (smallchess_face_state_t *)*context_ptr; + state->game = malloc(sizeof(SCL_Game)); + smallchess_init_board(*context_ptr); + } +} + +void smallchess_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} + +static void _smallchess_calc_moveable_pieces(smallchess_face_state_t *state) { + int moveable_pieces_idx = 0; + SCL_Game *game = (SCL_Game *)state->game; + for (int i = 0; i < SCL_BOARD_SQUARES; ++i) { + if (game->board[i] != '.' && + SCL_pieceIsWhite(game->board[i]) == SCL_boardWhitesTurn(game->board)) { + SCL_SquareSet moveable_pieces = SCL_SQUARE_SET_EMPTY; + SCL_boardGetMoves(game->board, i, moveable_pieces); + if (SCL_squareSetSize(moveable_pieces) != 0) { + state->moveable_pieces[moveable_pieces_idx] = i; + moveable_pieces_idx++; + } + } + } + state->moveable_pieces[moveable_pieces_idx] = PIECE_LIST_END_MARKER; + state->moveable_pieces_idx = 0; +} + +static void _smallchess_make_ai_move(smallchess_face_state_t *state) { + char ai_from_str[3] = {0}; + char ai_to_str[3] = {0}; + uint8_t rep_from, rep_to; + char ai_prom; + + watch_clear_display(); + watch_start_character_blink('C', 100); + SCL_gameGetRepetiotionMove(state->game, &rep_from, &rep_to); + +#ifndef __EMSCRIPTEN__ + hri_oscctrl_write_OSC16MCTRL_FSEL_bf(OSCCTRL, OSCCTRL_OSC16MCTRL_FSEL_16_Val); +#endif + SCL_getAIMove(state->game, 3, 0, 0, SCL_boardEvaluateStatic, NULL, 0, rep_from, rep_to, &state->ai_from_square, &state->ai_to_square, &ai_prom); +#ifndef __EMSCRIPTEN__ + hri_oscctrl_write_OSC16MCTRL_FSEL_bf(OSCCTRL, OSCCTRL_OSC16MCTRL_FSEL_4_Val); +#endif + + SCL_gameMakeMove(state->game, state->ai_from_square, state->ai_to_square, ai_prom); + watch_stop_blink(); + + watch_buzzer_play_sequence(cpu_done_beep, NULL); + + /* cache the move as a string for SHOW_CPU_MOVE state */ + SCL_squareToString(state->ai_from_square, ai_from_str); + SCL_squareToString(state->ai_to_square, ai_to_str); + snprintf(state->last_move_str, sizeof(state->last_move_str), " %s-%s", ai_from_str, ai_to_str); + + /* now cache the list of legal pieces we can move */ + _smallchess_calc_moveable_pieces(state); +} + +static char _smallchess_make_lowercase(char c) { + if (c < 0x61) + return c + 0x20; + + return c; +} + +static void _smallchess_get_endgame_string(smallchess_face_state_t *state, char *buf, uint8_t len) { + uint8_t endgame_state = ((SCL_Game *)state->game)->state; + uint16_t ply = ((SCL_Game *)state->game)->ply; + + switch (endgame_state) { + case SCL_GAME_STATE_WHITE_WIN: + snprintf(buf, len, "Wh%2dm&ate ", ply); + break; + case SCL_GAME_STATE_BLACK_WIN: + snprintf(buf, len, "bL%2dm&ate ", ply); + break; + case SCL_GAME_STATE_DRAW: + case SCL_GAME_STATE_DRAW_STALEMATE: + case SCL_GAME_STATE_DRAW_REPETITION: + case SCL_GAME_STATE_DRAW_50: + case SCL_GAME_STATE_DRAW_DEAD: + snprintf(buf, len, " %2d Drauu", ply); + break; + default: + snprintf(buf, len, " %2d Error", ply); + break; + } +} + +static void _smallchess_face_update_lcd(smallchess_face_state_t *state) { + uint8_t start_square; + uint8_t end_square; + char start_coord[3] = {0}; + char end_coord[3] = {0}; + char buf[14] = {0}; + + uint16_t ply = ((SCL_Game *)state->game)->ply; + + switch (state->state) { + case SMALLCHESS_MENU_RESUME: + snprintf(buf, sizeof(buf), "SC%2dResume", ply); + break; + case SMALLCHESS_MENU_UNDO: + snprintf(buf, sizeof(buf), "SC%2d Undo ", ply); + break; + case SMALLCHESS_MENU_SHOW_LAST_MOVE: + snprintf(buf, sizeof(buf), "SC%2dShLast", ply); + break; + case SMALLCHESS_MENU_NEW_WHITE: + snprintf(buf, sizeof(buf), "Wh%2dStart ", ply); + break; + case SMALLCHESS_MENU_NEW_BLACK: + snprintf(buf, sizeof(buf), "bL%2dStart ", ply); + break; + case SMALLCHESS_SHOW_CPU_MOVE: + case SMALLCHESS_SHOW_LAST_MOVE: + snprintf(buf, + sizeof(buf), + "%c %2d%s", + _smallchess_make_lowercase(((SCL_Game *)state->game)->board[state->ai_to_square]), + ply, + state->last_move_str); + + break; + case SMALLCHESS_SELECT_PIECE: + if (((SCL_Game *)state->game)->state != SCL_GAME_STATE_PLAYING) { + _smallchess_get_endgame_string(state, buf, sizeof(buf)); + break; + } + start_square = state->moveable_pieces[state->moveable_pieces_idx]; + SCL_squareToString(start_square, start_coord); + snprintf(buf, + sizeof(buf), + "%c %2d %s- ", + _smallchess_make_lowercase(((SCL_Game *)state->game)->board[start_square]), + ply + 1, + start_coord); + break; + case SMALLCHESS_SELECT_DEST: + start_square = state->moveable_pieces[state->moveable_pieces_idx]; + SCL_squareToString(start_square, start_coord); + end_square = state->moveable_dests[state->moveable_dests_idx]; + SCL_squareToString(end_square, end_coord); + snprintf(buf, + sizeof(buf), + "%c %2d %s-%s", + _smallchess_make_lowercase(((SCL_Game *)state->game)->board[start_square]), + ply + 1, + start_coord, + end_coord); + break; + default: + break; + } + + watch_display_string(buf, 0); +} + +static void _smallchess_select_main_menu_subitem(smallchess_face_state_t *state) { + char from_str[3] = {0}; + char to_str[3] = {0}; + char prom; + + switch (state->state) { + case SMALLCHESS_MENU_RESUME: + state->state = SMALLCHESS_SELECT_PIECE; + break; + case SMALLCHESS_MENU_UNDO: + /* undo twice to undo the CPU's move and our move */ + SCL_gameUndoMove((SCL_Game *)state->game); + SCL_gameUndoMove((SCL_Game *)state->game); + /* and re-calculate the moveable pieces for this new state */ + _smallchess_calc_moveable_pieces(state); + break; + case SMALLCHESS_MENU_NEW_WHITE: + SCL_gameInit((SCL_Game *)state->game, 0); + _smallchess_calc_moveable_pieces(state); + state->state = SMALLCHESS_SELECT_PIECE; + break; + case SMALLCHESS_MENU_NEW_BLACK: + SCL_gameInit((SCL_Game *)state->game, 0); + /* force a move since black is playing */ + _smallchess_make_ai_move(state); + state->state = SMALLCHESS_SHOW_CPU_MOVE; + break; + case SMALLCHESS_MENU_SHOW_LAST_MOVE: + /* fetch the move */ + SCL_recordGetMove(((SCL_Game *)state->game)->record, ((SCL_Game *)state->game)->ply - 1, &state->ai_from_square, &state->ai_to_square, &prom); + SCL_squareToString(state->ai_from_square, from_str); + SCL_squareToString(state->ai_to_square, to_str); + snprintf(state->last_move_str, sizeof(state->last_move_str), " %s-%s", from_str, to_str); + state->state = SMALLCHESS_SHOW_LAST_MOVE; + break; + default: + break; + } +} + +static void _smallchess_handle_select_piece_button_event(smallchess_face_state_t *state, movement_event_t event) { + SCL_SquareSet moveable_dests = SCL_SQUARE_SET_EMPTY; + + /* back to main menu on any event when game ends */ + if (((SCL_Game *)state->game)->state != SCL_GAME_STATE_PLAYING) { + state->state = SMALLCHESS_MENU_RESUME; + return; + } + + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + // check for no moves possible state (shouldn't happen but this will prevent weirdness) + if (state->moveable_pieces[0] == PIECE_LIST_END_MARKER) { + return; + } + + state->moveable_pieces_idx += 1; + if (state->moveable_pieces_idx >= NUM_ELEMENTS(state->moveable_pieces)) { + state->moveable_pieces_idx = 0; + } + + if (state->moveable_pieces[state->moveable_pieces_idx] == PIECE_LIST_END_MARKER) { + state->moveable_pieces_idx = 0; + } + break; + case EVENT_LIGHT_BUTTON_UP: + // check for no moves possible state (shouldn't happen but this will prevent weirdness) + if (state->moveable_pieces[0] == PIECE_LIST_END_MARKER) { + return; + } + + /* handle wrap around */ + if (state->moveable_pieces_idx == 0) { + for (unsigned int i = 0; i < NUM_ELEMENTS(state->moveable_pieces); i++) { + if (state->moveable_pieces[i] == 0xff) { + state->moveable_pieces_idx = i - 1; + break; + } + } + } else { + state->moveable_pieces_idx -= 1; + } + break; + case EVENT_LIGHT_LONG_PRESS: + if (((SCL_Game *)state->game)->ply == 0) { + state->state = SMALLCHESS_MENU_NEW_WHITE; + } else { + state->state = SMALLCHESS_MENU_RESUME; + } + break; + case EVENT_ALARM_LONG_PRESS: + /* pre-calculate the possible moves this piece can make */ + SCL_boardGetMoves(((SCL_Game *)state->game)->board, state->moveable_pieces[state->moveable_pieces_idx], moveable_dests); + state->moveable_dests_idx = 0; + SCL_SQUARE_SET_ITERATE_BEGIN(moveable_dests) + state->moveable_dests[state->moveable_dests_idx] = iteratedSquare; + state->moveable_dests_idx++; + SCL_SQUARE_SET_ITERATE_END + state->moveable_dests[state->moveable_dests_idx] = PIECE_LIST_END_MARKER; + state->moveable_dests_idx = 0; + state->state = SMALLCHESS_SELECT_DEST; + default: + break; + } +} + +static void _smallchess_handle_select_dest_button_event(smallchess_face_state_t *state, movement_event_t event) { + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + // check for no moves possible state (shouldn't happen but this will prevent weirdness) + if (state->moveable_dests[0] == PIECE_LIST_END_MARKER) { + return; + } + state->moveable_dests_idx += 1; + if (state->moveable_dests_idx >= (sizeof(state->moveable_dests) / sizeof(state->moveable_dests[0]))) { + state->moveable_dests_idx = 0; + } + + if (state->moveable_dests[state->moveable_dests_idx] == PIECE_LIST_END_MARKER) { + state->moveable_dests_idx = 0; + } + break; + case EVENT_LIGHT_BUTTON_UP: + // check for no moves possible state (shouldn't happen but this will prevent weirdness) + if (state->moveable_dests[0] == PIECE_LIST_END_MARKER) { + return; + } + + /* handle wrap around */ + if (state->moveable_dests_idx == 0) { + for (unsigned int i = 0; i < NUM_ELEMENTS(state->moveable_dests); i++) { + if (state->moveable_dests[i] == 0xff) { + state->moveable_dests_idx = i - 1; + break; + } + } + } else { + state->moveable_dests_idx -= 1; + } + break; + case EVENT_LIGHT_LONG_PRESS: + state->state = SMALLCHESS_SELECT_PIECE; + break; + case EVENT_ALARM_LONG_PRESS: + SCL_gameMakeMove((SCL_Game *)state->game, state->moveable_pieces[state->moveable_pieces_idx], state->moveable_dests[state->moveable_dests_idx], 'q'); + + /* if the player didn't win or draw here, calculate a move */ + if (((SCL_Game *)state->game)->state == SCL_GAME_STATE_PLAYING) { + _smallchess_make_ai_move(state); + state->state = SMALLCHESS_SHOW_CPU_MOVE; + } else { + /* player ended the game through mate or draw; jump to select piece screen to show state */ + state->state = SMALLCHESS_SELECT_PIECE; + } + break; + default: + break; + } +} + +/* this just waits until any button is hit */ +static void _smallchess_handle_show_cpu_move_button_event(smallchess_face_state_t *state, movement_event_t event) { + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_LONG_PRESS: + case EVENT_LIGHT_LONG_PRESS: + state->state = SMALLCHESS_SELECT_PIECE; + break; + default: + break; + } +} + +static void _smallchess_handle_show_last_move_button_event(smallchess_face_state_t *state, movement_event_t event) { + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + case EVENT_LIGHT_BUTTON_UP: + case EVENT_ALARM_LONG_PRESS: + case EVENT_LIGHT_LONG_PRESS: + state->state = SMALLCHESS_MENU_SHOW_LAST_MOVE; + break; + default: + break; + } +} + +static void _smallchess_handle_playing_button_event(smallchess_face_state_t *state, movement_event_t event) { + if (state->state == SMALLCHESS_SELECT_PIECE) { + _smallchess_handle_select_piece_button_event(state, event); + } else if (state->state == SMALLCHESS_SELECT_DEST) { + _smallchess_handle_select_dest_button_event(state, event); + } else if (state->state == SMALLCHESS_SHOW_CPU_MOVE) { + _smallchess_handle_show_cpu_move_button_event(state, event); + } else if (state->state == SMALLCHESS_SHOW_LAST_MOVE) { + _smallchess_handle_show_last_move_button_event(state, event); + } +} + +static void _smallchess_handle_main_menu_button_event(smallchess_face_state_t *state, movement_event_t event) { + uint16_t ply = ((SCL_Game *)state->game)->ply; + + switch (event.event_type) { + case EVENT_ALARM_BUTTON_UP: + /* no game started; only offer start white/start black */ + if (ply == 0) { + if (state->state == SMALLCHESS_MENU_NEW_WHITE) { + state->state = SMALLCHESS_MENU_NEW_BLACK; + } else { + state->state = SMALLCHESS_MENU_NEW_WHITE; + } + } else { + state->state++; + if (state->state >= SMALLCHESS_PLAYING_SPLIT) { + state->state = SMALLCHESS_MENU_RESUME; + } + } + + break; + case EVENT_LIGHT_BUTTON_UP: + /* no game started; only offer start white/start black */ + if (ply == 0) { + if (state->state == SMALLCHESS_MENU_NEW_BLACK) { + state->state = SMALLCHESS_MENU_NEW_WHITE; + } else { + state->state = SMALLCHESS_MENU_NEW_BLACK; + } + } else { + if (state->state == SMALLCHESS_MENU_RESUME) { + state->state = SMALLCHESS_PLAYING_SPLIT - 1; + } else { + state->state--; + } + } + + break; + case EVENT_ALARM_LONG_PRESS: + _smallchess_select_main_menu_subitem(state); + break; + default: + break; + } +} + +static void _smallchess_handle_button_event(smallchess_face_state_t *state, movement_event_t event) { + if (state->state < SMALLCHESS_PLAYING_SPLIT) { + /* in main menu */ + _smallchess_handle_main_menu_button_event(state, event); + } else if (state->state > SMALLCHESS_PLAYING_SPLIT) { + /* in piece selection */ + _smallchess_handle_playing_button_event(state, event); + } +} + +bool smallchess_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + smallchess_face_state_t *state = (smallchess_face_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + if (((SCL_Game *)state->game)->ply == 0) { + state->state = SMALLCHESS_MENU_NEW_WHITE; + } else { + state->state = SMALLCHESS_MENU_RESUME; + } + _smallchess_face_update_lcd(state); + break; + case EVENT_LIGHT_BUTTON_UP: + case EVENT_LIGHT_LONG_PRESS: + case EVENT_ALARM_BUTTON_UP: + case EVENT_ALARM_LONG_PRESS: + _smallchess_handle_button_event(state, event); + _smallchess_face_update_lcd(state); + break; + case EVENT_TICK: + break; + case EVENT_TIMEOUT: + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; + default: + movement_default_loop_handler(event, settings); + break; + } + + return true; +} + +void smallchess_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + watch_set_led_off(); +} diff --git a/movement/watch_faces/complication/smallchess_face.h b/movement/watch_faces/complication/smallchess_face.h new file mode 100644 index 0000000..2eb0786 --- /dev/null +++ b/movement/watch_faces/complication/smallchess_face.h @@ -0,0 +1,90 @@ +/* + * MIT License + * + * Copyright (c) 2023 Jeremy O'Brien + * + * 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 SMALLCHESS_FACE_H_ +#define SMALLCHESS_FACE_H_ + +#include "movement.h" + +/* + * Chess watchface + * + * Implements a (very) simple chess engine. + * Uses smallchesslib for the engine: https://codeberg.org/drummyfish/smallchesslib + * + * When moving a piece, only valid pieces and moves are presented. + * + * Interaction is done through a simple menu/submenu system: + * - Light button: navigate backwards through the current menu + * - Alarm button: navigate forwards through the current menu + * - Light button (long press): navigate up to the parent menu + * - Alarm button (long press): select the current item or submenu + */ + +enum smallchess_state { + /* main menu */ + SMALLCHESS_MENU_RESUME, + SMALLCHESS_MENU_SHOW_LAST_MOVE, + SMALLCHESS_MENU_UNDO, + SMALLCHESS_MENU_NEW_WHITE, + SMALLCHESS_MENU_NEW_BLACK, + + SMALLCHESS_PLAYING_SPLIT, + + /* playing game submenu */ + SMALLCHESS_SHOW_LAST_MOVE, + SMALLCHESS_SHOW_CPU_MOVE, + SMALLCHESS_SELECT_PIECE, + SMALLCHESS_SELECT_DEST, +}; + +#define NUM_ELEMENTS(a) (sizeof(a) / sizeof(a[0])) +#define SMALLCHESS_NUM_PIECES 16 // number of pieces each player has + +typedef struct { + void *game; + enum smallchess_state state; + uint8_t moveable_pieces[SMALLCHESS_NUM_PIECES + 1]; + uint8_t moveable_pieces_idx; + uint8_t moveable_dests[29]; // this magic number represents the maximum number of moves a piece can make (queen in center of board) + // plus one for the end list marker + uint8_t moveable_dests_idx; + char last_move_str[7]; + uint8_t ai_from_square, ai_to_square; +} smallchess_face_state_t; + +void smallchess_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void smallchess_face_activate(movement_settings_t *settings, void *context); +bool smallchess_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void smallchess_face_resign(movement_settings_t *settings, void *context); + +#define smallchess_face ((const watch_face_t){ \ + smallchess_face_setup, \ + smallchess_face_activate, \ + smallchess_face_loop, \ + smallchess_face_resign, \ + NULL, \ +}) + +#endif // SMALLCHESS_FACE_H_