Skip to content

Commit 78c061d

Browse files
committed
Finding valid move positions
1 parent 6b56c38 commit 78c061d

File tree

1 file changed

+90
-14
lines changed

1 file changed

+90
-14
lines changed

examples/reversi/board.rs

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::ops::Deref;
2+
use std::collections::HashSet;
23

34
use turtle::Color;
45

@@ -36,10 +37,6 @@ impl Piece {
3637
}
3738
}
3839

39-
fn valid_moves_for(tiles: Tiles, piece: Piece) -> Vec<Position> {
40-
Default::default() //TODO
41-
}
42-
4340
#[derive(Debug, Clone, PartialEq, Eq)]
4441
pub struct Board {
4542
current: Piece,
@@ -49,7 +46,7 @@ pub struct Board {
4946
///
5047
/// Each array in Board is a row of the board
5148
tiles: Tiles,
52-
valid_moves: Vec<Position>
49+
valid_moves: HashSet<Position>
5350
}
5451

5552
impl Deref for Board {
@@ -68,23 +65,20 @@ impl Board {
6865
tiles[4][3] = Some(Piece::B);
6966
tiles[4][4] = Some(Piece::A);
7067
let current = Piece::A;
71-
let valid_moves = valid_moves_for(tiles, current);
7268

73-
Self {
69+
let mut board = Self {
7470
current,
7571
tiles,
76-
valid_moves,
77-
}
72+
valid_moves: HashSet::new(),
73+
};
74+
board.update_valid_moves(current);
75+
board
7876
}
7977

8078
pub fn current(&self) -> Piece {
8179
self.current
8280
}
8381

84-
pub fn valid_moves(&self) -> &[Position] {
85-
&self.valid_moves
86-
}
87-
8882
pub fn is_valid_move(&self, position: &Position) -> bool {
8983
self.valid_moves.contains(position)
9084
}
@@ -95,8 +89,12 @@ impl Board {
9589
self.tiles[pos.0][pos.1] = Some(self.current);
9690
self.flip_tiles(pos);
9791

98-
self.valid_moves = vec![]; //TODO
9992
self.current = self.current.other();
93+
94+
//TODO: When nested method calls are enabled, this can be done in one line
95+
// Link: https://github.com/rust-lang/rust/issues/44100
96+
let current = self.current;
97+
self.update_valid_moves(current);
10098
}
10199
else {
102100
unreachable!("Game should check for whether a valid move was used before playing it");
@@ -106,4 +104,82 @@ impl Board {
106104
fn flip_tiles(&mut self, start: Position) {
107105
unimplemented!()
108106
}
107+
108+
fn update_valid_moves(&mut self, piece: Piece) {
109+
self.valid_moves.clear();
110+
111+
// Explanation: A valid move is an empty tile which has `piece` in a vertical, horizontal,
112+
// or diagonal line from it with only `piece.other()` between the empty tile and piece.
113+
// Example: E = empty, p = piece, o = other piece
114+
// A B C D E F G H I J K
115+
// E E o o o p o p p E o
116+
// Tile A is *not* a valid move. Tile B is a valid move for p. None of the other tiles are
117+
// valid moves for p.
118+
// Algorithm: For each empty tile, look for at least one adjacent `other` piece. If one is
119+
// found, look for another `piece` in that direction that isn't preceeded by an empty tile.
120+
121+
let other = piece.other();
122+
for (i, row) in self.tiles.iter().enumerate() {
123+
for (j, tile) in row.iter().enumerate() {
124+
// Only empty tiles can be valid moves
125+
if tile.is_some() {
126+
continue;
127+
}
128+
129+
for (row, col) in self.adjacent_positions((i, j)) {
130+
// Look for at least one `other` tile before finding `piece`
131+
if self.tiles[row][col] == Some(other)
132+
&& self.find_piece((i, j), (row, col), piece) {
133+
self.valid_moves.insert((i, j));
134+
// Don't want to keep searching this tile now that we've added it
135+
break;
136+
}
137+
}
138+
}
139+
}
140+
141+
// We need to shrink to fit because clear does not reduce the capacity and we do not want
142+
// to leak memory by allowing the valid_moves Vec to grow uncontrollably
143+
self.valid_moves.shrink_to_fit();
144+
}
145+
146+
/// Searches in the direction of the given target starting from the target. Returns true if it
147+
/// finds piece AND only encounters piece.other() along the way.
148+
fn find_piece(&self, pos: Position, (target_row, target_col): Position, piece: Piece) -> bool {
149+
let other = piece.other();
150+
151+
let delta_row = target_row as isize - pos.0 as isize;
152+
let delta_col = target_col as isize - pos.1 as isize;
153+
154+
let mut curr_row = target_row as isize + delta_row;
155+
let mut curr_col = target_col as isize + delta_col;
156+
while curr_row >= 0 && curr_row < self.tiles.len() as isize
157+
&& curr_col >= 0 && curr_col < self.tiles[0].len() as isize {
158+
let current = self.tiles[curr_row as usize][curr_col as usize];
159+
curr_row = curr_row + delta_row;
160+
curr_col = curr_col + delta_col;
161+
if current == Some(other) {
162+
continue;
163+
}
164+
else if current == Some(piece) {
165+
return true;
166+
}
167+
else {
168+
return false;
169+
}
170+
}
171+
return false;
172+
}
173+
174+
//TODO: Replace return type with `impl Iterator<Item=(usize, usize)>` when the "impl Trait"
175+
// feature is stable.
176+
fn adjacent_positions(&self, (row, col): Position) -> Vec<Position> {
177+
let rows = self.tiles.len();
178+
let cols = self.tiles[0].len();
179+
[(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)].into_iter()
180+
.map(|&(r, c)| (row as isize + r, col as isize + c))
181+
.filter(|&(r, c)| r >= 0 && c >= 0 && r < rows as isize && c < cols as isize)
182+
.map(|(r, c)| (r as usize, c as usize))
183+
.collect()
184+
}
109185
}

0 commit comments

Comments
 (0)