From 4f5de6e52e525c0678710328967fdb020f5782a7 Mon Sep 17 00:00:00 2001 From: Joel Therrien Date: Thu, 27 Jul 2023 21:25:50 -0700 Subject: [PATCH] Refactor; add word searching --- Cargo.toml | 2 +- src/lib.rs | 344 ++++++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 24 ++-- 3 files changed, 322 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34723dd..4d5e9dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "WordGrid" +name = "word_grid" version = "0.1.0" edition = "2021" diff --git a/src/lib.rs b/src/lib.rs index a8ac625..f051e15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,67 @@ pub const GRID_LENGTH: u8 = 15; pub const TRAY_LENGTH: u8 = 7; pub const ALL_LETTERS_BONUS: u32 = 50; +#[derive(Clone, Copy)] +enum Direction { + Row, Column +} + +impl Direction { + fn invert(&self) -> Self { + match &self { + Direction::Row => {Direction::Column} + Direction::Column => {Direction::Row} + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Coordinates (pub u8, pub u8); + +impl Coordinates { + fn add(&self, direction: Direction, i: i8) -> Option { + let proposed = match direction { + Direction::Column => {(self.0 as i8, self.1 as i8+i)} + Direction::Row => {(self.0 as i8+i, self.1 as i8)} + }; + + if proposed.0 < 0 || proposed.0 >= GRID_LENGTH as i8 || proposed.1 < 0 || proposed.1 >= GRID_LENGTH as i8 { + None + } else{ + Some(Coordinates(proposed.0 as u8, proposed.1 as u8)) + } + } + + fn increment(&self, direction: Direction) -> Option{ + self.add(direction, 1) + } + + fn decrement(&self, direction: Direction) -> Option{ + self.add(direction, -1) + } + + fn map_to_index(&self) -> usize { + (self.0 + GRID_LENGTH*self.1) as usize + } +} + #[derive(Debug)] -pub enum Letter { - FixedLetter{text: char, points: u32}, - Blank(char) +pub struct Letter { + text: char, + points: u32, + ephemeral: bool, + is_blank: bool, +} + +impl Letter { + pub fn new_fixed(text: char, points: u32) -> Self { + Letter { + text, + points, + ephemeral: false, + is_blank: false, + } + } } #[derive(Debug)] @@ -27,12 +84,9 @@ pub enum CellType { pub struct Cell { pub value: Option, cell_type: CellType, + coordinates: Coordinates, } -#[derive(Debug)] -pub struct Board { - cells: Vec, -} pub struct Dictionary { words: Vec, @@ -92,6 +146,16 @@ impl Dictionary { } +#[derive(Debug)] +pub struct Board { + cells: Vec, +} + +struct Word<'a> { + cells: Vec<&'a Cell>, + coords: Coordinates, +} + impl Board { pub fn new() -> Self { let mut cells = Vec::new(); @@ -112,10 +176,10 @@ impl Board { } } - for i in 0..GRID_LENGTH { - let i = map_to_corner(i); - for j in 0..GRID_LENGTH { - let j = map_to_corner(j); + for i_orig in 0..GRID_LENGTH { + let i = map_to_corner(i_orig); + for j_orig in 0..GRID_LENGTH { + let j = map_to_corner(j_orig); let mut typee = CellType::Normal; @@ -152,6 +216,7 @@ impl Board { cells.push(Cell { cell_type: typee, value: None, + coordinates: Coordinates(i, j), }) } @@ -160,23 +225,129 @@ impl Board { Board {cells} } - pub fn get_cell(&self, x: u8, y: u8) -> Result<&Cell, &str> { - if x >= GRID_LENGTH || y >= GRID_LENGTH { + pub fn get_cell(&self, coordinates: Coordinates) -> Result<&Cell, &str> { + if coordinates.0 >= GRID_LENGTH || coordinates.1 >= GRID_LENGTH { Err("x & y must be within the board's coordinates") } else { - let coord = (x + GRID_LENGTH*y) as usize; - Ok(self.cells.get(coord).unwrap()) + let index = coordinates.map_to_index(); + Ok(self.cells.get(index).unwrap()) } } - pub fn get_cell_mut(&mut self, x: u8, y: u8) -> Result<&mut Cell, &str> { - if x >= GRID_LENGTH || y >= GRID_LENGTH { + pub fn get_cell_mut(&mut self, coordinates: Coordinates) -> Result<&mut Cell, &str> { + if coordinates.0 >= GRID_LENGTH || coordinates.1 >= GRID_LENGTH { Err("x & y must be within the board's coordinates") } else { - let coord = (x + GRID_LENGTH*y) as usize; - Ok(self.cells.get_mut(coord).unwrap()) + let index = coordinates.map_to_index(); + Ok(self.cells.get_mut(index).unwrap()) } } + + pub fn score_move(&self) -> Result { + // We don't assume that the move is valid, so let's first establish that + + + // Let's first establish what rows and columns tiles were played in + let mut rows_played = HashSet::with_capacity(15); + let mut columns_played = HashSet::with_capacity(15); + let mut tiles_played = 0; + + for x in 0..GRID_LENGTH { + for y in 0..GRID_LENGTH { + let coords = Coordinates(x, y); + let cell = self.get_cell(coords).unwrap(); + match &cell.value { + Some(value) => { + if value.ephemeral { + rows_played.insert(x); + columns_played.insert(y); + tiles_played += 1; + } + } + _ => {} + } + } + } + + if rows_played.is_empty() { + return Err("Tiles need to be played") + } else if rows_played.len() > 1 && columns_played.len() > 1 { + return Err("Tiles need to be played on one row or column") + } + + let direction = if rows_played.len() > 1 { + Direction::Column + } else { + Direction::Row + }; + + let starting_row = *rows_played.iter().min().unwrap(); + let starting_column = *columns_played.iter().min().unwrap(); + + let mut starting_coords = Coordinates(starting_row, starting_column); + + + + // At this point we now know that we're at the start of the word and we have the direction. + // Now we'll head forward and look for every word that intersects one of the played tiles + + + + todo!() + } + + fn find_word(&self, mut start_coords: Coordinates, direction: Direction) -> Option { + // let's see how far we can backtrack to the start of the word + let mut times_moved = 0; + loop { + let one_back = start_coords.add(direction, -times_moved); + match one_back { + None => { break } + Some(new_coords) => { + let cell = self.get_cell(new_coords).unwrap(); + if cell.value.is_some(){ + times_moved += 1; + } else { + break + } + } + } + } + + if times_moved == 0 { + return None; + } + + start_coords = start_coords.add(direction, -times_moved + 1).unwrap(); + + // since we moved and we know that start_coords has started on a letter, we know we have a word + // we'll now keep track of the cells that form it + let mut cells = Vec::with_capacity(GRID_LENGTH as usize); + cells.push(self.get_cell(start_coords).unwrap()); + + loop { + let position = start_coords.add(direction, cells.len() as i8); + match position { + None => {break} + Some(x) => { + let cell = self.get_cell(x).unwrap(); + match cell.value { + None => {break} + Some(_) => { + cells.push(cell); + } + } + } + } + } + + Some(Word { + cells, + coords: start_coords, + }) + } + + } impl fmt::Display for Board { @@ -192,9 +363,10 @@ impl fmt::Display for Board { str.write_char('\n').unwrap(); for x in 0..GRID_LENGTH { - for y in 0..GRID_LENGTH { - let cell = self.get_cell(x, y).unwrap(); + let coords = Coordinates(x, y); + + let cell = self.get_cell(coords).unwrap(); let color = match cell.cell_type { CellType::Normal => {normal} @@ -207,10 +379,7 @@ impl fmt::Display for Board { let content = match &cell.value { None => {' '} - Some(letter) => {*match letter { - Letter::FixedLetter {text, points} => {text}, - Letter::Blank(text) => {text} - }} + Some(letter) => {letter.text} }; @@ -237,18 +406,123 @@ mod tests { fn test_cell_types() { let board = Board::new(); - assert!(matches!(board.get_cell(0, 0).unwrap().cell_type, CellType::TripleWord)); - assert!(matches!(board.get_cell(1, 0).unwrap().cell_type, CellType::Normal)); - assert!(matches!(board.get_cell(0, 1).unwrap().cell_type, CellType::Normal)); - assert!(matches!(board.get_cell(1, 1).unwrap().cell_type, CellType::DoubleWord)); + assert!(matches!(board.get_cell(Coordinates(0, 0)).unwrap().cell_type, CellType::TripleWord)); + assert!(matches!(board.get_cell(Coordinates(1, 0)).unwrap().cell_type, CellType::Normal)); + assert!(matches!(board.get_cell(Coordinates(0, 1)).unwrap().cell_type, CellType::Normal)); + assert!(matches!(board.get_cell(Coordinates(1, 1)).unwrap().cell_type, CellType::DoubleWord)); + + assert!(matches!(board.get_cell(Coordinates(13, 13)).unwrap().cell_type, CellType::DoubleWord)); + assert!(matches!(board.get_cell(Coordinates(14, 14)).unwrap().cell_type, CellType::TripleWord)); + assert!(matches!(board.get_cell(Coordinates(11, 14)).unwrap().cell_type, CellType::DoubleLetter)); + + assert!(matches!(board.get_cell(Coordinates(7, 7)).unwrap().cell_type, CellType::Start)); + assert!(matches!(board.get_cell(Coordinates(8, 6)).unwrap().cell_type, CellType::DoubleLetter)); + assert!(matches!(board.get_cell(Coordinates(5, 9)).unwrap().cell_type, CellType::TripleLetter)); + } + + #[test] + fn test_word_finding() { + let mut board = Board::new(); + + board.get_cell_mut(Coordinates(8, 6)).unwrap().value = Some(Letter::new_fixed('J', 0)); + board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(Letter::new_fixed('O', 0)); + board.get_cell_mut(Coordinates(8, 8)).unwrap().value = Some(Letter::new_fixed( 'E', 0)); + board.get_cell_mut(Coordinates(8, 9)).unwrap().value = Some(Letter::new_fixed( 'L', 0)); + + board.get_cell_mut(Coordinates(0, 0)).unwrap().value = Some(Letter::new_fixed('I', 0)); + board.get_cell_mut(Coordinates(1, 0)).unwrap().value = Some(Letter::new_fixed('S', 0)); + + board.get_cell_mut(Coordinates(3, 0)).unwrap().value = Some(Letter::new_fixed('C', 0)); + board.get_cell_mut(Coordinates(4, 0)).unwrap().value = Some(Letter::new_fixed('O', 0)); + board.get_cell_mut(Coordinates(5, 0)).unwrap().value = Some(Letter::new_fixed('O', 0)); + board.get_cell_mut(Coordinates(6, 0)).unwrap().value = Some(Letter::new_fixed('L', 0)); + + + board.get_cell_mut(Coordinates(9, 8)).unwrap().value = Some(Letter::new_fixed( 'G', 0)); + board.get_cell_mut(Coordinates(10, 8)).unwrap().value = Some(Letter::new_fixed( 'G', 0)); + + fn word_to_text(word: Word) -> String { + let mut text = String::with_capacity(word.cells.len()); + for cell in word.cells { + text.push(cell.value.as_ref().unwrap().text); + } + + text + } + + for x in vec![6, 7, 8, 9] { + println!("x is {}", x); + let first_word = board.find_word(Coordinates(8, x), Direction::Column); + match first_word { + None => {panic!("Expected to find word JOEL")} + Some(x) => { + assert_eq!(x.coords.0, 8); + assert_eq!(x.coords.1, 6); + + assert_eq!(word_to_text(x), "JOEL"); + + } + } + } + + let single_letter_word = board.find_word(Coordinates(8, 9), Direction::Row); + match single_letter_word { + None => {panic!("Expected to find letter L")} + Some(x) => { + assert_eq!(x.coords.0, 8); + assert_eq!(x.coords.1, 9); + + assert_eq!(word_to_text(x), "L"); + } + } + + for x in vec![0, 1] { + println!("x is {}", x); + let word = board.find_word(Coordinates(x, 0), Direction::Row); + match word { + None => {panic!("Expected to find word IS")} + Some(x) => { + assert_eq!(x.coords.0, 0); + assert_eq!(x.coords.1, 0); + + assert_eq!(word_to_text(x), "IS"); + + } + } + } + + for x in vec![3, 4, 5, 6] { + println!("x is {}", x); + let word = board.find_word(Coordinates(x, 0), Direction::Row); + match word { + None => {panic!("Expected to find word COOL")} + Some(x) => { + assert_eq!(x.coords.0, 3); + assert_eq!(x.coords.1, 0); + + assert_eq!(word_to_text(x), "COOL"); + + } + } + } + + let no_word = board.find_word(Coordinates(2, 0), Direction::Row); + assert!(no_word.is_none()); + + let word = board.find_word(Coordinates(10, 8), Direction::Row); + match word { + None => {panic!("Expected to find word EGG")} + Some(x) => { + assert_eq!(x.coords.0, 8); + assert_eq!(x.coords.1, 8); + + assert_eq!(word_to_text(x), "EGG"); + + } + } + - assert!(matches!(board.get_cell(13, 13).unwrap().cell_type, CellType::DoubleWord)); - assert!(matches!(board.get_cell(14, 14).unwrap().cell_type, CellType::TripleWord)); - assert!(matches!(board.get_cell(11, 14).unwrap().cell_type, CellType::DoubleLetter)); - assert!(matches!(board.get_cell(7, 7).unwrap().cell_type, CellType::Start)); - assert!(matches!(board.get_cell(8, 6).unwrap().cell_type, CellType::DoubleLetter)); - assert!(matches!(board.get_cell(5, 9).unwrap().cell_type, CellType::TripleLetter)); } #[test] diff --git a/src/main.rs b/src/main.rs index 552e2e6..00f780b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,21 @@ -use WordGrid::{Board, Letter}; +use word_grid::{Board, Coordinates, Letter}; fn main() { let mut board = Board::new(); - let c1 = board.get_cell_mut(8, 6).unwrap(); - c1.value = Some(Letter::FixedLetter {text: 'J', points: 0}); - board.get_cell_mut(8, 7).unwrap().value = Some(Letter::FixedLetter {text: 'o', points: 0}); - board.get_cell_mut(8, 8).unwrap().value = Some(Letter::FixedLetter {text: 'e', points: 0}); - board.get_cell_mut(8, 9).unwrap().value = Some(Letter::FixedLetter {text: 'l', points: 0}); + let c1 = board.get_cell_mut(Coordinates(8, 6)).unwrap(); + c1.value = Some(Letter::new_fixed( 'J', 0)); + board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(Letter::new_fixed('o', 0)); + board.get_cell_mut(Coordinates(8, 8)).unwrap().value = Some(Letter::new_fixed( 'e', 0)); + board.get_cell_mut(Coordinates(8, 9)).unwrap().value = Some(Letter::new_fixed( 'l', 0)); - board.get_cell_mut(9, 8).unwrap().value = Some(Letter::FixedLetter {text: 'i', points: 0}); - board.get_cell_mut(9, 9).unwrap().value = Some(Letter::FixedLetter {text: 's', points: 0}); + board.get_cell_mut(Coordinates(9, 8)).unwrap().value = Some(Letter::new_fixed( 'i', 0)); + board.get_cell_mut(Coordinates(9, 9)).unwrap().value = Some(Letter::new_fixed( 's', 0)); - board.get_cell_mut(10, 8).unwrap().value = Some(Letter::FixedLetter {text: 'c', points: 0}); - board.get_cell_mut(10, 9).unwrap().value = Some(Letter::FixedLetter {text: 'o', points: 0}); - board.get_cell_mut(10, 10).unwrap().value = Some(Letter::FixedLetter {text: 'o', points: 0}); - board.get_cell_mut(10, 11).unwrap().value = Some(Letter::FixedLetter {text: 'l', points: 0}); + board.get_cell_mut(Coordinates(10, 8)).unwrap().value = Some(Letter::new_fixed( 'c', 0)); + board.get_cell_mut(Coordinates(10, 9)).unwrap().value = Some(Letter::new_fixed( 'o', 0)); + board.get_cell_mut(Coordinates(10, 10)).unwrap().value = Some(Letter::new_fixed( 'o', 0)); + board.get_cell_mut(Coordinates(10, 11)).unwrap().value = Some(Letter::new_fixed( 'l', 0)); println!("{}", board);