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):