diff --git a/board-shared/src/board.rs b/board-shared/src/board.rs index 522afdf..4844622 100644 --- a/board-shared/src/board.rs +++ b/board-shared/src/board.rs @@ -1,3 +1,4 @@ +use crate::position::Position2d; use crate::tile::Tile; pub const BOARD_SIZE: usize = 25; @@ -16,38 +17,38 @@ impl Board { self.tiles[y * BOARD_SIZE + x] = Some(tile); } - pub fn difference(&self, other: &Board) -> Vec<(usize, usize)> { + pub fn difference(&self, other: &Board) -> Vec { let mut diff = Vec::new(); for x in 0..BOARD_SIZE { for y in 0..BOARD_SIZE { if self.get(x, y) != other.get(x, y) { - diff.push((x, y)); + diff.push(Position2d::new(x, y)); } } } diff } - pub fn is_contiguous(positions: &[(usize, usize)]) -> Option { + pub fn is_contiguous(positions: &[Position2d]) -> Option { let mut it = positions.iter(); let first = *it.next()?; let mut second = *it.next()?; - if first.0 == second.0 { + if first.x == second.x { // Vertical - for &(x, y) in it { - if x != first.0 || y != second.1 + 1 { + for &pos in it { + if pos.x != first.x || pos.y != second.y + 1 { return Some(false); } - second = (x, y); + second = pos; } Some(true) - } else if first.1 == second.1 { + } else if first.y == second.y { // Horizontal - for &(x, y) in it { - if y != first.1 || x != second.0 + 1 { + for &pos in it { + if pos.y != first.y || pos.y != second.y + 1 { return Some(false); } - second = (x, y); + second = pos; } Some(true) } else { @@ -68,18 +69,27 @@ impl Default for Board { mod tests { use super::*; + fn positions(input: &[(usize, usize)]) -> Vec { + input + .iter() + .map(|(x, y)| Position2d::new(*x, *y)) + .collect::>() + } + #[test] fn test_is_contiguous() { assert_eq!(Board::is_contiguous(&[]), None); - assert_eq!(Board::is_contiguous(&[(0, 0)]), None); - assert_eq!(Board::is_contiguous(&[(0, 0), (0, 1), (0, 2)]), Some(true)); + assert_eq!(Board::is_contiguous(&positions(&[(0, 0)])), None); assert_eq!( - Board::is_contiguous(&[(1, 0), (2, 0), (3, 0), (4, 0)]), + Board::is_contiguous(&positions(&[(0, 0), (0, 1), (0, 2)])), Some(true) ); - assert_eq!(Board::is_contiguous(&[(0, 0), (0, 1), (1, 3)]), Some(false)); assert_eq!( - Board::is_contiguous(&[(0, 0), (0, 1), (0, 2), (1, 2)]), + Board::is_contiguous(&positions(&[(0, 0), (0, 1), (1, 3)])), + Some(false) + ); + assert_eq!( + Board::is_contiguous(&positions(&[(0, 0), (0, 1), (0, 2), (1, 2)])), Some(false) ); } diff --git a/board-shared/src/expr.rs b/board-shared/src/expr.rs index 61e9bde..bf19feb 100644 --- a/board-shared/src/expr.rs +++ b/board-shared/src/expr.rs @@ -2,6 +2,7 @@ use crate::board::Board; use crate::lexer::lexer; use crate::parser; use crate::parser::{Expression, Expressions}; +use crate::position::Position2d; use crate::tile::{Operator, Tile}; pub fn calculate(expr: &Expression) -> f64 { @@ -36,10 +37,10 @@ pub fn are_valid_expressions(expr: &Expressions) -> bool { res.is_some() } -pub fn is_valid_guess(board: &Board, positions: &[(usize, usize)]) -> Result { +pub fn is_valid_guess(board: &Board, positions: &[Position2d]) -> Result { let tiles = positions .iter() - .map(|&(x, y)| board.get(x, y)) + .map(|&pos| board.get(pos.x, pos.y)) .collect::>>() .ok_or(())?; diff --git a/board-shared/src/lib.rs b/board-shared/src/lib.rs index a85819d..de5a1b5 100644 --- a/board-shared/src/lib.rs +++ b/board-shared/src/lib.rs @@ -3,4 +3,5 @@ pub mod expr; pub mod game; mod lexer; mod parser; +mod position; pub mod tile; diff --git a/board-shared/src/position.rs b/board-shared/src/position.rs new file mode 100644 index 0000000..e686355 --- /dev/null +++ b/board-shared/src/position.rs @@ -0,0 +1,139 @@ +/// A position in a 2d grid. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Position2d { + pub x: usize, + pub y: usize, +} + +/// An alignment in a 2d grid. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Alignment { + Horizontal, + Vertical, +} + +/// A direction in a 2d grid. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +impl Alignment { + /// Returns the direction to the start of an alignment. + pub fn start(self) -> Direction { + match self { + Alignment::Horizontal => Direction::Left, + Alignment::Vertical => Direction::Up, + } + } + + /// Returns the direction to the end of an alignment. + pub fn end(self) -> Direction { + match self { + Alignment::Horizontal => Direction::Right, + Alignment::Vertical => Direction::Down, + } + } + + /// Test if two positions are aligned in this direction. + pub fn is_aligned(self, a: Position2d, b: Position2d) -> bool { + match self { + Alignment::Horizontal => a.x == b.x, + Alignment::Vertical => a.y == b.y, + } + } +} + +impl Position2d { + pub fn new(x: usize, y: usize) -> Self { + Self { x, y } + } + + /// Returns the position relative to this position in the given direction. + /// If the position is out of bounds, returns None. + pub fn relative(self, dir: Direction, sized: &impl Grid2d) -> Option { + let (x, y) = match dir { + Direction::Up => (self.x, self.y.checked_sub(1)?), + Direction::Down => (self.x, self.y.checked_add(1)?), + Direction::Left => (self.x.checked_sub(1)?, self.y), + Direction::Right => (self.x.checked_add(1)?, self.y), + }; + if x < sized.width() && y < sized.height() { + Some(Position2d::new(x, y)) + } else { + None + } + } +} + +impl From<(usize, usize)> for Position2d { + fn from((x, y): (usize, usize)) -> Self { + Self { x, y } + } +} + +/// Trait for elements that have a size. +pub trait Grid2d { + /// Returns the grid width. + fn width(&self) -> usize; + + /// Returns the grid height. + fn height(&self) -> usize; +} + +#[cfg(test)] +mod tests { + use super::*; + + struct Rectangle { + width: usize, + height: usize, + } + + impl Rectangle { + fn new(width: usize, height: usize) -> Self { + Self { width, height } + } + } + + impl Grid2d for Rectangle { + fn width(&self) -> usize { + self.width + } + + fn height(&self) -> usize { + self.height + } + } + + #[test] + fn test_relative() { + let rect = Rectangle::new(3, 3); + let pos = Position2d::new(0, 0); + assert_eq!(pos.relative(Direction::Up, &rect), None); + assert_eq!( + pos.relative(Direction::Down, &rect), + Some(Position2d::new(0, 1)) + ); + assert_eq!(pos.relative(Direction::Left, &rect), None); + assert_eq!( + pos.relative(Direction::Right, &rect), + Some(Position2d::new(1, 0)) + ); + + let pos = Position2d::new(2, 2); + assert_eq!( + pos.relative(Direction::Up, &rect), + Some(Position2d::new(2, 1)) + ); + assert_eq!(pos.relative(Direction::Down, &rect), None); + assert_eq!( + pos.relative(Direction::Left, &rect), + Some(Position2d::new(1, 2)) + ); + assert_eq!(pos.relative(Direction::Right, &rect), None); + } +}