11use std:: ops:: Deref ;
2+ use std:: collections:: HashSet ;
23
34use 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 ) ]
4441pub 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
5552impl 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