1use crate::boggle_board::Board;
2use word_trie::trie::{Trie,TrieNode};
3use rand::Rng;
4use rand::distr::{Distribution, Uniform};
5use std::collections::{HashSet,HashMap};
6
7pub struct GeneticBoard{
8 board:Board,
9 age:u32,
10 score:usize,
11}
12
13impl GeneticBoard{
14 pub fn get_board(dictionary:&Trie, minimum_score:usize, board_x:usize,board_y:usize)->Board{
15 const POPULATION_SIZE:usize = 10;
16 let mut choromosomes : Vec<GeneticBoard> = Self::init_population(dictionary,POPULATION_SIZE, board_x, board_y);
17
18 let mut generation = 0;
19 loop{
20
21 if let Some(x) = choromosomes.first() {
23 println!("hello board, genetic generated : {} : {} : {}", x.board.hash(),x.age,x.score);
24 if x.score > minimum_score || generation>20 {
25 return x.board.copy();
26 }
27 }
28
29 choromosomes = Self::evolve_population(dictionary,POPULATION_SIZE,&choromosomes);
30 generation+=1;
31 }
32
33 }
34
35 fn evolve_population(dictionary:&Trie,size:usize, prev_generation : &Vec<GeneticBoard>) -> Vec<GeneticBoard>{
46 let mut choromosomes : Vec<GeneticBoard> = Vec::new();
47 let new_borns : HashSet<String> = HashSet::new();
48 let uniform = Uniform::try_from(0..size).unwrap();
49 let mut rng = rand::rng();
50 while choromosomes.len() < size*10 {
51 let mut a;
52 {
53 a = uniform.sample(&mut rng);
54 for _i in 0..1 {
55 let c = uniform.sample(&mut rng);
56 if prev_generation[c].score > prev_generation[a].score {
57 a = c;
58 }
59 }
60 }
61 let mut b = a;
62 while b==a {
63 b = uniform.sample(&mut rng);
64 for _i in 0..1 {
65 let c = uniform.sample(&mut rng);
66 if prev_generation[c].score > prev_generation[b].score {
67 b = c;
68 }
69 }
70 }
71
72 let mut born = prev_generation[a].merge(&prev_generation[b]);
74 let key = born.board.hash();
75 if new_borns.contains(&key) {
76 continue;
77 }
78 born.score = Self::get_board_score(dictionary,&born.board);
79 choromosomes.push(born);
80 }
81 for i in 0..1 {
82 if let Some(x) = prev_generation.get(i) {
83 let key = x.board.hash();
84 if !new_borns.contains(&key) {
85 choromosomes.push(Self{
86 board:x.board.copy(),
87 age:x.age,
88 score:x.score
89 });
90 }
91 }
92 }
93
94 choromosomes.sort_by(|a,b| b.score.cmp(&a.score));
95 choromosomes.truncate(size);
96
97 choromosomes
98 }
99 fn init_population(dictionary:&Trie,size:usize, board_x:usize,board_y:usize) -> Vec<GeneticBoard>{
100 let mut choromosomes : Vec<GeneticBoard> = Vec::new();
101 let mut new_borns = HashSet::new();
102 while new_borns.len()<size {
103 let brd = Board::new_random(board_x,board_y);
104 let key = brd.hash();
105 if new_borns.contains(&key) {
106 continue;
107 }
108 let score = Self::get_board_score(dictionary,&brd);
109
110 let gen_board = Self{
111 board:brd,
112 age:0,
113 score:score
114 };
115 new_borns.insert(key);
116 choromosomes.push(gen_board);
117 }
118 choromosomes.sort_by(|a,b| b.score.cmp(&a.score));
119
120 choromosomes
121 }
122
123 fn merge(&self, other : &GeneticBoard) -> Self {
124 let boards = Self::order(&self,other);
125
126 let mut merged = Self{
127 board:boards.0.board.copy(),
128 age:boards.0.age+1,
129 score:0
130 };
131
132 let similarity = Self::similarity(&boards.0.board,&boards.1.board);
133
134 let first_share = if similarity < 0.80 { 70 } else { 70 };
135 let second_share = if similarity < 0.80 { 99 } else { 80 };
136
137 let uniform = Uniform::try_from(0..101).unwrap();
138 let uniform2 = Uniform::try_from(0..26).unwrap();
139 let mut rng = rand::rng();
140
141 for i in 0..boards.0.board.width() {
142 for j in 0..boards.0.board.length() {
143 let rand = uniform.sample(&mut rng);
144 if rand < first_share {continue;}else if rand < second_share {merged.board.set(i,j,boards.1.board.get(i,j).expect("must have value always"));} else {
147 let rnd = uniform2.sample(&mut rng);
149 let random_char = (b'A' + rnd) as char;
150 merged.board.set(i,j,random_char);
151 }
152 }
153 }
154
155 merged
156 }
157
158 fn order<'a>(this: &'a GeneticBoard,that: &'a GeneticBoard)->(&'a GeneticBoard,&'a GeneticBoard){
161 if this.score>that.score { return (this,that); }
162 if this.score<that.score { return (that,this); }
163 let mut rng = rand::rng();
164 let rnd = rng.random_range(0..=1);
165 if rnd==0 { return (this,that);}
166 (that,this)
167 }
168
169 fn similarity(this:&Board, that:&Board) -> f32{
170 let mut same:f32 = 0.0;
171 for i in 0..this.width() {
172 for j in 0..this.length() {
173 let ch1 = this.get(i,j).expect("must have value always");
174 let ch2 = that.get(i,j).expect("must have value always");
175 if ch1==ch2 { same+=1.0;}
176 }
177 }
178 same/(this.width() as f32 * this.length() as f32)
179 }
180
181 fn get_board_score(dictionary:&Trie,brd:&Board) -> usize{
206 let mut set = HashMap::new();
207 let mut visited = HashMap::new();
208
209 let mut current = String::new();
210 for i in 0..brd.width(){
211 for j in 0..brd.length(){
212 Self::get_board_score_from(&dictionary.root,brd,&mut set,&mut visited,i,j,&mut current);
213 }
214 }
215
216 let mut score = 0;
217
218 for (_, val) in &set {
219 score += val;
220 }
221
222 score
223 }
224
225 fn get_board_score_from(mut node:&TrieNode ,
226 brd:&Board,
227 set:&mut HashMap<String,usize>,
228 visited:&mut HashMap<usize,bool>,
229 x:usize,
230 y:usize,
231 current:&mut String
232 ){
233
234 let cell_index = x*brd.width() + y;
235 let is_visited = visited.entry(cell_index).or_insert(false);
236 if *is_visited {
238 return;
239 }
240
241 *is_visited = true;
243 let ch = brd.get(x,y).expect("the board must has value in all cells");
244
245 let ch_index = ch.to_ascii_lowercase();
247 match node.nodes.get(&ch_index){
248 Some(next_node) => node = next_node,
249 None => {
250 visited.insert(cell_index,false);
252 return;
253 },
254 }
255 current.push(ch);
257
258 if node.is_word && !set.contains_key(current) {
262 set.insert(current.to_string(),Self::get_score(current.len()));
263 }
264
265 for (a,b) in [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]{
267 let next_x = x as i8 + a;
268 let next_y = y as i8 + b;
269 if (0..brd.width() as i8).contains(&next_x) && (0..brd.length() as i8).contains(&next_y){
270 Self::get_board_score_from(node,brd, set, visited,next_x as usize,next_y as usize, current);
271 }
272 }
273
274 current.pop();
276
277 visited.insert(cell_index,false);
279 }
280
281 fn get_score(len:usize)->usize{
282 match len{
283 0..=2 => 0,
284 3..=4 => 1,
285 5 => 2,
286 6 => 3,
287 7 => 5,
288 _ => 11
289 }
290 }
291}