phihung commited on
Commit
8454f02
·
1 Parent(s): d43de39

feat: Exhaustive search

Browse files
Files changed (4) hide show
  1. README.md +1 -0
  2. python/benchmark.py +8 -4
  3. python/othello/ui.py +1 -1
  4. src/ai.rs +20 -8
README.md CHANGED
@@ -21,5 +21,6 @@ othello-ui
21
 
22
  ```bash
23
  uv sync
 
24
  python python/othello/ui.py
25
  ```
 
21
 
22
  ```bash
23
  uv sync
24
+ maturin develop
25
  python python/othello/ui.py
26
  ```
python/benchmark.py CHANGED
@@ -23,16 +23,20 @@ def run_matches(bot1, bot2, n):
23
  return cnts
24
 
25
 
 
 
 
 
26
  if __name__ == "__main__":
27
  for i in range(2, 10):
28
- bot1 = AlphaBetaBot(i)
29
- bot2 = AlphaBetaBot(i + 1)
30
- print(f"AlphaBetaBot({bot1.depth}) vs AlphaBetaBot({bot2.depth})")
31
  draw, win, lost = run_matches(bot1, bot2, 10)
32
  print(f"Win: {win} | Draw: {draw} | Lost: {lost}")
33
  print("----")
34
 
35
- print(f"AlphaBetaBot({bot2.depth}) vs AlphaBetaBot({bot1.depth})")
36
  draw, win, lost = run_matches(bot2, bot1, 10)
37
  print(f"Win: {win} | Draw: {draw} | Lost: {lost}")
38
  print("----")
 
23
  return cnts
24
 
25
 
26
+ def _str(bot):
27
+ return f"AlphaBetaBot({bot.depth}, {bot.exhaustive_depth})"
28
+
29
+
30
  if __name__ == "__main__":
31
  for i in range(2, 10):
32
+ bot1 = AlphaBetaBot(i, 14)
33
+ bot2 = AlphaBetaBot(i + 1, 14)
34
+ print(f"{_str(bot1)} vs {_str(bot2)})")
35
  draw, win, lost = run_matches(bot1, bot2, 10)
36
  print(f"Win: {win} | Draw: {draw} | Lost: {lost}")
37
  print("----")
38
 
39
+ print(f"{_str(bot2)} vs {_str(bot1)})")
40
  draw, win, lost = run_matches(bot2, bot1, 10)
41
  print(f"Win: {win} | Draw: {draw} | Lost: {lost}")
42
  print("----")
python/othello/ui.py CHANGED
@@ -20,7 +20,7 @@ app, rt = fast_app(
20
  )
21
 
22
  games = {}
23
- bot = AlphaBetaBot(8)
24
 
25
 
26
  @rt("/")
 
20
  )
21
 
22
  games = {}
23
+ bot = AlphaBetaBot(7, 15)
24
 
25
 
26
  @rt("/")
src/ai.rs CHANGED
@@ -12,16 +12,25 @@ pub trait Bot {
12
  pub struct AlphaBetaBot {
13
  #[pyo3(get)]
14
  depth: usize,
 
 
 
 
15
  }
16
 
17
  impl Bot for AlphaBetaBot {
18
  fn find_move(&self, g: &Game) -> i32 {
19
  let count = (g.board.0 + g.board.1).count_ones() as usize;
 
 
 
 
 
20
  let (_, move_) = self.do_search(
21
  &AlphaBetaEval { count },
22
  &mut rand::thread_rng(),
23
  &g.board,
24
- self.depth,
25
  -i32::MAX,
26
  i32::MAX,
27
  );
@@ -32,8 +41,11 @@ impl Bot for AlphaBetaBot {
32
  #[pymethods]
33
  impl AlphaBetaBot {
34
  #[new]
35
- pub fn new(depth: usize) -> Self {
36
- Self { depth }
 
 
 
37
  }
38
 
39
  #[pyo3(name = "find_move")]
@@ -60,7 +72,7 @@ impl AlphaBetaBot {
60
  let board = board.pass_move();
61
  let moves = board.available_moves();
62
  if moves == 0 {
63
- return (eval.final_evaluate(&board), -1);
64
  }
65
  let (score, _) = self.do_search(eval, rng, &board, depth - 1, -beta, -alpha);
66
  return (-score, -1);
@@ -106,7 +118,7 @@ impl AlphaBetaEval {
106
  let mut score = scorer(board.0) - scorer(board.1) + 10 * n_moves0 - 10 * n_moves1;
107
  if self.count > 54 {
108
  let (cnt0, cnt1) = board.count();
109
- score += 2 * (self.count as i32 - 54) * (cnt0 - cnt1) as i32;
110
  }
111
  score
112
  }
@@ -114,9 +126,9 @@ impl AlphaBetaEval {
114
  fn final_evaluate(&self, board: &BitBoard) -> i32 {
115
  let (sc1, sc2) = board.count();
116
  if sc1 < sc2 {
117
- -i32::MAX
118
  } else if sc1 > sc2 {
119
- i32::MAX
120
  } else {
121
  0
122
  }
@@ -144,7 +156,7 @@ mod tests {
144
  #[test]
145
  fn test() {
146
  let mut b = Game::default();
147
- let ai = [AlphaBetaBot { depth: 6 }, AlphaBetaBot { depth: 3 }];
148
 
149
  while !b.state.ended {
150
  let pos = ai[b.current_player].find_move(&b);
 
12
  pub struct AlphaBetaBot {
13
  #[pyo3(get)]
14
  depth: usize,
15
+
16
+ // Do exhaustive search when there is less than exhaustive_depth empty square
17
+ #[pyo3(get)]
18
+ exhaustive_depth: usize,
19
  }
20
 
21
  impl Bot for AlphaBetaBot {
22
  fn find_move(&self, g: &Game) -> i32 {
23
  let count = (g.board.0 + g.board.1).count_ones() as usize;
24
+ let depth = if count > 64 - self.exhaustive_depth {
25
+ 100
26
+ } else {
27
+ self.depth
28
+ };
29
  let (_, move_) = self.do_search(
30
  &AlphaBetaEval { count },
31
  &mut rand::thread_rng(),
32
  &g.board,
33
+ depth,
34
  -i32::MAX,
35
  i32::MAX,
36
  );
 
41
  #[pymethods]
42
  impl AlphaBetaBot {
43
  #[new]
44
+ pub fn new(depth: usize, exhaustive_depth: usize) -> Self {
45
+ Self {
46
+ depth,
47
+ exhaustive_depth,
48
+ }
49
  }
50
 
51
  #[pyo3(name = "find_move")]
 
72
  let board = board.pass_move();
73
  let moves = board.available_moves();
74
  if moves == 0 {
75
+ return (-eval.final_evaluate(&board), -1);
76
  }
77
  let (score, _) = self.do_search(eval, rng, &board, depth - 1, -beta, -alpha);
78
  return (-score, -1);
 
118
  let mut score = scorer(board.0) - scorer(board.1) + 10 * n_moves0 - 10 * n_moves1;
119
  if self.count > 54 {
120
  let (cnt0, cnt1) = board.count();
121
+ score += 2 * (self.count as i32 - 54) * (cnt0 - cnt1) as i32
122
  }
123
  score
124
  }
 
126
  fn final_evaluate(&self, board: &BitBoard) -> i32 {
127
  let (sc1, sc2) = board.count();
128
  if sc1 < sc2 {
129
+ -i32::MAX + (64 + sc1 - sc2)
130
  } else if sc1 > sc2 {
131
+ i32::MAX - (64 + sc1 - sc2)
132
  } else {
133
  0
134
  }
 
156
  #[test]
157
  fn test() {
158
  let mut b = Game::default();
159
+ let ai = [AlphaBetaBot::new(3, 0), AlphaBetaBot::new(6, 10)];
160
 
161
  while !b.state.ended {
162
  let pos = ai[b.current_player].find_move(&b);