From 4fd2f49fed0ce47df74e57df4995cc534b5f5aaf Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Fri, 8 Dec 2017 20:15:06 -0500 Subject: [PATCH 01/13] Started to work on reversi example --- examples/reversi.rs | 196 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 examples/reversi.rs diff --git a/examples/reversi.rs b/examples/reversi.rs new file mode 100644 index 00000000..272e4b49 --- /dev/null +++ b/examples/reversi.rs @@ -0,0 +1,196 @@ +//! Reversi +//! +//! https://en.wikipedia.org/wiki/Reversi + +extern crate turtle; + +use std::f64::consts::PI; + +use turtle::{Turtle, Event, Color}; +use turtle::event::{MouseButton}; + +/// None - empty tile +/// Some(Piece::A) - occupied by piece A +/// Some(Piece::B) - occupied by piece B +/// +/// Each array in Board is a row of the board +type Board = [[Option; 8]; 8]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Piece { + A, + B, +} + +impl Piece { + fn other(self) -> Self { + match self { + Piece::A => Piece::B, + Piece::B => Piece::A, + } + } + + fn color(self) -> Color { + match self { + Piece::A => "#f44336".into(), + Piece::B => "#2196F3".into(), + } + } +} + +#[derive(Debug, Clone)] +struct Dimensions { + pub width: f64, + pub height: f64, + pub rows: usize, + pub cols: usize, + pub tile_width: f64, + pub tile_height: f64, +} + +fn main() { + let mut turtle = Turtle::new(); + turtle.set_background_color("#B3E5FC"); + turtle.set_pen_color("#757575"); + turtle.set_pen_size(2.0); + turtle.set_speed(8); + + let width = 580.0; + let height = 580.0; + let board: Board = Default::default(); + let rows = board.len(); + let cols = board[0].len(); + + // 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 + let dim = Dimensions { + width, + height, + rows, + cols, + tile_width: width / cols as f64, + tile_height: height / rows as f64, + }; + + turtle.pen_up(); + turtle.forward(height / 2.0); + turtle.right(90.0); + turtle.backward(width / 2.0); + turtle.pen_down(); + + println!("Drawing the board...\n"); + draw_board(&mut turtle, &dim); + // Get rid of any events that may have accumulated while drawing + drain_events(&mut turtle); + + play_game(&mut turtle, board, &dim); +} + +fn draw_board(turtle: &mut Turtle, dim: &Dimensions) { + turtle.forward(dim.width); + for i in 0..dim.rows { + turtle.right((i % 2) as f64 * -180.0 + 90.0); + turtle.pen_up(); + turtle.forward(dim.height / dim.rows as f64); + turtle.pen_down(); + turtle.right((i % 2) as f64 * -180.0 + 90.0); + turtle.forward(dim.width); + } + + turtle.left(90.0); + turtle.forward(dim.height); + for i in 0..dim.cols { + turtle.left((i % 2) as f64 * -180.0 + 90.0); + turtle.pen_up(); + turtle.forward(dim.width / dim.cols as f64); + turtle.pen_down(); + turtle.left((i % 2) as f64 * -180.0 + 90.0); + turtle.forward(dim.height); + } +} + +fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { + println!("Click on a tile to make a move."); + turtle.set_speed(9); + + let mut mouse = [0.0, 0.0]; + let mut current = Piece::A; + loop { + let event = turtle.poll_event(); + // Sometimes it is more convenient to use `if let` instead of `match`. In this case, it's + // really up to your personal preference. We chose to demonstrate what `if let` would look + // like if used for this code. + if let Some(Event::MouseMove {x, y}) = event { + mouse = [x, y]; + } + else if let Some(Event::MouseButtonReleased(MouseButton::Left)) = event { + // Figure out which row and column was clicked + // If these formulas seem unclear, try some example values to see what you get + 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; + + if row >= 0 && row < dim.rows as isize && col >= 0 && col < dim.cols as isize + && board[row as usize][col as usize].is_none() { + let row = row as usize; + let col = col as usize; + board[row][col] = Some(current); + //TODO: Implement rules checking, winning, etc. + + move_to_tile(turtle, row, col, &dim); + draw_piece(turtle, current, &dim); + current = current.other(); + + // Get rid of any events that may have accumulated while drawing + drain_events(turtle); + } + } + } +} + +/// Moves to the center of the given tile +fn move_to_tile(turtle: &mut Turtle, row: usize, col: usize, dim: &Dimensions) { + let x = col as f64 / dim.cols as f64 * dim.width + dim.tile_width / 2.0 - dim.width / 2.0; + let y = -(row as f64) / dim.rows as f64 * dim.height - dim.tile_height / 2.0 + dim.height / 2.0; + + turtle.pen_up(); + + turtle.turn_towards([x, y]); + turtle.go_to([x, y]); + turtle.set_heading(90.0); + + turtle.pen_down(); +} + +/// Draws the given piece +fn draw_piece(turtle: &mut Turtle, piece: Piece, dim: &Dimensions) { + let radius = dim.tile_width.min(dim.tile_height) / 2.0 * 0.9; + + turtle.set_fill_color(piece.color()); + turtle.pen_up(); + turtle.begin_fill(); + + turtle.forward(radius); + turtle.right(90.0); + circle(turtle, radius); + + turtle.end_fill(); + turtle.pen_down(); +} + +fn circle(turtle: &mut Turtle, radius: f64) { + let degrees = 180.0; + + let circumference = 2.0*PI*radius; + let step = circumference / degrees; + let rotation = 360.0 / degrees; + + for _ in 0..degrees as i32 { + turtle.forward(step); + turtle.right(rotation); + } +} + +/// Clear out all events that may have accumulated +fn drain_events(turtle: &mut Turtle) { + while let Some(_) = turtle.poll_event() {} +} From d476892b75c1e49629bf80432ab9ac76683e3563 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Sat, 16 Dec 2017 20:29:47 -0500 Subject: [PATCH 02/13] Refactored reversi example into separate modules --- examples/reversi/board.rs | 30 +++++++++++++++++++++ examples/{reversi.rs => reversi/main.rs} | 33 +++--------------------- 2 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 examples/reversi/board.rs rename examples/{reversi.rs => reversi/main.rs} (89%) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs new file mode 100644 index 00000000..5f12d33c --- /dev/null +++ b/examples/reversi/board.rs @@ -0,0 +1,30 @@ +use turtle::Color; + +/// None - empty tile +/// Some(Piece::A) - occupied by piece A +/// Some(Piece::B) - occupied by piece B +/// +/// Each array in Board is a row of the board +pub type Board = [[Option; 8]; 8]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Piece { + A, + B, +} + +impl Piece { + pub fn other(self) -> Self { + match self { + Piece::A => Piece::B, + Piece::B => Piece::A, + } + } + + pub fn color(self) -> Color { + match self { + Piece::A => "#f44336".into(), + Piece::B => "#2196F3".into(), + } + } +} diff --git a/examples/reversi.rs b/examples/reversi/main.rs similarity index 89% rename from examples/reversi.rs rename to examples/reversi/main.rs index 272e4b49..6ba798bb 100644 --- a/examples/reversi.rs +++ b/examples/reversi/main.rs @@ -4,39 +4,14 @@ extern crate turtle; +mod board; + use std::f64::consts::PI; -use turtle::{Turtle, Event, Color}; +use turtle::{Turtle, Event}; use turtle::event::{MouseButton}; -/// None - empty tile -/// Some(Piece::A) - occupied by piece A -/// Some(Piece::B) - occupied by piece B -/// -/// Each array in Board is a row of the board -type Board = [[Option; 8]; 8]; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Piece { - A, - B, -} - -impl Piece { - fn other(self) -> Self { - match self { - Piece::A => Piece::B, - Piece::B => Piece::A, - } - } - - fn color(self) -> Color { - match self { - Piece::A => "#f44336".into(), - Piece::B => "#2196F3".into(), - } - } -} +use board::{Board, Piece}; #[derive(Debug, Clone)] struct Dimensions { From 2ceec96dc0554ad9c257d6bb83cb7ad47d7aa940 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Sat, 16 Dec 2017 21:13:25 -0500 Subject: [PATCH 03/13] Refactoring board representation and drawing initial pieces on the screen --- examples/reversi/board.rs | 91 ++++++++++++++++++++++++++++++++++++--- examples/reversi/main.rs | 35 ++++++++++----- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 5f12d33c..67e365e4 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -1,11 +1,11 @@ +use std::ops::Deref; + use turtle::Color; -/// None - empty tile -/// Some(Piece::A) - occupied by piece A -/// Some(Piece::B) - occupied by piece B -/// -/// Each array in Board is a row of the board -pub type Board = [[Option; 8]; 8]; +/// (Row, Column) +pub type Position = (usize, usize); + +type Tiles = [[Option; 8]; 8]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Piece { @@ -14,6 +14,13 @@ pub enum Piece { } impl Piece { + pub fn name(self) -> &'static str { + match self { + Piece::A => "red", + Piece::B => "blue", + } + } + pub fn other(self) -> Self { match self { Piece::A => Piece::B, @@ -28,3 +35,75 @@ impl Piece { } } } + +fn valid_moves_for(tiles: Tiles, piece: Piece) -> Vec { + Default::default() //TODO +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Board { + current: Piece, + /// None - empty tile + /// Some(Piece::A) - occupied by piece A + /// Some(Piece::B) - occupied by piece B + /// + /// Each array in Board is a row of the board + tiles: Tiles, + valid_moves: Vec +} + +impl Deref for Board { + type Target = Tiles; + + fn deref(&self) -> &Self::Target { + &self.tiles + } +} + +impl Board { + pub fn new() -> Self { + let mut tiles: Tiles = Default::default(); + tiles[3][3] = Some(Piece::A); + tiles[3][4] = Some(Piece::B); + tiles[4][3] = Some(Piece::B); + tiles[4][4] = Some(Piece::A); + let current = Piece::A; + let valid_moves = valid_moves_for(tiles, current); + + Self { + current, + tiles, + valid_moves, + } + } + + pub fn current(&self) -> Piece { + self.current + } + + pub fn valid_moves(&self) -> &[Position] { + &self.valid_moves + } + + pub fn is_valid_move(&self, position: &Position) -> bool { + self.valid_moves.contains(position) + } + + pub fn play_piece(&mut self, pos: Position) { + if self.is_valid_move(&pos) { + assert!(self[pos.0][pos.1].is_none(), "Valid move was not an empty tile!"); + self.tiles[pos.0][pos.1] = Some(self.current); + self.flip_tiles(pos); + + self.valid_moves = vec![]; //TODO + self.current = self.current.other(); + } + else { + unreachable!("Game should check for whether a valid move was used before playing it"); + } + } + + fn flip_tiles(&mut self, start: Position) { + unimplemented!() + } +} diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index 6ba798bb..07aa145f 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -32,7 +32,7 @@ fn main() { let width = 580.0; let height = 580.0; - let board: Board = Default::default(); + let board = Board::new(); let rows = board.len(); let cols = board[0].len(); @@ -55,6 +55,7 @@ fn main() { println!("Drawing the board...\n"); draw_board(&mut turtle, &dim); + draw_board_pieces(&mut turtle, &board, &dim); // Get rid of any events that may have accumulated while drawing drain_events(&mut turtle); @@ -84,12 +85,24 @@ 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() { + if let &Some(piece) = piece { + move_to_tile(turtle, (row, col), &dim); + draw_piece(turtle, piece, &dim); + } + } + } +} + fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { println!("Click on a tile to make a move."); + println!("Current Player: {}", board.current().name()); turtle.set_speed(9); let mut mouse = [0.0, 0.0]; - let mut current = Piece::A; loop { let event = turtle.poll_event(); // Sometimes it is more convenient to use `if let` instead of `match`. In this case, it's @@ -104,16 +117,18 @@ 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; - if row >= 0 && row < dim.rows as isize && col >= 0 && col < dim.cols as isize - && board[row as usize][col as usize].is_none() { + 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)) { let row = row as usize; let col = col as usize; - board[row][col] = Some(current); - //TODO: Implement rules checking, winning, etc. + board.play_piece((row, col)); + + move_to_tile(turtle, (row, col), &dim); + draw_piece(turtle, board.current(), &dim); - move_to_tile(turtle, row, col, &dim); - draw_piece(turtle, current, &dim); - current = current.other(); + println!("Current Player: {}", board.current().name()); // Get rid of any events that may have accumulated while drawing drain_events(turtle); @@ -123,7 +138,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { } /// Moves to the center of the given tile -fn move_to_tile(turtle: &mut Turtle, row: usize, col: usize, dim: &Dimensions) { +fn move_to_tile(turtle: &mut Turtle, (row, col): (usize, usize), dim: &Dimensions) { let x = col as f64 / dim.cols as f64 * dim.width + dim.tile_width / 2.0 - dim.width / 2.0; let y = -(row as f64) / dim.rows as f64 * dim.height - dim.tile_height / 2.0 + dim.height / 2.0; From fa7af3505d560a4c392e0c0e96bb91f5e1f15938 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Sat, 16 Dec 2017 21:52:46 -0500 Subject: [PATCH 04/13] Giving tiles its own two-axis iterator implementation based on the Grid struct in the maze example --- examples/reversi/board.rs | 11 +- examples/reversi/main.rs | 11 +- examples/reversi/tiles.rs | 229 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 examples/reversi/tiles.rs 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) + } +} From d1f6446cc331bb4667292ca4cb6ca9b7c3d2e61c Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 13:17:00 -0700 Subject: [PATCH 05/13] Some more implementation that didn't work out --- examples/reversi/board.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 8fcd6251..fc3ac2ab 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -34,7 +34,26 @@ impl Piece { } fn valid_moves_for(tiles: &Tiles, piece: Piece) -> Vec { - Default::default() //TODO + // ALGORITHM: Go through all rows, columns and diagonals. Look through each row forwards and + // backwards. Find an empty tile followed by the other piece. If you can then find another + // `piece` before finding an empty tile, the empty tile is a valid move. + + let other = piece.other(); + //TODO: Convert each row into an iterator of (pos, tile) + let rows = search_row(tiles.rows().enumerate().map(|(i, r)| r.enumerate().map(|(j, c)| ((i, j), c)))) + .chain(search_row(tiles.cols().enumerate().map(|(j, c)| c.enumerate().map(|(i, r)| ((i, j), c))))) + .chain(tiles.diagonals_tlbr()) + .chain(tiles.diagonals_trbl()) + .collect(); + for pieces in rows { + let potential_move = None; + } +} + +//TODO: Change return type into iterator +fn search_row(row: R) -> Vec + where R: Iterator, + C: Iterator)> { } #[derive(Debug)] From 8b3acd539a7c4fceb8fe8e55ea40781ca84ae150 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 13:18:36 -0700 Subject: [PATCH 06/13] Revert "Some more implementation that didn't work out" This reverts commit 05f07b436232062d5f4d887ae831ec5d98290aaa. --- examples/reversi/board.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index fc3ac2ab..8fcd6251 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -34,26 +34,7 @@ impl Piece { } fn valid_moves_for(tiles: &Tiles, piece: Piece) -> Vec { - // ALGORITHM: Go through all rows, columns and diagonals. Look through each row forwards and - // backwards. Find an empty tile followed by the other piece. If you can then find another - // `piece` before finding an empty tile, the empty tile is a valid move. - - let other = piece.other(); - //TODO: Convert each row into an iterator of (pos, tile) - let rows = search_row(tiles.rows().enumerate().map(|(i, r)| r.enumerate().map(|(j, c)| ((i, j), c)))) - .chain(search_row(tiles.cols().enumerate().map(|(j, c)| c.enumerate().map(|(i, r)| ((i, j), c))))) - .chain(tiles.diagonals_tlbr()) - .chain(tiles.diagonals_trbl()) - .collect(); - for pieces in rows { - let potential_move = None; - } -} - -//TODO: Change return type into iterator -fn search_row(row: R) -> Vec - where R: Iterator, - C: Iterator)> { + Default::default() //TODO } #[derive(Debug)] From 8fa2c5e62eff23d2825e1e3cb527f505cefd4e4b Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 13:18:55 -0700 Subject: [PATCH 07/13] Revert "Giving tiles its own two-axis iterator implementation based on the Grid struct in the maze example" This reverts commit 5bbbc0cefdb17938ab5279c108e5e1ecbb412429. --- examples/reversi/board.rs | 11 +- examples/reversi/main.rs | 11 +- examples/reversi/tiles.rs | 229 -------------------------------------- 3 files changed, 12 insertions(+), 239 deletions(-) delete mode 100644 examples/reversi/tiles.rs diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 8fcd6251..67e365e4 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -2,7 +2,10 @@ use std::ops::Deref; use turtle::Color; -use tiles::{Tiles, Position}; +/// (Row, Column) +pub type Position = (usize, usize); + +type Tiles = [[Option; 8]; 8]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Piece { @@ -33,11 +36,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)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Board { current: Piece, /// None - empty tile @@ -65,7 +68,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 aa0d7376..07aa145f 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -5,7 +5,6 @@ extern crate turtle; mod board; -mod tiles; use std::f64::consts::PI; @@ -34,8 +33,8 @@ fn main() { let width = 580.0; let height = 580.0; let board = Board::new(); - let rows = board.col_size(); - let cols = board.row_size(); + let rows = board.len(); + let cols = board[0].len(); // 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 @@ -48,7 +47,6 @@ 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); @@ -89,8 +87,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.rows().enumerate() { - for (col, piece) in row_pieces.enumerate() { + for (row, row_pieces) in board.iter().enumerate() { + for (col, piece) in row_pieces.iter().enumerate() { if let &Some(piece) = piece { move_to_tile(turtle, (row, col), &dim); draw_piece(turtle, piece, &dim); @@ -119,6 +117,7 @@ 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 deleted file mode 100644 index 096276a9..00000000 --- a/examples/reversi/tiles.rs +++ /dev/null @@ -1,229 +0,0 @@ -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) - } -} From db6f9bc5e4d98e50b842d509d817b60f2e87ddd2 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 14:57:38 -0700 Subject: [PATCH 08/13] Finding valid move positions --- examples/reversi/board.rs | 104 +++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 14 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 67e365e4..084f29e0 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -1,4 +1,5 @@ use std::ops::Deref; +use std::collections::HashSet; use turtle::Color; @@ -36,10 +37,6 @@ impl Piece { } } -fn valid_moves_for(tiles: Tiles, piece: Piece) -> Vec { - Default::default() //TODO -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Board { current: Piece, @@ -49,7 +46,7 @@ pub struct Board { /// /// Each array in Board is a row of the board tiles: Tiles, - valid_moves: Vec + valid_moves: HashSet } impl Deref for Board { @@ -68,23 +65,20 @@ 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); - Self { + let mut board = Self { current, tiles, - valid_moves, - } + valid_moves: HashSet::new(), + }; + board.update_valid_moves(current); + board } pub fn current(&self) -> Piece { self.current } - pub fn valid_moves(&self) -> &[Position] { - &self.valid_moves - } - pub fn is_valid_move(&self, position: &Position) -> bool { self.valid_moves.contains(position) } @@ -95,8 +89,12 @@ impl Board { self.tiles[pos.0][pos.1] = Some(self.current); self.flip_tiles(pos); - self.valid_moves = vec![]; //TODO self.current = self.current.other(); + + //TODO: When nested method calls are enabled, this can be done in one line + // Link: https://github.com/rust-lang/rust/issues/44100 + let current = self.current; + self.update_valid_moves(current); } else { unreachable!("Game should check for whether a valid move was used before playing it"); @@ -106,4 +104,82 @@ impl Board { fn flip_tiles(&mut self, start: Position) { unimplemented!() } + + fn update_valid_moves(&mut self, piece: Piece) { + self.valid_moves.clear(); + + // Explanation: A valid move is an empty tile which has `piece` in a vertical, horizontal, + // or diagonal line from it with only `piece.other()` between the empty tile and piece. + // Example: E = empty, p = piece, o = other piece + // A B C D E F G H I J K + // E E o o o p o p p E o + // Tile A is *not* a valid move. Tile B is a valid move for p. None of the other tiles are + // valid moves for p. + // Algorithm: For each empty tile, look for at least one adjacent `other` piece. If one is + // found, look for another `piece` in that direction that isn't preceeded by an empty tile. + + let other = piece.other(); + for (i, row) in self.tiles.iter().enumerate() { + for (j, tile) in row.iter().enumerate() { + // Only empty tiles can be valid moves + if tile.is_some() { + continue; + } + + for (row, col) in self.adjacent_positions((i, j)) { + // Look for at least one `other` tile before finding `piece` + if self.tiles[row][col] == Some(other) + && self.find_piece((i, j), (row, col), piece) { + self.valid_moves.insert((i, j)); + // Don't want to keep searching this tile now that we've added it + break; + } + } + } + } + + // We need to shrink to fit because clear does not reduce the capacity and we do not want + // to leak memory by allowing the valid_moves Vec to grow uncontrollably + self.valid_moves.shrink_to_fit(); + } + + /// Searches in the direction of the given target starting from the target. Returns true if it + /// finds piece AND only encounters piece.other() along the way. + fn find_piece(&self, pos: Position, (target_row, target_col): Position, piece: Piece) -> bool { + let other = piece.other(); + + let delta_row = target_row as isize - pos.0 as isize; + let delta_col = target_col as isize - pos.1 as isize; + + let mut curr_row = target_row as isize + delta_row; + let mut curr_col = target_col as isize + delta_col; + while curr_row >= 0 && curr_row < self.tiles.len() as isize + && curr_col >= 0 && curr_col < self.tiles[0].len() as isize { + let current = self.tiles[curr_row as usize][curr_col as usize]; + curr_row = curr_row + delta_row; + curr_col = curr_col + delta_col; + if current == Some(other) { + continue; + } + else if current == Some(piece) { + return true; + } + else { + return false; + } + } + return false; + } + + //TODO: Replace return type with `impl Iterator` when the "impl Trait" + // feature is stable. + fn adjacent_positions(&self, (row, col): Position) -> Vec { + let rows = self.tiles.len(); + let cols = self.tiles[0].len(); + [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)].into_iter() + .map(|&(r, c)| (row as isize + r, col as isize + c)) + .filter(|&(r, c)| r >= 0 && c >= 0 && r < rows as isize && c < cols as isize) + .map(|(r, c)| (r as usize, c as usize)) + .collect() + } } From 1d0b9e6c89225cd23aeceb01ddb23f48355b0463 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 15:33:14 -0700 Subject: [PATCH 09/13] Drawing indicators for valid moves --- examples/reversi/board.rs | 6 +++- examples/reversi/main.rs | 59 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 084f29e0..3d22f89f 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -79,6 +79,10 @@ impl Board { self.current } + pub fn valid_moves(&self) -> &HashSet { + &self.valid_moves + } + pub fn is_valid_move(&self, position: &Position) -> bool { self.valid_moves.contains(position) } @@ -171,7 +175,7 @@ impl Board { return false; } - //TODO: Replace return type with `impl Iterator` when the "impl Trait" + //TODO: Replace return type with `impl Iterator` when the "impl Trait" // feature is stable. fn adjacent_positions(&self, (row, col): Position) -> Vec { let rows = self.tiles.len(); diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index 07aa145f..ce0d05b1 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -8,7 +8,7 @@ mod board; use std::f64::consts::PI; -use turtle::{Turtle, Event}; +use turtle::{Turtle, Event, Color}; use turtle::event::{MouseButton}; use board::{Board, Piece}; @@ -56,6 +56,8 @@ fn main() { println!("Drawing the board...\n"); draw_board(&mut turtle, &dim); draw_board_pieces(&mut turtle, &board, &dim); + draw_valid_moves(&mut turtle, &board, &dim); + // Get rid of any events that may have accumulated while drawing drain_events(&mut turtle); @@ -117,16 +119,18 @@ 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)) { let row = row as usize; let col = col as usize; + erase_valid_moves(turtle, &board, dim); + board.play_piece((row, col)); move_to_tile(turtle, (row, col), &dim); draw_piece(turtle, board.current(), &dim); + draw_valid_moves(turtle, &board, dim); println!("Current Player: {}", board.current().name()); @@ -151,11 +155,58 @@ fn move_to_tile(turtle: &mut Turtle, (row, col): (usize, usize), dim: &Dimension turtle.pen_down(); } +fn erase_valid_moves(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { + let background = turtle.background_color(); + draw_tile_circles( + turtle, + 0.5, + background, + dim, + board.valid_moves().iter(), + ); +} + +fn draw_valid_moves(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { + draw_tile_circles( + turtle, + 0.2, + board.current().color().with_alpha(0.8), + dim, + board.valid_moves().iter(), + ); +} + +fn draw_tile_circles<'a, T: Iterator>( + turtle: &mut Turtle, + relative_size: f64, + fill: Color, + dim: &Dimensions, + tiles: T, +) { + let speed = turtle.speed(); + turtle.set_speed("instant"); + for pos in tiles { + move_to_tile(turtle, *pos, &dim); + tile_circle(turtle, relative_size, fill, dim); + } + turtle.set_speed(speed); +} + /// Draws the given piece fn draw_piece(turtle: &mut Turtle, piece: Piece, dim: &Dimensions) { - let radius = dim.tile_width.min(dim.tile_height) / 2.0 * 0.9; + turtle.show(); + tile_circle(turtle, 0.8, piece.color(), dim); + turtle.hide(); +} + +fn tile_circle(turtle: &mut Turtle, relative_size: f64, fill: Color, dim: &Dimensions) { + let radius = dim.tile_width.min(dim.tile_height) / 2.0 * relative_size; + + filled_circle(turtle, radius, fill); +} - turtle.set_fill_color(piece.color()); +fn filled_circle(turtle: &mut Turtle, radius: f64, fill: Color) { + turtle.set_fill_color(fill); turtle.pen_up(); turtle.begin_fill(); From 4456b393a074ce275cc19b48c4e626fe1e023b03 Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 16:26:19 -0700 Subject: [PATCH 10/13] Implementing tile flipping --- examples/reversi/board.rs | 45 ++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 3d22f89f..47a07ed2 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -87,11 +87,12 @@ impl Board { self.valid_moves.contains(position) } - pub fn play_piece(&mut self, pos: Position) { + /// Returns the tiles that were flipped + pub fn play_piece(&mut self, pos: Position) -> Vec { if self.is_valid_move(&pos) { assert!(self[pos.0][pos.1].is_none(), "Valid move was not an empty tile!"); self.tiles[pos.0][pos.1] = Some(self.current); - self.flip_tiles(pos); + let flipped = self.flip_tiles(pos); self.current = self.current.other(); @@ -99,14 +100,41 @@ impl Board { // Link: https://github.com/rust-lang/rust/issues/44100 let current = self.current; self.update_valid_moves(current); + flipped } else { unreachable!("Game should check for whether a valid move was used before playing it"); } } - fn flip_tiles(&mut self, start: Position) { - unimplemented!() + fn flip_tiles(&mut self, (row, col): Position) -> Vec { + let piece = self.current; + assert_eq!(self.tiles[row][col], Some(piece)); + let other = piece.other(); + let rows = self.tiles.len() as isize; + let cols = self.tiles[0].len() as isize; + + let mut flipped = Vec::new(); + for (adj_row, adj_col) in self.adjacent_positions((row, col)) { + if self.tiles[adj_row][adj_col] == Some(other) + && self.find_piece((row, col), (adj_row, adj_col), piece) { + // Perform flips + let delta_row = adj_row as isize - row as isize; + let delta_col = adj_col as isize - col as isize; + let mut curr_row = adj_row as isize; + let mut curr_col = adj_col as isize; + while curr_row >= 0 && curr_row < rows && curr_col >= 0 && curr_col < cols { + let current = &mut self.tiles[curr_row as usize][curr_col as usize]; + if *current == Some(other) { + *current = Some(piece); + flipped.push((curr_row as usize, curr_col as usize)); + } + curr_row += delta_row; + curr_col += delta_col; + } + } + } + flipped } fn update_valid_moves(&mut self, piece: Piece) { @@ -151,17 +179,18 @@ impl Board { /// finds piece AND only encounters piece.other() along the way. fn find_piece(&self, pos: Position, (target_row, target_col): Position, piece: Piece) -> bool { let other = piece.other(); + let rows = self.tiles.len() as isize; + let cols = self.tiles[0].len() as isize; let delta_row = target_row as isize - pos.0 as isize; let delta_col = target_col as isize - pos.1 as isize; let mut curr_row = target_row as isize + delta_row; let mut curr_col = target_col as isize + delta_col; - while curr_row >= 0 && curr_row < self.tiles.len() as isize - && curr_col >= 0 && curr_col < self.tiles[0].len() as isize { + while curr_row >= 0 && curr_row < rows && curr_col >= 0 && curr_col < cols { let current = self.tiles[curr_row as usize][curr_col as usize]; - curr_row = curr_row + delta_row; - curr_col = curr_col + delta_col; + curr_row += delta_row; + curr_col += delta_col; if current == Some(other) { continue; } From ebf0c8ed76572ab0c151ed7111fda0f6426b30bc Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Mon, 18 Dec 2017 16:36:47 -0700 Subject: [PATCH 11/13] Drawing flipped tiles --- examples/reversi/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index ce0d05b1..5dd1511d 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -126,10 +126,16 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { let col = col as usize; erase_valid_moves(turtle, &board, dim); - board.play_piece((row, col)); + let current = board.current(); + let flipped = board.play_piece((row, col)); move_to_tile(turtle, (row, col), &dim); - draw_piece(turtle, board.current(), &dim); + draw_piece(turtle, current, &dim); + + let background = turtle.background_color(); + draw_tile_circles(turtle, 0.9, background, dim, flipped.iter()); + draw_tile_circles(turtle, 0.8, current.color(), dim, flipped.iter()); + draw_valid_moves(turtle, &board, dim); println!("Current Player: {}", board.current().name()); From abc3a42da54427f549e97d6bbad331acc02ceb8d Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Thu, 21 Dec 2017 21:52:39 -0700 Subject: [PATCH 12/13] Updated to account for new API changes to poll_event and background color --- examples/reversi/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index 5dd1511d..897057f3 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -25,7 +25,7 @@ struct Dimensions { fn main() { let mut turtle = Turtle::new(); - turtle.set_background_color("#B3E5FC"); + turtle.drawing_mut().set_background_color("#B3E5FC"); turtle.set_pen_color("#757575"); turtle.set_pen_size(2.0); turtle.set_speed(8); @@ -106,7 +106,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { let mut mouse = [0.0, 0.0]; loop { - let event = turtle.poll_event(); + let event = turtle.drawing_mut().poll_event(); // Sometimes it is more convenient to use `if let` instead of `match`. In this case, it's // really up to your personal preference. We chose to demonstrate what `if let` would look // like if used for this code. @@ -132,7 +132,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { move_to_tile(turtle, (row, col), &dim); draw_piece(turtle, current, &dim); - let background = turtle.background_color(); + let background = turtle.drawing().background_color(); draw_tile_circles(turtle, 0.9, background, dim, flipped.iter()); draw_tile_circles(turtle, 0.8, current.color(), dim, flipped.iter()); @@ -162,7 +162,7 @@ fn move_to_tile(turtle: &mut Turtle, (row, col): (usize, usize), dim: &Dimension } fn erase_valid_moves(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { - let background = turtle.background_color(); + let background = turtle.drawing().background_color(); draw_tile_circles( turtle, 0.5, @@ -239,5 +239,5 @@ fn circle(turtle: &mut Turtle, radius: f64) { /// Clear out all events that may have accumulated fn drain_events(turtle: &mut Turtle) { - while let Some(_) = turtle.poll_event() {} + while let Some(_) = turtle.drawing_mut().poll_event() {} } From 544e7c97781ad7a5851a1558a97b3c88fce3112a Mon Sep 17 00:00:00 2001 From: Sunjay Varma Date: Sat, 30 May 2020 19:20:41 -0600 Subject: [PATCH 13/13] Updating reversi example based on latest API changes --- examples/reversi/board.rs | 2 +- examples/reversi/main.rs | 51 +++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/examples/reversi/board.rs b/examples/reversi/board.rs index 47a07ed2..4d534ac9 100644 --- a/examples/reversi/board.rs +++ b/examples/reversi/board.rs @@ -209,7 +209,7 @@ impl Board { fn adjacent_positions(&self, (row, col): Position) -> Vec { let rows = self.tiles.len(); let cols = self.tiles[0].len(); - [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)].into_iter() + [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)].iter() .map(|&(r, c)| (row as isize + r, col as isize + c)) .filter(|&(r, c)| r >= 0 && c >= 0 && r < rows as isize && c < cols as isize) .map(|(r, c)| (r as usize, c as usize)) diff --git a/examples/reversi/main.rs b/examples/reversi/main.rs index 897057f3..8f5c534e 100644 --- a/examples/reversi/main.rs +++ b/examples/reversi/main.rs @@ -2,14 +2,18 @@ //! //! https://en.wikipedia.org/wiki/Reversi +// To run this example, use the command: cargo run --features unstable --example reversi +#[cfg(all(not(feature = "unstable")))] +compile_error!("This example relies on unstable features. Run with `--features unstable`"); + extern crate turtle; mod board; use std::f64::consts::PI; -use turtle::{Turtle, Event, Color}; -use turtle::event::{MouseButton}; +use turtle::{Drawing, Turtle, Point, Color, Event}; +use turtle::event::{MouseButton, PressedState}; use board::{Board, Piece}; @@ -24,11 +28,12 @@ struct Dimensions { } fn main() { - let mut turtle = Turtle::new(); - turtle.drawing_mut().set_background_color("#B3E5FC"); + let mut drawing = Drawing::new(); + let mut turtle = drawing.add_turtle(); + drawing.set_background_color("#B3E5FC"); turtle.set_pen_color("#757575"); turtle.set_pen_size(2.0); - turtle.set_speed(8); + turtle.set_speed(23); let width = 580.0; let height = 580.0; @@ -59,9 +64,9 @@ fn main() { draw_valid_moves(&mut turtle, &board, &dim); // Get rid of any events that may have accumulated while drawing - drain_events(&mut turtle); + drain_events(&mut drawing); - play_game(&mut turtle, board, &dim); + play_game(&mut drawing, &mut turtle, board, &dim); } fn draw_board(turtle: &mut Turtle, dim: &Dimensions) { @@ -99,21 +104,20 @@ fn draw_board_pieces(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { } } -fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { +fn play_game(drawing: &mut Drawing, turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { println!("Click on a tile to make a move."); println!("Current Player: {}", board.current().name()); - turtle.set_speed(9); - let mut mouse = [0.0, 0.0]; + let mut mouse = Point::origin(); loop { - let event = turtle.drawing_mut().poll_event(); + let event = drawing.poll_event(); // Sometimes it is more convenient to use `if let` instead of `match`. In this case, it's // really up to your personal preference. We chose to demonstrate what `if let` would look // like if used for this code. - if let Some(Event::MouseMove {x, y}) = event { - mouse = [x, y]; + if let Some(Event::MouseMove(mouse_pos)) = event { + mouse = mouse_pos; } - else if let Some(Event::MouseButtonReleased(MouseButton::Left)) = event { + else if let Some(Event::MouseButton(MouseButton::LeftButton, PressedState::Released)) = event { // Figure out which row and column was clicked // If these formulas seem unclear, try some example values to see what you get let row = ((1.0 - (mouse[1] + dim.height/2.0) / dim.height) * dim.rows as f64).floor() as isize; @@ -124,7 +128,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { && board.is_valid_move(&(row as usize, col as usize)) { let row = row as usize; let col = col as usize; - erase_valid_moves(turtle, &board, dim); + erase_valid_moves(drawing, turtle, &board, dim); let current = board.current(); let flipped = board.play_piece((row, col)); @@ -132,7 +136,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { move_to_tile(turtle, (row, col), &dim); draw_piece(turtle, current, &dim); - let background = turtle.drawing().background_color(); + let background = drawing.background_color(); draw_tile_circles(turtle, 0.9, background, dim, flipped.iter()); draw_tile_circles(turtle, 0.8, current.color(), dim, flipped.iter()); @@ -141,7 +145,7 @@ fn play_game(turtle: &mut Turtle, mut board: Board, dim: &Dimensions) { println!("Current Player: {}", board.current().name()); // Get rid of any events that may have accumulated while drawing - drain_events(turtle); + drain_events(drawing); } } } @@ -161,8 +165,8 @@ fn move_to_tile(turtle: &mut Turtle, (row, col): (usize, usize), dim: &Dimension turtle.pen_down(); } -fn erase_valid_moves(turtle: &mut Turtle, board: &Board, dim: &Dimensions) { - let background = turtle.drawing().background_color(); +fn erase_valid_moves(drawing: &Drawing, turtle: &mut Turtle, board: &Board, dim: &Dimensions) { + let background = drawing.background_color(); draw_tile_circles( turtle, 0.5, @@ -214,13 +218,14 @@ fn tile_circle(turtle: &mut Turtle, relative_size: f64, fill: Color, dim: &Dimen fn filled_circle(turtle: &mut Turtle, radius: f64, fill: Color) { turtle.set_fill_color(fill); turtle.pen_up(); - turtle.begin_fill(); turtle.forward(radius); turtle.right(90.0); - circle(turtle, radius); + turtle.begin_fill(); + circle(turtle, radius); turtle.end_fill(); + turtle.pen_down(); } @@ -238,6 +243,6 @@ fn circle(turtle: &mut Turtle, radius: f64) { } /// Clear out all events that may have accumulated -fn drain_events(turtle: &mut Turtle) { - while let Some(_) = turtle.drawing_mut().poll_event() {} +fn drain_events(drawing: &mut Drawing) { + while let Some(_) = drawing.poll_event() {} }