1use crate::board::Tile;
2use crate::{Board, Direction, Game, GameConfig, GameResult};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum AIAlgorithm {
7 Greedy,
9 Expectimax,
11 MCTS,
13}
14
15pub struct AIPlayer {
17 algorithm: AIAlgorithm,
18 max_depth: usize,
19 simulation_count: usize,
20}
21
22impl AIPlayer {
23 pub fn new(algorithm: AIAlgorithm) -> Self {
25 let max_depth = match algorithm {
26 AIAlgorithm::Greedy => 1,
27 AIAlgorithm::Expectimax => 4,
28 AIAlgorithm::MCTS => 1000,
29 };
30
31 let simulation_count = match algorithm {
32 AIAlgorithm::Greedy => 1,
33 AIAlgorithm::Expectimax => 1,
34 AIAlgorithm::MCTS => 100,
35 };
36
37 Self {
38 algorithm,
39 max_depth,
40 simulation_count,
41 }
42 }
43
44 pub fn with_max_depth(mut self, depth: usize) -> Self {
46 self.max_depth = depth;
47 self
48 }
49
50 pub fn with_simulation_count(mut self, count: usize) -> Self {
52 self.simulation_count = count;
53 self
54 }
55
56 pub fn get_best_move(&self, game: &Game) -> GameResult<Direction> {
58 match self.algorithm {
59 AIAlgorithm::Greedy => self.greedy_move(game),
60 AIAlgorithm::Expectimax => self.expectimax_move(game),
61 AIAlgorithm::MCTS => self.mcts_move(game),
62 }
63 }
64
65 fn greedy_move(&self, game: &Game) -> GameResult<Direction> {
67 let mut best_score = 0;
68 let mut best_direction = Direction::Up;
69
70 for &direction in &[
71 Direction::Up,
72 Direction::Down,
73 Direction::Left,
74 Direction::Right,
75 ] {
76 let mut game_copy = game.clone();
77 if let Ok(moved) = game_copy.make_move(direction) {
78 if moved {
79 let score = game_copy.score().current();
80 if score > best_score {
81 best_score = score;
82 best_direction = direction;
83 }
84 }
85 }
86 }
87
88 Ok(best_direction)
89 }
90
91 fn expectimax_move(&self, game: &Game) -> GameResult<Direction> {
93 let mut best_score = f64::NEG_INFINITY;
94 let mut best_direction = Direction::Up;
95
96 for &direction in &[
97 Direction::Up,
98 Direction::Down,
99 Direction::Left,
100 Direction::Right,
101 ] {
102 let mut game_copy = game.clone();
103 if let Ok(moved) = game_copy.make_move(direction) {
104 if moved {
105 let score = self.expectimax_search(&game_copy, self.max_depth - 1, false);
106 if score > best_score {
107 best_score = score;
108 best_direction = direction;
109 }
110 }
111 }
112 }
113
114 Ok(best_direction)
115 }
116
117 fn expectimax_search(&self, game: &Game, depth: usize, is_maximizing: bool) -> f64 {
119 if depth == 0 || game.state() != crate::GameState::Playing {
120 return self.evaluate_board(game.board());
121 }
122
123 if is_maximizing {
124 let mut max_score = f64::NEG_INFINITY;
126 for &direction in &[
127 Direction::Up,
128 Direction::Down,
129 Direction::Left,
130 Direction::Right,
131 ] {
132 let mut game_copy = game.clone();
133 if let Ok(moved) = game_copy.make_move(direction) {
134 if moved {
135 let score = self.expectimax_search(&game_copy, depth - 1, false);
136 max_score = max_score.max(score);
137 }
138 }
139 }
140 max_score
141 } else {
142 let empty_positions = game.board().empty_positions();
144 if empty_positions.is_empty() {
145 return self.evaluate_board(game.board());
146 }
147
148 let mut total_score = 0.0;
149 let mut count = 0;
150
151 for _ in 0..self.simulation_count.min(empty_positions.len()) {
153 let mut game_copy = game.clone();
154 if let Ok(()) = self.add_random_tile_simulation(&mut game_copy) {
155 let score = self.expectimax_search(&game_copy, depth - 1, true);
156 total_score += score;
157 count += 1;
158 }
159 }
160
161 if count > 0 {
162 total_score / count as f64
163 } else {
164 self.evaluate_board(game.board())
165 }
166 }
167 }
168
169 fn mcts_move(&self, game: &Game) -> GameResult<Direction> {
171 let mut root = MCTSNode::new(game.clone());
172
173 for _ in 0..self.simulation_count {
174 let mut current = &mut root;
175 let mut game_state = game.clone();
176
177 while !current.children.is_empty() && current.visits > 0 {
179 current = current.select_child();
180 if let Some(direction) = current.last_move {
181 let _ = game_state.make_move(direction);
182 }
183 }
184
185 if current.visits > 0 && game_state.state() == crate::GameState::Playing {
187 current.expand(&game_state);
188 }
189
190 let mut simulation_game = game_state.clone();
192 let simulation_result = self.simulate_random_game(&mut simulation_game);
193
194 current.backpropagate(simulation_result);
196 }
197
198 let best_child = root
200 .children
201 .iter()
202 .max_by(|a, b| a.visits.cmp(&b.visits))
203 .ok_or_else(|| crate::GameError::InvalidOperation("No valid moves".to_string()))?;
204
205 Ok(best_child.last_move.unwrap_or(Direction::Up))
206 }
207
208 fn simulate_random_game(&self, game: &mut Game) -> f64 {
210 let mut moves = 0;
211 let max_moves = 1000; while game.state() == crate::GameState::Playing && moves < max_moves {
214 let directions = [
215 Direction::Up,
216 Direction::Down,
217 Direction::Left,
218 Direction::Right,
219 ];
220 let mut moved = false;
221
222 for &direction in &directions {
223 if let Ok(did_move) = game.make_move(direction) {
224 if did_move {
225 moved = true;
226 break;
227 }
228 }
229 }
230
231 if !moved {
232 break; }
234
235 moves += 1;
236 }
237
238 self.evaluate_board(game.board())
239 }
240
241 fn add_random_tile_simulation(&self, game: &mut Game) -> GameResult<()> {
243 let empty_positions = game.board().empty_positions();
244 if empty_positions.is_empty() {
245 return Ok(());
246 }
247
248 let random_index = (empty_positions.len() as f64 * 0.5) as usize; let (row, col) = empty_positions[random_index];
251 let value = if rand::random::<u64>() % 10 < 9 { 2 } else { 4 };
252
253 game.board_mut().set_tile(row, col, Tile::new(value))?;
254 Ok(())
255 }
256
257 fn evaluate_board(&self, board: &Board) -> f64 {
259 let mut score = 0.0;
260 let size = board.size();
261
262 let weights = [
264 vec![4.0, 2.0, 2.0, 4.0],
265 vec![2.0, 1.0, 1.0, 2.0],
266 vec![2.0, 1.0, 1.0, 2.0],
267 vec![4.0, 2.0, 2.0, 4.0],
268 ];
269
270 for row in 0..size {
272 for col in 0..size {
273 if let Ok(tile) = board.get_tile(row, col) {
274 if !tile.is_empty() {
275 let weight = if row < weights.len() && col < weights[row].len() {
276 weights[row][col]
277 } else {
278 1.0
279 };
280 score += (tile.value as f64) * weight;
281 }
282 }
283 }
284 }
285
286 score += self.corner_bonus(board);
288
289 score -= self.scattered_penalty(board);
291
292 score += self.smoothness_bonus(board);
294
295 score
296 }
297
298 fn corner_bonus(&self, board: &Board) -> f64 {
300 let size = board.size();
301 let mut bonus = 0.0;
302
303 let corners = [(0, 0), (0, size - 1), (size - 1, 0), (size - 1, size - 1)];
304
305 for (row, col) in corners {
306 if let Ok(tile) = board.get_tile(row, col) {
307 if !tile.is_empty() {
308 bonus += tile.value as f64 * 2.0;
309 }
310 }
311 }
312
313 bonus
314 }
315
316 fn scattered_penalty(&self, board: &Board) -> f64 {
318 let size = board.size();
319 let mut penalty = 0.0;
320 let mut small_tiles = 0;
321
322 for row in 0..size {
323 for col in 0..size {
324 if let Ok(tile) = board.get_tile(row, col) {
325 if !tile.is_empty() && tile.value <= 8 {
326 small_tiles += 1;
327 }
328 }
329 }
330 }
331
332 penalty += small_tiles as f64 * 0.5;
333 penalty
334 }
335
336 fn smoothness_bonus(&self, board: &Board) -> f64 {
338 let size = board.size();
339 let mut bonus = 0.0;
340
341 for row in 0..size {
343 for col in 0..size - 1 {
344 if let (Ok(tile1), Ok(tile2)) =
345 (board.get_tile(row, col), board.get_tile(row, col + 1))
346 {
347 if !tile1.is_empty() && !tile2.is_empty() {
348 let diff = (tile1.value as f64 - tile2.value as f64).abs();
349 bonus -= diff * 0.1;
350 }
351 }
352 }
353 }
354
355 for row in 0..size - 1 {
357 for col in 0..size {
358 if let (Ok(tile1), Ok(tile2)) =
359 (board.get_tile(row, col), board.get_tile(row + 1, col))
360 {
361 if !tile1.is_empty() && !tile2.is_empty() {
362 let diff = (tile1.value as f64 - tile2.value as f64).abs();
363 bonus -= diff * 0.1;
364 }
365 }
366 }
367 }
368
369 bonus
370 }
371}
372
373struct MCTSNode {
375 #[allow(dead_code)]
376 game: Game,
377 children: Vec<MCTSNode>,
378 visits: usize,
379 total_score: f64,
380 last_move: Option<Direction>,
381}
382
383impl MCTSNode {
384 fn new(game: Game) -> Self {
385 Self {
386 game,
387 children: Vec::new(),
388 visits: 0,
389 total_score: 0.0,
390 last_move: None,
391 }
392 }
393
394 fn select_child(&mut self) -> &mut MCTSNode {
395 let c = 1.414; let log_parent_visits = (self.visits as f64).ln();
398
399 if self.children.is_empty() {
400 panic!("Cannot select child from empty children list");
401 }
402
403 let mut best_index = 0;
404 let mut best_ucb = f64::NEG_INFINITY;
405
406 for (i, child) in self.children.iter().enumerate() {
407 let ucb = child.total_score / child.visits as f64
408 + c * (log_parent_visits / child.visits as f64).sqrt();
409 if ucb > best_ucb {
410 best_ucb = ucb;
411 best_index = i;
412 }
413 }
414
415 &mut self.children[best_index]
416 }
417
418 fn expand(&mut self, game: &Game) {
419 for &direction in &[
420 Direction::Up,
421 Direction::Down,
422 Direction::Left,
423 Direction::Right,
424 ] {
425 let mut game_copy = game.clone();
426 if let Ok(moved) = game_copy.make_move(direction) {
427 if moved {
428 let mut child = MCTSNode::new(game_copy);
429 child.last_move = Some(direction);
430 self.children.push(child);
431 }
432 }
433 }
434 }
435
436 fn backpropagate(&mut self, score: f64) {
437 self.visits += 1;
438 self.total_score += score;
439 }
440}
441
442pub struct AIGameController {
444 ai_player: AIPlayer,
445 game: Game,
446 auto_play: bool,
447 move_delay_ms: u64,
448}
449
450impl AIGameController {
451 pub fn new(config: GameConfig, algorithm: AIAlgorithm) -> GameResult<Self> {
453 let ai_player = AIPlayer::new(algorithm);
454 let game = Game::new(config)?;
455
456 Ok(Self {
457 ai_player,
458 game,
459 auto_play: false,
460 move_delay_ms: 500,
461 })
462 }
463
464 pub fn set_auto_play(&mut self, auto_play: bool) {
466 self.auto_play = auto_play;
467 }
468
469 pub fn set_move_delay(&mut self, delay_ms: u64) {
471 self.move_delay_ms = delay_ms;
472 }
473
474 pub fn game(&self) -> &Game {
476 &self.game
477 }
478
479 pub fn game_mut(&mut self) -> &mut Game {
481 &mut self.game
482 }
483
484 pub fn make_ai_move(&mut self) -> GameResult<bool> {
486 if self.game.state() != crate::GameState::Playing {
487 return Ok(false);
488 }
489
490 let best_move = self.ai_player.get_best_move(&self.game)?;
491 self.game.make_move(best_move)
492 }
493
494 pub fn new_game(&mut self) -> GameResult<()> {
496 self.game.new_game()
497 }
498
499 pub fn algorithm(&self) -> AIAlgorithm {
501 self.ai_player.algorithm
502 }
503}
504
505mod rand {
507 use std::collections::hash_map::DefaultHasher;
508 use std::hash::{Hash, Hasher};
509 use std::time::SystemTime;
510
511 pub fn random<T>() -> T
512 where
513 T: Copy + From<u64>,
514 {
515 let mut hasher = DefaultHasher::new();
516 SystemTime::now().hash(&mut hasher);
517 let hash = hasher.finish();
518 T::from(hash)
519 }
520}