boggle_maker/
boggle_board_solver.rs1use std::collections::{HashSet, HashMap};
2use word_trie::trie::Trie;
3use word_trie::TrieBuilder;
4use crate::boggle_dfs::{WordVisitor,BoggleDfsContext,BoggleDfs,get_word_score};
5use crate::boggle_board::Board;
6
7pub struct BoggleBoardResult {
9 words: HashSet<String>,
10 path_tracks: Vec<Vec<u16>>,
11 counts: Vec<u32>,
12 length_map: HashMap<usize, usize>,
13}
14
15impl BoggleBoardResult {
16 pub fn new() -> Self {
18 Self{
19 words: HashSet::new(),
20 path_tracks: Vec::new(),
21 counts: vec![0;12],
22 length_map: HashMap::new(),
23 }
24 }
25
26 pub fn how_many(&self, score: usize) -> u32 {
28 if score > 11 { 0 } else { self.counts[score] }
29 }
30
31 pub fn score_counts(&self) -> &Vec<u32> {
33 &self.counts
34 }
35
36 pub fn len_counts(&self) -> &HashMap<usize,usize> {
38 &self.length_map
39 }
40
41 pub fn words(&self) -> &HashSet<String> {
43 &self.words
44 }
45
46 pub fn path_tracks(&self) -> &Vec<Vec<u16>> {
48 &self.path_tracks
49 }
50
51 pub(crate) fn add_word(&mut self, word: String){
52 let score = get_word_score(&word);
54 self.inc_score(score);
55
56 let word_len = word.len();
57 let entry = self.length_map.entry(word_len).or_insert(0);
58 *entry += 1;
59
60 self.words.insert(word);
62 }
63
64 pub(crate) fn add_path(&mut self, path: Vec<u16>){
65 self.path_tracks.push(path);
66 }
67
68 fn inc_score(&mut self, score: u32){
69 Self::score_guard(score);
70 self.counts[score as usize] += 1;
71 }
72
73 fn score_guard(score: u32){
74 if score > 12 {
75 panic!("the functionality has not been implemented for score bigger than 11");
76 }
77 }
78}
79
80struct BoggleBoardSolverVisitor(BoggleBoardResult);
81
82impl WordVisitor for BoggleBoardSolverVisitor {
83 fn visit(&mut self, word: &str, path: &Vec<u16>){
84
85 self.0.add_path(path.to_vec());
87
88 if self.0.words().contains(word) {
89 return;
90 }
91
92 self.0.add_word(word.to_string());
94 }
95}
96
97#[derive(Default)]
99pub struct BoggleBoardSolver (Option<Trie>);
100
101impl BoggleBoardSolver {
102 pub fn new() -> Self {
104 Self::default()
105 }
106
107 pub fn with_dictionary<P: Into<String>>(mut self, path: P) -> Result<Self, std::io::Error> {
109 self.0 = Some(TrieBuilder::new()
110 .from_file(path.into())
111 .expect("Failed to load trie from file"));
112
113 Ok(self)
114 }
115
116 pub fn solve_vec(&self, board: &Vec<char>, width: usize, length: usize) -> Option<BoggleBoardResult> {
118 match &self.0 {
119 Some(trie) => {
120 let context = BoggleDfsContext::new(&trie, width, length);
121
122 if board.len() != context.count() {
123 panic!("The board size must be fit to the length:{0} and width:{1}", length, width);
124 }
125
126 let mut visitor = BoggleBoardSolverVisitor(BoggleBoardResult::new());
127 BoggleDfs::new(&context, board).with_visitor(&mut visitor).search();
128
129 Some(visitor.0)
130 },
131 None => None
132 }
133 }
134
135 pub fn solve(&self, board: &Board) -> Option<BoggleBoardResult> {
137 self.solve_vec(board.value(), board.width(), board.length())
138 }
139}
140
141#[cfg(test)]
142pub mod tests {
143 use super::*;
144 use crate::builder::BoggleBuilder;
145
146 fn get_sample_solver() -> Result<BoggleBoardSolver, std::io::Error> {
147 BoggleBoardSolver::new().with_dictionary("words.txt")
148 }
149
150 fn get_sample_board() -> Vec<char> {
151 vec!['S','E','R','S','P','A','T','G','L','I','N','E','S','E','R','S']
152 }
153
154 fn solve_sample_board() -> BoggleBoardResult {
155 let solver = get_sample_solver().unwrap();
156 let board = get_sample_board();
157
158 let result = solver.solve_vec(&board, 4, 4);
159 assert!(result.is_some(), "the result does not have value");
160
161 result.unwrap()
162 }
163
164 #[test]
165 fn sample_solver_loads_successfully(){
166 let solver = get_sample_solver();
167 assert!(solver.is_ok(), "Failed to load trie from file");
168 }
169
170 #[test]
171 fn can_solve_sample_board(){
172 let _ = solve_sample_board();
173 }
174
175 #[test]
176 fn there_should_be_atleast_10_words_with_score_3(){
177 let result = solve_sample_board();
178 let three_count = result.how_many(3);
179 assert!(three_count>10);
180 println!("There are {three_count} words with score equal to 3");
181 }
182
183 #[test]
184 fn there_should_be_no_words_with_score_4(){
185 let result = solve_sample_board();
186 let four_count = result.how_many(4);
187 assert_eq!(four_count,0);
188 println!("There are {four_count} words with score equal to 4");
189 }
190
191 #[test]
192 fn there_should_be_some_words_with_3_scores_for_a_board_generated(){
193 let solver = get_sample_solver().unwrap();
194
195 let board = BoggleBuilder::new()
196 .with_dictionary_path("words.txt")
197 .with_target_score(3000)
198 .with_length(4)
199 .with_width(4)
200 .build()
201 .unwrap();
202 assert!(board.is_some());
203 let board = board.unwrap();
204
205 let result = solver.solve(&board).unwrap();
206 let three_count = result.how_many(3);
207 assert!(three_count > 0);
208 println!("There are {three_count} words with score equal to 3");
209 }
210
211 #[test]
212 fn there_should_a_word_with_length_7(){
213 let result = solve_sample_board();
214 let key = 7;
215 assert!(result.len_counts().contains_key(&key));
216 println!("There are {} words with length equal to 7", result.len_counts()[&key]);
217 }
218}