WordGrid/src/board.rs

950 lines
31 KiB
Rust
Raw Normal View History

2023-08-01 03:00:58 +00:00
use std::collections::HashSet;
use std::fmt;
use std::fmt::{Formatter, Write};
use std::borrow::BorrowMut;
2023-08-06 01:59:30 +00:00
use serde::{Deserialize, Serialize};
use tsify::Tsify;
2023-08-01 03:00:58 +00:00
use crate::constants::{ALL_LETTERS_BONUS, GRID_LENGTH, TRAY_LENGTH};
use crate::dictionary::DictionaryImpl;
2023-08-01 03:00:58 +00:00
#[derive(Clone, Copy)]
2023-09-07 02:59:45 +00:00
pub enum Direction {
2023-08-01 03:00:58 +00:00
Row, Column
}
impl Direction {
2023-09-07 02:59:45 +00:00
pub fn invert(&self) -> Self {
2023-08-01 03:00:58 +00:00
match &self {
Direction::Row => {Direction::Column}
Direction::Column => {Direction::Row}
}
}
}
2023-09-07 02:59:45 +00:00
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
2023-08-01 03:00:58 +00:00
pub struct Coordinates (pub u8, pub u8);
impl Coordinates {
pub fn new_from_index(index: usize) -> Self {
let y = index / GRID_LENGTH as usize;
let x = index % GRID_LENGTH as usize;
Coordinates(x as u8, y as u8)
}
2023-08-01 03:00:58 +00:00
fn add(&self, direction: Direction, i: i8) -> Option<Self> {
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))
}
}
2023-09-07 02:59:45 +00:00
pub fn increment(&self, direction: Direction) -> Option<Self>{
2023-08-01 03:00:58 +00:00
self.add(direction, 1)
}
2023-09-07 02:59:45 +00:00
pub fn decrement(&self, direction: Direction) -> Option<Self>{
2023-08-01 03:00:58 +00:00
self.add(direction, -1)
}
2023-09-07 02:59:45 +00:00
pub fn map_to_index(&self) -> usize {
2023-08-01 03:00:58 +00:00
(self.0 + GRID_LENGTH*self.1) as usize
}
}
2023-09-07 02:59:45 +00:00
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Tsify, PartialEq, Eq, Hash)]
#[tsify(from_wasm_abi)]
2023-08-01 03:00:58 +00:00
pub struct Letter {
2023-08-03 03:00:01 +00:00
pub text: char,
pub points: u32,
pub ephemeral: bool,
pub is_blank: bool,
2023-08-01 03:00:58 +00:00
}
impl Letter {
pub fn new_fixed(text: char, points: u32) -> Self {
Letter {
text,
points,
ephemeral: false,
is_blank: false,
}
}
2023-08-03 03:00:01 +00:00
pub fn new(text: Option<char>, points: u32) -> Letter {
match text {
None => {
Letter {
text: ' ',
points,
ephemeral: true,
is_blank: true,
}
}
Some(text) => {
Letter {
text,
points,
ephemeral: true,
is_blank: false,
}
}
}
}
pub fn partial_match(&self, other: &Letter) -> bool {
self == other || (self.is_blank && other.is_blank && self.points == other.points)
}
2023-08-01 03:00:58 +00:00
}
2023-08-10 03:00:14 +00:00
#[derive(Debug, Copy, Clone, Serialize)]
2023-08-01 03:00:58 +00:00
pub enum CellType {
Normal,
DoubleWord,
DoubleLetter,
TripleLetter,
TripleWord,
Start,
}
#[derive(Debug, Clone)]
2023-08-01 03:00:58 +00:00
pub struct Cell {
pub value: Option<Letter>,
2023-08-10 03:00:14 +00:00
pub cell_type: CellType,
pub coordinates: Coordinates,
2023-08-01 03:00:58 +00:00
}
#[derive(Debug, Clone)]
2023-08-01 03:00:58 +00:00
pub struct Board {
2023-08-10 03:00:14 +00:00
pub cells: Vec<Cell>,
2023-08-01 03:00:58 +00:00
}
pub struct Word<'a> {
cells: Vec<&'a Cell>,
coords: Coordinates,
}
impl<'a> ToString for Word<'a> {
fn to_string(&self) -> String {
let mut text = String::with_capacity(self.cells.len());
for cell in self.cells.as_slice() {
text.push(cell.value.as_ref().unwrap().text);
}
text
}
}
impl <'a> Word<'a> {
2023-09-07 02:59:45 +00:00
pub fn calculate_score(&self) -> u32{
2023-08-01 03:00:58 +00:00
let mut multiplier = 1;
let mut unmultiplied_score = 0;
for cell in self.cells.as_slice() {
let cell_value = cell.value.unwrap();
if cell_value.ephemeral {
let cell_multiplier =
match cell.cell_type {
CellType::Normal => {1}
CellType::DoubleWord => {
multiplier *= 2;
1
}
CellType::DoubleLetter => {2}
CellType::TripleLetter => {3}
CellType::TripleWord => {
multiplier *= 3;
1
}
CellType::Start => {
multiplier *= 2;
1
}
};
unmultiplied_score += cell_value.points * cell_multiplier;
} else {
// no cell multiplier unfortunately
unmultiplied_score += cell_value.points;
}
}
unmultiplied_score * multiplier
}
}
impl Board {
pub fn new() -> Self {
let mut cells = Vec::new();
/// Since the board is symmetrical in both directions for the purposes of our logic we can keep our coordinates in one corner
///
/// # Arguments
///
/// * `x`: A coordinate
///
/// returns: u8 The coordinate mapped onto the lower-half
fn map_to_corner(x: u8) -> u8 {
return if x > GRID_LENGTH / 2 {
GRID_LENGTH - x - 1
} else {
x
}
}
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;
// double word scores are diagonals
if i == j {
typee = CellType::DoubleWord;
}
// Triple letters
if (i % 4 == 1) && j % 4 == 1 && !(i == 1 && j == 1) {
typee = CellType::TripleLetter;
}
// Double letters
if (i % 4 == 2) && (j % 4 == 2) && !(
i == 2 && j == 2
) {
typee = CellType::DoubleLetter;
}
if (i.min(j) == 0 && i.max(j) == 3) || (i.min(j)==3 && i.max(j) == 7) {
typee = CellType::DoubleLetter;
}
// Triple word scores
if (i % 7 == 0) && (j % 7 == 0) {
typee = CellType::TripleWord;
}
// Start
if i == 7 && j == 7 {
typee = CellType::Start;
}
cells.push(Cell {
cell_type: typee,
value: None,
coordinates: Coordinates(j_orig, i_orig),
})
}
}
Board {cells}
}
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 index = coordinates.map_to_index();
Ok(self.cells.get(index).unwrap())
}
}
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 index = coordinates.map_to_index();
Ok(self.cells.get_mut(index).unwrap())
}
}
pub fn calculate_scores(&self, dictionary: &DictionaryImpl) -> Result<(Vec<(Word, u32)>, u32), String> {
let (words, tiles_played) = self.find_played_words()?;
let mut words_and_scores = Vec::new();
let mut total_score = 0;
for word in words {
if !dictionary.contains_key(&word.to_string()) {
return Err(format!("{} is not a valid word", word.to_string()));
}
let score = word.calculate_score();
total_score += score;
words_and_scores.push((word, score));
}
if tiles_played == TRAY_LENGTH {
total_score += ALL_LETTERS_BONUS;
}
Ok((words_and_scores, total_score))
}
pub fn find_played_words(&self) -> Result<(Vec<Word>, u8), &str> {
// 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::Row
} else {
Direction::Column
};
let starting_row = *rows_played.iter().min().unwrap();
let starting_column = *columns_played.iter().min().unwrap();
2023-09-07 02:59:45 +00:00
let starting_coords = Coordinates(starting_row, starting_column);
2023-08-01 03:00:58 +00:00
let main_word = self.find_word_at_position(starting_coords, direction).unwrap();
let mut words = Vec::new();
let mut observed_tiles_played = 0;
// 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
for cell in main_word.cells.as_slice() {
if cell.value.as_ref().unwrap().ephemeral {
observed_tiles_played += 1;
let side_word = self.find_word_at_position(cell.coordinates, direction.invert());
match side_word {
None => {}
Some(side_word) => {
if side_word.cells.len() > 1 {
words.push(side_word);
}
}
}
}
}
// there are tiles not part of the main word
if observed_tiles_played != tiles_played {
return Err("Played tiles cannot have empty gap");
}
// don't want the case of a single letter word
if main_word.cells.len() > 1 {
words.push(main_word);
} else if words.is_empty() {
return Err("All words must be at least one letter");
}
// need to verify that the play is 'anchored'
let mut anchored = false;
'outer: for word in words.as_slice() {
for cell in word.cells.as_slice() {
// either one of the letters
if !cell.value.as_ref().unwrap().ephemeral || (cell.coordinates.0 == GRID_LENGTH / 2 && cell.coordinates.1 == GRID_LENGTH / 2){
anchored = true;
break 'outer;
}
}
}
if anchored {
Ok((words, tiles_played))
} else {
return Err("Played tiles must be anchored to something")
}
}
2023-09-07 02:59:45 +00:00
pub fn find_word_at_position(&self, mut start_coords: Coordinates, direction: Direction) -> Option<Word> {
2023-08-01 03:00:58 +00:00
// 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,
})
}
pub fn receive_play(&mut self, play: Vec<(Letter, Coordinates)>) -> Result<(), String> {
for (mut letter, coords) in play {
{
2023-09-07 02:59:45 +00:00
let cell = match self.get_cell_mut(coords) {
Ok(cell) => {cell}
Err(e) => {return Err(e.to_string())}
};
if cell.value.is_some() {
return Err(format!("There's already a letter at {:?}", coords));
}
letter.ephemeral = true;
cell.value = Some(letter);
}
}
Ok(())
}
2023-08-01 03:00:58 +00:00
pub fn fix_tiles(&mut self) {
for cell in self.cells.iter_mut() {
match cell.value.borrow_mut() {
None => {}
Some(x) => {
x.ephemeral = false;
}
}
}
}
2023-08-01 03:00:58 +00:00
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut str = String::new();
let normal = "\x1b[48;5;174m\x1b[38;5;0m";
let triple_word = "\x1b[48;5;196m\x1b[38;5;0m";
let double_word = "\x1b[48;5;204m\x1b[38;5;0m";
let triple_letter = "\x1b[48;5;21m\x1b[38;5;15m";
let double_letter = "\x1b[48;5;51m\x1b[38;5;0m";
str.write_char('\n').unwrap();
for x in 0..GRID_LENGTH {
for y in 0..GRID_LENGTH {
let coords = Coordinates(x, y);
let cell = self.get_cell(coords).unwrap();
let color = match cell.cell_type {
CellType::Normal => {normal}
CellType::DoubleWord => {double_word}
CellType::DoubleLetter => {double_letter}
CellType::TripleLetter => {triple_letter}
CellType::TripleWord => {triple_word}
CellType::Start => {double_word}
};
let content = match &cell.value {
None => {' '}
Some(letter) => {letter.text}
};
str.write_str(color).unwrap();
str.write_char(content).unwrap();
}
str.write_str("\x1b[0m\n").unwrap();
}
write!(f, "{}", str)
}
}
#[cfg(test)]
mod tests {
use crate::dictionary::Dictionary;
use super::*;
#[test]
fn test_cell_types() {
let board = Board::new();
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_cell_coordinates() {
let board = Board::new();
for x in 0..GRID_LENGTH {
for y in 0..GRID_LENGTH {
let cell = board.get_cell(Coordinates(x, y)).unwrap();
let coords = cell.coordinates;
assert_eq!(x, coords.0);
assert_eq!(y, coords.1);
}
}
}
#[test]
fn test_word_finding_at_position() {
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));
for x in vec![6, 7, 8, 9] {
println!("x is {}", x);
let first_word = board.find_word_at_position(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!(x.to_string(), "JOEL");
}
}
}
let single_letter_word = board.find_word_at_position(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!(x.to_string(), "L");
}
}
for x in vec![0, 1] {
println!("x is {}", x);
let word = board.find_word_at_position(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!(x.to_string(), "IS");
}
}
}
for x in vec![3, 4, 5, 6] {
println!("x is {}", x);
let word = board.find_word_at_position(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!(x.to_string(), "COOL");
}
}
}
let no_word = board.find_word_at_position(Coordinates(2, 0), Direction::Row);
assert!(no_word.is_none());
let word = board.find_word_at_position(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!(x.to_string(), "EGG");
}
}
}
#[test]
fn test_word_finding_one_letter() {
let mut board = Board::new();
board.get_cell_mut(Coordinates(7, 7)).unwrap().value = Some(Letter {
text: 'I',
points: 1,
ephemeral: true,
is_blank: false,
});
match board.find_played_words() {
Ok(_) => {panic!("Expected error")}
Err(e) => {assert_eq!(e, "All words must be at least one letter");}
}
board.get_cell_mut(Coordinates(7, 7)).unwrap().value = Some(Letter {
text: 'I',
points: 1,
ephemeral: false, // fixed now
is_blank: false,
});
board.get_cell_mut(Coordinates(7, 8)).unwrap().value = Some(Letter {
text: 'S',
points: 1,
ephemeral: true,
is_blank: false,
});
let (words, tiles_played) = board.find_played_words().unwrap();
assert_eq!(tiles_played, 1);
assert_eq!(words.len(), 1);
let word = words.first().unwrap();
assert_eq!(word.calculate_score(), 2);
// making fixed
board.get_cell_mut(Coordinates(7, 8)).unwrap().value = Some(Letter {
text: 'S',
points: 1,
ephemeral: false,
is_blank: false,
});
// trying other orientation
board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(Letter {
text: 'S',
points: 1,
ephemeral: true,
is_blank: false,
});
let (words, tiles_played) = board.find_played_words().unwrap();
assert_eq!(tiles_played, 1);
assert_eq!(words.len(), 1);
let word = words.first().unwrap();
assert_eq!(word.calculate_score(), 2);
}
#[test]
fn test_word_finding_anchor() {
let mut board = Board::new();
fn make_letter(x: char, ephemeral: bool) -> Letter {
Letter {
text: x,
points: 0,
ephemeral,
is_blank: false,
}
}
board.get_cell_mut(Coordinates(8, 6)).unwrap().value = Some(make_letter('J', true));
board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(make_letter('O', true));
board.get_cell_mut(Coordinates(8, 8)).unwrap().value = Some(make_letter('E', true));
board.get_cell_mut(Coordinates(8, 9)).unwrap().value = Some(make_letter('L', true));
let words = board.find_played_words();
match words {
Ok(_) => {panic!("Expected the not-anchored error")}
Err(x) => {assert_eq!(x, "Played tiles must be anchored to something")}
}
// Adding anchor
board.get_cell_mut(Coordinates(7, 6)).unwrap().value = Some(make_letter('I', false));
assert!(board.find_played_words().is_ok());
board = Board::new();
// we go through center so this is anchored
board.get_cell_mut(Coordinates(7, 7)).unwrap().value = Some(make_letter('J', true));
board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(make_letter('O', true));
board.get_cell_mut(Coordinates(9, 7)).unwrap().value = Some(make_letter('E', true));
board.get_cell_mut(Coordinates(10, 7)).unwrap().value = Some(make_letter('L', true));
assert!(board.find_played_words().is_ok());
}
#[test]
fn test_word_finding_with_break() {
// Verify that if I play my tiles on one row or column but with a break in-between I get an error
let mut board = Board::new();
fn make_letter(x: char, ephemeral: bool) -> Letter {
Letter {
text: x,
points: 0,
ephemeral,
is_blank: false,
}
}
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(make_letter('O', true));
board.get_cell_mut(Coordinates(8, 8)).unwrap().value = Some(make_letter('E', true));
board.get_cell_mut(Coordinates(8, 9)).unwrap().value = Some(Letter::new_fixed( 'L', 0));
board.get_cell_mut(Coordinates(8, 11)).unwrap().value = Some(make_letter('I', true));
board.get_cell_mut(Coordinates(8, 12)).unwrap().value = Some(Letter::new_fixed('S', 0));
let words = board.find_played_words();
match words {
Ok(_) => {panic!("Expected to find an error!")}
Err(x) => {
assert_eq!(x, "Played tiles cannot have empty gap")
}
}
}
#[test]
fn test_word_finding_whole_board() {
let mut board = Board::new();
fn make_letter(x: char, ephemeral: bool, points: u32) -> Letter {
Letter {
text: x,
points,
ephemeral,
is_blank: false,
}
}
let words = board.find_played_words();
match words {
Ok(_) => {panic!("Expected to find no words")}
Err(x) => {assert_eq!(x, "Tiles need to be played")}
}
board.get_cell_mut(Coordinates(8, 6)).unwrap().value = Some(Letter::new_fixed('J', 8));
board.get_cell_mut(Coordinates(8, 7)).unwrap().value = Some(make_letter('O', true, 1));
board.get_cell_mut(Coordinates(8, 8)).unwrap().value = Some(make_letter('E', true, 1));
board.get_cell_mut(Coordinates(8, 9)).unwrap().value = Some(Letter::new_fixed( 'L', 1));
board.get_cell_mut(Coordinates(0, 0)).unwrap().value = Some(Letter::new_fixed('I', 1));
board.get_cell_mut(Coordinates(1, 0)).unwrap().value = Some(Letter::new_fixed('S', 1));
board.get_cell_mut(Coordinates(3, 0)).unwrap().value = Some(Letter::new_fixed('C', 3));
board.get_cell_mut(Coordinates(4, 0)).unwrap().value = Some(Letter::new_fixed('O', 1));
board.get_cell_mut(Coordinates(5, 0)).unwrap().value = Some(Letter::new_fixed('O', 1));
board.get_cell_mut(Coordinates(6, 0)).unwrap().value = Some(Letter::new_fixed('L', 1));
fn check_board(board: &mut Board, inverted: bool) {
let dictionary = DictionaryImpl::create_from_path("resources/dictionary.csv");
2023-08-01 03:00:58 +00:00
println!("{}", board);
let words = board.find_played_words();
match words {
Ok((x, tiles_played)) => {
assert_eq!(tiles_played, 2);
assert_eq!(x.len(), 1);
let word = x.get(0).unwrap();
assert_eq!(word.to_string(), "JOEL");
assert!(!dictionary.is_word_valid(word));
assert_eq!(word.calculate_score(), 8 + 1 + 2 + 1);
}
Err(e) => { panic!("Expected to find a word to play; found error {}", e) }
}
let maybe_invert = |coords: Coordinates| {
if inverted {
return Coordinates(coords.1, coords.0);
}
return coords;
};
let maybe_invert_direction = |direction: Direction| {
if inverted {
return direction.invert();
}
return direction;
};
board.get_cell_mut(maybe_invert(Coordinates(9, 8))).unwrap().value = Some(Letter::new_fixed('G', 2));
board.get_cell_mut(maybe_invert(Coordinates(10, 8))).unwrap().value = Some(Letter::new_fixed('G', 2));
let word = board.find_word_at_position(Coordinates(8, 8), maybe_invert_direction(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!(x.to_string(), "EGG");
assert_eq!(x.calculate_score(), 2 + 2 + 2);
assert!(dictionary.is_word_valid(&x));
}
}
let words = board.find_played_words();
match words {
Ok((x, tiled_played)) => {
assert_eq!(tiled_played, 2);
assert_eq!(x.len(), 2);
let word = x.get(0).unwrap();
assert_eq!(word.to_string(), "EGG");
assert_eq!(word.calculate_score(), 2 + 2 + 2);
assert!(dictionary.is_word_valid(word));
let word = x.get(1).unwrap();
assert_eq!(word.to_string(), "JOEL");
assert_eq!(word.calculate_score(), 8 + 1 + 2 + 1);
assert!(!dictionary.is_word_valid(word));
}
Err(e) => { panic!("Expected to find a word to play; found error {}", e) }
}
let scores = board.calculate_scores(&dictionary);
match scores {
Ok(_) => {panic!("Expected an error")}
Err(e) => {assert_eq!(e, "JOEL is not a valid word")}
}
let mut alt_dictionary = DictionaryImpl::new();
alt_dictionary.insert("JOEL".to_string(), 0.5);
alt_dictionary.insert("EGG".to_string(), 0.5);
let scores = board.calculate_scores(&alt_dictionary);
match scores {
Ok((words, total_score)) => {
assert_eq!(words.len(), 2);
let (word, score) = words.get(0).unwrap();
assert_eq!(word.to_string(), "EGG");
assert_eq!(word.calculate_score(), 2 + 2 + 2);
assert_eq!(*score, 2 + 2 + 2);
let (word, score) = words.get(1).unwrap();
assert_eq!(word.to_string(), "JOEL");
assert_eq!(word.calculate_score(), 8 + 1 + 2 + 1);
assert_eq!(*score, 8 + 1 + 2 + 1);
assert_eq!(total_score, 18);
}
Err(e) => {panic!("Wasn't expecting to encounter error {e}")}
}
// replace one of the 'G' in EGG with an ephemeral to trigger an error
board.get_cell_mut(maybe_invert(Coordinates(9, 8))).unwrap().value = Some(make_letter('G', true, 2));
let words = board.find_played_words();
match words {
Ok(_) => { panic!("Expected error as we played tiles in multiple rows and columns") }
Err(e) => { assert_eq!(e, "Tiles need to be played on one row or column") }
}
}
// make a copy of the board now with x and y swapped
let mut inverted_board = Board::new();
for x in 0..GRID_LENGTH {
for y in 0..GRID_LENGTH {
let cell_original = board.get_cell(Coordinates(x, y)).unwrap();
let cell_new = inverted_board.get_cell_mut(Coordinates(y, x)).unwrap();
match &cell_original.value {
None => {}
Some(x) => {
cell_new.value = Some(*x);
}
}
}
}
println!("Checking original board");
check_board(&mut board, false);
println!("Checking inverted board");
check_board(&mut inverted_board, true);
}
}