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

feat: Highlight last move

Browse files
Files changed (3) hide show
  1. python/othello/ui.py +24 -18
  2. src/bits.rs +0 -30
  3. src/game.rs +7 -5
python/othello/ui.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  from uuid import uuid1
2
  from fasthtml.common import (
3
  fast_app,
@@ -18,7 +20,7 @@ app, rt = fast_app(
18
  )
19
 
20
  games = {}
21
- bot = AlphaBetaBot(7)
22
 
23
 
24
  @rt("/")
@@ -34,7 +36,7 @@ def get(uuid: str = None):
34
 
35
  @app.get("/new")
36
  def new(uuid: str = None):
37
- if uuid is not None:
38
  del games[uuid]
39
  return RedirectResponse("/")
40
 
@@ -45,7 +47,13 @@ def make_app(uuid):
45
  Div(
46
  make_status_bar(state),
47
  Div(
48
- *(make_cell(state.cells[i], i, uuid) for i in range(64)),
 
 
 
 
 
 
49
  cls="grid grid-cols-8 gap-0 bg-green-300 mb-5 lg:mt-5",
50
  hx_ext="ws",
51
  ws_connect="/wscon",
@@ -57,26 +65,23 @@ def make_app(uuid):
57
  )
58
 
59
 
60
- def make_cell(v, pos, uuid):
61
  style = "m-2 size-8 lg:size-12 rounded-full"
62
- stone = None
 
 
63
  if v == "?":
64
- stone = Div(
65
  hx_trigger="click",
66
  hx_vals=f'{{"pos": {pos}, "uuid": "{uuid}"}}',
67
  ws_send=True,
68
  cls=f"{style} cursor-pointer bg-purple-200 hover:bg-purple-300",
69
  )
70
  elif v == "B":
71
- stone = Div(cls=f"{style} shadow-sm bg-black shadow-white")
72
  elif v == "W":
73
- stone = Div(cls=f"{style} shadow-sm bg-white shadow-black")
74
- return Div(
75
- stone,
76
- id=f"cell-{pos}",
77
- cls="size-12 xl:size-16 border border-sky-100",
78
- hx_swap_oob="true",
79
- )
80
 
81
 
82
  def make_status_bar(state):
@@ -119,12 +124,11 @@ async def ws(uuid: str, pos: int, send):
119
  prev_state = game.state
120
  state = game.make_move(pos) if pos >= 0 else game.pass_move()
121
 
122
- await send(make_cell(state.cells[pos], pos, uuid))
123
- # await asyncio.sleep(1)
124
 
125
  for i, (c1, c2) in enumerate(zip(prev_state.cells, state.cells)):
126
- if i != pos and c1 != c2:
127
- await send(make_cell(c2, i, uuid))
128
  await send(make_status_bar(state))
129
  return state
130
 
@@ -135,7 +139,9 @@ async def ws(uuid: str, pos: int, send):
135
 
136
  # Bot
137
  while True:
 
138
  pos = bot.find_move(game) if state.can_move else -1
 
139
  state = await play(pos)
140
  if not state.can_move and not state.ended:
141
  # Human has no move
 
1
+ import asyncio
2
+ import time
3
  from uuid import uuid1
4
  from fasthtml.common import (
5
  fast_app,
 
20
  )
21
 
22
  games = {}
23
+ bot = AlphaBetaBot(8)
24
 
25
 
26
  @rt("/")
 
36
 
37
  @app.get("/new")
38
  def new(uuid: str = None):
39
+ if uuid is not None and uuid in games:
40
  del games[uuid]
41
  return RedirectResponse("/")
42
 
 
47
  Div(
48
  make_status_bar(state),
49
  Div(
50
+ *(
51
+ Div(
52
+ make_stone(state.cells[i], i, uuid),
53
+ cls="size-12 xl:size-16 border border-sky-100",
54
+ )
55
+ for i in range(64)
56
+ ),
57
  cls="grid grid-cols-8 gap-0 bg-green-300 mb-5 lg:mt-5",
58
  hx_ext="ws",
59
  ws_connect="/wscon",
 
65
  )
66
 
67
 
68
+ def make_stone(v, pos, uuid, highlight=False):
69
  style = "m-2 size-8 lg:size-12 rounded-full"
70
+ if highlight:
71
+ style += " border-indigo-500 border-2"
72
+ stone = {}
73
  if v == "?":
74
+ stone = dict(
75
  hx_trigger="click",
76
  hx_vals=f'{{"pos": {pos}, "uuid": "{uuid}"}}',
77
  ws_send=True,
78
  cls=f"{style} cursor-pointer bg-purple-200 hover:bg-purple-300",
79
  )
80
  elif v == "B":
81
+ stone = dict(cls=f"{style} shadow-sm bg-black shadow-white")
82
  elif v == "W":
83
+ stone = dict(cls=f"{style} shadow-sm bg-white shadow-black")
84
+ return Div(**stone, id=f"cell-{pos}", hx_swap_oob="true")
 
 
 
 
 
85
 
86
 
87
  def make_status_bar(state):
 
124
  prev_state = game.state
125
  state = game.make_move(pos) if pos >= 0 else game.pass_move()
126
 
127
+ await send(make_stone(state.cells[pos], pos, uuid, highlight=True))
 
128
 
129
  for i, (c1, c2) in enumerate(zip(prev_state.cells, state.cells)):
130
+ if i != pos and c1 != c2 or i == prev_state.last_move:
131
+ await send(make_stone(c2, i, uuid))
132
  await send(make_status_bar(state))
133
  return state
134
 
 
139
 
140
  # Bot
141
  while True:
142
+ now = time.time()
143
  pos = bot.find_move(game) if state.can_move else -1
144
+ await asyncio.sleep(abs(1 - time.time() + now))
145
  state = await play(pos)
146
  if not state.can_move and not state.ended:
147
  # Human has no move
src/bits.rs CHANGED
@@ -1,18 +1,9 @@
1
- use std::fmt::Write;
2
-
3
  use pyo3::prelude::*;
4
 
5
  #[pyclass]
6
  #[derive(Clone)]
7
  pub struct BitBoard(pub u64, pub u64);
8
 
9
- impl core::fmt::Debug for BitBoard {
10
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
11
- f.write_str(&self.to_string(('B', 'W')))?;
12
- Ok(())
13
- }
14
- }
15
-
16
  #[pymethods]
17
  impl BitBoard {
18
  #[new]
@@ -20,27 +11,6 @@ impl BitBoard {
20
  Self(0x0000_0008_1000_0000, 0x0000_0010_0800_0000)
21
  }
22
 
23
- fn __repr__(&self) -> String {
24
- self.to_string(('B', 'W'))
25
- }
26
-
27
- pub fn to_string(&self, players: (char, char)) -> String {
28
- let mut s = String::with_capacity(64 + 8);
29
- for i in (0..64).rev() {
30
- s.write_char(match (self.0 >> i & 1, self.1 >> i & 1) {
31
- (0, 0) => '.',
32
- (1, 0) => players.0,
33
- (0, 1) => players.1,
34
- (_, _) => unreachable!(),
35
- })
36
- .unwrap();
37
- if i % 8 == 0 {
38
- s.write_char('\n').unwrap();
39
- }
40
- }
41
- s
42
- }
43
-
44
  /// Returns bitboards of `self`.
45
  #[must_use]
46
  pub const fn get(&self) -> [u64; 2] {
 
 
 
1
  use pyo3::prelude::*;
2
 
3
  #[pyclass]
4
  #[derive(Clone)]
5
  pub struct BitBoard(pub u64, pub u64);
6
 
 
 
 
 
 
 
 
7
  #[pymethods]
8
  impl BitBoard {
9
  #[new]
 
11
  Self(0x0000_0008_1000_0000, 0x0000_0010_0800_0000)
12
  }
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  /// Returns bitboards of `self`.
15
  #[must_use]
16
  pub const fn get(&self) -> [u64; 2] {
src/game.rs CHANGED
@@ -22,6 +22,7 @@ pub struct State {
22
  pub white_score: i32,
23
  pub cells: Vec<char>,
24
  pub can_move: bool,
 
25
  }
26
 
27
  #[pymethods]
@@ -36,7 +37,7 @@ impl Game {
36
  #[staticmethod]
37
  pub fn default() -> Self {
38
  let board = BitBoard::default();
39
- let state = Self::compute_state(&board, 0);
40
  Self {
41
  board,
42
  current_player: 0,
@@ -52,7 +53,7 @@ impl Game {
52
  pub fn pass_move(&mut self) -> State {
53
  self.board = self.board.pass_move();
54
  self.current_player = 1 - self.current_player;
55
- self.state = Self::compute_state(&self.board, self.current_player);
56
  self.state.clone()
57
  }
58
 
@@ -60,7 +61,7 @@ impl Game {
60
  let next = self.board.make_move(place).unwrap();
61
  self.current_player = 1 - self.current_player;
62
  self.board = next;
63
- self.state = Self::compute_state(&self.board, self.current_player);
64
  self.state.clone()
65
  }
66
 
@@ -91,7 +92,7 @@ impl Game {
91
  }
92
 
93
  impl Game {
94
- fn compute_state(board: &BitBoard, current_player: usize) -> State {
95
  let (cnt0, cnt1) = board.count();
96
  let moves = board.available_moves();
97
  let cells: Vec<_> = (0..64)
@@ -115,6 +116,7 @@ impl Game {
115
  white_score: if player == 'W' { cnt0 } else { cnt1 },
116
  cells,
117
  can_move: moves != 0,
 
118
  }
119
  }
120
  }
@@ -146,7 +148,7 @@ mod tests {
146
  let b = BitBoard(2, 1);
147
  let mut g = Game {
148
  current_player: 0,
149
- state: Game::compute_state(&b, 0),
150
  board: b,
151
  };
152
  assert_eq!(g.state.can_move, false);
 
22
  pub white_score: i32,
23
  pub cells: Vec<char>,
24
  pub can_move: bool,
25
+ pub last_move: i32,
26
  }
27
 
28
  #[pymethods]
 
37
  #[staticmethod]
38
  pub fn default() -> Self {
39
  let board = BitBoard::default();
40
+ let state = Self::compute_state(&board, 0, -1);
41
  Self {
42
  board,
43
  current_player: 0,
 
53
  pub fn pass_move(&mut self) -> State {
54
  self.board = self.board.pass_move();
55
  self.current_player = 1 - self.current_player;
56
+ self.state = Self::compute_state(&self.board, self.current_player, -1);
57
  self.state.clone()
58
  }
59
 
 
61
  let next = self.board.make_move(place).unwrap();
62
  self.current_player = 1 - self.current_player;
63
  self.board = next;
64
+ self.state = Self::compute_state(&self.board, self.current_player, place as i32);
65
  self.state.clone()
66
  }
67
 
 
92
  }
93
 
94
  impl Game {
95
+ fn compute_state(board: &BitBoard, current_player: usize, last_move: i32) -> State {
96
  let (cnt0, cnt1) = board.count();
97
  let moves = board.available_moves();
98
  let cells: Vec<_> = (0..64)
 
116
  white_score: if player == 'W' { cnt0 } else { cnt1 },
117
  cells,
118
  can_move: moves != 0,
119
+ last_move,
120
  }
121
  }
122
  }
 
148
  let b = BitBoard(2, 1);
149
  let mut g = Game {
150
  current_player: 0,
151
+ state: Game::compute_state(&b, 0, -1),
152
  board: b,
153
  };
154
  assert_eq!(g.state.can_move, false);