├── .gitignore ├── LICENSE.txt ├── src ├── engine │ ├── game_error.rs │ ├── utils.rs │ ├── mod.rs │ └── words.rs ├── events.rs ├── main.rs ├── theme.rs ├── app.rs └── ui.rs ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | do whatever you want -------------------------------------------------------------------------------- /src/engine/game_error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq)] 2 | pub enum GameError { 3 | GameNotLostError, 4 | } 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wordlet" 3 | version = "0.2.0" 4 | authors = ["Scott Luptowski "] 5 | edition = "2021" 6 | description = "A terminal-based clone of wordle" 7 | keywords = ["wordlet", "wordle", "word", "game"] 8 | license-file = "LICENSE.txt" 9 | 10 | [dependencies] 11 | clap = { version = "3.0.6", features = ["derive"] } 12 | chrono = "0.4" 13 | crossterm = "0.19" 14 | tui = { version = "0.14", default-features = false, features = ['crossterm'] } 15 | rand = "0.8.0" -------------------------------------------------------------------------------- /src/engine/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::words::dictionary_words; 2 | use rand::seq::SliceRandom; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | pub fn dictionary() -> HashSet { 6 | let mut dict = HashSet::new(); 7 | for w in dictionary_words() { 8 | dict.insert(w); 9 | } 10 | dict 11 | } 12 | 13 | pub fn get_random_word() -> String { 14 | let dict = dictionary(); 15 | let list = Vec::from_iter(dict.iter()); 16 | list.choose(&mut rand::thread_rng()).unwrap().to_string() 17 | } 18 | 19 | pub fn build_letter_counts(word: &str) -> HashMap { 20 | let mut counts = HashMap::new(); 21 | for c in word.chars() { 22 | match counts.get_mut(&c) { 23 | Some(v) => *v += 1, 24 | None => { 25 | counts.insert(c, 1); 26 | () 27 | } 28 | }; 29 | } 30 | counts 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wordlet 2 | 3 | Wordlet is a command line clone of 4 | [Wordle](https://www.powerlanguage.co.uk/wordle/), written in Rust. 5 | 6 | ![gameplay](https://user-images.githubusercontent.com/3178471/150548930-9dab1e11-2997-48da-af33-6e3386017a50.gif) 7 | 8 | ## Installation 9 | 10 | `$ cargo install wordlet` 11 | 12 | ## Usage 13 | 14 | After installation, start Wordlet by typing `wordlet` in your terminal. Unlike 15 | Wordle, you can play at any time, and you can play multiple times per day. The 16 | game uses the same dictionary as Wordle. 17 | 18 | Valid options are: 19 | 20 | - `--difficulty`, default is "easy". Can also be "hard". 21 | - `--theme`, default is "dark". Can also be "light" 22 | 23 | You quit the game by pressing escape. 24 | 25 | ## Nerd stuff 26 | 27 | Building the app locally requires Rust 1.58 or higher. 28 | 29 | This was an exercise in writing a fully functional Rust program. There are 30 | probably better and more performant ways to implement the Wordlet algorithm but 31 | I purposely did not look at how Wordle was implemented. 32 | 33 | I learned a lot (and lifted some code) from these other games: 34 | [Minesweeper](https://github.com/cpcloud/minesweep-rs) and 35 | [Battleship](https://github.com/deepu105/battleship-rs). Both of those links 36 | came from the [tui documentation](https://github.com/fdehau/tui-rs). 37 | 38 | ## Etc 39 | 40 | - No rights reserved; use this code for whatever. 41 | - I have no connection to Wordle or its author. 42 | - [My twitter profile is here](https://twitter.com/scottluptowski) 43 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::{self, Event as CEvent, KeyEvent}; 2 | use std::sync::mpsc; 3 | use std::thread; 4 | use std::time::{Duration, Instant}; 5 | 6 | pub enum AppEvent { 7 | Input(I), 8 | Tick, 9 | } 10 | 11 | pub struct Events { 12 | rx: mpsc::Receiver>, 13 | } 14 | 15 | impl Events { 16 | // a lot of this code comes from these two sources: 17 | // https://github.com/deepu105/battleship-rs/blob/main/src/event.rs 18 | // https://github.com/zupzup/rust-commandline-example/blob/main/src/main.rs 19 | pub fn new(tick_rate: Duration) -> Self { 20 | let (tx, rx) = mpsc::channel(); 21 | 22 | thread::spawn(move || { 23 | let mut last_tick = Instant::now(); 24 | 25 | loop { 26 | let timeout = tick_rate 27 | .checked_sub(last_tick.elapsed()) 28 | .unwrap_or_else(|| Duration::from_secs(0)); 29 | 30 | if event::poll(timeout).expect("poll works") { 31 | if let CEvent::Key(key) = event::read().expect("can read events") { 32 | tx.send(AppEvent::Input(key)).expect("can send events"); 33 | } 34 | } 35 | 36 | if last_tick.elapsed() >= tick_rate { 37 | if let Ok(_) = tx.send(AppEvent::Tick) { 38 | last_tick = Instant::now(); 39 | } 40 | } 41 | } 42 | }); 43 | 44 | Events { rx: rx } 45 | } 46 | 47 | pub fn next(&self) -> Result, mpsc::RecvError> { 48 | self.rx.recv() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod engine; 3 | mod events; 4 | mod theme; 5 | mod ui; 6 | 7 | use crate::app::{App, AppOptions}; 8 | use crate::engine::{GameDifficulty, GameOptions}; 9 | use crate::events::{AppEvent, Events}; 10 | use crate::theme::Theme; 11 | 12 | use clap::Parser; 13 | use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; 14 | use std::io; 15 | use std::time::Duration; 16 | use tui::{backend::CrosstermBackend, Terminal}; 17 | 18 | #[derive(Parser, Debug)] 19 | #[clap(about = "Wordlet is a command line Wordle clone.", version, author)] 20 | struct Args { 21 | #[clap( 22 | short, 23 | long, 24 | default_value = "easy", 25 | help = "Change the game's difficulty. Valid values are easy and hard" 26 | )] 27 | difficulty: String, 28 | 29 | #[clap( 30 | short, 31 | long, 32 | default_value = "dark", 33 | help = "Change the display colors. Valid values are light and dark" 34 | )] 35 | theme: String, 36 | } 37 | 38 | fn main() -> Result<(), Box> { 39 | enable_raw_mode()?; 40 | 41 | let args = Args::parse(); 42 | let difficulty = match args.difficulty.as_ref() { 43 | "hard" => GameDifficulty::Hard, 44 | _ => GameDifficulty::Easy, 45 | }; 46 | 47 | let theme = match args.theme.as_ref() { 48 | "light" => Theme::light_theme(), 49 | _ => Theme::dark_theme(), 50 | }; 51 | 52 | let mut app = App::new(AppOptions { 53 | theme: theme, 54 | game_config: GameOptions { 55 | answer: None, 56 | difficulty: difficulty, 57 | }, 58 | }); 59 | 60 | let tick_rate = Duration::from_millis(100); 61 | let events = Events::new(tick_rate); 62 | 63 | let stdout = io::stdout(); 64 | let backend = CrosstermBackend::new(stdout); 65 | let mut terminal = Terminal::new(backend)?; 66 | terminal.clear()?; 67 | 68 | loop { 69 | terminal.draw(|frame| { 70 | let _r = ui::draw(frame, &mut app); 71 | })?; 72 | 73 | match events.next()? { 74 | AppEvent::Input(event) => app.on_key(event), 75 | AppEvent::Tick => {} 76 | } 77 | 78 | if app.should_quit { 79 | disable_raw_mode()?; 80 | terminal.clear()?; 81 | terminal.show_cursor()?; 82 | break; 83 | } 84 | } 85 | 86 | Ok(()) 87 | } 88 | -------------------------------------------------------------------------------- /src/theme.rs: -------------------------------------------------------------------------------- 1 | use tui::{ 2 | style::{Color, Modifier}, 3 | widgets::BorderType, 4 | }; 5 | 6 | pub struct Theme { 7 | pub active_row_input_color: Color, 8 | pub border_color: Color, 9 | pub header_text_error_color: Color, 10 | pub header_text_success_color: Color, 11 | pub empty_row_block_color: Color, 12 | pub guess_in_right_place_color: Color, 13 | pub guess_in_word_color: Color, 14 | pub guess_not_in_word_color: Color, 15 | pub keyboard_not_guessed_color: Color, 16 | pub keyboard_in_right_place_color: Color, 17 | pub keyboard_in_word_color: Color, 18 | pub keyboard_not_in_word_color: Color, 19 | pub row_border_thickness: BorderType, 20 | pub guessed_row_border_thickness: BorderType, 21 | pub welcome_message_color: Color, 22 | } 23 | 24 | impl Default for Theme { 25 | fn default() -> Self { 26 | Self::dark_theme() 27 | } 28 | } 29 | 30 | impl Theme { 31 | pub fn light_theme() -> Self { 32 | Self { 33 | border_color: Color::Black, 34 | active_row_input_color: Color::Black, 35 | welcome_message_color: Color::Black, 36 | header_text_success_color: Color::Green, 37 | header_text_error_color: Color::Red, 38 | empty_row_block_color: Color::Gray, 39 | guess_in_right_place_color: Color::Green, 40 | guess_in_word_color: Color::Yellow, 41 | guess_not_in_word_color: Color::Black, 42 | keyboard_not_guessed_color: Color::Black, 43 | keyboard_in_right_place_color: Color::Green, 44 | keyboard_in_word_color: Color::Yellow, 45 | keyboard_not_in_word_color: Color::Gray, 46 | row_border_thickness: BorderType::Plain, 47 | guessed_row_border_thickness: BorderType::Thick, 48 | } 49 | } 50 | 51 | pub fn dark_theme() -> Self { 52 | Theme { 53 | border_color: Color::White, 54 | active_row_input_color: Color::White, 55 | welcome_message_color: Color::White, 56 | keyboard_not_guessed_color: Color::White, 57 | keyboard_not_in_word_color: Color::Gray, 58 | ..Theme::light_theme() 59 | } 60 | } 61 | } 62 | 63 | pub struct BlockTheme { 64 | pub border_brightness: Modifier, 65 | pub border_color: Color, 66 | pub border_thickness: BorderType, 67 | pub text_color: Color, 68 | } 69 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::{Game, GameOptions, GameStatus, GuessResult}; 2 | use crate::theme::Theme; 3 | use crossterm::event::{KeyCode, KeyEvent}; 4 | 5 | #[derive(PartialEq)] 6 | pub enum Disclaimer { 7 | MoveFeedback(GuessResult), 8 | GameWonMessage, 9 | GameOverMessage(String), 10 | WelcomeMessage, 11 | } 12 | 13 | pub struct App { 14 | pub game: Game, 15 | pub input: String, 16 | pub disclaimer: Option, 17 | pub should_quit: bool, 18 | pub theme: Theme, 19 | } 20 | 21 | pub struct AppOptions { 22 | pub theme: Theme, 23 | pub game_config: GameOptions, 24 | } 25 | 26 | impl App { 27 | pub fn new(args: AppOptions) -> Self { 28 | App { 29 | game: Game::new(args.game_config), 30 | input: String::from(""), 31 | disclaimer: Some(Disclaimer::WelcomeMessage), 32 | should_quit: false, 33 | theme: args.theme, 34 | } 35 | } 36 | 37 | pub fn on_key(&mut self, key: KeyEvent) -> () { 38 | if self.game.game_status() != GameStatus::InProgress { 39 | self.should_quit = true; 40 | return; 41 | } 42 | 43 | match key.code { 44 | KeyCode::Esc => { 45 | self.should_quit = true; 46 | } 47 | KeyCode::Backspace => self.on_backspace(), 48 | KeyCode::Enter => self.on_enter_press(), 49 | KeyCode::Char(letter) => self.on_letter_entered(letter), 50 | _ => (), 51 | }; 52 | } 53 | 54 | pub fn on_valid_word(&mut self) -> () { 55 | self.disclaimer = None; 56 | self.input = String::from(""); 57 | } 58 | 59 | pub fn on_backspace(&mut self) -> () { 60 | let _ = self.input.pop(); 61 | () 62 | } 63 | 64 | pub fn on_letter_entered(&mut self, letter: char) -> () { 65 | if self.input.chars().count() <= 4 { 66 | self.input.push(letter); 67 | } 68 | } 69 | 70 | pub fn on_enter_press(&mut self) -> () { 71 | // clear the disclaimer the first time a word is played 72 | if self.disclaimer == Some(Disclaimer::WelcomeMessage) { 73 | self.disclaimer = None; 74 | } 75 | 76 | if &self.input.chars().count() != &5 { 77 | return (); 78 | } 79 | 80 | match self.game.guess(&self.input) { 81 | (GameStatus::Lost, _) => { 82 | if let Ok(answer) = self.game.get_answer() { 83 | self.disclaimer = Some(Disclaimer::GameOverMessage(answer.to_string())); 84 | } 85 | } 86 | (GameStatus::Won, _) => { 87 | self.disclaimer = Some(Disclaimer::GameWonMessage); 88 | } 89 | (_, word_res) => match word_res { 90 | GuessResult::Valid => { 91 | let _ = &self.on_valid_word(); 92 | } 93 | result => { 94 | self.disclaimer = Some(Disclaimer::MoveFeedback(result)); 95 | } 96 | }, 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | use crate::app::{App, Disclaimer}; 2 | use crate::engine::{GuessResult, HitAccuracy, RowState}; 3 | use crate::theme::BlockTheme; 4 | use tui::{ 5 | backend::Backend, 6 | layout::{Alignment, Constraint, Direction, Layout, Rect}, 7 | style::{Modifier, Style}, 8 | text::{Span, Spans}, 9 | widgets::{Block, BorderType, Borders, Paragraph, Wrap}, 10 | Frame, 11 | }; 12 | 13 | pub enum Error { 14 | ConvertUsizeToU16(std::num::TryFromIntError), 15 | } 16 | 17 | use Disclaimer::*; 18 | use GuessResult::*; 19 | 20 | const ROWS: usize = 6; 21 | const COLUMNS: usize = 5; 22 | const CELL_WIDTH: usize = 5; 23 | const CELL_HEIGHT: usize = 3; 24 | const PADDING: usize = 1; 25 | 26 | pub fn draw(frame: &mut Frame, app: &mut App) -> Result<(), crate::ui::Error> { 27 | // a LOT of this code comes from a Minesweeper implementation in Rust, found at: 28 | // https://github.com/cpcloud/minesweep-rs/blob/main/src/ui.rs 29 | let terminal_rect = frame.size(); 30 | let grid_width = 31 | u16::try_from(CELL_WIDTH * COLUMNS + 2 * PADDING).map_err(Error::ConvertUsizeToU16)?; 32 | let grid_height = 33 | u16::try_from(CELL_HEIGHT * ROWS + 2 * PADDING).map_err(Error::ConvertUsizeToU16)?; 34 | 35 | let row_constraints = std::iter::repeat(Constraint::Length( 36 | u16::try_from(CELL_HEIGHT).map_err(Error::ConvertUsizeToU16)?, 37 | )) 38 | .take(ROWS) 39 | .collect::>(); 40 | 41 | let col_constraints = std::iter::repeat(Constraint::Length( 42 | u16::try_from(CELL_WIDTH).map_err(Error::ConvertUsizeToU16)?, 43 | )) 44 | .take(COLUMNS) 45 | .collect::>(); 46 | 47 | let outer_rects = Layout::default() 48 | .direction(Direction::Vertical) 49 | .vertical_margin(1) 50 | .horizontal_margin(1) 51 | .constraints(vec![Constraint::Min(grid_height)]) 52 | .split(frame.size()); 53 | 54 | let game_rectangle = outer_rects[0]; 55 | 56 | let horizontal_pad_block_width = (terminal_rect.width - grid_width) / 2; 57 | let center_center_horizontally = Layout::default() 58 | .direction(Direction::Horizontal) 59 | .constraints(vec![ 60 | Constraint::Min(horizontal_pad_block_width), 61 | Constraint::Length(grid_width), 62 | Constraint::Min(horizontal_pad_block_width), 63 | ]) 64 | .split(game_rectangle); 65 | 66 | let vertical_pad_block_height = (game_rectangle.height - grid_height) / 2; 67 | let center_content_vertically = Layout::default() 68 | .direction(Direction::Vertical) 69 | .constraints(vec![ 70 | Constraint::Min(vertical_pad_block_height), 71 | Constraint::Length(grid_height), 72 | Constraint::Min(vertical_pad_block_height), 73 | ]) 74 | .split(center_center_horizontally[1]); 75 | 76 | let top_section_render_thing = Layout::default() 77 | .direction(Direction::Vertical) 78 | .constraints(vec![Constraint::Percentage(60), Constraint::Percentage(40)]) 79 | .split(center_content_vertically[0]); 80 | 81 | let keyboard_render_things = Layout::default() 82 | .direction(Direction::Vertical) 83 | .constraints(vec![Constraint::Percentage(30), Constraint::Percentage(70)]) 84 | .split(center_content_vertically[2]); 85 | 86 | let game_board = Block::default() 87 | .borders(Borders::ALL) 88 | .border_type(BorderType::Rounded); 89 | 90 | let game_board_section = center_content_vertically[1]; 91 | frame.render_widget(game_board, game_board_section); 92 | draw_header(frame, app, top_section_render_thing[0]); 93 | draw_keyboard(frame, app, keyboard_render_things[1]); 94 | 95 | let row_chunks = Layout::default() 96 | .direction(Direction::Vertical) 97 | .vertical_margin(1) 98 | .horizontal_margin(0) 99 | .constraints(row_constraints.clone()) 100 | .split(game_board_section); 101 | 102 | let board_state = app.game.row_states(); 103 | 104 | for (row_index, row_chunk) in row_chunks.into_iter().enumerate() { 105 | let row = board_state[row_index]; 106 | 107 | let chunks = Layout::default() 108 | .direction(Direction::Horizontal) 109 | .vertical_margin(0) 110 | .horizontal_margin(1) 111 | .constraints(col_constraints.clone()) 112 | .split(row_chunk); 113 | 114 | match row { 115 | RowState::Current => render_active_row(frame, app, chunks), 116 | RowState::Empty => render_empty_row(frame, app, chunks), 117 | RowState::AlreadyGuessed => render_already_guessed_row(frame, app, row_index, chunks), 118 | } 119 | } 120 | 121 | Ok(()) 122 | } 123 | 124 | pub fn render_empty_row( 125 | frame: &mut Frame, 126 | app: &mut App, 127 | cell_chunks: Vec, 128 | ) -> () { 129 | for cell_chunk in cell_chunks.into_iter() { 130 | let content = render_cell_with_text_and_colors( 131 | " ".to_string(), 132 | BlockTheme { 133 | border_color: app.theme.empty_row_block_color, 134 | text_color: app.theme.empty_row_block_color, 135 | border_thickness: app.theme.row_border_thickness, 136 | border_brightness: Modifier::empty(), 137 | }, 138 | ); 139 | 140 | frame.render_widget(content, cell_chunk); 141 | } 142 | } 143 | 144 | pub fn render_active_row( 145 | frame: &mut Frame, 146 | app: &mut App, 147 | cell_chunks: Vec, 148 | ) -> () { 149 | let mut chars = app.input.chars(); 150 | 151 | for cell_chunk in cell_chunks.into_iter() { 152 | let text = match chars.next() { 153 | Some(l) => l.to_string(), 154 | _ => " ".to_string(), 155 | }; 156 | let content = render_cell_with_text_and_colors( 157 | text, 158 | BlockTheme { 159 | border_color: app.theme.border_color, 160 | text_color: app.theme.active_row_input_color, 161 | border_thickness: app.theme.row_border_thickness, 162 | border_brightness: Modifier::empty(), 163 | }, 164 | ); 165 | frame.render_widget(content, cell_chunk); 166 | } 167 | } 168 | 169 | pub fn render_already_guessed_row( 170 | frame: &mut Frame, 171 | app: &mut App, 172 | row_index: usize, 173 | chunks: Vec, 174 | ) -> () { 175 | if let Some(word_guess) = app.game.guesses().get(row_index) { 176 | let items = chunks.iter().zip(word_guess.letters.iter()); 177 | 178 | for (chunk, guess_letter) in items { 179 | let character = guess_letter.letter.to_string(); 180 | let accuracy = guess_letter.accuracy; 181 | 182 | let color = match accuracy { 183 | HitAccuracy::InRightPlace => app.theme.guess_in_right_place_color, 184 | HitAccuracy::InWord => app.theme.guess_in_word_color, 185 | HitAccuracy::NotInWord => app.theme.guess_not_in_word_color, 186 | }; 187 | 188 | let brightness = match accuracy { 189 | HitAccuracy::NotInWord => Modifier::DIM, 190 | _ => Modifier::empty(), 191 | }; 192 | 193 | let content = render_cell_with_text_and_colors( 194 | character, 195 | BlockTheme { 196 | border_color: color, 197 | text_color: color, 198 | border_thickness: app.theme.guessed_row_border_thickness, 199 | border_brightness: brightness, 200 | }, 201 | ); 202 | 203 | frame.render_widget(content, *chunk); 204 | } 205 | } 206 | } 207 | 208 | pub fn render_cell_with_text_and_colors( 209 | text: String, 210 | block_theme: BlockTheme, 211 | ) -> Paragraph<'static> { 212 | let text = formatted_cell_text(text); 213 | 214 | Paragraph::new(text) 215 | .block( 216 | Block::default() 217 | .borders(Borders::ALL) 218 | .border_type(block_theme.border_thickness) 219 | .border_style(Style::default().fg(block_theme.border_color)) 220 | .style( 221 | Style::default() 222 | .add_modifier(block_theme.border_brightness) 223 | .add_modifier(Modifier::BOLD), 224 | ), 225 | ) 226 | .alignment(Alignment::Center) 227 | .style(Style::default().fg(block_theme.text_color)) 228 | } 229 | 230 | // This is taken directly from the minesweeper app 231 | // https://github.com/cpcloud/minesweep-rs/blob/main/src/ui.rs 232 | fn formatted_cell_text(text: String) -> String { 233 | let single_row_text = format!("{:^length$}", text, length = CELL_WIDTH - 2); 234 | let pad_line = " ".repeat(CELL_WIDTH); 235 | let num_pad_lines = CELL_HEIGHT - 3; 236 | 237 | std::iter::repeat(pad_line.clone()) 238 | .take(num_pad_lines / 2) 239 | .chain(std::iter::once(single_row_text.clone())) 240 | .chain(std::iter::repeat(pad_line).take(num_pad_lines / 2)) 241 | .collect::>() 242 | .join("\n") 243 | } 244 | 245 | pub fn draw_header(frame: &mut Frame, app: &mut App, chunk: Rect) { 246 | let text = match &app.disclaimer { 247 | Some(GameWonMessage) => String::from("Game is over! You win! Press any key to exit."), 248 | Some(GameOverMessage(answer)) => { 249 | format!("Game over! The answer was '{answer}'. Press any key to exit.") 250 | } 251 | Some(MoveFeedback(f)) => match f { 252 | DoesNotIncludeRequiredLetter(letter) => { 253 | format!("Does not include the required letter '{letter}'") 254 | } 255 | LetterDoesNotMatch(ch, idx) => { 256 | let number = match idx { 257 | 1 => "1st".to_string(), 258 | 2 => "2nd".to_string(), 259 | 3 => "3rd".to_string(), 260 | _ => format!("{ch}th"), 261 | }; 262 | format!("The {number} letter must be '{ch}'") 263 | } 264 | IncorrectCharacterCount => String::from("Your guess must be 5 characters long!"), 265 | NotInDictionary => String::from("Not a valid word!"), 266 | DuplicateGuess => String::from("You already guessed that!"), 267 | GameIsAlreadyOver => String::from("The game is already over!"), 268 | Valid => String::from(""), 269 | }, 270 | Some(WelcomeMessage) => { 271 | String::from("Welcome to Wordlet. You have six tries to guess the answer. Good luck!") 272 | } 273 | None => String::from(""), 274 | }; 275 | 276 | let header_text_color = match &app.disclaimer { 277 | Some(GameWonMessage) => app.theme.header_text_success_color, 278 | Some(WelcomeMessage) => app.theme.welcome_message_color, 279 | _ => app.theme.header_text_error_color, 280 | }; 281 | 282 | let header_text = Paragraph::new(text) 283 | .wrap(Wrap { trim: true }) 284 | .style(Style::default().fg(header_text_color)) 285 | .alignment(Alignment::Center) 286 | .block( 287 | Block::default() 288 | .borders(Borders::ALL) 289 | .style(Style::default().fg(app.theme.border_color)) 290 | .title("Wordlet") 291 | .border_type(BorderType::Plain), 292 | ); 293 | 294 | frame.render_widget(header_text, chunk); 295 | } 296 | 297 | pub fn draw_keyboard(frame: &mut Frame, app: &mut App, chunk: Rect) { 298 | let keyboard_key_rows = vec!["qwertyuiop", "asdfghjkl", "zxcvbnm"]; 299 | let keyboard_spans = keyboard_key_rows 300 | .iter() 301 | .fold(vec![], |mut acc, keyboard_row| { 302 | // when we draw the keyboard, we want a blank space after every character 303 | // except for the last character, so that we don't go off-center 304 | let letters: Vec = keyboard_row 305 | .chars() 306 | .into_iter() 307 | .enumerate() 308 | .map(|(letter_index, letter)| { 309 | let use_offset = letter_index != keyboard_row.len() - 1; 310 | keyboard_letter(&app, letter, use_offset) 311 | }) 312 | .collect(); 313 | 314 | acc.push(Spans::from(letters)); 315 | acc 316 | }); 317 | 318 | let keyboard_visualization = Paragraph::new(keyboard_spans) 319 | .style(Style::default()) 320 | .alignment(Alignment::Center) 321 | .block( 322 | Block::default() 323 | .borders(Borders::ALL) 324 | .style(Style::default().fg(app.theme.border_color)) 325 | .title("Available Letters") 326 | .border_type(BorderType::Plain), 327 | ); 328 | 329 | frame.render_widget(keyboard_visualization, chunk); 330 | } 331 | 332 | pub fn keyboard_letter<'a>(app: &'a App, le: char, use_offset: bool) -> Span<'a> { 333 | use HitAccuracy::*; 334 | let key_state = app.game.get_letter_match_state(le); 335 | 336 | let color = match key_state { 337 | None => app.theme.keyboard_not_guessed_color, 338 | Some(InRightPlace) => app.theme.keyboard_in_right_place_color, 339 | Some(InWord) => app.theme.keyboard_in_word_color, 340 | Some(NotInWord) => app.theme.keyboard_not_in_word_color, 341 | }; 342 | 343 | let display_modifier = match key_state { 344 | Some(NotInWord) => Modifier::DIM, 345 | _ => Modifier::empty(), 346 | }; 347 | 348 | let key_string = match use_offset { 349 | true => format!("{le} "), 350 | false => le.to_string(), 351 | }; 352 | 353 | Span::styled( 354 | key_string, 355 | Style::default().fg(color).add_modifier(display_modifier), 356 | ) 357 | } 358 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.0.1" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "cassowary" 30 | version = "0.3.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 33 | 34 | [[package]] 35 | name = "cfg-if" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 39 | 40 | [[package]] 41 | name = "chrono" 42 | version = "0.4.19" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 45 | dependencies = [ 46 | "libc", 47 | "num-integer", 48 | "num-traits", 49 | "time", 50 | "winapi", 51 | ] 52 | 53 | [[package]] 54 | name = "clap" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "1957aa4a5fb388f0a0a73ce7556c5b42025b874e5cdc2c670775e346e97adec0" 58 | dependencies = [ 59 | "atty", 60 | "bitflags", 61 | "clap_derive", 62 | "indexmap", 63 | "lazy_static", 64 | "os_str_bytes", 65 | "strsim", 66 | "termcolor", 67 | "textwrap", 68 | ] 69 | 70 | [[package]] 71 | name = "clap_derive" 72 | version = "3.0.6" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" 75 | dependencies = [ 76 | "heck", 77 | "proc-macro-error", 78 | "proc-macro2", 79 | "quote", 80 | "syn", 81 | ] 82 | 83 | [[package]] 84 | name = "crossterm" 85 | version = "0.18.2" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb" 88 | dependencies = [ 89 | "bitflags", 90 | "crossterm_winapi 0.6.2", 91 | "lazy_static", 92 | "libc", 93 | "mio", 94 | "parking_lot", 95 | "signal-hook", 96 | "winapi", 97 | ] 98 | 99 | [[package]] 100 | name = "crossterm" 101 | version = "0.19.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" 104 | dependencies = [ 105 | "bitflags", 106 | "crossterm_winapi 0.7.0", 107 | "lazy_static", 108 | "libc", 109 | "mio", 110 | "parking_lot", 111 | "signal-hook", 112 | "winapi", 113 | ] 114 | 115 | [[package]] 116 | name = "crossterm_winapi" 117 | version = "0.6.2" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db" 120 | dependencies = [ 121 | "winapi", 122 | ] 123 | 124 | [[package]] 125 | name = "crossterm_winapi" 126 | version = "0.7.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" 129 | dependencies = [ 130 | "winapi", 131 | ] 132 | 133 | [[package]] 134 | name = "getrandom" 135 | version = "0.2.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 138 | dependencies = [ 139 | "cfg-if", 140 | "libc", 141 | "wasi", 142 | ] 143 | 144 | [[package]] 145 | name = "hashbrown" 146 | version = "0.11.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 149 | 150 | [[package]] 151 | name = "heck" 152 | version = "0.4.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 155 | 156 | [[package]] 157 | name = "hermit-abi" 158 | version = "0.1.19" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 161 | dependencies = [ 162 | "libc", 163 | ] 164 | 165 | [[package]] 166 | name = "indexmap" 167 | version = "1.8.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 170 | dependencies = [ 171 | "autocfg", 172 | "hashbrown", 173 | ] 174 | 175 | [[package]] 176 | name = "instant" 177 | version = "0.1.12" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 180 | dependencies = [ 181 | "cfg-if", 182 | ] 183 | 184 | [[package]] 185 | name = "lazy_static" 186 | version = "1.4.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 189 | 190 | [[package]] 191 | name = "libc" 192 | version = "0.2.112" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 195 | 196 | [[package]] 197 | name = "lock_api" 198 | version = "0.4.5" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 201 | dependencies = [ 202 | "scopeguard", 203 | ] 204 | 205 | [[package]] 206 | name = "log" 207 | version = "0.4.14" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 210 | dependencies = [ 211 | "cfg-if", 212 | ] 213 | 214 | [[package]] 215 | name = "memchr" 216 | version = "2.4.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 219 | 220 | [[package]] 221 | name = "mio" 222 | version = "0.7.14" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 225 | dependencies = [ 226 | "libc", 227 | "log", 228 | "miow", 229 | "ntapi", 230 | "winapi", 231 | ] 232 | 233 | [[package]] 234 | name = "miow" 235 | version = "0.3.7" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 238 | dependencies = [ 239 | "winapi", 240 | ] 241 | 242 | [[package]] 243 | name = "ntapi" 244 | version = "0.3.6" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 247 | dependencies = [ 248 | "winapi", 249 | ] 250 | 251 | [[package]] 252 | name = "num-integer" 253 | version = "0.1.44" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 256 | dependencies = [ 257 | "autocfg", 258 | "num-traits", 259 | ] 260 | 261 | [[package]] 262 | name = "num-traits" 263 | version = "0.2.14" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 266 | dependencies = [ 267 | "autocfg", 268 | ] 269 | 270 | [[package]] 271 | name = "os_str_bytes" 272 | version = "6.0.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 275 | dependencies = [ 276 | "memchr", 277 | ] 278 | 279 | [[package]] 280 | name = "parking_lot" 281 | version = "0.11.2" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 284 | dependencies = [ 285 | "instant", 286 | "lock_api", 287 | "parking_lot_core", 288 | ] 289 | 290 | [[package]] 291 | name = "parking_lot_core" 292 | version = "0.8.5" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 295 | dependencies = [ 296 | "cfg-if", 297 | "instant", 298 | "libc", 299 | "redox_syscall", 300 | "smallvec", 301 | "winapi", 302 | ] 303 | 304 | [[package]] 305 | name = "ppv-lite86" 306 | version = "0.2.16" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 309 | 310 | [[package]] 311 | name = "proc-macro-error" 312 | version = "1.0.4" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 315 | dependencies = [ 316 | "proc-macro-error-attr", 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | "version_check", 321 | ] 322 | 323 | [[package]] 324 | name = "proc-macro-error-attr" 325 | version = "1.0.4" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 328 | dependencies = [ 329 | "proc-macro2", 330 | "quote", 331 | "version_check", 332 | ] 333 | 334 | [[package]] 335 | name = "proc-macro2" 336 | version = "1.0.36" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 339 | dependencies = [ 340 | "unicode-xid", 341 | ] 342 | 343 | [[package]] 344 | name = "quote" 345 | version = "1.0.14" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" 348 | dependencies = [ 349 | "proc-macro2", 350 | ] 351 | 352 | [[package]] 353 | name = "rand" 354 | version = "0.8.4" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 357 | dependencies = [ 358 | "libc", 359 | "rand_chacha", 360 | "rand_core", 361 | "rand_hc", 362 | ] 363 | 364 | [[package]] 365 | name = "rand_chacha" 366 | version = "0.3.1" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 369 | dependencies = [ 370 | "ppv-lite86", 371 | "rand_core", 372 | ] 373 | 374 | [[package]] 375 | name = "rand_core" 376 | version = "0.6.3" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 379 | dependencies = [ 380 | "getrandom", 381 | ] 382 | 383 | [[package]] 384 | name = "rand_hc" 385 | version = "0.3.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 388 | dependencies = [ 389 | "rand_core", 390 | ] 391 | 392 | [[package]] 393 | name = "redox_syscall" 394 | version = "0.2.10" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 397 | dependencies = [ 398 | "bitflags", 399 | ] 400 | 401 | [[package]] 402 | name = "scopeguard" 403 | version = "1.1.0" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 406 | 407 | [[package]] 408 | name = "signal-hook" 409 | version = "0.1.17" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" 412 | dependencies = [ 413 | "libc", 414 | "mio", 415 | "signal-hook-registry", 416 | ] 417 | 418 | [[package]] 419 | name = "signal-hook-registry" 420 | version = "1.4.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 423 | dependencies = [ 424 | "libc", 425 | ] 426 | 427 | [[package]] 428 | name = "smallvec" 429 | version = "1.8.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 432 | 433 | [[package]] 434 | name = "strsim" 435 | version = "0.10.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 438 | 439 | [[package]] 440 | name = "syn" 441 | version = "1.0.85" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" 444 | dependencies = [ 445 | "proc-macro2", 446 | "quote", 447 | "unicode-xid", 448 | ] 449 | 450 | [[package]] 451 | name = "termcolor" 452 | version = "1.1.2" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 455 | dependencies = [ 456 | "winapi-util", 457 | ] 458 | 459 | [[package]] 460 | name = "textwrap" 461 | version = "0.14.2" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 464 | 465 | [[package]] 466 | name = "time" 467 | version = "0.1.43" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 470 | dependencies = [ 471 | "libc", 472 | "winapi", 473 | ] 474 | 475 | [[package]] 476 | name = "tui" 477 | version = "0.14.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9" 480 | dependencies = [ 481 | "bitflags", 482 | "cassowary", 483 | "crossterm 0.18.2", 484 | "unicode-segmentation", 485 | "unicode-width", 486 | ] 487 | 488 | [[package]] 489 | name = "unicode-segmentation" 490 | version = "1.8.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 493 | 494 | [[package]] 495 | name = "unicode-width" 496 | version = "0.1.9" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 499 | 500 | [[package]] 501 | name = "unicode-xid" 502 | version = "0.2.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 505 | 506 | [[package]] 507 | name = "version_check" 508 | version = "0.9.4" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 511 | 512 | [[package]] 513 | name = "wasi" 514 | version = "0.10.2+wasi-snapshot-preview1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 517 | 518 | [[package]] 519 | name = "winapi" 520 | version = "0.3.9" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 523 | dependencies = [ 524 | "winapi-i686-pc-windows-gnu", 525 | "winapi-x86_64-pc-windows-gnu", 526 | ] 527 | 528 | [[package]] 529 | name = "winapi-i686-pc-windows-gnu" 530 | version = "0.4.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 533 | 534 | [[package]] 535 | name = "winapi-util" 536 | version = "0.1.5" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 539 | dependencies = [ 540 | "winapi", 541 | ] 542 | 543 | [[package]] 544 | name = "winapi-x86_64-pc-windows-gnu" 545 | version = "0.4.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 548 | 549 | [[package]] 550 | name = "wordlet" 551 | version = "0.2.0" 552 | dependencies = [ 553 | "chrono", 554 | "clap", 555 | "crossterm 0.19.0", 556 | "rand", 557 | "tui", 558 | ] 559 | -------------------------------------------------------------------------------- /src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::game_error::GameError; 2 | 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | mod game_error; 6 | mod utils; 7 | mod words; 8 | 9 | #[derive(Copy, Clone, Debug, PartialEq)] 10 | pub enum GameStatus { 11 | Won, 12 | InProgress, 13 | Lost, 14 | } 15 | 16 | #[derive(Copy, Clone, Debug, PartialEq)] 17 | pub enum GuessResult { 18 | DoesNotIncludeRequiredLetter(char), 19 | LetterDoesNotMatch(char, usize), 20 | DuplicateGuess, 21 | GameIsAlreadyOver, 22 | IncorrectCharacterCount, 23 | NotInDictionary, 24 | Valid, 25 | } 26 | 27 | #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] 28 | pub enum HitAccuracy { 29 | InRightPlace, 30 | InWord, 31 | NotInWord, 32 | } 33 | 34 | #[derive(Copy, Clone, Debug, PartialEq)] 35 | pub enum GameDifficulty { 36 | Easy, 37 | Hard, 38 | } 39 | 40 | pub struct Game { 41 | guesses: Vec, 42 | answer: String, 43 | difficulty: GameDifficulty, 44 | game_status: GameStatus, 45 | correct_positions: HashSet, 46 | dictionary: HashSet, 47 | played_letters: HashMap, 48 | row_states: Vec, 49 | } 50 | 51 | #[derive(Debug, PartialEq)] 52 | pub struct WordGuess { 53 | pub letters: Vec, 54 | } 55 | 56 | impl WordGuess { 57 | pub fn word(&self) -> String { 58 | self.letters 59 | .as_slice() 60 | .into_iter() 61 | .map(|gl| gl.letter) 62 | .collect() 63 | } 64 | 65 | pub fn letters(&self) -> &[GuessLetter] { 66 | self.letters.as_slice() 67 | } 68 | } 69 | 70 | #[derive(Copy, Clone, Debug, PartialEq)] 71 | pub struct GuessLetter { 72 | pub letter: char, 73 | pub accuracy: HitAccuracy, 74 | } 75 | 76 | #[derive(Copy, Clone, Debug, PartialEq)] 77 | pub enum RowState { 78 | Empty, 79 | Current, 80 | AlreadyGuessed, 81 | } 82 | 83 | pub struct GameOptions { 84 | pub answer: Option, 85 | pub difficulty: GameDifficulty, 86 | } 87 | 88 | impl Default for GameOptions { 89 | fn default() -> Self { 90 | GameOptions { 91 | answer: None, 92 | difficulty: GameDifficulty::Easy, 93 | } 94 | } 95 | } 96 | 97 | impl Game { 98 | pub fn new(args: GameOptions) -> Self { 99 | Game { 100 | guesses: Vec::with_capacity(6), 101 | answer: args 102 | .answer 103 | .map_or_else(|| utils::get_random_word(), |a| a.to_string()), 104 | difficulty: args.difficulty, 105 | game_status: GameStatus::InProgress, 106 | correct_positions: HashSet::new(), 107 | dictionary: utils::dictionary(), 108 | played_letters: HashMap::new(), 109 | row_states: vec![ 110 | RowState::Current, 111 | RowState::Empty, 112 | RowState::Empty, 113 | RowState::Empty, 114 | RowState::Empty, 115 | RowState::Empty, 116 | ], 117 | } 118 | } 119 | 120 | pub fn game_status(&self) -> GameStatus { 121 | self.game_status 122 | } 123 | 124 | pub fn get_answer(&self) -> Result { 125 | if self.game_status == GameStatus::Lost { 126 | Ok(self.answer.to_string()) 127 | } else { 128 | Err(GameError::GameNotLostError) 129 | } 130 | } 131 | 132 | pub fn guesses(&self) -> &[WordGuess] { 133 | self.guesses.as_slice() 134 | } 135 | 136 | fn in_dictionary(&self, word: &str) -> bool { 137 | self.dictionary.get(word).is_some() 138 | } 139 | 140 | fn answer_char_at_index(&self, index: usize) -> char { 141 | self.answer.chars().nth(index).unwrap() 142 | } 143 | 144 | fn matches_answer_at_index(&self, index: usize, letter: char) -> bool { 145 | letter == self.answer_char_at_index(index) 146 | } 147 | 148 | fn recalculate_row_states(&mut self) -> () { 149 | let number_of_guesses_so_far = self.guesses().len(); 150 | 151 | let row_states = vec![1, 2, 3, 4, 5, 6] 152 | .into_iter() 153 | .map(|i| { 154 | if number_of_guesses_so_far == 6 { 155 | return RowState::AlreadyGuessed; 156 | } 157 | 158 | if i <= number_of_guesses_so_far { 159 | return RowState::AlreadyGuessed; 160 | } 161 | 162 | if i == number_of_guesses_so_far + 1 { 163 | if self.game_status == GameStatus::Won { 164 | return RowState::Empty; 165 | } 166 | return RowState::Current; 167 | } 168 | 169 | RowState::Empty 170 | }) 171 | .collect(); 172 | 173 | self.row_states = row_states; 174 | () 175 | } 176 | 177 | fn recalculate_played_letter_registry(&mut self, guess: &WordGuess) -> () { 178 | for gl in guess.letters() { 179 | match self.played_letters.get_mut(&gl.letter) { 180 | None => { 181 | self.played_letters.insert(gl.letter, gl.accuracy); 182 | } 183 | Some(accuracy_value) => { 184 | if gl.accuracy < *accuracy_value { 185 | *accuracy_value = gl.accuracy; 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | fn guess_already_exists(&self, guess_input: &str) -> bool { 193 | let existing_guesses: Vec = self.guesses.iter().map(|g| g.word()).collect(); 194 | existing_guesses.contains(&guess_input.to_string()) 195 | } 196 | 197 | pub fn guess(&mut self, guess_input: &str) -> (GameStatus, GuessResult) { 198 | if self.game_status == GameStatus::Won || self.game_status == GameStatus::Lost { 199 | return (self.game_status, GuessResult::GameIsAlreadyOver); 200 | } 201 | 202 | if guess_input.len() != 5 { 203 | return (self.game_status, GuessResult::IncorrectCharacterCount); 204 | } 205 | 206 | if self.guess_already_exists(&guess_input) { 207 | return (self.game_status, GuessResult::DuplicateGuess); 208 | } 209 | 210 | if !self.in_dictionary(&guess_input) { 211 | return (self.game_status, GuessResult::NotInDictionary); 212 | } 213 | 214 | if self.difficulty == GameDifficulty::Hard { 215 | for (index, letter) in guess_input.chars().enumerate() { 216 | if self.correct_positions.contains(&index) { 217 | if !self.matches_answer_at_index(index, letter) { 218 | let char_at_index = self.answer_char_at_index(index); 219 | return ( 220 | self.game_status, 221 | // we start counting at 1, so we can say "the first letter" 222 | GuessResult::LetterDoesNotMatch(char_at_index, index + 1), 223 | ); 224 | } 225 | } 226 | } 227 | 228 | for letter in self.answer.chars() { 229 | let is_discovered = self.is_letter_uncovered(letter); 230 | 231 | if is_discovered { 232 | if !guess_input.contains(letter) { 233 | return ( 234 | self.game_status, 235 | GuessResult::DoesNotIncludeRequiredLetter(letter), 236 | ); 237 | } 238 | } 239 | } 240 | } 241 | 242 | let guess = self.build_guess(&guess_input); 243 | self.recalculate_played_letter_registry(&guess); 244 | 245 | self.guesses.push(guess); 246 | 247 | if guess_input == self.answer { 248 | self.game_status = GameStatus::Won; 249 | } 250 | 251 | // we need to do this _after setting the game state to 'won', but before returning 252 | // This way the board does not update with a duplicate row in the next 'current' row 253 | self.recalculate_row_states(); 254 | 255 | if self.game_status == GameStatus::Won { 256 | return (self.game_status, GuessResult::Valid); 257 | } 258 | 259 | if self.guesses.len() == 6 { 260 | self.game_status = GameStatus::Lost; 261 | } 262 | 263 | (self.game_status, GuessResult::Valid) 264 | } 265 | 266 | pub fn row_states(&self) -> Vec { 267 | self.row_states.clone() 268 | } 269 | 270 | pub fn is_letter_uncovered(&self, letter: char) -> bool { 271 | match &self.get_letter_match_state(letter) { 272 | None => false, 273 | Some(HitAccuracy::NotInWord) => false, 274 | Some(_) => true, 275 | } 276 | } 277 | 278 | fn build_guess(&mut self, guess_input: &str) -> WordGuess { 279 | let mut discoverable_letters = utils::build_letter_counts(&self.answer); 280 | let mut guess_letters: Vec> = vec![None, None, None, None, None]; 281 | 282 | // Weird stuff. We walk the word twice; We go over the correct guesses first, so that we 283 | // can subtract their letters from the count of available letters to colorize. 284 | for (idx, c) in guess_input.chars().enumerate() { 285 | if self.matches_answer_at_index(idx, c) { 286 | guess_letters[idx] = 287 | Some(self.build_guess_letter_with_accuracy(idx, c, &mut discoverable_letters)) 288 | } 289 | } 290 | 291 | // Then we go over the letters that are not correct. 292 | for (idx, c) in guess_input.chars().enumerate() { 293 | if guess_letters[idx].is_none() { 294 | guess_letters[idx] = 295 | Some(self.build_guess_letter_with_accuracy(idx, c, &mut discoverable_letters)) 296 | } 297 | } 298 | 299 | WordGuess { 300 | letters: guess_letters.iter().map(|o| o.unwrap()).collect(), 301 | } 302 | } 303 | 304 | fn build_guess_letter_with_accuracy( 305 | &mut self, 306 | letter_index: usize, 307 | raw_letter: char, 308 | discoverable_letters: &mut HashMap, 309 | ) -> GuessLetter { 310 | let accuracy = match &self.answer.contains(raw_letter) { 311 | true => { 312 | let in_same_place = self.matches_answer_at_index(letter_index, raw_letter); 313 | 314 | if in_same_place { 315 | if let Some(ch) = discoverable_letters.get_mut(&raw_letter) { 316 | *ch -= 1; 317 | } 318 | self.correct_positions.insert(letter_index); 319 | HitAccuracy::InRightPlace 320 | } else { 321 | if let Some(ch) = discoverable_letters.get_mut(&raw_letter) { 322 | if (*ch) >= 1 { 323 | *ch -= 1; 324 | HitAccuracy::InWord 325 | } else { 326 | HitAccuracy::NotInWord 327 | } 328 | } else { 329 | HitAccuracy::NotInWord 330 | } 331 | } 332 | } 333 | false => HitAccuracy::NotInWord, 334 | }; 335 | 336 | GuessLetter { 337 | letter: raw_letter, 338 | accuracy: accuracy, 339 | } 340 | } 341 | 342 | pub fn get_letter_match_state(&self, letter: char) -> Option { 343 | self.played_letters.get(&letter).cloned() 344 | } 345 | } 346 | 347 | #[cfg(test)] 348 | mod tests { 349 | use super::*; 350 | 351 | #[test] 352 | fn test_add_guess() { 353 | let mut game = Game::new(GameOptions { 354 | answer: Some("slump".to_string()), 355 | ..Default::default() 356 | }); 357 | game.guess("pasta"); 358 | assert_eq!(game.guesses.len(), 1) 359 | } 360 | 361 | #[rustfmt::skip] 362 | #[test] 363 | fn test_a_guess_is_stored_correctly() { 364 | let mut game = Game::new(GameOptions { answer: Some("haste".to_string()), difficulty: GameDifficulty::Easy}); 365 | game.guess("heart"); 366 | 367 | let spell_guess = super::WordGuess { 368 | letters: vec![ 369 | GuessLetter { letter: 'h', accuracy: HitAccuracy::InRightPlace }, 370 | GuessLetter { letter: 'e', accuracy: HitAccuracy::InWord }, 371 | GuessLetter { letter: 'a', accuracy: HitAccuracy::InWord }, 372 | GuessLetter { letter: 'r', accuracy: HitAccuracy::NotInWord }, 373 | GuessLetter { letter: 't', accuracy: HitAccuracy::InWord } 374 | ], 375 | }; 376 | assert_eq!(game.guesses[0], spell_guess) 377 | } 378 | 379 | #[rustfmt::skip] 380 | #[test] 381 | fn test_letters_are_marked_in_word_until_the_count_of_letters_is_met() { 382 | let mut game = Game::new(GameOptions { answer: Some("sleep".to_string()), difficulty: GameDifficulty::Easy}); 383 | game.guess("spell"); 384 | // we guess spell. Only one of the l's should match as InWord, because there is only one l in sleep 385 | // Similarly, only one of the e's should match 386 | 387 | let spell_guess = super::WordGuess { 388 | letters: vec![ 389 | GuessLetter { letter: 's', accuracy: HitAccuracy::InRightPlace }, 390 | GuessLetter { letter: 'p', accuracy: HitAccuracy::InWord }, 391 | GuessLetter { letter: 'e', accuracy: HitAccuracy::InRightPlace }, 392 | GuessLetter { letter: 'l', accuracy: HitAccuracy::InWord }, 393 | GuessLetter { letter: 'l', accuracy: HitAccuracy::NotInWord } 394 | ], 395 | }; 396 | assert_eq!(game.guesses[0], spell_guess) 397 | } 398 | 399 | #[rustfmt::skip] 400 | #[test] 401 | fn test_counts_apply_to_the_in_right_place_characters_first() { 402 | let mut game = Game::new(GameOptions { answer: Some("ahead".to_string()), difficulty: GameDifficulty::Easy}); 403 | game.guess("added"); 404 | // The guess 'added' has 3 'd' characters, but the answer only has one. 405 | // The 'd' char in the correct place (the last char) should be marked as in the right place, 406 | // and the other chars should be marked as NotInWord 407 | 408 | let spell_guess = super::WordGuess { 409 | letters: vec![ 410 | GuessLetter { letter: 'a', accuracy: HitAccuracy::InRightPlace }, 411 | GuessLetter { letter: 'd', accuracy: HitAccuracy::NotInWord }, 412 | GuessLetter { letter: 'd', accuracy: HitAccuracy::NotInWord }, 413 | GuessLetter { letter: 'e', accuracy: HitAccuracy::InWord }, 414 | GuessLetter { letter: 'd', accuracy: HitAccuracy::InRightPlace } 415 | ], 416 | }; 417 | assert_eq!(game.guesses[0], spell_guess) 418 | } 419 | 420 | #[test] 421 | fn test_cannot_add_duplicate_guess() { 422 | let mut game = Game::new(GameOptions { 423 | answer: Some("slump".to_string()), 424 | ..Default::default() 425 | }); 426 | game.guess("pasta"); 427 | let (_, duplicate_guess) = game.guess("pasta"); 428 | assert_eq!(duplicate_guess, GuessResult::DuplicateGuess); 429 | } 430 | 431 | #[test] 432 | fn test_a_correct_guess_wins_the_game() { 433 | let mut game = Game::new(GameOptions { 434 | answer: Some("slump".to_string()), 435 | ..Default::default() 436 | }); 437 | let (won_the_game, _) = game.guess("slump"); 438 | assert_eq!(won_the_game, GameStatus::Won); 439 | } 440 | 441 | #[test] 442 | fn test_a_guess_cannot_be_less_than_five_characters() { 443 | let mut game = Game::new(GameOptions { 444 | answer: Some("slump".to_string()), 445 | ..Default::default() 446 | }); 447 | let (_, char_count_wrong) = game.guess("slp"); 448 | assert_eq!(char_count_wrong, GuessResult::IncorrectCharacterCount); 449 | } 450 | 451 | #[test] 452 | fn test_a_guess_cannot_be_more_than_five_characters() { 453 | let mut game = Game::new(GameOptions { 454 | answer: Some("slump".to_string()), 455 | ..Default::default() 456 | }); 457 | let (_, char_count_wrong) = game.guess("slumffffp"); 458 | assert_eq!(char_count_wrong, GuessResult::IncorrectCharacterCount); 459 | } 460 | 461 | #[test] 462 | fn test_the_game_is_lost_after_six_incorrect_guesses() { 463 | let mut game = Game::new(GameOptions { 464 | answer: Some("slump".to_string()), 465 | ..Default::default() 466 | }); 467 | game.guess("admit"); 468 | game.guess("adorn"); 469 | game.guess("adult"); 470 | game.guess("affix"); 471 | game.guess("afire"); 472 | let (lost_the_game, _) = game.guess("after"); 473 | assert_eq!(lost_the_game, GameStatus::Lost); 474 | } 475 | 476 | #[test] 477 | fn test_cannot_add_guesses_after_the_game_is_won() { 478 | let mut game = Game::new(GameOptions { 479 | answer: Some("slump".to_string()), 480 | ..Default::default() 481 | }); 482 | game.guess("slump"); 483 | let (won_the_game, game_already_over) = game.guess("adept"); 484 | 485 | assert_eq!(won_the_game, GameStatus::Won); 486 | assert_eq!(game_already_over, GuessResult::GameIsAlreadyOver); 487 | } 488 | 489 | #[test] 490 | fn test_cannot_add_guesses_after_the_game_is_lost() { 491 | let mut game = Game::new(GameOptions { 492 | answer: Some("slump".to_string()), 493 | ..Default::default() 494 | }); 495 | game.guess("admit"); 496 | game.guess("adorn"); 497 | game.guess("adult"); 498 | game.guess("affix"); 499 | game.guess("afire"); 500 | game.guess("aging"); 501 | 502 | let (lost_the_game, game_already_over) = game.guess("agony"); 503 | assert_eq!(lost_the_game, GameStatus::Lost); 504 | assert_eq!(game_already_over, GuessResult::GameIsAlreadyOver); 505 | } 506 | 507 | #[test] 508 | fn test_cannot_add_a_word_that_does_not_exist() { 509 | let mut game = Game::new(GameOptions { 510 | answer: Some("slump".to_string()), 511 | ..Default::default() 512 | }); 513 | let (game_continues, invalid_word) = game.guess("djkle"); 514 | assert_eq!(game_continues, GameStatus::InProgress); 515 | assert_eq!(invalid_word, GuessResult::NotInDictionary); 516 | } 517 | 518 | #[test] 519 | fn test_can_get_the_answer_after_the_game_is_lost() { 520 | let mut game = Game::new(GameOptions { 521 | answer: Some("slump".to_string()), 522 | ..Default::default() 523 | }); 524 | game.guess("admit"); 525 | game.guess("adorn"); 526 | game.guess("adult"); 527 | game.guess("affix"); 528 | game.guess("afire"); 529 | 530 | assert_eq!(game.get_answer(), Err(GameError::GameNotLostError)); 531 | game.guess("aging"); 532 | assert_eq!(game.get_answer(), Ok("slump".to_string())); 533 | } 534 | 535 | #[test] 536 | fn test_hard_mode_requires_guessing_letters_that_have_been_found_in_place() { 537 | let mut game = Game::new(GameOptions { 538 | answer: Some("abbey".to_string()), 539 | difficulty: GameDifficulty::Hard, 540 | }); 541 | game.guess("sleep"); 542 | 543 | let (_, required_letter) = game.guess("hours"); 544 | assert_eq!(required_letter, GuessResult::LetterDoesNotMatch('e', 4)); 545 | } 546 | 547 | #[test] 548 | fn test_hard_mode_requires_guessing_letters_that_have_been_found_in_the_word() { 549 | let mut game = Game::new(GameOptions { 550 | answer: Some("abbey".to_string()), 551 | difficulty: GameDifficulty::Hard, 552 | }); 553 | let (_, valid_word) = game.guess("slept"); 554 | assert_eq!(valid_word, GuessResult::Valid); 555 | 556 | let (_, required_letter) = game.guess("grift"); 557 | assert_eq!( 558 | required_letter, 559 | GuessResult::DoesNotIncludeRequiredLetter('e') 560 | ); 561 | } 562 | 563 | #[test] 564 | fn test_hard_mode_can_include_guesses_with_old_and_new_letters() { 565 | let mut game = Game::new(GameOptions { 566 | answer: Some("slump".to_string()), 567 | difficulty: GameDifficulty::Hard, 568 | }); 569 | game.guess("sleep"); 570 | 571 | let (game_continues, valid_word) = game.guess("sloop"); 572 | assert_eq!(game_continues, GameStatus::InProgress); 573 | assert_eq!(valid_word, GuessResult::Valid); 574 | } 575 | 576 | #[test] 577 | fn test_keeps_track_of_which_letters_matched() { 578 | let mut game = Game::new(GameOptions { 579 | answer: Some("slump".to_string()), 580 | ..Default::default() 581 | }); 582 | game.guess("slept"); 583 | 584 | assert_eq!( 585 | game.get_letter_match_state('s'), 586 | Some(HitAccuracy::InRightPlace) 587 | ); 588 | assert_eq!( 589 | game.get_letter_match_state('l'), 590 | Some(HitAccuracy::InRightPlace) 591 | ); 592 | assert_eq!( 593 | game.get_letter_match_state('e'), 594 | Some(HitAccuracy::NotInWord) 595 | ); 596 | assert_eq!(game.get_letter_match_state('p'), Some(HitAccuracy::InWord)); 597 | assert_eq!( 598 | game.get_letter_match_state('t'), 599 | Some(HitAccuracy::NotInWord) 600 | ); 601 | 602 | assert_eq!(game.get_letter_match_state('u'), None); 603 | assert_eq!(game.get_letter_match_state('m'), None); 604 | } 605 | 606 | #[test] 607 | fn test_letters_matches_are_not_overwritten_by_lesser_tiers() { 608 | let mut game = Game::new(GameOptions { 609 | answer: Some("laugh".to_string()), 610 | ..Default::default() 611 | }); 612 | game.guess("larva"); 613 | 614 | assert_eq!( 615 | game.get_letter_match_state('l'), 616 | Some(HitAccuracy::InRightPlace) 617 | ); 618 | assert_eq!( 619 | game.get_letter_match_state('a'), 620 | Some(HitAccuracy::InRightPlace) 621 | ); 622 | assert_eq!( 623 | game.get_letter_match_state('r'), 624 | Some(HitAccuracy::NotInWord) 625 | ); 626 | assert_eq!( 627 | game.get_letter_match_state('v'), 628 | Some(HitAccuracy::NotInWord) 629 | ); 630 | assert_eq!( 631 | game.get_letter_match_state('a'), 632 | Some(HitAccuracy::InRightPlace) 633 | ); 634 | 635 | assert_eq!(game.get_letter_match_state('g'), None); 636 | assert_eq!(game.get_letter_match_state('h'), None); 637 | } 638 | 639 | #[test] 640 | fn test_letters_matches_are_not_overwritten_by_subsequent_incorrect_guesses() { 641 | let mut game = Game::new(GameOptions { 642 | answer: Some("ahead".to_string()), 643 | ..Default::default() 644 | }); 645 | // we guess 'lease'. The first 'e' should match as InWord, and the second should be NotInWord 646 | // When we ask for the letter match state, it should respond with InWord 647 | game.guess("lease"); 648 | assert_eq!(game.get_letter_match_state('e'), Some(HitAccuracy::InWord)); 649 | 650 | // now we've guessed the correct letter, so it should correct to InRightPlace 651 | game.guess("preen"); 652 | assert_eq!( 653 | game.get_letter_match_state('e'), 654 | Some(HitAccuracy::InRightPlace) 655 | ); 656 | } 657 | 658 | #[test] 659 | fn test_row_states_are_tracked_at_the_start_of_the_game() { 660 | let game = Game::new(GameOptions { 661 | answer: Some("laugh".to_string()), 662 | ..Default::default() 663 | }); 664 | assert_eq!( 665 | game.row_states(), 666 | vec![ 667 | RowState::Current, 668 | RowState::Empty, 669 | RowState::Empty, 670 | RowState::Empty, 671 | RowState::Empty, 672 | RowState::Empty 673 | ] 674 | ); 675 | } 676 | 677 | #[test] 678 | fn test_row_states_are_tracked_in_the_middle_of_the_game() { 679 | let mut game = Game::new(GameOptions { 680 | answer: Some("laugh".to_string()), 681 | ..Default::default() 682 | }); 683 | game.guess("admit"); 684 | 685 | assert_eq!( 686 | game.row_states(), 687 | vec![ 688 | RowState::AlreadyGuessed, 689 | RowState::Current, 690 | RowState::Empty, 691 | RowState::Empty, 692 | RowState::Empty, 693 | RowState::Empty 694 | ] 695 | ); 696 | } 697 | 698 | #[test] 699 | fn test_row_states_are_tracked_when_you_win_before_the_end() { 700 | let mut game = Game::new(GameOptions { 701 | answer: Some("laugh".to_string()), 702 | ..Default::default() 703 | }); 704 | game.guess("admit"); 705 | game.guess("laugh"); 706 | 707 | assert_eq!( 708 | game.row_states(), 709 | vec![ 710 | RowState::AlreadyGuessed, 711 | RowState::AlreadyGuessed, 712 | RowState::Empty, 713 | RowState::Empty, 714 | RowState::Empty, 715 | RowState::Empty 716 | ] 717 | ); 718 | } 719 | 720 | #[test] 721 | fn test_row_states_are_tracked_at_the_end_of_the_game() { 722 | let mut game = Game::new(GameOptions { 723 | answer: Some("laugh".to_string()), 724 | ..Default::default() 725 | }); 726 | game.guess("admit"); 727 | game.guess("adorn"); 728 | game.guess("adult"); 729 | game.guess("affix"); 730 | game.guess("afire"); 731 | game.guess("aging"); 732 | assert_eq!( 733 | game.row_states(), 734 | vec![ 735 | RowState::AlreadyGuessed, 736 | RowState::AlreadyGuessed, 737 | RowState::AlreadyGuessed, 738 | RowState::AlreadyGuessed, 739 | RowState::AlreadyGuessed, 740 | RowState::AlreadyGuessed 741 | ] 742 | ); 743 | } 744 | } 745 | -------------------------------------------------------------------------------- /src/engine/words.rs: -------------------------------------------------------------------------------- 1 | // lol. we do this here so we don't have to hit the file system when we build the dictionary 2 | 3 | pub fn dictionary_words() -> Vec { 4 | vec![ 5 | "aback".to_string(), 6 | "abase".to_string(), 7 | "abate".to_string(), 8 | "abbey".to_string(), 9 | "abbot".to_string(), 10 | "abhor".to_string(), 11 | "abide".to_string(), 12 | "abled".to_string(), 13 | "abode".to_string(), 14 | "abort".to_string(), 15 | "about".to_string(), 16 | "above".to_string(), 17 | "abuse".to_string(), 18 | "abyss".to_string(), 19 | "acorn".to_string(), 20 | "acrid".to_string(), 21 | "actor".to_string(), 22 | "acute".to_string(), 23 | "adage".to_string(), 24 | "adapt".to_string(), 25 | "added".to_string(), 26 | "adept".to_string(), 27 | "adieu".to_string(), 28 | "admin".to_string(), 29 | "admit".to_string(), 30 | "adobe".to_string(), 31 | "adopt".to_string(), 32 | "adore".to_string(), 33 | "adorn".to_string(), 34 | "adult".to_string(), 35 | "affix".to_string(), 36 | "afire".to_string(), 37 | "afoot".to_string(), 38 | "afoul".to_string(), 39 | "after".to_string(), 40 | "again".to_string(), 41 | "agape".to_string(), 42 | "agate".to_string(), 43 | "agent".to_string(), 44 | "agile".to_string(), 45 | "aging".to_string(), 46 | "aglow".to_string(), 47 | "agony".to_string(), 48 | "agora".to_string(), 49 | "agree".to_string(), 50 | "ahead".to_string(), 51 | "aider".to_string(), 52 | "aisle".to_string(), 53 | "alarm".to_string(), 54 | "album".to_string(), 55 | "alert".to_string(), 56 | "algae".to_string(), 57 | "alibi".to_string(), 58 | "alien".to_string(), 59 | "align".to_string(), 60 | "alike".to_string(), 61 | "alive".to_string(), 62 | "allay".to_string(), 63 | "alley".to_string(), 64 | "allot".to_string(), 65 | "allow".to_string(), 66 | "alloy".to_string(), 67 | "aloft".to_string(), 68 | "alone".to_string(), 69 | "along".to_string(), 70 | "aloof".to_string(), 71 | "aloud".to_string(), 72 | "alpha".to_string(), 73 | "altar".to_string(), 74 | "alter".to_string(), 75 | "amass".to_string(), 76 | "amaze".to_string(), 77 | "amber".to_string(), 78 | "amble".to_string(), 79 | "amend".to_string(), 80 | "amiss".to_string(), 81 | "amity".to_string(), 82 | "among".to_string(), 83 | "ample".to_string(), 84 | "amply".to_string(), 85 | "amuse".to_string(), 86 | "angel".to_string(), 87 | "anger".to_string(), 88 | "angle".to_string(), 89 | "angry".to_string(), 90 | "angst".to_string(), 91 | "anime".to_string(), 92 | "ankle".to_string(), 93 | "annex".to_string(), 94 | "annoy".to_string(), 95 | "annul".to_string(), 96 | "anode".to_string(), 97 | "antic".to_string(), 98 | "anvil".to_string(), 99 | "aorta".to_string(), 100 | "apart".to_string(), 101 | "aphid".to_string(), 102 | "aping".to_string(), 103 | "apnea".to_string(), 104 | "apple".to_string(), 105 | "apply".to_string(), 106 | "apron".to_string(), 107 | "aptly".to_string(), 108 | "arbor".to_string(), 109 | "ardor".to_string(), 110 | "arena".to_string(), 111 | "argue".to_string(), 112 | "arise".to_string(), 113 | "armor".to_string(), 114 | "aroma".to_string(), 115 | "arose".to_string(), 116 | "array".to_string(), 117 | "arrow".to_string(), 118 | "arson".to_string(), 119 | "artsy".to_string(), 120 | "ascot".to_string(), 121 | "ashen".to_string(), 122 | "aside".to_string(), 123 | "askew".to_string(), 124 | "assay".to_string(), 125 | "asset".to_string(), 126 | "atoll".to_string(), 127 | "atone".to_string(), 128 | "attic".to_string(), 129 | "audio".to_string(), 130 | "audit".to_string(), 131 | "augur".to_string(), 132 | "aunty".to_string(), 133 | "avail".to_string(), 134 | "avert".to_string(), 135 | "avian".to_string(), 136 | "avoid".to_string(), 137 | "await".to_string(), 138 | "awake".to_string(), 139 | "award".to_string(), 140 | "aware".to_string(), 141 | "awash".to_string(), 142 | "awful".to_string(), 143 | "awoke".to_string(), 144 | "axial".to_string(), 145 | "axiom".to_string(), 146 | "axion".to_string(), 147 | "azure".to_string(), 148 | "bacon".to_string(), 149 | "badge".to_string(), 150 | "badly".to_string(), 151 | "bagel".to_string(), 152 | "baggy".to_string(), 153 | "baker".to_string(), 154 | "baler".to_string(), 155 | "balmy".to_string(), 156 | "banal".to_string(), 157 | "banjo".to_string(), 158 | "barge".to_string(), 159 | "baron".to_string(), 160 | "basal".to_string(), 161 | "basic".to_string(), 162 | "basil".to_string(), 163 | "basin".to_string(), 164 | "basis".to_string(), 165 | "baste".to_string(), 166 | "batch".to_string(), 167 | "bathe".to_string(), 168 | "baton".to_string(), 169 | "batty".to_string(), 170 | "bawdy".to_string(), 171 | "bayou".to_string(), 172 | "beach".to_string(), 173 | "beady".to_string(), 174 | "beard".to_string(), 175 | "beast".to_string(), 176 | "beech".to_string(), 177 | "beefy".to_string(), 178 | "befit".to_string(), 179 | "began".to_string(), 180 | "begat".to_string(), 181 | "beget".to_string(), 182 | "begin".to_string(), 183 | "begun".to_string(), 184 | "being".to_string(), 185 | "belch".to_string(), 186 | "belie".to_string(), 187 | "belle".to_string(), 188 | "belly".to_string(), 189 | "below".to_string(), 190 | "bench".to_string(), 191 | "beret".to_string(), 192 | "berry".to_string(), 193 | "berth".to_string(), 194 | "beset".to_string(), 195 | "betel".to_string(), 196 | "bevel".to_string(), 197 | "bezel".to_string(), 198 | "bible".to_string(), 199 | "bicep".to_string(), 200 | "biddy".to_string(), 201 | "bigot".to_string(), 202 | "bilge".to_string(), 203 | "billy".to_string(), 204 | "binge".to_string(), 205 | "bingo".to_string(), 206 | "biome".to_string(), 207 | "birch".to_string(), 208 | "birth".to_string(), 209 | "bison".to_string(), 210 | "bitty".to_string(), 211 | "black".to_string(), 212 | "blade".to_string(), 213 | "blame".to_string(), 214 | "bland".to_string(), 215 | "blank".to_string(), 216 | "blare".to_string(), 217 | "blast".to_string(), 218 | "blaze".to_string(), 219 | "bleak".to_string(), 220 | "bleat".to_string(), 221 | "bleed".to_string(), 222 | "bleep".to_string(), 223 | "blend".to_string(), 224 | "bless".to_string(), 225 | "blimp".to_string(), 226 | "blind".to_string(), 227 | "blink".to_string(), 228 | "bliss".to_string(), 229 | "blitz".to_string(), 230 | "bloat".to_string(), 231 | "block".to_string(), 232 | "bloke".to_string(), 233 | "blond".to_string(), 234 | "blood".to_string(), 235 | "bloom".to_string(), 236 | "blown".to_string(), 237 | "bluer".to_string(), 238 | "bluff".to_string(), 239 | "blunt".to_string(), 240 | "blurb".to_string(), 241 | "blurt".to_string(), 242 | "blush".to_string(), 243 | "board".to_string(), 244 | "boast".to_string(), 245 | "bobby".to_string(), 246 | "boney".to_string(), 247 | "bongo".to_string(), 248 | "bonus".to_string(), 249 | "booby".to_string(), 250 | "boost".to_string(), 251 | "booth".to_string(), 252 | "booty".to_string(), 253 | "booze".to_string(), 254 | "boozy".to_string(), 255 | "borax".to_string(), 256 | "borne".to_string(), 257 | "bosom".to_string(), 258 | "bossy".to_string(), 259 | "botch".to_string(), 260 | "bough".to_string(), 261 | "boule".to_string(), 262 | "bound".to_string(), 263 | "bowel".to_string(), 264 | "boxer".to_string(), 265 | "brace".to_string(), 266 | "braid".to_string(), 267 | "brain".to_string(), 268 | "brake".to_string(), 269 | "brand".to_string(), 270 | "brash".to_string(), 271 | "brass".to_string(), 272 | "brave".to_string(), 273 | "bravo".to_string(), 274 | "brawl".to_string(), 275 | "brawn".to_string(), 276 | "bread".to_string(), 277 | "break".to_string(), 278 | "breed".to_string(), 279 | "briar".to_string(), 280 | "bribe".to_string(), 281 | "brick".to_string(), 282 | "bride".to_string(), 283 | "brief".to_string(), 284 | "brine".to_string(), 285 | "bring".to_string(), 286 | "brink".to_string(), 287 | "briny".to_string(), 288 | "brisk".to_string(), 289 | "broad".to_string(), 290 | "broil".to_string(), 291 | "broke".to_string(), 292 | "brood".to_string(), 293 | "brook".to_string(), 294 | "broom".to_string(), 295 | "broth".to_string(), 296 | "brown".to_string(), 297 | "brunt".to_string(), 298 | "brush".to_string(), 299 | "brute".to_string(), 300 | "buddy".to_string(), 301 | "budge".to_string(), 302 | "buggy".to_string(), 303 | "bugle".to_string(), 304 | "build".to_string(), 305 | "built".to_string(), 306 | "bulge".to_string(), 307 | "bulky".to_string(), 308 | "bully".to_string(), 309 | "bunch".to_string(), 310 | "bunny".to_string(), 311 | "burly".to_string(), 312 | "burnt".to_string(), 313 | "burst".to_string(), 314 | "bused".to_string(), 315 | "bushy".to_string(), 316 | "butch".to_string(), 317 | "butte".to_string(), 318 | "buxom".to_string(), 319 | "buyer".to_string(), 320 | "bylaw".to_string(), 321 | "cabal".to_string(), 322 | "cabby".to_string(), 323 | "cabin".to_string(), 324 | "cable".to_string(), 325 | "cacao".to_string(), 326 | "cache".to_string(), 327 | "cacti".to_string(), 328 | "caddy".to_string(), 329 | "cadet".to_string(), 330 | "cagey".to_string(), 331 | "cairn".to_string(), 332 | "camel".to_string(), 333 | "cameo".to_string(), 334 | "canal".to_string(), 335 | "candy".to_string(), 336 | "canny".to_string(), 337 | "canoe".to_string(), 338 | "canon".to_string(), 339 | "caper".to_string(), 340 | "caput".to_string(), 341 | "carat".to_string(), 342 | "cargo".to_string(), 343 | "carol".to_string(), 344 | "carry".to_string(), 345 | "carve".to_string(), 346 | "caste".to_string(), 347 | "catch".to_string(), 348 | "cater".to_string(), 349 | "catty".to_string(), 350 | "caulk".to_string(), 351 | "cause".to_string(), 352 | "cavil".to_string(), 353 | "cease".to_string(), 354 | "cedar".to_string(), 355 | "cello".to_string(), 356 | "chafe".to_string(), 357 | "chaff".to_string(), 358 | "chain".to_string(), 359 | "chair".to_string(), 360 | "chalk".to_string(), 361 | "champ".to_string(), 362 | "chant".to_string(), 363 | "chaos".to_string(), 364 | "chard".to_string(), 365 | "charm".to_string(), 366 | "chart".to_string(), 367 | "chase".to_string(), 368 | "chasm".to_string(), 369 | "cheap".to_string(), 370 | "cheat".to_string(), 371 | "check".to_string(), 372 | "cheek".to_string(), 373 | "cheer".to_string(), 374 | "chess".to_string(), 375 | "chest".to_string(), 376 | "chick".to_string(), 377 | "chide".to_string(), 378 | "chief".to_string(), 379 | "child".to_string(), 380 | "chili".to_string(), 381 | "chill".to_string(), 382 | "chime".to_string(), 383 | "china".to_string(), 384 | "chirp".to_string(), 385 | "chock".to_string(), 386 | "choir".to_string(), 387 | "choke".to_string(), 388 | "chord".to_string(), 389 | "chore".to_string(), 390 | "chose".to_string(), 391 | "chuck".to_string(), 392 | "chump".to_string(), 393 | "chunk".to_string(), 394 | "churn".to_string(), 395 | "chute".to_string(), 396 | "cider".to_string(), 397 | "cigar".to_string(), 398 | "cinch".to_string(), 399 | "circa".to_string(), 400 | "civic".to_string(), 401 | "civil".to_string(), 402 | "clack".to_string(), 403 | "claim".to_string(), 404 | "clamp".to_string(), 405 | "clang".to_string(), 406 | "clank".to_string(), 407 | "clash".to_string(), 408 | "clasp".to_string(), 409 | "class".to_string(), 410 | "clean".to_string(), 411 | "clear".to_string(), 412 | "cleat".to_string(), 413 | "cleft".to_string(), 414 | "clerk".to_string(), 415 | "click".to_string(), 416 | "cliff".to_string(), 417 | "climb".to_string(), 418 | "cling".to_string(), 419 | "clink".to_string(), 420 | "cloak".to_string(), 421 | "clock".to_string(), 422 | "clone".to_string(), 423 | "close".to_string(), 424 | "cloth".to_string(), 425 | "cloud".to_string(), 426 | "clout".to_string(), 427 | "clove".to_string(), 428 | "clown".to_string(), 429 | "cluck".to_string(), 430 | "clued".to_string(), 431 | "clump".to_string(), 432 | "clung".to_string(), 433 | "coach".to_string(), 434 | "coast".to_string(), 435 | "cobra".to_string(), 436 | "cocoa".to_string(), 437 | "colon".to_string(), 438 | "color".to_string(), 439 | "comet".to_string(), 440 | "comfy".to_string(), 441 | "comic".to_string(), 442 | "comma".to_string(), 443 | "conch".to_string(), 444 | "condo".to_string(), 445 | "conic".to_string(), 446 | "copse".to_string(), 447 | "coral".to_string(), 448 | "corer".to_string(), 449 | "corny".to_string(), 450 | "couch".to_string(), 451 | "cough".to_string(), 452 | "could".to_string(), 453 | "count".to_string(), 454 | "coupe".to_string(), 455 | "court".to_string(), 456 | "coven".to_string(), 457 | "cover".to_string(), 458 | "covet".to_string(), 459 | "covey".to_string(), 460 | "cower".to_string(), 461 | "coyly".to_string(), 462 | "crack".to_string(), 463 | "craft".to_string(), 464 | "cramp".to_string(), 465 | "crane".to_string(), 466 | "crank".to_string(), 467 | "crash".to_string(), 468 | "crass".to_string(), 469 | "crate".to_string(), 470 | "crave".to_string(), 471 | "crawl".to_string(), 472 | "craze".to_string(), 473 | "crazy".to_string(), 474 | "creak".to_string(), 475 | "cream".to_string(), 476 | "credo".to_string(), 477 | "creed".to_string(), 478 | "creek".to_string(), 479 | "creep".to_string(), 480 | "creme".to_string(), 481 | "crepe".to_string(), 482 | "crept".to_string(), 483 | "cress".to_string(), 484 | "crest".to_string(), 485 | "crick".to_string(), 486 | "cried".to_string(), 487 | "crier".to_string(), 488 | "crime".to_string(), 489 | "crimp".to_string(), 490 | "crisp".to_string(), 491 | "croak".to_string(), 492 | "crock".to_string(), 493 | "crone".to_string(), 494 | "crony".to_string(), 495 | "crook".to_string(), 496 | "cross".to_string(), 497 | "croup".to_string(), 498 | "crowd".to_string(), 499 | "crown".to_string(), 500 | "crude".to_string(), 501 | "cruel".to_string(), 502 | "crumb".to_string(), 503 | "crump".to_string(), 504 | "crush".to_string(), 505 | "crust".to_string(), 506 | "crypt".to_string(), 507 | "cubic".to_string(), 508 | "cumin".to_string(), 509 | "curio".to_string(), 510 | "curly".to_string(), 511 | "curry".to_string(), 512 | "curse".to_string(), 513 | "curve".to_string(), 514 | "curvy".to_string(), 515 | "cutie".to_string(), 516 | "cyber".to_string(), 517 | "cycle".to_string(), 518 | "cynic".to_string(), 519 | "daddy".to_string(), 520 | "daily".to_string(), 521 | "dairy".to_string(), 522 | "daisy".to_string(), 523 | "dally".to_string(), 524 | "dance".to_string(), 525 | "dandy".to_string(), 526 | "datum".to_string(), 527 | "daunt".to_string(), 528 | "dealt".to_string(), 529 | "death".to_string(), 530 | "debar".to_string(), 531 | "debit".to_string(), 532 | "debug".to_string(), 533 | "debut".to_string(), 534 | "decal".to_string(), 535 | "decay".to_string(), 536 | "decor".to_string(), 537 | "decoy".to_string(), 538 | "decry".to_string(), 539 | "defer".to_string(), 540 | "deign".to_string(), 541 | "deity".to_string(), 542 | "delay".to_string(), 543 | "delta".to_string(), 544 | "delve".to_string(), 545 | "demon".to_string(), 546 | "demur".to_string(), 547 | "denim".to_string(), 548 | "dense".to_string(), 549 | "depot".to_string(), 550 | "depth".to_string(), 551 | "derby".to_string(), 552 | "deter".to_string(), 553 | "detox".to_string(), 554 | "deuce".to_string(), 555 | "devil".to_string(), 556 | "diary".to_string(), 557 | "dicey".to_string(), 558 | "digit".to_string(), 559 | "dilly".to_string(), 560 | "dimly".to_string(), 561 | "diner".to_string(), 562 | "dingo".to_string(), 563 | "dingy".to_string(), 564 | "diode".to_string(), 565 | "dirge".to_string(), 566 | "dirty".to_string(), 567 | "disco".to_string(), 568 | "ditch".to_string(), 569 | "ditto".to_string(), 570 | "ditty".to_string(), 571 | "diver".to_string(), 572 | "dizzy".to_string(), 573 | "dodge".to_string(), 574 | "dodgy".to_string(), 575 | "dogma".to_string(), 576 | "doing".to_string(), 577 | "dolly".to_string(), 578 | "donor".to_string(), 579 | "donut".to_string(), 580 | "dopey".to_string(), 581 | "doubt".to_string(), 582 | "dough".to_string(), 583 | "dowdy".to_string(), 584 | "dowel".to_string(), 585 | "downy".to_string(), 586 | "dowry".to_string(), 587 | "dozen".to_string(), 588 | "draft".to_string(), 589 | "drain".to_string(), 590 | "drake".to_string(), 591 | "drama".to_string(), 592 | "drank".to_string(), 593 | "drape".to_string(), 594 | "drawl".to_string(), 595 | "drawn".to_string(), 596 | "dread".to_string(), 597 | "dream".to_string(), 598 | "dress".to_string(), 599 | "dried".to_string(), 600 | "drier".to_string(), 601 | "drift".to_string(), 602 | "drill".to_string(), 603 | "drink".to_string(), 604 | "drive".to_string(), 605 | "droit".to_string(), 606 | "droll".to_string(), 607 | "drone".to_string(), 608 | "drool".to_string(), 609 | "droop".to_string(), 610 | "dross".to_string(), 611 | "drove".to_string(), 612 | "drown".to_string(), 613 | "druid".to_string(), 614 | "drunk".to_string(), 615 | "dryer".to_string(), 616 | "dryly".to_string(), 617 | "duchy".to_string(), 618 | "dully".to_string(), 619 | "dummy".to_string(), 620 | "dumpy".to_string(), 621 | "dunce".to_string(), 622 | "dusky".to_string(), 623 | "dusty".to_string(), 624 | "dutch".to_string(), 625 | "duvet".to_string(), 626 | "dwarf".to_string(), 627 | "dwell".to_string(), 628 | "dwelt".to_string(), 629 | "dying".to_string(), 630 | "eager".to_string(), 631 | "eagle".to_string(), 632 | "early".to_string(), 633 | "earth".to_string(), 634 | "easel".to_string(), 635 | "eaten".to_string(), 636 | "eater".to_string(), 637 | "ebony".to_string(), 638 | "eclat".to_string(), 639 | "edict".to_string(), 640 | "edify".to_string(), 641 | "eerie".to_string(), 642 | "egret".to_string(), 643 | "eight".to_string(), 644 | "eject".to_string(), 645 | "eking".to_string(), 646 | "elate".to_string(), 647 | "elbow".to_string(), 648 | "elder".to_string(), 649 | "elect".to_string(), 650 | "elegy".to_string(), 651 | "elfin".to_string(), 652 | "elide".to_string(), 653 | "elite".to_string(), 654 | "elope".to_string(), 655 | "elude".to_string(), 656 | "email".to_string(), 657 | "embed".to_string(), 658 | "ember".to_string(), 659 | "emcee".to_string(), 660 | "empty".to_string(), 661 | "enact".to_string(), 662 | "endow".to_string(), 663 | "enema".to_string(), 664 | "enemy".to_string(), 665 | "enjoy".to_string(), 666 | "ennui".to_string(), 667 | "ensue".to_string(), 668 | "enter".to_string(), 669 | "entry".to_string(), 670 | "envoy".to_string(), 671 | "epoch".to_string(), 672 | "epoxy".to_string(), 673 | "equal".to_string(), 674 | "equip".to_string(), 675 | "erase".to_string(), 676 | "erect".to_string(), 677 | "erode".to_string(), 678 | "error".to_string(), 679 | "erupt".to_string(), 680 | "essay".to_string(), 681 | "ester".to_string(), 682 | "ether".to_string(), 683 | "ethic".to_string(), 684 | "ethos".to_string(), 685 | "etude".to_string(), 686 | "evade".to_string(), 687 | "event".to_string(), 688 | "every".to_string(), 689 | "evict".to_string(), 690 | "evoke".to_string(), 691 | "exact".to_string(), 692 | "exalt".to_string(), 693 | "excel".to_string(), 694 | "exert".to_string(), 695 | "exile".to_string(), 696 | "exist".to_string(), 697 | "expel".to_string(), 698 | "extol".to_string(), 699 | "extra".to_string(), 700 | "exult".to_string(), 701 | "eying".to_string(), 702 | "fable".to_string(), 703 | "facet".to_string(), 704 | "faint".to_string(), 705 | "fairy".to_string(), 706 | "faith".to_string(), 707 | "false".to_string(), 708 | "fancy".to_string(), 709 | "fanny".to_string(), 710 | "farce".to_string(), 711 | "fatal".to_string(), 712 | "fatty".to_string(), 713 | "fault".to_string(), 714 | "fauna".to_string(), 715 | "favor".to_string(), 716 | "feast".to_string(), 717 | "fecal".to_string(), 718 | "feign".to_string(), 719 | "fella".to_string(), 720 | "felon".to_string(), 721 | "femme".to_string(), 722 | "femur".to_string(), 723 | "fence".to_string(), 724 | "feral".to_string(), 725 | "ferry".to_string(), 726 | "fetal".to_string(), 727 | "fetch".to_string(), 728 | "fetid".to_string(), 729 | "fetus".to_string(), 730 | "fever".to_string(), 731 | "fewer".to_string(), 732 | "fiber".to_string(), 733 | "fibre".to_string(), 734 | "ficus".to_string(), 735 | "field".to_string(), 736 | "fiend".to_string(), 737 | "fiery".to_string(), 738 | "fifth".to_string(), 739 | "fifty".to_string(), 740 | "fight".to_string(), 741 | "filer".to_string(), 742 | "filet".to_string(), 743 | "filly".to_string(), 744 | "filmy".to_string(), 745 | "filth".to_string(), 746 | "final".to_string(), 747 | "finch".to_string(), 748 | "finer".to_string(), 749 | "first".to_string(), 750 | "fishy".to_string(), 751 | "fixer".to_string(), 752 | "fizzy".to_string(), 753 | "fjord".to_string(), 754 | "flack".to_string(), 755 | "flail".to_string(), 756 | "flair".to_string(), 757 | "flake".to_string(), 758 | "flaky".to_string(), 759 | "flame".to_string(), 760 | "flank".to_string(), 761 | "flare".to_string(), 762 | "flash".to_string(), 763 | "flask".to_string(), 764 | "fleck".to_string(), 765 | "fleet".to_string(), 766 | "flesh".to_string(), 767 | "flick".to_string(), 768 | "flier".to_string(), 769 | "fling".to_string(), 770 | "flint".to_string(), 771 | "flirt".to_string(), 772 | "float".to_string(), 773 | "flock".to_string(), 774 | "flood".to_string(), 775 | "floor".to_string(), 776 | "flora".to_string(), 777 | "floss".to_string(), 778 | "flour".to_string(), 779 | "flout".to_string(), 780 | "flown".to_string(), 781 | "fluff".to_string(), 782 | "fluid".to_string(), 783 | "fluke".to_string(), 784 | "flume".to_string(), 785 | "flung".to_string(), 786 | "flunk".to_string(), 787 | "flush".to_string(), 788 | "flute".to_string(), 789 | "flyer".to_string(), 790 | "foamy".to_string(), 791 | "focal".to_string(), 792 | "focus".to_string(), 793 | "foggy".to_string(), 794 | "foist".to_string(), 795 | "folio".to_string(), 796 | "folly".to_string(), 797 | "foray".to_string(), 798 | "force".to_string(), 799 | "forge".to_string(), 800 | "forgo".to_string(), 801 | "forte".to_string(), 802 | "forth".to_string(), 803 | "forty".to_string(), 804 | "forum".to_string(), 805 | "found".to_string(), 806 | "foyer".to_string(), 807 | "frail".to_string(), 808 | "frame".to_string(), 809 | "frank".to_string(), 810 | "fraud".to_string(), 811 | "freak".to_string(), 812 | "freed".to_string(), 813 | "freer".to_string(), 814 | "fresh".to_string(), 815 | "friar".to_string(), 816 | "fried".to_string(), 817 | "frill".to_string(), 818 | "frisk".to_string(), 819 | "fritz".to_string(), 820 | "frock".to_string(), 821 | "frond".to_string(), 822 | "front".to_string(), 823 | "frost".to_string(), 824 | "froth".to_string(), 825 | "frown".to_string(), 826 | "froze".to_string(), 827 | "fruit".to_string(), 828 | "fudge".to_string(), 829 | "fugue".to_string(), 830 | "fully".to_string(), 831 | "fungi".to_string(), 832 | "funky".to_string(), 833 | "funny".to_string(), 834 | "furor".to_string(), 835 | "furry".to_string(), 836 | "fussy".to_string(), 837 | "fuzzy".to_string(), 838 | "gaffe".to_string(), 839 | "gaily".to_string(), 840 | "gamer".to_string(), 841 | "gamma".to_string(), 842 | "gamut".to_string(), 843 | "gassy".to_string(), 844 | "gaudy".to_string(), 845 | "gauge".to_string(), 846 | "gaunt".to_string(), 847 | "gauze".to_string(), 848 | "gavel".to_string(), 849 | "gawky".to_string(), 850 | "gayer".to_string(), 851 | "gayly".to_string(), 852 | "gazer".to_string(), 853 | "gecko".to_string(), 854 | "geeky".to_string(), 855 | "geese".to_string(), 856 | "genie".to_string(), 857 | "genre".to_string(), 858 | "ghost".to_string(), 859 | "ghoul".to_string(), 860 | "giant".to_string(), 861 | "giddy".to_string(), 862 | "gipsy".to_string(), 863 | "girly".to_string(), 864 | "girth".to_string(), 865 | "given".to_string(), 866 | "giver".to_string(), 867 | "glade".to_string(), 868 | "gland".to_string(), 869 | "glare".to_string(), 870 | "glass".to_string(), 871 | "glaze".to_string(), 872 | "gleam".to_string(), 873 | "glean".to_string(), 874 | "glide".to_string(), 875 | "glint".to_string(), 876 | "gloat".to_string(), 877 | "globe".to_string(), 878 | "gloom".to_string(), 879 | "glory".to_string(), 880 | "gloss".to_string(), 881 | "glove".to_string(), 882 | "glyph".to_string(), 883 | "gnash".to_string(), 884 | "gnome".to_string(), 885 | "godly".to_string(), 886 | "going".to_string(), 887 | "golem".to_string(), 888 | "golly".to_string(), 889 | "gonad".to_string(), 890 | "goner".to_string(), 891 | "goody".to_string(), 892 | "gooey".to_string(), 893 | "goofy".to_string(), 894 | "goose".to_string(), 895 | "gorge".to_string(), 896 | "gouge".to_string(), 897 | "gourd".to_string(), 898 | "grace".to_string(), 899 | "grade".to_string(), 900 | "graft".to_string(), 901 | "grail".to_string(), 902 | "grain".to_string(), 903 | "grand".to_string(), 904 | "grant".to_string(), 905 | "grape".to_string(), 906 | "graph".to_string(), 907 | "grasp".to_string(), 908 | "grass".to_string(), 909 | "grate".to_string(), 910 | "grave".to_string(), 911 | "gravy".to_string(), 912 | "graze".to_string(), 913 | "great".to_string(), 914 | "greed".to_string(), 915 | "green".to_string(), 916 | "greet".to_string(), 917 | "grief".to_string(), 918 | "grift".to_string(), 919 | "grill".to_string(), 920 | "grime".to_string(), 921 | "grimy".to_string(), 922 | "grind".to_string(), 923 | "gripe".to_string(), 924 | "groan".to_string(), 925 | "groin".to_string(), 926 | "groom".to_string(), 927 | "grope".to_string(), 928 | "gross".to_string(), 929 | "group".to_string(), 930 | "grout".to_string(), 931 | "grove".to_string(), 932 | "growl".to_string(), 933 | "grown".to_string(), 934 | "gruel".to_string(), 935 | "gruff".to_string(), 936 | "grunt".to_string(), 937 | "guard".to_string(), 938 | "guava".to_string(), 939 | "guess".to_string(), 940 | "guest".to_string(), 941 | "guide".to_string(), 942 | "guild".to_string(), 943 | "guile".to_string(), 944 | "guilt".to_string(), 945 | "guise".to_string(), 946 | "gulch".to_string(), 947 | "gully".to_string(), 948 | "gumbo".to_string(), 949 | "gummy".to_string(), 950 | "guppy".to_string(), 951 | "gusto".to_string(), 952 | "gusty".to_string(), 953 | "gypsy".to_string(), 954 | "habit".to_string(), 955 | "hairy".to_string(), 956 | "halve".to_string(), 957 | "handy".to_string(), 958 | "happy".to_string(), 959 | "hardy".to_string(), 960 | "harem".to_string(), 961 | "harpy".to_string(), 962 | "harry".to_string(), 963 | "harsh".to_string(), 964 | "haste".to_string(), 965 | "hasty".to_string(), 966 | "hatch".to_string(), 967 | "hater".to_string(), 968 | "haunt".to_string(), 969 | "haute".to_string(), 970 | "haven".to_string(), 971 | "havoc".to_string(), 972 | "hazel".to_string(), 973 | "heady".to_string(), 974 | "heard".to_string(), 975 | "heart".to_string(), 976 | "heath".to_string(), 977 | "heave".to_string(), 978 | "heavy".to_string(), 979 | "hedge".to_string(), 980 | "hefty".to_string(), 981 | "heist".to_string(), 982 | "helix".to_string(), 983 | "hello".to_string(), 984 | "hence".to_string(), 985 | "heron".to_string(), 986 | "hilly".to_string(), 987 | "hinge".to_string(), 988 | "hippo".to_string(), 989 | "hippy".to_string(), 990 | "hitch".to_string(), 991 | "hoard".to_string(), 992 | "hobby".to_string(), 993 | "hoist".to_string(), 994 | "holly".to_string(), 995 | "homer".to_string(), 996 | "honey".to_string(), 997 | "honor".to_string(), 998 | "horde".to_string(), 999 | "horny".to_string(), 1000 | "horse".to_string(), 1001 | "hotel".to_string(), 1002 | "hotly".to_string(), 1003 | "hound".to_string(), 1004 | "hours".to_string(), 1005 | "house".to_string(), 1006 | "hovel".to_string(), 1007 | "hover".to_string(), 1008 | "howdy".to_string(), 1009 | "human".to_string(), 1010 | "humid".to_string(), 1011 | "humor".to_string(), 1012 | "humph".to_string(), 1013 | "humus".to_string(), 1014 | "hunch".to_string(), 1015 | "hunky".to_string(), 1016 | "hurry".to_string(), 1017 | "husky".to_string(), 1018 | "hussy".to_string(), 1019 | "hutch".to_string(), 1020 | "hydro".to_string(), 1021 | "hyena".to_string(), 1022 | "hymen".to_string(), 1023 | "hyper".to_string(), 1024 | "icily".to_string(), 1025 | "icing".to_string(), 1026 | "ideal".to_string(), 1027 | "idiom".to_string(), 1028 | "idiot".to_string(), 1029 | "idler".to_string(), 1030 | "idyll".to_string(), 1031 | "igloo".to_string(), 1032 | "iliac".to_string(), 1033 | "image".to_string(), 1034 | "imbue".to_string(), 1035 | "impel".to_string(), 1036 | "imply".to_string(), 1037 | "inane".to_string(), 1038 | "inbox".to_string(), 1039 | "incur".to_string(), 1040 | "index".to_string(), 1041 | "inept".to_string(), 1042 | "inert".to_string(), 1043 | "infer".to_string(), 1044 | "ingot".to_string(), 1045 | "inlay".to_string(), 1046 | "inlet".to_string(), 1047 | "inner".to_string(), 1048 | "input".to_string(), 1049 | "inter".to_string(), 1050 | "intro".to_string(), 1051 | "ionic".to_string(), 1052 | "irate".to_string(), 1053 | "irony".to_string(), 1054 | "islet".to_string(), 1055 | "issue".to_string(), 1056 | "itchy".to_string(), 1057 | "ivory".to_string(), 1058 | "jaunt".to_string(), 1059 | "jazzy".to_string(), 1060 | "jelly".to_string(), 1061 | "jerky".to_string(), 1062 | "jetty".to_string(), 1063 | "jewel".to_string(), 1064 | "jiffy".to_string(), 1065 | "joint".to_string(), 1066 | "joist".to_string(), 1067 | "joker".to_string(), 1068 | "jolly".to_string(), 1069 | "joust".to_string(), 1070 | "judge".to_string(), 1071 | "juice".to_string(), 1072 | "juicy".to_string(), 1073 | "jumbo".to_string(), 1074 | "jumpy".to_string(), 1075 | "junta".to_string(), 1076 | "junto".to_string(), 1077 | "juror".to_string(), 1078 | "kappa".to_string(), 1079 | "karma".to_string(), 1080 | "kayak".to_string(), 1081 | "kebab".to_string(), 1082 | "khaki".to_string(), 1083 | "kinky".to_string(), 1084 | "kiosk".to_string(), 1085 | "kitty".to_string(), 1086 | "knack".to_string(), 1087 | "knave".to_string(), 1088 | "knead".to_string(), 1089 | "kneed".to_string(), 1090 | "kneel".to_string(), 1091 | "knelt".to_string(), 1092 | "knife".to_string(), 1093 | "knock".to_string(), 1094 | "knoll".to_string(), 1095 | "known".to_string(), 1096 | "koala".to_string(), 1097 | "krill".to_string(), 1098 | "label".to_string(), 1099 | "labor".to_string(), 1100 | "laden".to_string(), 1101 | "ladle".to_string(), 1102 | "lager".to_string(), 1103 | "lance".to_string(), 1104 | "lanky".to_string(), 1105 | "lapel".to_string(), 1106 | "lapse".to_string(), 1107 | "large".to_string(), 1108 | "larva".to_string(), 1109 | "lasso".to_string(), 1110 | "latch".to_string(), 1111 | "later".to_string(), 1112 | "lathe".to_string(), 1113 | "latte".to_string(), 1114 | "laugh".to_string(), 1115 | "layer".to_string(), 1116 | "leach".to_string(), 1117 | "leafy".to_string(), 1118 | "leaky".to_string(), 1119 | "leant".to_string(), 1120 | "leapt".to_string(), 1121 | "learn".to_string(), 1122 | "lease".to_string(), 1123 | "leash".to_string(), 1124 | "least".to_string(), 1125 | "leave".to_string(), 1126 | "ledge".to_string(), 1127 | "leech".to_string(), 1128 | "leery".to_string(), 1129 | "lefty".to_string(), 1130 | "legal".to_string(), 1131 | "leggy".to_string(), 1132 | "lemon".to_string(), 1133 | "lemur".to_string(), 1134 | "leper".to_string(), 1135 | "level".to_string(), 1136 | "lever".to_string(), 1137 | "libel".to_string(), 1138 | "liege".to_string(), 1139 | "light".to_string(), 1140 | "liken".to_string(), 1141 | "lilac".to_string(), 1142 | "limbo".to_string(), 1143 | "limit".to_string(), 1144 | "linen".to_string(), 1145 | "liner".to_string(), 1146 | "lingo".to_string(), 1147 | "lipid".to_string(), 1148 | "lithe".to_string(), 1149 | "liver".to_string(), 1150 | "livid".to_string(), 1151 | "llama".to_string(), 1152 | "loamy".to_string(), 1153 | "loath".to_string(), 1154 | "lobby".to_string(), 1155 | "local".to_string(), 1156 | "locus".to_string(), 1157 | "lodge".to_string(), 1158 | "lofty".to_string(), 1159 | "logic".to_string(), 1160 | "login".to_string(), 1161 | "loopy".to_string(), 1162 | "loose".to_string(), 1163 | "lorry".to_string(), 1164 | "loser".to_string(), 1165 | "louse".to_string(), 1166 | "lousy".to_string(), 1167 | "lover".to_string(), 1168 | "lower".to_string(), 1169 | "lowly".to_string(), 1170 | "loyal".to_string(), 1171 | "lucid".to_string(), 1172 | "lucky".to_string(), 1173 | "lumen".to_string(), 1174 | "lumpy".to_string(), 1175 | "lunar".to_string(), 1176 | "lunch".to_string(), 1177 | "lunge".to_string(), 1178 | "lupus".to_string(), 1179 | "lurch".to_string(), 1180 | "lurid".to_string(), 1181 | "lusty".to_string(), 1182 | "lying".to_string(), 1183 | "lymph".to_string(), 1184 | "lynch".to_string(), 1185 | "lyric".to_string(), 1186 | "macaw".to_string(), 1187 | "macho".to_string(), 1188 | "macro".to_string(), 1189 | "madam".to_string(), 1190 | "madly".to_string(), 1191 | "mafia".to_string(), 1192 | "magic".to_string(), 1193 | "magma".to_string(), 1194 | "maize".to_string(), 1195 | "major".to_string(), 1196 | "maker".to_string(), 1197 | "mambo".to_string(), 1198 | "mamma".to_string(), 1199 | "mammy".to_string(), 1200 | "manga".to_string(), 1201 | "mange".to_string(), 1202 | "mango".to_string(), 1203 | "mangy".to_string(), 1204 | "mania".to_string(), 1205 | "manic".to_string(), 1206 | "manly".to_string(), 1207 | "manor".to_string(), 1208 | "maple".to_string(), 1209 | "march".to_string(), 1210 | "marry".to_string(), 1211 | "marsh".to_string(), 1212 | "mason".to_string(), 1213 | "masse".to_string(), 1214 | "match".to_string(), 1215 | "matey".to_string(), 1216 | "mauve".to_string(), 1217 | "maxim".to_string(), 1218 | "maybe".to_string(), 1219 | "mayor".to_string(), 1220 | "mealy".to_string(), 1221 | "meant".to_string(), 1222 | "meaty".to_string(), 1223 | "mecca".to_string(), 1224 | "medal".to_string(), 1225 | "media".to_string(), 1226 | "medic".to_string(), 1227 | "melee".to_string(), 1228 | "melon".to_string(), 1229 | "mercy".to_string(), 1230 | "merge".to_string(), 1231 | "merit".to_string(), 1232 | "merry".to_string(), 1233 | "metal".to_string(), 1234 | "meter".to_string(), 1235 | "metro".to_string(), 1236 | "micro".to_string(), 1237 | "midge".to_string(), 1238 | "midst".to_string(), 1239 | "might".to_string(), 1240 | "milky".to_string(), 1241 | "mimic".to_string(), 1242 | "mince".to_string(), 1243 | "miner".to_string(), 1244 | "minim".to_string(), 1245 | "minor".to_string(), 1246 | "minty".to_string(), 1247 | "minus".to_string(), 1248 | "mirth".to_string(), 1249 | "miser".to_string(), 1250 | "missy".to_string(), 1251 | "mocha".to_string(), 1252 | "modal".to_string(), 1253 | "model".to_string(), 1254 | "modem".to_string(), 1255 | "mogul".to_string(), 1256 | "moist".to_string(), 1257 | "molar".to_string(), 1258 | "moldy".to_string(), 1259 | "money".to_string(), 1260 | "month".to_string(), 1261 | "moody".to_string(), 1262 | "moose".to_string(), 1263 | "moral".to_string(), 1264 | "moron".to_string(), 1265 | "morph".to_string(), 1266 | "mossy".to_string(), 1267 | "motel".to_string(), 1268 | "motif".to_string(), 1269 | "motor".to_string(), 1270 | "motto".to_string(), 1271 | "moult".to_string(), 1272 | "mound".to_string(), 1273 | "mount".to_string(), 1274 | "mourn".to_string(), 1275 | "mouse".to_string(), 1276 | "mouth".to_string(), 1277 | "mover".to_string(), 1278 | "movie".to_string(), 1279 | "mower".to_string(), 1280 | "mucky".to_string(), 1281 | "mucus".to_string(), 1282 | "muddy".to_string(), 1283 | "mulch".to_string(), 1284 | "mummy".to_string(), 1285 | "munch".to_string(), 1286 | "mural".to_string(), 1287 | "murky".to_string(), 1288 | "mushy".to_string(), 1289 | "music".to_string(), 1290 | "musky".to_string(), 1291 | "musty".to_string(), 1292 | "myrrh".to_string(), 1293 | "nadir".to_string(), 1294 | "naive".to_string(), 1295 | "nanny".to_string(), 1296 | "nasal".to_string(), 1297 | "nasty".to_string(), 1298 | "natal".to_string(), 1299 | "naval".to_string(), 1300 | "navel".to_string(), 1301 | "needy".to_string(), 1302 | "neigh".to_string(), 1303 | "nerdy".to_string(), 1304 | "nerve".to_string(), 1305 | "never".to_string(), 1306 | "newer".to_string(), 1307 | "newly".to_string(), 1308 | "nicer".to_string(), 1309 | "niche".to_string(), 1310 | "niece".to_string(), 1311 | "night".to_string(), 1312 | "ninja".to_string(), 1313 | "ninny".to_string(), 1314 | "ninth".to_string(), 1315 | "noble".to_string(), 1316 | "nobly".to_string(), 1317 | "noise".to_string(), 1318 | "noisy".to_string(), 1319 | "nomad".to_string(), 1320 | "noose".to_string(), 1321 | "north".to_string(), 1322 | "nosey".to_string(), 1323 | "notch".to_string(), 1324 | "novel".to_string(), 1325 | "nudge".to_string(), 1326 | "nurse".to_string(), 1327 | "nutty".to_string(), 1328 | "nylon".to_string(), 1329 | "nymph".to_string(), 1330 | "oaken".to_string(), 1331 | "obese".to_string(), 1332 | "occur".to_string(), 1333 | "ocean".to_string(), 1334 | "octal".to_string(), 1335 | "octet".to_string(), 1336 | "odder".to_string(), 1337 | "oddly".to_string(), 1338 | "offal".to_string(), 1339 | "offer".to_string(), 1340 | "often".to_string(), 1341 | "olden".to_string(), 1342 | "older".to_string(), 1343 | "olive".to_string(), 1344 | "ombre".to_string(), 1345 | "omega".to_string(), 1346 | "onion".to_string(), 1347 | "onset".to_string(), 1348 | "opera".to_string(), 1349 | "opine".to_string(), 1350 | "opium".to_string(), 1351 | "optic".to_string(), 1352 | "orbit".to_string(), 1353 | "order".to_string(), 1354 | "organ".to_string(), 1355 | "other".to_string(), 1356 | "otter".to_string(), 1357 | "ought".to_string(), 1358 | "ounce".to_string(), 1359 | "outdo".to_string(), 1360 | "outer".to_string(), 1361 | "outgo".to_string(), 1362 | "ovary".to_string(), 1363 | "ovate".to_string(), 1364 | "overt".to_string(), 1365 | "ovine".to_string(), 1366 | "ovoid".to_string(), 1367 | "owing".to_string(), 1368 | "owner".to_string(), 1369 | "oxide".to_string(), 1370 | "ozone".to_string(), 1371 | "paddy".to_string(), 1372 | "pagan".to_string(), 1373 | "paint".to_string(), 1374 | "paler".to_string(), 1375 | "palsy".to_string(), 1376 | "panel".to_string(), 1377 | "panic".to_string(), 1378 | "pansy".to_string(), 1379 | "papal".to_string(), 1380 | "paper".to_string(), 1381 | "parer".to_string(), 1382 | "parka".to_string(), 1383 | "parry".to_string(), 1384 | "parse".to_string(), 1385 | "party".to_string(), 1386 | "pasta".to_string(), 1387 | "paste".to_string(), 1388 | "pasty".to_string(), 1389 | "patch".to_string(), 1390 | "patio".to_string(), 1391 | "patsy".to_string(), 1392 | "patty".to_string(), 1393 | "pause".to_string(), 1394 | "payee".to_string(), 1395 | "payer".to_string(), 1396 | "peace".to_string(), 1397 | "peach".to_string(), 1398 | "pearl".to_string(), 1399 | "pecan".to_string(), 1400 | "pedal".to_string(), 1401 | "penal".to_string(), 1402 | "pence".to_string(), 1403 | "penne".to_string(), 1404 | "penny".to_string(), 1405 | "perch".to_string(), 1406 | "peril".to_string(), 1407 | "perky".to_string(), 1408 | "pesky".to_string(), 1409 | "pesto".to_string(), 1410 | "petal".to_string(), 1411 | "petty".to_string(), 1412 | "phase".to_string(), 1413 | "phone".to_string(), 1414 | "phony".to_string(), 1415 | "photo".to_string(), 1416 | "piano".to_string(), 1417 | "picky".to_string(), 1418 | "piece".to_string(), 1419 | "piety".to_string(), 1420 | "piggy".to_string(), 1421 | "pilot".to_string(), 1422 | "pinch".to_string(), 1423 | "piney".to_string(), 1424 | "pinky".to_string(), 1425 | "pinto".to_string(), 1426 | "piper".to_string(), 1427 | "pique".to_string(), 1428 | "pitch".to_string(), 1429 | "pithy".to_string(), 1430 | "pivot".to_string(), 1431 | "pixel".to_string(), 1432 | "pixie".to_string(), 1433 | "pizza".to_string(), 1434 | "place".to_string(), 1435 | "plaid".to_string(), 1436 | "plain".to_string(), 1437 | "plait".to_string(), 1438 | "plane".to_string(), 1439 | "plank".to_string(), 1440 | "plant".to_string(), 1441 | "plate".to_string(), 1442 | "plaza".to_string(), 1443 | "plead".to_string(), 1444 | "pleat".to_string(), 1445 | "plied".to_string(), 1446 | "plier".to_string(), 1447 | "pluck".to_string(), 1448 | "plumb".to_string(), 1449 | "plume".to_string(), 1450 | "plump".to_string(), 1451 | "plunk".to_string(), 1452 | "plush".to_string(), 1453 | "poesy".to_string(), 1454 | "point".to_string(), 1455 | "poise".to_string(), 1456 | "poker".to_string(), 1457 | "polar".to_string(), 1458 | "polka".to_string(), 1459 | "polyp".to_string(), 1460 | "pooch".to_string(), 1461 | "poppy".to_string(), 1462 | "porch".to_string(), 1463 | "poser".to_string(), 1464 | "posit".to_string(), 1465 | "posse".to_string(), 1466 | "pouch".to_string(), 1467 | "pound".to_string(), 1468 | "pouty".to_string(), 1469 | "power".to_string(), 1470 | "prank".to_string(), 1471 | "prawn".to_string(), 1472 | "preen".to_string(), 1473 | "press".to_string(), 1474 | "price".to_string(), 1475 | "prick".to_string(), 1476 | "pride".to_string(), 1477 | "pried".to_string(), 1478 | "prime".to_string(), 1479 | "primo".to_string(), 1480 | "print".to_string(), 1481 | "prior".to_string(), 1482 | "prism".to_string(), 1483 | "privy".to_string(), 1484 | "prize".to_string(), 1485 | "probe".to_string(), 1486 | "prone".to_string(), 1487 | "prong".to_string(), 1488 | "proof".to_string(), 1489 | "prose".to_string(), 1490 | "proud".to_string(), 1491 | "prove".to_string(), 1492 | "prowl".to_string(), 1493 | "proxy".to_string(), 1494 | "prude".to_string(), 1495 | "prune".to_string(), 1496 | "psalm".to_string(), 1497 | "pubic".to_string(), 1498 | "pudgy".to_string(), 1499 | "puffy".to_string(), 1500 | "pulpy".to_string(), 1501 | "pulse".to_string(), 1502 | "punch".to_string(), 1503 | "pupal".to_string(), 1504 | "pupil".to_string(), 1505 | "puppy".to_string(), 1506 | "puree".to_string(), 1507 | "purer".to_string(), 1508 | "purge".to_string(), 1509 | "purse".to_string(), 1510 | "pushy".to_string(), 1511 | "putty".to_string(), 1512 | "pygmy".to_string(), 1513 | "quack".to_string(), 1514 | "quail".to_string(), 1515 | "quake".to_string(), 1516 | "qualm".to_string(), 1517 | "quark".to_string(), 1518 | "quart".to_string(), 1519 | "quash".to_string(), 1520 | "quasi".to_string(), 1521 | "queen".to_string(), 1522 | "queer".to_string(), 1523 | "quell".to_string(), 1524 | "query".to_string(), 1525 | "quest".to_string(), 1526 | "queue".to_string(), 1527 | "quick".to_string(), 1528 | "quiet".to_string(), 1529 | "quill".to_string(), 1530 | "quilt".to_string(), 1531 | "quirk".to_string(), 1532 | "quite".to_string(), 1533 | "quota".to_string(), 1534 | "quote".to_string(), 1535 | "quoth".to_string(), 1536 | "rabbi".to_string(), 1537 | "rabid".to_string(), 1538 | "racer".to_string(), 1539 | "radar".to_string(), 1540 | "radii".to_string(), 1541 | "radio".to_string(), 1542 | "rainy".to_string(), 1543 | "raise".to_string(), 1544 | "rajah".to_string(), 1545 | "rally".to_string(), 1546 | "ralph".to_string(), 1547 | "ramen".to_string(), 1548 | "ranch".to_string(), 1549 | "randy".to_string(), 1550 | "range".to_string(), 1551 | "rapid".to_string(), 1552 | "rarer".to_string(), 1553 | "raspy".to_string(), 1554 | "ratio".to_string(), 1555 | "ratty".to_string(), 1556 | "raven".to_string(), 1557 | "rayon".to_string(), 1558 | "razor".to_string(), 1559 | "reach".to_string(), 1560 | "react".to_string(), 1561 | "ready".to_string(), 1562 | "realm".to_string(), 1563 | "rearm".to_string(), 1564 | "rebar".to_string(), 1565 | "rebel".to_string(), 1566 | "rebus".to_string(), 1567 | "rebut".to_string(), 1568 | "recap".to_string(), 1569 | "recur".to_string(), 1570 | "recut".to_string(), 1571 | "reedy".to_string(), 1572 | "refer".to_string(), 1573 | "refit".to_string(), 1574 | "regal".to_string(), 1575 | "rehab".to_string(), 1576 | "reign".to_string(), 1577 | "relax".to_string(), 1578 | "relay".to_string(), 1579 | "relic".to_string(), 1580 | "remit".to_string(), 1581 | "renal".to_string(), 1582 | "renew".to_string(), 1583 | "repay".to_string(), 1584 | "repel".to_string(), 1585 | "reply".to_string(), 1586 | "rerun".to_string(), 1587 | "reset".to_string(), 1588 | "resin".to_string(), 1589 | "retch".to_string(), 1590 | "retro".to_string(), 1591 | "retry".to_string(), 1592 | "reuse".to_string(), 1593 | "revel".to_string(), 1594 | "revue".to_string(), 1595 | "rhino".to_string(), 1596 | "rhyme".to_string(), 1597 | "rider".to_string(), 1598 | "ridge".to_string(), 1599 | "rifle".to_string(), 1600 | "right".to_string(), 1601 | "rigid".to_string(), 1602 | "rigor".to_string(), 1603 | "rinse".to_string(), 1604 | "ripen".to_string(), 1605 | "riper".to_string(), 1606 | "risen".to_string(), 1607 | "riser".to_string(), 1608 | "risky".to_string(), 1609 | "rival".to_string(), 1610 | "river".to_string(), 1611 | "rivet".to_string(), 1612 | "roach".to_string(), 1613 | "roast".to_string(), 1614 | "robin".to_string(), 1615 | "robot".to_string(), 1616 | "rocky".to_string(), 1617 | "rodeo".to_string(), 1618 | "roger".to_string(), 1619 | "rogue".to_string(), 1620 | "roomy".to_string(), 1621 | "roost".to_string(), 1622 | "rotor".to_string(), 1623 | "rouge".to_string(), 1624 | "rough".to_string(), 1625 | "round".to_string(), 1626 | "rouse".to_string(), 1627 | "route".to_string(), 1628 | "rover".to_string(), 1629 | "rowdy".to_string(), 1630 | "rower".to_string(), 1631 | "royal".to_string(), 1632 | "ruddy".to_string(), 1633 | "ruder".to_string(), 1634 | "rugby".to_string(), 1635 | "ruler".to_string(), 1636 | "rumba".to_string(), 1637 | "rumor".to_string(), 1638 | "rupee".to_string(), 1639 | "rural".to_string(), 1640 | "rusty".to_string(), 1641 | "sadly".to_string(), 1642 | "safer".to_string(), 1643 | "saint".to_string(), 1644 | "salad".to_string(), 1645 | "sally".to_string(), 1646 | "salon".to_string(), 1647 | "salsa".to_string(), 1648 | "salty".to_string(), 1649 | "salve".to_string(), 1650 | "salvo".to_string(), 1651 | "sandy".to_string(), 1652 | "saner".to_string(), 1653 | "sappy".to_string(), 1654 | "sassy".to_string(), 1655 | "satin".to_string(), 1656 | "satyr".to_string(), 1657 | "sauce".to_string(), 1658 | "saucy".to_string(), 1659 | "sauna".to_string(), 1660 | "saute".to_string(), 1661 | "savor".to_string(), 1662 | "savoy".to_string(), 1663 | "savvy".to_string(), 1664 | "scald".to_string(), 1665 | "scale".to_string(), 1666 | "scalp".to_string(), 1667 | "scaly".to_string(), 1668 | "scamp".to_string(), 1669 | "scant".to_string(), 1670 | "scare".to_string(), 1671 | "scarf".to_string(), 1672 | "scary".to_string(), 1673 | "scene".to_string(), 1674 | "scent".to_string(), 1675 | "scion".to_string(), 1676 | "scoff".to_string(), 1677 | "scold".to_string(), 1678 | "scone".to_string(), 1679 | "scoop".to_string(), 1680 | "scope".to_string(), 1681 | "score".to_string(), 1682 | "scorn".to_string(), 1683 | "scour".to_string(), 1684 | "scout".to_string(), 1685 | "scowl".to_string(), 1686 | "scram".to_string(), 1687 | "scrap".to_string(), 1688 | "scree".to_string(), 1689 | "screw".to_string(), 1690 | "scrub".to_string(), 1691 | "scrum".to_string(), 1692 | "scuba".to_string(), 1693 | "sedan".to_string(), 1694 | "seedy".to_string(), 1695 | "segue".to_string(), 1696 | "seize".to_string(), 1697 | "semen".to_string(), 1698 | "sense".to_string(), 1699 | "sepia".to_string(), 1700 | "serif".to_string(), 1701 | "serum".to_string(), 1702 | "serve".to_string(), 1703 | "setup".to_string(), 1704 | "seven".to_string(), 1705 | "sever".to_string(), 1706 | "sewer".to_string(), 1707 | "shack".to_string(), 1708 | "shade".to_string(), 1709 | "shady".to_string(), 1710 | "shaft".to_string(), 1711 | "shake".to_string(), 1712 | "shaky".to_string(), 1713 | "shale".to_string(), 1714 | "shall".to_string(), 1715 | "shalt".to_string(), 1716 | "shame".to_string(), 1717 | "shank".to_string(), 1718 | "shape".to_string(), 1719 | "shard".to_string(), 1720 | "share".to_string(), 1721 | "shark".to_string(), 1722 | "sharp".to_string(), 1723 | "shave".to_string(), 1724 | "shawl".to_string(), 1725 | "shear".to_string(), 1726 | "sheen".to_string(), 1727 | "sheep".to_string(), 1728 | "sheer".to_string(), 1729 | "sheet".to_string(), 1730 | "sheik".to_string(), 1731 | "shelf".to_string(), 1732 | "shell".to_string(), 1733 | "shied".to_string(), 1734 | "shift".to_string(), 1735 | "shine".to_string(), 1736 | "shiny".to_string(), 1737 | "shire".to_string(), 1738 | "shirk".to_string(), 1739 | "shirt".to_string(), 1740 | "shoal".to_string(), 1741 | "shock".to_string(), 1742 | "shone".to_string(), 1743 | "shook".to_string(), 1744 | "shoot".to_string(), 1745 | "shore".to_string(), 1746 | "shorn".to_string(), 1747 | "short".to_string(), 1748 | "shout".to_string(), 1749 | "shove".to_string(), 1750 | "shown".to_string(), 1751 | "showy".to_string(), 1752 | "shrew".to_string(), 1753 | "shrub".to_string(), 1754 | "shrug".to_string(), 1755 | "shuck".to_string(), 1756 | "shunt".to_string(), 1757 | "shush".to_string(), 1758 | "shyly".to_string(), 1759 | "siege".to_string(), 1760 | "sieve".to_string(), 1761 | "sight".to_string(), 1762 | "sigma".to_string(), 1763 | "silky".to_string(), 1764 | "silly".to_string(), 1765 | "since".to_string(), 1766 | "sinew".to_string(), 1767 | "singe".to_string(), 1768 | "siren".to_string(), 1769 | "sissy".to_string(), 1770 | "sixth".to_string(), 1771 | "sixty".to_string(), 1772 | "skate".to_string(), 1773 | "skier".to_string(), 1774 | "skiff".to_string(), 1775 | "skill".to_string(), 1776 | "skimp".to_string(), 1777 | "skirt".to_string(), 1778 | "skulk".to_string(), 1779 | "skull".to_string(), 1780 | "skunk".to_string(), 1781 | "slack".to_string(), 1782 | "slain".to_string(), 1783 | "slang".to_string(), 1784 | "slant".to_string(), 1785 | "slash".to_string(), 1786 | "slate".to_string(), 1787 | "slave".to_string(), 1788 | "sleek".to_string(), 1789 | "sleep".to_string(), 1790 | "sleet".to_string(), 1791 | "slept".to_string(), 1792 | "slice".to_string(), 1793 | "slick".to_string(), 1794 | "slide".to_string(), 1795 | "slime".to_string(), 1796 | "slimy".to_string(), 1797 | "sling".to_string(), 1798 | "slink".to_string(), 1799 | "sloop".to_string(), 1800 | "slope".to_string(), 1801 | "slosh".to_string(), 1802 | "sloth".to_string(), 1803 | "slump".to_string(), 1804 | "slung".to_string(), 1805 | "slunk".to_string(), 1806 | "slurp".to_string(), 1807 | "slush".to_string(), 1808 | "slyly".to_string(), 1809 | "smack".to_string(), 1810 | "small".to_string(), 1811 | "smart".to_string(), 1812 | "smash".to_string(), 1813 | "smear".to_string(), 1814 | "smell".to_string(), 1815 | "smelt".to_string(), 1816 | "smile".to_string(), 1817 | "smirk".to_string(), 1818 | "smite".to_string(), 1819 | "smith".to_string(), 1820 | "smock".to_string(), 1821 | "smoke".to_string(), 1822 | "smoky".to_string(), 1823 | "smote".to_string(), 1824 | "snack".to_string(), 1825 | "snail".to_string(), 1826 | "snake".to_string(), 1827 | "snaky".to_string(), 1828 | "snare".to_string(), 1829 | "snarl".to_string(), 1830 | "sneak".to_string(), 1831 | "sneer".to_string(), 1832 | "snide".to_string(), 1833 | "sniff".to_string(), 1834 | "snipe".to_string(), 1835 | "snoop".to_string(), 1836 | "snore".to_string(), 1837 | "snort".to_string(), 1838 | "snout".to_string(), 1839 | "snowy".to_string(), 1840 | "snuck".to_string(), 1841 | "snuff".to_string(), 1842 | "soapy".to_string(), 1843 | "sober".to_string(), 1844 | "soggy".to_string(), 1845 | "solar".to_string(), 1846 | "solid".to_string(), 1847 | "solve".to_string(), 1848 | "sonar".to_string(), 1849 | "sonic".to_string(), 1850 | "sooth".to_string(), 1851 | "sooty".to_string(), 1852 | "sorry".to_string(), 1853 | "sound".to_string(), 1854 | "south".to_string(), 1855 | "sower".to_string(), 1856 | "space".to_string(), 1857 | "spade".to_string(), 1858 | "spank".to_string(), 1859 | "spare".to_string(), 1860 | "spark".to_string(), 1861 | "spasm".to_string(), 1862 | "spawn".to_string(), 1863 | "speak".to_string(), 1864 | "spear".to_string(), 1865 | "speck".to_string(), 1866 | "speed".to_string(), 1867 | "spell".to_string(), 1868 | "spelt".to_string(), 1869 | "spend".to_string(), 1870 | "spent".to_string(), 1871 | "sperm".to_string(), 1872 | "spice".to_string(), 1873 | "spicy".to_string(), 1874 | "spied".to_string(), 1875 | "spiel".to_string(), 1876 | "spike".to_string(), 1877 | "spiky".to_string(), 1878 | "spill".to_string(), 1879 | "spilt".to_string(), 1880 | "spine".to_string(), 1881 | "spiny".to_string(), 1882 | "spire".to_string(), 1883 | "spite".to_string(), 1884 | "splat".to_string(), 1885 | "split".to_string(), 1886 | "spoil".to_string(), 1887 | "spoke".to_string(), 1888 | "spoof".to_string(), 1889 | "spook".to_string(), 1890 | "spool".to_string(), 1891 | "spoon".to_string(), 1892 | "spore".to_string(), 1893 | "sport".to_string(), 1894 | "spout".to_string(), 1895 | "spray".to_string(), 1896 | "spree".to_string(), 1897 | "sprig".to_string(), 1898 | "spunk".to_string(), 1899 | "spurn".to_string(), 1900 | "spurt".to_string(), 1901 | "squad".to_string(), 1902 | "squat".to_string(), 1903 | "squib".to_string(), 1904 | "stack".to_string(), 1905 | "staff".to_string(), 1906 | "stage".to_string(), 1907 | "staid".to_string(), 1908 | "stain".to_string(), 1909 | "stair".to_string(), 1910 | "stake".to_string(), 1911 | "stale".to_string(), 1912 | "stalk".to_string(), 1913 | "stall".to_string(), 1914 | "stamp".to_string(), 1915 | "stand".to_string(), 1916 | "stank".to_string(), 1917 | "stare".to_string(), 1918 | "stark".to_string(), 1919 | "start".to_string(), 1920 | "stash".to_string(), 1921 | "state".to_string(), 1922 | "stave".to_string(), 1923 | "stead".to_string(), 1924 | "steak".to_string(), 1925 | "steal".to_string(), 1926 | "steam".to_string(), 1927 | "steed".to_string(), 1928 | "steel".to_string(), 1929 | "steep".to_string(), 1930 | "steer".to_string(), 1931 | "stein".to_string(), 1932 | "stern".to_string(), 1933 | "stick".to_string(), 1934 | "stiff".to_string(), 1935 | "still".to_string(), 1936 | "stilt".to_string(), 1937 | "sting".to_string(), 1938 | "stink".to_string(), 1939 | "stint".to_string(), 1940 | "stock".to_string(), 1941 | "stoic".to_string(), 1942 | "stoke".to_string(), 1943 | "stole".to_string(), 1944 | "stomp".to_string(), 1945 | "stone".to_string(), 1946 | "stony".to_string(), 1947 | "stood".to_string(), 1948 | "stool".to_string(), 1949 | "stoop".to_string(), 1950 | "store".to_string(), 1951 | "stork".to_string(), 1952 | "storm".to_string(), 1953 | "story".to_string(), 1954 | "stout".to_string(), 1955 | "stove".to_string(), 1956 | "strap".to_string(), 1957 | "straw".to_string(), 1958 | "stray".to_string(), 1959 | "strip".to_string(), 1960 | "strut".to_string(), 1961 | "stuck".to_string(), 1962 | "study".to_string(), 1963 | "stuff".to_string(), 1964 | "stump".to_string(), 1965 | "stung".to_string(), 1966 | "stunk".to_string(), 1967 | "stunt".to_string(), 1968 | "style".to_string(), 1969 | "suave".to_string(), 1970 | "sugar".to_string(), 1971 | "suing".to_string(), 1972 | "suite".to_string(), 1973 | "sulky".to_string(), 1974 | "sully".to_string(), 1975 | "sumac".to_string(), 1976 | "sunny".to_string(), 1977 | "super".to_string(), 1978 | "surer".to_string(), 1979 | "surge".to_string(), 1980 | "surly".to_string(), 1981 | "sushi".to_string(), 1982 | "swami".to_string(), 1983 | "swamp".to_string(), 1984 | "swarm".to_string(), 1985 | "swash".to_string(), 1986 | "swath".to_string(), 1987 | "swear".to_string(), 1988 | "sweat".to_string(), 1989 | "sweep".to_string(), 1990 | "sweet".to_string(), 1991 | "swell".to_string(), 1992 | "swept".to_string(), 1993 | "swift".to_string(), 1994 | "swill".to_string(), 1995 | "swine".to_string(), 1996 | "swing".to_string(), 1997 | "swirl".to_string(), 1998 | "swish".to_string(), 1999 | "swoon".to_string(), 2000 | "swoop".to_string(), 2001 | "sword".to_string(), 2002 | "swore".to_string(), 2003 | "sworn".to_string(), 2004 | "swung".to_string(), 2005 | "synod".to_string(), 2006 | "syrup".to_string(), 2007 | "tabby".to_string(), 2008 | "table".to_string(), 2009 | "taboo".to_string(), 2010 | "tacit".to_string(), 2011 | "tacky".to_string(), 2012 | "taffy".to_string(), 2013 | "taint".to_string(), 2014 | "taken".to_string(), 2015 | "taker".to_string(), 2016 | "tally".to_string(), 2017 | "talon".to_string(), 2018 | "tamer".to_string(), 2019 | "tango".to_string(), 2020 | "tangy".to_string(), 2021 | "taper".to_string(), 2022 | "tapir".to_string(), 2023 | "tardy".to_string(), 2024 | "tarot".to_string(), 2025 | "taste".to_string(), 2026 | "tasty".to_string(), 2027 | "tatty".to_string(), 2028 | "taunt".to_string(), 2029 | "tawny".to_string(), 2030 | "teach".to_string(), 2031 | "teary".to_string(), 2032 | "tease".to_string(), 2033 | "teddy".to_string(), 2034 | "teeth".to_string(), 2035 | "tempo".to_string(), 2036 | "tenet".to_string(), 2037 | "tenor".to_string(), 2038 | "tense".to_string(), 2039 | "tenth".to_string(), 2040 | "tepee".to_string(), 2041 | "tepid".to_string(), 2042 | "terra".to_string(), 2043 | "terse".to_string(), 2044 | "testy".to_string(), 2045 | "thank".to_string(), 2046 | "theft".to_string(), 2047 | "their".to_string(), 2048 | "theme".to_string(), 2049 | "there".to_string(), 2050 | "these".to_string(), 2051 | "theta".to_string(), 2052 | "thick".to_string(), 2053 | "thief".to_string(), 2054 | "thigh".to_string(), 2055 | "thing".to_string(), 2056 | "think".to_string(), 2057 | "third".to_string(), 2058 | "thong".to_string(), 2059 | "thorn".to_string(), 2060 | "those".to_string(), 2061 | "three".to_string(), 2062 | "threw".to_string(), 2063 | "throb".to_string(), 2064 | "throw".to_string(), 2065 | "thrum".to_string(), 2066 | "thumb".to_string(), 2067 | "thump".to_string(), 2068 | "thyme".to_string(), 2069 | "tiara".to_string(), 2070 | "tibia".to_string(), 2071 | "tidal".to_string(), 2072 | "tiger".to_string(), 2073 | "tight".to_string(), 2074 | "tilde".to_string(), 2075 | "timer".to_string(), 2076 | "timid".to_string(), 2077 | "tipsy".to_string(), 2078 | "titan".to_string(), 2079 | "tithe".to_string(), 2080 | "title".to_string(), 2081 | "toast".to_string(), 2082 | "today".to_string(), 2083 | "toddy".to_string(), 2084 | "token".to_string(), 2085 | "tonal".to_string(), 2086 | "tonga".to_string(), 2087 | "tonic".to_string(), 2088 | "tooth".to_string(), 2089 | "topaz".to_string(), 2090 | "topic".to_string(), 2091 | "torch".to_string(), 2092 | "torso".to_string(), 2093 | "torus".to_string(), 2094 | "total".to_string(), 2095 | "totem".to_string(), 2096 | "touch".to_string(), 2097 | "tough".to_string(), 2098 | "towel".to_string(), 2099 | "tower".to_string(), 2100 | "toxic".to_string(), 2101 | "toxin".to_string(), 2102 | "trace".to_string(), 2103 | "track".to_string(), 2104 | "tract".to_string(), 2105 | "trade".to_string(), 2106 | "trail".to_string(), 2107 | "train".to_string(), 2108 | "trait".to_string(), 2109 | "tramp".to_string(), 2110 | "trash".to_string(), 2111 | "trawl".to_string(), 2112 | "tread".to_string(), 2113 | "treat".to_string(), 2114 | "trend".to_string(), 2115 | "triad".to_string(), 2116 | "trial".to_string(), 2117 | "tribe".to_string(), 2118 | "trice".to_string(), 2119 | "trick".to_string(), 2120 | "tried".to_string(), 2121 | "tripe".to_string(), 2122 | "trite".to_string(), 2123 | "troll".to_string(), 2124 | "troop".to_string(), 2125 | "trope".to_string(), 2126 | "trout".to_string(), 2127 | "trove".to_string(), 2128 | "truce".to_string(), 2129 | "truck".to_string(), 2130 | "truer".to_string(), 2131 | "truly".to_string(), 2132 | "trump".to_string(), 2133 | "trunk".to_string(), 2134 | "truss".to_string(), 2135 | "trust".to_string(), 2136 | "truth".to_string(), 2137 | "tryst".to_string(), 2138 | "tubal".to_string(), 2139 | "tuber".to_string(), 2140 | "tulip".to_string(), 2141 | "tulle".to_string(), 2142 | "tumor".to_string(), 2143 | "tunic".to_string(), 2144 | "turbo".to_string(), 2145 | "tutor".to_string(), 2146 | "twang".to_string(), 2147 | "tweak".to_string(), 2148 | "tweed".to_string(), 2149 | "tweet".to_string(), 2150 | "twice".to_string(), 2151 | "twine".to_string(), 2152 | "twirl".to_string(), 2153 | "twist".to_string(), 2154 | "twixt".to_string(), 2155 | "tying".to_string(), 2156 | "udder".to_string(), 2157 | "ulcer".to_string(), 2158 | "ultra".to_string(), 2159 | "umbra".to_string(), 2160 | "uncle".to_string(), 2161 | "uncut".to_string(), 2162 | "under".to_string(), 2163 | "undid".to_string(), 2164 | "undue".to_string(), 2165 | "unfed".to_string(), 2166 | "unfit".to_string(), 2167 | "unify".to_string(), 2168 | "union".to_string(), 2169 | "unite".to_string(), 2170 | "unity".to_string(), 2171 | "unlit".to_string(), 2172 | "unmet".to_string(), 2173 | "unset".to_string(), 2174 | "untie".to_string(), 2175 | "until".to_string(), 2176 | "unwed".to_string(), 2177 | "unzip".to_string(), 2178 | "upper".to_string(), 2179 | "upset".to_string(), 2180 | "urban".to_string(), 2181 | "urine".to_string(), 2182 | "usage".to_string(), 2183 | "usher".to_string(), 2184 | "using".to_string(), 2185 | "usual".to_string(), 2186 | "usurp".to_string(), 2187 | "utile".to_string(), 2188 | "utter".to_string(), 2189 | "vague".to_string(), 2190 | "valet".to_string(), 2191 | "valid".to_string(), 2192 | "valor".to_string(), 2193 | "value".to_string(), 2194 | "valve".to_string(), 2195 | "vapid".to_string(), 2196 | "vapor".to_string(), 2197 | "vault".to_string(), 2198 | "vaunt".to_string(), 2199 | "vegan".to_string(), 2200 | "venom".to_string(), 2201 | "venue".to_string(), 2202 | "verge".to_string(), 2203 | "verse".to_string(), 2204 | "verso".to_string(), 2205 | "verve".to_string(), 2206 | "vicar".to_string(), 2207 | "video".to_string(), 2208 | "vigil".to_string(), 2209 | "vigor".to_string(), 2210 | "villa".to_string(), 2211 | "vinyl".to_string(), 2212 | "viola".to_string(), 2213 | "viper".to_string(), 2214 | "viral".to_string(), 2215 | "virus".to_string(), 2216 | "visit".to_string(), 2217 | "visor".to_string(), 2218 | "vista".to_string(), 2219 | "vital".to_string(), 2220 | "vivid".to_string(), 2221 | "vixen".to_string(), 2222 | "vocal".to_string(), 2223 | "vodka".to_string(), 2224 | "vogue".to_string(), 2225 | "voice".to_string(), 2226 | "voila".to_string(), 2227 | "vomit".to_string(), 2228 | "voter".to_string(), 2229 | "vouch".to_string(), 2230 | "vowel".to_string(), 2231 | "vying".to_string(), 2232 | "wacky".to_string(), 2233 | "wafer".to_string(), 2234 | "wager".to_string(), 2235 | "wagon".to_string(), 2236 | "waist".to_string(), 2237 | "waive".to_string(), 2238 | "waltz".to_string(), 2239 | "warty".to_string(), 2240 | "waste".to_string(), 2241 | "watch".to_string(), 2242 | "water".to_string(), 2243 | "waver".to_string(), 2244 | "waxen".to_string(), 2245 | "weary".to_string(), 2246 | "weave".to_string(), 2247 | "wedge".to_string(), 2248 | "weedy".to_string(), 2249 | "weigh".to_string(), 2250 | "weird".to_string(), 2251 | "welch".to_string(), 2252 | "welsh".to_string(), 2253 | "wench".to_string(), 2254 | "whack".to_string(), 2255 | "whale".to_string(), 2256 | "wharf".to_string(), 2257 | "wheat".to_string(), 2258 | "wheel".to_string(), 2259 | "whelp".to_string(), 2260 | "where".to_string(), 2261 | "which".to_string(), 2262 | "whiff".to_string(), 2263 | "while".to_string(), 2264 | "whine".to_string(), 2265 | "whiny".to_string(), 2266 | "whirl".to_string(), 2267 | "whisk".to_string(), 2268 | "white".to_string(), 2269 | "whole".to_string(), 2270 | "whoop".to_string(), 2271 | "whose".to_string(), 2272 | "widen".to_string(), 2273 | "wider".to_string(), 2274 | "widow".to_string(), 2275 | "width".to_string(), 2276 | "wield".to_string(), 2277 | "wight".to_string(), 2278 | "willy".to_string(), 2279 | "wimpy".to_string(), 2280 | "wince".to_string(), 2281 | "winch".to_string(), 2282 | "windy".to_string(), 2283 | "wiser".to_string(), 2284 | "wispy".to_string(), 2285 | "witch".to_string(), 2286 | "witty".to_string(), 2287 | "woken".to_string(), 2288 | "woman".to_string(), 2289 | "women".to_string(), 2290 | "woody".to_string(), 2291 | "wooer".to_string(), 2292 | "wooly".to_string(), 2293 | "woozy".to_string(), 2294 | "wordy".to_string(), 2295 | "world".to_string(), 2296 | "worry".to_string(), 2297 | "worse".to_string(), 2298 | "worst".to_string(), 2299 | "worth".to_string(), 2300 | "would".to_string(), 2301 | "wound".to_string(), 2302 | "woven".to_string(), 2303 | "wrack".to_string(), 2304 | "wrath".to_string(), 2305 | "wreak".to_string(), 2306 | "wreck".to_string(), 2307 | "wrest".to_string(), 2308 | "wring".to_string(), 2309 | "wrist".to_string(), 2310 | "write".to_string(), 2311 | "wrong".to_string(), 2312 | "wrote".to_string(), 2313 | "wrung".to_string(), 2314 | "wryly".to_string(), 2315 | "yacht".to_string(), 2316 | "yearn".to_string(), 2317 | "yeast".to_string(), 2318 | "yield".to_string(), 2319 | "young".to_string(), 2320 | "youth".to_string(), 2321 | "zebra".to_string(), 2322 | "zesty".to_string(), 2323 | "zonal".to_string(), 2324 | ] 2325 | } 2326 | --------------------------------------------------------------------------------