1use std::fs::File;
4use std::io::Read;
5
6use indicatif::ProgressBar;
7
8pub struct Guess {
9 pub value: String,
10 pub entropy: f64,
11}
12
13pub fn get_words() -> Vec<String> {
14 let args: Vec<String> = std::env::args().collect();
15 let mut file;
16 let mut words: String = String::new();
17 let result;
18 if args.len() < 2 {
19 file = match File::open("words.txt") {
20 Ok(f) => f,
21 Err(_) => {
22 println!("When executing ezwordle, you must specify a raw text file containing Wordle's allowed word list.");
23 println!("You may download this file from https://github.com/hobbsbros/ezwordle/blob/main/src/words/words.txt\n");
24 println!("\nFor example: $ ezwordle words.txt\n");
25 println!("If the file words.txt exists within the directory, you do not need to specify words.txt as an argument.");
26 panic!("No word list specified");
27 }
28 };
29 result = file.read_to_string(&mut words);
30 } else {
31 let filename = args[1].clone();
32 let mut file = match File::open(filename) {
33 Ok(f) => f,
34 Err(_) => {
35 println!("ERROR: EZWordle could not find the file specified.\n");
36 panic!("Could not find specified word list file");
37 }
38 };
39 result = file.read_to_string(&mut words);
40 }
41 match result {
42 Ok(_) => {},
43 Err(_) => {
44 println!("ERROR: EZWordle found the file specified but was unable to read it.\n");
45 panic!("Could not read word list file");
46 }
47 }
48 let output: Vec<String> = words.lines().map(String::from).collect();
49 output
50}
51
52pub fn check_match(colored_word: String, combination: String, word_to_check: String) -> bool {
55 for ((i, letter), truth) in colored_word.chars().enumerate().zip(combination.chars()) {
58 let letter_to_check: char = word_to_check.as_bytes()[i] as char;
59 if truth == '.' {
60 if letter_to_check != letter {
61 return false;
62 }
63 } else if truth == '/' {
64 if !word_to_check.contains(letter) || letter_to_check == letter {
65 return false;
66 }
67 } else if truth == 'x' {
68 if word_to_check.contains(letter) {
69 return false;
70 }
71 }
72 }
73 return true;
74}
75
76pub fn get_matches(colored_word: String, wordlist: Vec<String>, combination: String) -> Vec<String> {
78 let mut words: Vec<String> = Vec::new();
79 for word in wordlist.iter() {
80 let chk: bool = check_match(colored_word.clone(), combination.clone(), word.to_string().clone());
81 if chk {
82 words.push(word.to_string());
83 }
84 }
85 words
86}
87
88pub fn get_all_combinations(len: u8) -> Vec<String> {
90 let mut result: Vec<String> = Vec::new();
91
92 if len == 1 {
94 return vec!["x", "/", "."].into_iter().map(String::from).collect::<Vec<String>>();
95 }
96
97 for sub in get_all_combinations(len - 1) {
99 result.push(sub.clone() + "x");
100 result.push(sub.clone() + "/");
101 result.push(sub.clone() + ".");
102 }
103 result
104}
105
106pub fn compute_contribution(colored_word: String, wordlist: Vec<String>, combination: String) -> f64 {
108 let new_wordlist = get_matches(colored_word.clone(), wordlist.clone(), combination.clone());
109 if new_wordlist.len() == 0 {
110 return 0.0;
111 }
112 let p: f64 = (new_wordlist.len() as f64)/(wordlist.len() as f64);
113 let mut logp: f64 = 0.0;
114 if p > 0.0 {
115 logp = p.ln();
116 }
117 return -p * logp;
118}
119
120pub fn compute_entropy(colored_word: String, wordlist: Vec<String>) -> f64 {
122 let combos: Vec<String> = get_all_combinations(5);
123 let mut entropy: f64 = 0.0;
124 for combo in combos {
125 entropy += compute_contribution(colored_word.clone(), wordlist.clone(), combo.clone());
126 }
127 entropy
128}
129
130pub fn guess(wordlist: Vec<String>, verbose: bool) -> (String, f64) {
132 let num_of_words = wordlist.len();
133 let mut guesses: Vec<Guess> = Vec::new();
134
135 let bar = ProgressBar::new(num_of_words as u64);
136
137 if verbose {
138 println!("Searching {} words for the optimal guess...", num_of_words);
139 }
140
141 for word in wordlist.clone().into_iter() {
142 guesses.push(Guess {value: word.clone(), entropy: compute_entropy(word.clone(), wordlist.clone())});
143 if verbose {bar.inc(1)}
144 }
145 if verbose {
146 bar.finish();
147 println!("Done searching!\n");
148 }
149
150 let sorted_guesses = &mut guesses[..];
151 if sorted_guesses.len() == 0 {
152 return (String::new(), 0.0);
153 }
154 sorted_guesses.sort_by(|x, y| y.entropy.partial_cmp(&x.entropy).unwrap());
155
156 let top_guess = sorted_guesses[0].value.clone();
157 let guess_entropy = sorted_guesses[0].entropy;
158
159 return (top_guess.to_string(), guess_entropy);
160}