1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
use crate::boggle_board::Board;
use word_trie::trie::{Trie,TrieNode};
use rand::Rng;
use rand::distr::{Distribution, Uniform};
use std::collections::{HashSet,HashMap};
pub struct GeneticBoard{
board:Board,
age:u32,
score:usize,
}
impl GeneticBoard{
pub fn get_board(dictionary:&Trie, minimum_score:usize, board_x:usize,board_y:usize)->Board{
const POPULATION_SIZE:usize = 10;
let mut choromosomes : Vec<GeneticBoard> = Self::init_population(dictionary,POPULATION_SIZE, board_x, board_y);
let mut generation = 0;
loop{
//stop mating if the minimum requiremet is met.
if let Some(x) = choromosomes.first() {
println!("hello board, genetic generated : {} : {} : {}", x.board.hash(),x.age,x.score);
if x.score > minimum_score || generation>20 {
return x.board.copy();
}
}
choromosomes = Self::evolve_population(dictionary,POPULATION_SIZE,&choromosomes);
generation+=1;
}
}
// fn tournament_select(choromosomes : &Vec<GeneticBoard>, uniform: &Uniform<u32>, rng: &mut impl Rng)->usize{
// let mut a = uniform.sample(&mut rng);
// for i in 0..4 {
// let b = uniform.sample(&mut rng);
// if choromosomes[b].score > choromosomes[a].score {
// a = b;
// }
// }
// return a;
// }
fn evolve_population(dictionary:&Trie,size:usize, prev_generation : &Vec<GeneticBoard>) -> Vec<GeneticBoard>{
let mut choromosomes : Vec<GeneticBoard> = Vec::new();
let new_borns : HashSet<String> = HashSet::new();
let uniform = Uniform::try_from(0..size).unwrap();
let mut rng = rand::rng();
while choromosomes.len() < size*10 {
let mut a;
{
a = uniform.sample(&mut rng);
for _i in 0..1 {
let c = uniform.sample(&mut rng);
if prev_generation[c].score > prev_generation[a].score {
a = c;
}
}
}
let mut b = a;
while b==a {
b = uniform.sample(&mut rng);
for _i in 0..1 {
let c = uniform.sample(&mut rng);
if prev_generation[c].score > prev_generation[b].score {
b = c;
}
}
}
//let b = Self::tournament_select(prev_generation, &between,&mut rng);
let mut born = prev_generation[a].merge(&prev_generation[b]);
let key = born.board.hash();
if new_borns.contains(&key) {
continue;
}
born.score = Self::get_board_score(dictionary,&born.board);
choromosomes.push(born);
}
for i in 0..1 {
if let Some(x) = prev_generation.get(i) {
let key = x.board.hash();
if !new_borns.contains(&key) {
choromosomes.push(Self{
board:x.board.copy(),
age:x.age,
score:x.score
});
}
}
}
choromosomes.sort_by(|a,b| b.score.cmp(&a.score));
choromosomes.truncate(size);
choromosomes
}
fn init_population(dictionary:&Trie,size:usize, board_x:usize,board_y:usize) -> Vec<GeneticBoard>{
let mut choromosomes : Vec<GeneticBoard> = Vec::new();
let mut new_borns = HashSet::new();
while new_borns.len()<size {
let brd = Board::new_random(board_x,board_y);
let key = brd.hash();
if new_borns.contains(&key) {
continue;
}
let score = Self::get_board_score(dictionary,&brd);
let gen_board = Self{
board:brd,
age:0,
score:score
};
new_borns.insert(key);
choromosomes.push(gen_board);
}
choromosomes.sort_by(|a,b| b.score.cmp(&a.score));
choromosomes
}
fn merge(&self, other : &GeneticBoard) -> Self {
let boards = Self::order(&self,other);
let mut merged = Self{
board:boards.0.board.copy(),
age:boards.0.age+1,
score:0
};
let similarity = Self::similarity(&boards.0.board,&boards.1.board);
let first_share = if similarity < 0.80 { 70 } else { 70 };
let second_share = if similarity < 0.80 { 99 } else { 80 };
let uniform = Uniform::try_from(0..101).unwrap();
let uniform2 = Uniform::try_from(0..26).unwrap();
let mut rng = rand::rng();
for i in 0..boards.0.board.width() {
for j in 0..boards.0.board.length() {
let rand = uniform.sample(&mut rng);
if rand < first_share {continue;}//the board cell can stay the initilized value.
else if rand < second_share {merged.board.set(i,j,boards.1.board.get(i,j).expect("must have value always"));} // get chromosoms from the second board
else {
//mutate
let rnd = uniform2.sample(&mut rng);
let random_char = (b'A' + rnd) as char;
merged.board.set(i,j,random_char);
}
}
}
merged
}
//this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `this` or `that`
//help: consider introducing a named lifetime parameter
fn order<'a>(this: &'a GeneticBoard,that: &'a GeneticBoard)->(&'a GeneticBoard,&'a GeneticBoard){
if this.score>that.score { return (this,that); }
if this.score<that.score { return (that,this); }
let mut rng = rand::rng();
let rnd = rng.random_range(0..=1);
if rnd==0 { return (this,that);}
(that,this)
}
fn similarity(this:&Board, that:&Board) -> f32{
let mut same:f32 = 0.0;
for i in 0..this.width() {
for j in 0..this.length() {
let ch1 = this.get(i,j).expect("must have value always");
let ch2 = that.get(i,j).expect("must have value always");
if ch1==ch2 { same+=1.0;}
}
}
same/(this.width() as f32 * this.length() as f32)
}
/// gets the score of a boggle board.
///
/// # Examples
///
/// ```
/// use word_trie::TrieBuilder;
/// use boggle_maker::Board;
/// let trie = TrieBuilder::new()
/// .from_file("words.txt")
/// .expect("Failed to load trie from file");
///
/// //SERSPATGLINESERS
/// let sample = Board::new(
/// vec!['S','E','R','S','P','A','T','G','L','I','N','E','S','E','R','S']
/// ,4,4, None);
/// //SOTGPRNSEAIESTTL
/// let sample2 = Board::new(
/// vec!['S','O','T','G','P','R','N','S','E','A','I','E','S','T','T','L']
/// ,4,4,None);
/// // let score = get_board_score(&trie,&sample);
/// //assert!(score>3999);
/// //let score = get_board_score(&trie,&sample2);
/// //assert!(score>3999);
/// ```
fn get_board_score(dictionary:&Trie,brd:&Board) -> usize{
let mut set = HashMap::new();
let mut visited = HashMap::new();
let mut current = String::new();
for i in 0..brd.width(){
for j in 0..brd.length(){
Self::get_board_score_from(&dictionary.root,brd,&mut set,&mut visited,i,j,&mut current);
}
}
let mut score = 0;
for (_, val) in &set {
score += val;
}
score
}
fn get_board_score_from(mut node:&TrieNode ,
brd:&Board,
set:&mut HashMap<String,usize>,
visited:&mut HashMap<usize,bool>,
x:usize,
y:usize,
current:&mut String
){
let cell_index = x*brd.width() + y;
let is_visited = visited.entry(cell_index).or_insert(false);
//if the board's current cell is visited then return.
if *is_visited {
return;
}
//mark the current board's cell as visited
*is_visited = true;
let ch = brd.get(x,y).expect("the board must has value in all cells");
//check if the trie has this node
let ch_index = ch.to_ascii_lowercase();
match node.nodes.get(&ch_index){
Some(next_node) => node = next_node,
None => {
//if the trie current node does not have the board's current cell's letter then revert the status and return
visited.insert(cell_index,false);
return;
},
}
//add current cell's letter to the current word
current.push(ch);
//check if the current word is a valid word in the dictionary
//if yes then check if it's not been added to the result set yet
//if not added then add it to the result set with a proper calculated score
if node.is_word && !set.contains_key(current) {
set.insert(current.to_string(),Self::get_score(current.len()));
}
//Recursively check all neighbour cells
for (a,b) in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]{
let next_x = x as i8 + a;
let next_y = y as i8 + b;
if (0..brd.width() as i8).contains(&next_x) && (0..brd.length() as i8).contains(&next_y){
Self::get_board_score_from(node,brd, set, visited,next_x as usize,next_y as usize, current);
}
}
//Remove current visited cell letter from current word end.
current.pop();
//mark the current cell as not visited
visited.insert(cell_index,false);
}
fn get_score(len:usize)->usize{
match len{
0..=2 => 0,
3..=4 => 1,
5 => 2,
6 => 3,
7 => 5,
_ => 11
}
}
}