Spaces:
Build error
Build error
| """ | |
| Keep the state in a 1D int array | |
| index[0] = remaining steps | |
| [[status, status, status, status, status] | |
| for _ in "ABCD..."] | |
| where status has codes | |
| [0, 0, 0] - no information about the char | |
| [1, 0, 0] - char is definitely not in this spot | |
| [0, 1, 0] - char is maybe in this spot | |
| [0, 0, 1] - char is definitely in this spot | |
| """ | |
| import collections | |
| from typing import List, Tuple | |
| import numpy as np | |
| from .const import CHAR_REWARD, WORDLE_CHARS, WORDLE_N | |
| WordleState = np.ndarray | |
| def get_nvec(max_turns: int): | |
| return [max_turns] + [2] * 3 * WORDLE_N * len(WORDLE_CHARS) | |
| def new(max_turns: int) -> WordleState: | |
| return np.array( | |
| [max_turns] + [0, 0, 0] * WORDLE_N * len(WORDLE_CHARS), dtype=np.int32 | |
| ) | |
| def remaining_steps(state: WordleState) -> int: | |
| return state[0] | |
| NO = 0 | |
| SOMEWHERE = 1 | |
| YES = 2 | |
| def update_from_mask(state: WordleState, word: str, mask: List[int]) -> WordleState: | |
| """ | |
| return a copy of state that has been updated to new state | |
| From a mask we need slighty different logic since we don't know the | |
| goal word. | |
| :param state: | |
| :param word: | |
| :param goal_word: | |
| :return: | |
| """ | |
| state = state.copy() | |
| prior_yes = [] | |
| prior_maybe = [] | |
| # We need two passes because first pass sets definitely yesses | |
| # second pass sets the no's for those who aren't already yes | |
| state[0] -= 1 | |
| for i, c in enumerate(word): | |
| cint = ord(c) - ord(WORDLE_CHARS[0]) | |
| offset = 1 + cint * WORDLE_N * 3 | |
| if mask[i] == YES: | |
| prior_yes.append(c) | |
| _set_yes(state, offset, cint, i) | |
| for i, c in enumerate(word): | |
| cint = ord(c) - ord(WORDLE_CHARS[0]) | |
| offset = 1 + cint * WORDLE_N * 3 | |
| if mask[i] == SOMEWHERE: | |
| prior_maybe.append(c) | |
| # Char at position i = no, | |
| # and in other positions maybe except it had a value before, | |
| # other chars stay as they are | |
| _set_no(state, offset, i) | |
| _set_if_cero(state, offset, [0, 1, 0]) | |
| elif mask[i] == NO: | |
| # Need to check this first in case there's prior maybe or yes | |
| if c in prior_yes or c in prior_maybe: | |
| # Definitely not here | |
| _set_no(state, offset, i) | |
| # It's zero everywhere except the yesses and maybes | |
| _set_if_cero(state, offset, [1, 0, 0]) | |
| else: | |
| # Just straight up no | |
| _set_all_no(state, offset) | |
| return state | |
| def get_mask(word: str, goal_word: str) -> List[int]: | |
| # Definite yesses first | |
| mask = [0, 0, 0, 0, 0] | |
| counts = collections.Counter(goal_word) | |
| for i, c in enumerate(word): | |
| if goal_word[i] == c: | |
| mask[i] = 2 | |
| counts[c] -= 1 | |
| for i, c in enumerate(word): | |
| if mask[i] == 2: | |
| continue | |
| elif c in counts: | |
| if counts[c] > 0: | |
| mask[i] = 1 | |
| counts[c] -= 1 | |
| else: | |
| for j in range(i + 1, len(mask)): | |
| if mask[j] == 2: | |
| continue | |
| mask[j] = 0 | |
| return mask | |
| def update_mask(state: WordleState, word: str, goal_word: str) -> WordleState: | |
| """ | |
| return a copy of state that has been updated to new state | |
| :param state: | |
| :param word: | |
| :param goal_word: | |
| :return: | |
| """ | |
| mask = get_mask(word, goal_word) | |
| return update_from_mask(state, word, mask) | |
| def update(state: WordleState, word: str, goal_word: str) -> Tuple[WordleState, float]: | |
| state = state.copy() | |
| reward = 0 | |
| state[0] -= 1 | |
| processed_letters = [] | |
| for i, c in enumerate(word): | |
| cint = ord(c) - ord(WORDLE_CHARS[0]) | |
| offset = 1 + cint * WORDLE_N * 3 | |
| if goal_word[i] == c: | |
| # char at position i = yes, all other chars at position i == no | |
| reward += CHAR_REWARD | |
| _set_yes(state, offset, cint, i) | |
| processed_letters.append(c) | |
| for i, c in enumerate(word): | |
| cint = ord(c) - ord(WORDLE_CHARS[0]) | |
| offset = 1 + cint * WORDLE_N * 3 | |
| if goal_word[i] != c: | |
| if c in goal_word and goal_word.count(c) > processed_letters.count(c): | |
| # Char at position i = no, | |
| # and in other positions maybe except it had a value before, | |
| # other chars stay as they are | |
| _set_no(state, offset, i) | |
| _set_if_cero(state, offset, [0, 1, 0]) | |
| reward += CHAR_REWARD * 0.1 | |
| elif c not in goal_word: | |
| # Char at all positions = no | |
| _set_all_no(state, offset) | |
| else: | |
| # goal_word.count(c) <= processed_letters.count(c) | |
| # and goal in word | |
| # At i and in every position which is not set = no | |
| _set_no(state, offset, i) | |
| _set_if_cero(state, offset, [1, 0, 0]) | |
| processed_letters.append(c) | |
| return state, reward | |
| def _set_if_cero(state, offset, value): | |
| # set offset character with value at all positions | |
| # but only if it didnt have a value before | |
| for char_idx in range(0, WORDLE_N * 3, 3): | |
| char_offset = offset + char_idx | |
| if tuple(state[char_offset : char_offset + 3]) == (0, 0, 0): | |
| state[char_offset : char_offset + 3] = value | |
| def _set_yes(state, offset, char_int, char_pos): | |
| # char at position char_pos = yes, | |
| # all other chars at position char_pos == no | |
| pos_offset = 3 * char_pos | |
| state[offset + pos_offset : offset + pos_offset + 3] = [0, 0, 1] | |
| for ocint in range(len(WORDLE_CHARS)): | |
| if ocint != char_int: | |
| oc_offset = 1 + ocint * WORDLE_N * 3 | |
| yes_index = oc_offset + pos_offset | |
| state[yes_index : yes_index + 3] = [1, 0, 0] | |
| def _set_no(state, offset, char_pos): | |
| # Set offset character = no at char_pos position | |
| state[offset + 3 * char_pos : offset + 3 * char_pos + 3] = [1, 0, 0] | |
| def _set_all_no(state, offset): | |
| # Set offset character = no at all positions | |
| state[offset : offset + 3 * WORDLE_N] = [1, 0, 0] * WORDLE_N | |