diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 67e365e4..8fcd6251 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -2,10 +2,7 @@ use std::ops::Deref; use turtle::Color; -/// (Row, Column) -pub type Position = (usize, usize); - -type Tiles = [[Option; 8]; 8]; +use tiles::{Tiles, Position}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Piece { @@ -36,11 +33,11 @@ impl Piece { } } -fn valid_moves_for(tiles: Tiles, piece: Piece) -> Vec { +fn valid_moves_for(tiles: &Tiles, piece: Piece) -> Vec { Default::default() //TODO } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub struct Board { current: Piece, /// None - empty tile @@ -68,7 +65,7 @@ impl Board { tiles[4][3] = Some(Piece::B); tiles[4][4] = Some(Piece::A); let current = Piece::A; - let valid_moves = valid_moves_for(tiles, current); + let valid_moves = valid_moves_for(&tiles, current); Self { current, diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index 07aa145f..aa0d7376 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -5,6 +5,7 @@ extern crate turtle; mod board; +mod tiles; use std::f64::consts::PI; @@ -33,8 +34,8 @@ fn main() { let width = 580.0; let height = 580.0; let board = Board::new(); - let rows = board.len(); - let cols = board[0].len(); + let rows = board.col_size(); + let cols = board.row_size(); // These values are used quite often, so it makes sense to compute them in advance so that // we don't need to keep repeating ourselves @@ -47,6 +48,7 @@ fn main() { tile_height: height / rows as f64, }; + turtle.set_speed("instant"); //TODO: Remove this line turtle.pen_up(); turtle.forward(height / 2.0); turtle.right(90.0); @@ -87,8 +89,8 @@ fn draw_board(turtle: &mut Turtle, dim: &Dimensions) { fn draw_board_pieces(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { // Draw starting pieces - for (row, row_pieces) in board.iter().enumerate() { - for (col, piece) in row_pieces.iter().enumerate() { + for (row, row_pieces) in board.rows().enumerate() { + for (col, piece) in row_pieces.enumerate() { if let &Some(piece) = piece { move_to_tile(turtle, (row, col), &dim); draw_piece(turtle, piece, &dim); @@ -117,7 +119,6 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { let row = ((1.0 - (mouse[1] + dim.height/2.0) / dim.height) * dim.rows as f64).floor() as isize; let col = ((mouse[0] + dim.width/2.0) / dim.width * dim.cols as f64).floor() as isize; - println!("Play {:?}", (row, col)); if row >= 0 && row < dim.rows as isize && col >= 0 && col < dim.cols as isize && board.is_valid_move(&(row as usize, col as usize)) { diff --git a/examples/reversi/tiles.rs b/examples/reversi/tiles.rs new file mode 100644 index 00000000..096276a9 --- /dev/null +++ b/examples/reversi/tiles.rs @@ -0,0 +1,229 @@ +use std::ops::{Index, IndexMut}; + +use board::Piece; + +const SIZE: usize = 8; + +/// (Row, Column) +pub type Position = (usize, usize); + +/// A row or column of pieces +pub type Pieces = [Option; SIZE]; + +/// Used to determine whether to iterate over rows or columns +#[derive(Clone, Copy)] +enum TilesIterTarget { + Rows, + Columns, + DiagonalsTLBR, + DiagonalsTRBL, +} + +pub struct TilesIter<'a> { + grid: &'a [Pieces; SIZE], + target: TilesIterTarget, + current: usize, + end: usize, +} + +impl<'a> TilesIter<'a> { + fn new(grid: &'a [Pieces; SIZE], target: TilesIterTarget) -> Self { + Self { + grid, + target, + current: 0, + end: match target { + TilesIterTarget::Rows => grid.len(), + TilesIterTarget::Columns => grid[0].len(), + TilesIterTarget::DiagonalsTLBR => unimplemented!(), + TilesIterTarget::DiagonalsTRBL => unimplemented!(), + }, + } + } +} + +impl<'a> Iterator for TilesIter<'a> { + type Item = TilesPieceIter<'a>; + + fn next(&mut self) -> Option { + if self.current >= self.end { + return None; + } + + let iter = TilesPieceIter::new( + self.grid, + self.target, + self.current + ); + + self.current += 1; + Some(iter) + } +} + +impl<'a> DoubleEndedIterator for TilesIter<'a> { + fn next_back(&mut self) -> Option { + if self.current >= self.end { + return None; + } + + let iter = TilesPieceIter::new( + self.grid, + self.target, + self.end - 1 + ); + + self.end -= 1; + Some(iter) + } +} + +pub struct TilesPieceIter<'a> { + grid: &'a [Pieces; SIZE], + target: TilesIterTarget, + /// The row or column being iterated over + index: usize, + /// The current position in the row or column being iterated over + current: usize, + // Index of the item just after the last item in the row or column being iterated over + end: usize, +} + +impl<'a> TilesPieceIter<'a> { + fn new(grid: &'a [Pieces; SIZE], target: TilesIterTarget, index: usize) -> Self { + Self { + grid, + target, + index, + current: 0, + end: match target { + TilesIterTarget::Rows => grid[0].len(), + TilesIterTarget::Columns => grid.len(), + TilesIterTarget::DiagonalsTLBR => unimplemented!(), + TilesIterTarget::DiagonalsTRBL => unimplemented!(), + }, + } + } + + fn get(&self, current: usize) -> &'a Option { + match self.target { + TilesIterTarget::Rows => { + &self.grid[self.index][current] + }, + TilesIterTarget::Columns => { + &self.grid[current][self.index] + }, + TilesIterTarget::DiagonalsTLBR => unimplemented!(), + TilesIterTarget::DiagonalsTRBL => unimplemented!(), + } + } +} + +impl<'a> Iterator for TilesPieceIter<'a> { + type Item = &'a Option; + + fn next(&mut self) -> Option { + if self.current >= self.end { + return None; + } + + let cell = self.get(self.current); + + self.current += 1; + Some(cell) + } +} + +impl<'a> DoubleEndedIterator for TilesPieceIter<'a> { + fn next_back(&mut self) -> Option { + if self.current >= self.end { + return None; + } + + let cell = self.get(self.end - 1); + + self.end -= 1; + Some(cell) + } +} + +#[derive(Debug, Default)] +pub struct Tiles([Pieces; SIZE]); + +impl Index for Tiles { + type Output = Pieces; + + fn index(&self, index: usize) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for Tiles { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.0.index_mut(index) + } +} + +impl Tiles { + /// Returns the positions of adjacent pieces + pub fn adjacent_pieces(&self, (row, col): Position) -> Vec { + let row = row as isize; + let col = col as isize; + let test: [(isize, isize); 8] = [ + (row-1, col-1), + (row-1, col), + (row-1, col+1), + (row, col-1), + (row, col+1), + (row+1, col-1), + (row+1, col), + (row+1, col+1), + ]; + + test.into_iter().filter_map(|&pos| { + self.get(pos).map(|_| (pos.0 as usize, pos.1 as usize)) + }).collect() + } + + /// Gets the piece at the given position, if there is any + pub fn get(&self, (row, col): (isize, isize)) -> Option { + if row > 0 && col > 0 { + *self.0.get(row as usize) + .map(|r| r.get(col as usize).unwrap_or(&None)) + .unwrap_or(&None) + } + else { + None + } + } + + /// Returns the size of each row + pub fn row_size(&self) -> usize { + self.0[0].len() + } + + /// Returns the size of each column + pub fn col_size(&self) -> usize { + self.0.len() + } + + /// Returns an iterator over each row + pub fn rows(&self) -> TilesIter { + TilesIter::new(&self.0, TilesIterTarget::Rows) + } + + /// Returns an iterator over each row + pub fn cols(&self) -> TilesIter { + TilesIter::new(&self.0, TilesIterTarget::Columns) + } + + /// Returns an iterator over each top-left to bottom-right diagonal + pub fn diagonals_tlbr(&self) -> TilesIter { + TilesIter::new(&self.0, TilesIterTarget::DiagonalsTLBR) + } + + /// Returns an iterator over each top-right to bottom-left diagonal + pub fn diagonals_trbl(&self) -> TilesIter { + TilesIter::new(&self.0, TilesIterTarget::DiagonalsTRBL) + } +}