1
1
use std:: ops:: Deref ;
2
+ use std:: collections:: HashSet ;
2
3
3
4
use turtle:: Color ;
4
5
@@ -36,10 +37,6 @@ impl Piece {
36
37
}
37
38
}
38
39
39
- fn valid_moves_for ( tiles : Tiles , piece : Piece ) -> Vec < Position > {
40
- Default :: default ( ) //TODO
41
- }
42
-
43
40
#[ derive( Debug , Clone , PartialEq , Eq ) ]
44
41
pub struct Board {
45
42
current : Piece ,
@@ -49,7 +46,7 @@ pub struct Board {
49
46
///
50
47
/// Each array in Board is a row of the board
51
48
tiles : Tiles ,
52
- valid_moves : Vec < Position >
49
+ valid_moves : HashSet < Position >
53
50
}
54
51
55
52
impl Deref for Board {
@@ -68,23 +65,20 @@ impl Board {
68
65
tiles[ 4 ] [ 3 ] = Some ( Piece :: B ) ;
69
66
tiles[ 4 ] [ 4 ] = Some ( Piece :: A ) ;
70
67
let current = Piece :: A ;
71
- let valid_moves = valid_moves_for ( tiles, current) ;
72
68
73
- Self {
69
+ let mut board = Self {
74
70
current,
75
71
tiles,
76
- valid_moves,
77
- }
72
+ valid_moves : HashSet :: new ( ) ,
73
+ } ;
74
+ board. update_valid_moves ( current) ;
75
+ board
78
76
}
79
77
80
78
pub fn current ( & self ) -> Piece {
81
79
self . current
82
80
}
83
81
84
- pub fn valid_moves ( & self ) -> & [ Position ] {
85
- & self . valid_moves
86
- }
87
-
88
82
pub fn is_valid_move ( & self , position : & Position ) -> bool {
89
83
self . valid_moves . contains ( position)
90
84
}
@@ -95,8 +89,12 @@ impl Board {
95
89
self . tiles [ pos. 0 ] [ pos. 1 ] = Some ( self . current ) ;
96
90
self . flip_tiles ( pos) ;
97
91
98
- self . valid_moves = vec ! [ ] ; //TODO
99
92
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) ;
100
98
}
101
99
else {
102
100
unreachable ! ( "Game should check for whether a valid move was used before playing it" ) ;
@@ -106,4 +104,82 @@ impl Board {
106
104
fn flip_tiles ( & mut self , start : Position ) {
107
105
unimplemented ! ( )
108
106
}
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
+ }
109
185
}
0 commit comments