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
|
use crate::board::{Board, Coord, Tile};
use crate::{GameState, check_state};
#[derive(Debug)]
pub struct BestMove {
pub coord: Coord,
eval: i8,
}
fn eval_for_player(board: &Board, player: Tile) -> i8 {
match player {
Tile::PlayerOne => {
return match check_state(board) {
GameState::PlayerTwoWin => -100,
GameState::Draw => -10,
GameState::InProgress => 0,
GameState::PlayerOneWin => 100,
};
}
Tile::PlayerTwo => {
return match check_state(board) {
GameState::PlayerOneWin => -100,
GameState::Draw => -10,
GameState::InProgress => 0,
GameState::PlayerTwoWin => 100,
};
}
Tile::Unowned => {
return match check_state(board) {
GameState::PlayerOneWin => -100,
GameState::PlayerTwoWin => -100,
GameState::InProgress => 0,
GameState::Draw => 100,
};
}
}
}
pub fn get_best_move(board: &Board, player: Tile, is_tl: bool) -> BestMove {
//base case
//game is over, return eval
let eval = eval_for_player(board, player);
//eval 0 means the game is in progress
if eval != 0 {
return BestMove {
coord: (99, 99),
eval,
};
}
//get all possible moves
let possible_moves: Vec<Coord> = (0..9)
.map(|i| {
return (i % 3, i / 3);
})
.filter(|coord| {
return board.get_at_coord(*coord) == Tile::Unowned;
})
.collect();
let mut cur_best = BestMove {
eval: i8::min_value(),
coord: possible_moves[0],
};
let p_move_count = possible_moves.len();
for p_move in possible_moves {
let mut new_board = board.clone();
new_board.set_at_coord(p_move, player);
let other_player = match player {
Tile::PlayerOne => Tile::PlayerTwo,
Tile::PlayerTwo => Tile::PlayerOne,
//this allows the AI to set as player 'unowned', which will cause it to try to draw
//todo: fix this
Tile::Unowned => Tile::PlayerOne,
};
let response_move = get_best_move(&new_board, other_player, false);
if response_move.coord.0 != 99 {
new_board.set_at_coord(response_move.coord, other_player);
}
//this is the inverse of the response eval
let response_eval = -response_move.eval;
//slightly bias for the centre to beat weak players
let centre_bias = if p_move == (1, 1) { 1 } else { 0 };
if response_eval > cur_best.eval {
cur_best = BestMove {
coord: p_move,
eval: response_eval + centre_bias,
};
}
}
if is_tl {
//log thought process
println!("AI > There are {p_move_count} moves that I can play");
let sentiment = match cur_best.eval {
-100 | -101 => "I'll lose this game if my opponent plays right",
-9 | -10 | -11 => "If my opponent plays well, this will be a draw",
100 | 101 => "I'll win this game",
_ => "I can't predict the outcome of this game",
};
println!("AI > ({sentiment})");
println!("AI > I'll play {cur_best:?}")
}
return cur_best;
}
|