1use serde::{Serialize, Deserialize};
2use super::super::constants::{BOARD, BOARD_SIZE, DICTIONARY};
3use super::super::error::{Error, Result};
4use super::tile::Tile;
5use super::{Direction, Point, Strip};
6use std::fmt;
7
8#[derive(Debug, PartialEq)]
12struct BoardCellMultiplier {
13 pub word: u32,
14 pub letter: u32,
15}
16
17impl BoardCellMultiplier {
18 pub fn new(word: u32, letter: u32) -> Self {
19 BoardCellMultiplier { word, letter }
20 }
21}
22
23#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
27pub enum BoardCell {
28 StartingSpot,
29 Empty,
30 DoubleLetter,
31 TripleLetter,
32 DoubleWord,
33 TripleWord,
34 Tile(Tile),
35}
36
37impl BoardCell {
38 fn get_multiplier(&self) -> BoardCellMultiplier {
39 match self {
40 Self::DoubleLetter => BoardCellMultiplier::new(1, 2),
41 Self::TripleLetter => BoardCellMultiplier::new(1, 3),
42 Self::DoubleWord => BoardCellMultiplier::new(2, 1),
43 Self::TripleWord => BoardCellMultiplier::new(3, 1),
44 _ => BoardCellMultiplier::new(1, 1),
45 }
46 }
47}
48
49impl From<char> for BoardCell {
50 fn from(c: char) -> Self {
51 match c {
52 '.' => Self::Empty,
53 '3' => Self::TripleWord,
54 '2' => Self::DoubleWord,
55 '@' => Self::DoubleLetter,
56 '#' => Self::TripleLetter,
57 '+' => Self::StartingSpot,
58 'A'..='Z' => Self::Tile(Tile::Letter(c)),
59 _ => unreachable!("BoardCell:from Parsing invalid tile character {}", c),
60 }
61 }
62}
63
64impl Into<char> for &BoardCell {
65 fn into(self) -> char {
66 match *self {
67 BoardCell::StartingSpot => '+',
68 BoardCell::Empty => '.',
69 BoardCell::DoubleLetter => '@',
70 BoardCell::TripleLetter => '#',
71 BoardCell::DoubleWord => '2',
72 BoardCell::TripleWord => '3',
73 BoardCell::Tile(Tile::Letter(letter)) => letter,
74 BoardCell::Tile(Tile::Blank) => unreachable!()
75 }
76 }
77}
78
79impl fmt::Display for BoardCell {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 write!(f, "{}", Into::<char>::into(self))
82 }
83}
84
85trait ReadableBoard {
89 fn is_in_bounds(&self, point: Point) -> bool;
90 fn get(&self, point: Point) -> Option<&BoardCell>;
91}
92
93#[inline]
94fn xy_to_idx(width: u32, point: Point) -> usize {
95 (point.y * width as i32 + point.x) as usize
96}
97
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct Board {
100 pub cells: Vec<BoardCell>,
101}
102
103impl fmt::Display for Board {
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 for y in 0..BOARD_SIZE {
106 for x in 0..BOARD_SIZE {
107 self.get(Point::new(x as i32, y as i32)).unwrap().fmt(f)?;
108 }
109 writeln!(f)?
110 }
111 Ok(())
112 }
113}
114
115impl ReadableBoard for Board {
116 #[inline]
117 fn is_in_bounds(&self, point: Point) -> bool {
118 (0..BOARD_SIZE).contains(&(point.x as u32)) && (0..BOARD_SIZE).contains(&(point.y as u32))
119 }
120
121 fn get(&self, point: Point) -> Option<&BoardCell> {
122 if !self.is_in_bounds(point) {
123 return None;
124 }
125
126 self.cells.get(xy_to_idx(BOARD_SIZE, point))
127 }
128}
129
130impl Board {
131 pub fn new() -> Board {
132 let cells = BOARD.chars().map(|x| BoardCell::from(x)).collect();
133 Board { cells }
134 }
135
136 #[allow(dead_code)]
137 fn set(&mut self, point: Point, bc: BoardCell) -> Result<()> {
138 if !self.is_in_bounds(point) {
139 return Err(Error::BadAction("Out of bounds".to_string()).into());
140 }
141 self.cells[xy_to_idx(BOARD_SIZE, point)] = bc;
142 Ok(())
143 }
144
145 fn get_mut(&mut self, point: Point) -> Option<&mut BoardCell> {
146 if !self.is_in_bounds(point) {
147 return None;
148 }
149
150 self.cells.get_mut(xy_to_idx(BOARD_SIZE, point))
151 }
152
153 fn for_each_mut(&mut self, strip: &Strip, f: &mut dyn FnMut(Point, &mut BoardCell) -> bool) {
154 let mut loc = strip.start;
155
156 for _ in 0..strip.len {
157 if let Some(bc) = self.get_mut(loc) {
158 if !f(loc, bc) {
159 return;
160 }
161 } else {
162 return;
163 }
164
165 loc += strip.dir;
166 }
167 }
168}
169
170pub struct BoardWithOverlay {
175 board: Board,
176 strip: Strip,
177 board_cells: Vec<Option<BoardCell>>,
178}
179
180pub struct OverlaidWord(Vec<(BoardCell, Option<BoardCell>)>);
181
182impl std::ops::Deref for OverlaidWord {
183 type Target = Vec<(BoardCell, Option<BoardCell>)>;
184
185 fn deref(&self) -> &Self::Target {
186 &self.0
187 }
188}
189
190impl OverlaidWord {
191 pub fn calculate_word_and_score(&self) -> Result<(String, u32)> {
192 let mut aggregate_word = Vec::<char>::with_capacity(self.len());
193
194 let mut letter_score = 0;
195 let mut word_multiplier = 1;
196
197 for (bc, bottom_bc) in self.iter() {
198 let curr_letter_val = match bc {
199 BoardCell::Tile(tile) => {
200 match tile {
201 Tile::Letter(letter) => aggregate_word.push(*letter),
202 _ => unreachable!(),
203 }
204 tile.point_value()
205 }
206 _ => unreachable!(),
207 };
208
209 if let Some(under_board_cell) = bottom_bc {
210 let BoardCellMultiplier {
211 word: word_mult,
212 letter: letter_mult,
213 } = under_board_cell.get_multiplier();
214
215 letter_score += curr_letter_val * letter_mult;
216 word_multiplier *= word_mult;
217 } else {
218 letter_score += curr_letter_val;
219 }
220 }
221
222 let word = aggregate_word.into_iter().collect::<String>();
223 if !DICTIONARY.contains(&word[..]) {
224 Err(Error::InvalidWord(word).into())
225 } else {
226 Ok((word, letter_score * word_multiplier))
227 }
228 }
229
230 pub fn ensure_word_covering_starting_spot(&self) -> Result<()> {
231 for (_, under_bc) in self.iter() {
232 if let Some(BoardCell::StartingSpot) = under_bc {
233 return Ok(());
234 }
235 }
236
237 Err(Error::StartingTileNotCovered.into())
238 }
239}
240
241impl BoardWithOverlay {
242 fn get_overlay_mask(
243 board: &Board,
244 strip: &Strip,
245 word: &str,
246 ) -> Result<Vec<Option<BoardCell>>> {
247 let mut curr_point = strip.start;
248
249 let mut mask = Vec::<Option<BoardCell>>::with_capacity(strip.len as usize);
250
251 for curr_letter in word.chars() {
252 let board_cell = board.get(curr_point).ok_or_else(|| {
253 Error::BadAction("This placement goes off of the board!".to_string())
254 })?;
255
256 match board_cell {
257 BoardCell::Tile(Tile::Letter(letter)) => {
258 if letter == &curr_letter {
259 mask.push(None);
260 } else {
261 return Err(Error::BadAction("Pieces do not fit".to_string()).into());
262 }
263 }
264 _ => mask.push(Some(BoardCell::Tile(Tile::Letter(curr_letter)))),
265 }
266
267 curr_point += strip.dir;
268 }
269
270 Ok(mask)
271 }
272
273 pub fn try_overlay(
274 board: Board,
275 point: Point,
276 dir: Direction,
277 word: &str,
278 ) -> Result<BoardWithOverlay> {
279 let strip = Strip::new(point, dir, word.len() as i32);
280
281 let overlay_mask = Self::get_overlay_mask(&board, &strip, word)?;
282
283 let bwo = BoardWithOverlay {
284 board,
285 strip,
286 board_cells: overlay_mask,
287 };
288
289 Ok(bwo)
290 }
291
292 pub fn get_overlaid_letters(&self) -> Vec<Tile> {
293 self.board_cells
294 .iter()
295 .filter(|w| w.is_some())
296 .map(|w| match *w {
297 Some(BoardCell::Tile(tile)) => tile,
298 _ => unreachable!(),
299 })
300 .collect()
301 }
302
303 fn get_overlay_at(&self, point: Point) -> Option<&BoardCell> {
304 let dist_from_start = self.strip.distance_in(point)?;
305
306 self.board_cells[dist_from_start as usize].as_ref()
307 }
308
309 fn is_point_covered(&self, point: Point) -> bool {
310 self.get_overlay_at(point).is_some()
311 }
312
313 pub fn get_connecting_letters_from(
317 &self,
318 start: Point,
319 dir: Direction,
320 ) -> Vec<(BoardCell, Option<BoardCell>)> {
321 let mut accum_vec = Vec::<(BoardCell, Option<BoardCell>)>::new();
322
323 self.for_each_until(start, dir, &mut |point, board_cell| match *board_cell {
324 BoardCell::Tile(_) => {
325 accum_vec.push((
326 (*board_cell).clone(),
327 if self.strip.contains(point) {
328 Some((*self.board.get(point).unwrap()).clone())
329 } else {
330 None
331 },
332 ));
333 true
334 }
335 _ => false,
336 });
337
338 accum_vec
339 }
340
341 pub fn get_whole_word(&self, start: Point, dir: Direction) -> OverlaidWord {
342 let mut word = Vec::new();
343
344 let mut opposite_dir = self.get_connecting_letters_from(start + (dir * -1), dir * -1);
345 opposite_dir.reverse();
346
347 word.append(&mut opposite_dir);
348 word.append(&mut self.get_connecting_letters_from(start, dir));
349
350 OverlaidWord(word)
351 }
352
353 pub fn get_formed_words(&self) -> (OverlaidWord, Vec<OverlaidWord>) {
354 let main_line_word = self.get_whole_word(self.strip.start, self.strip.dir);
355
356 let mut branching_words = Vec::new();
357
358 let perp_direction = if self.strip.dir.is_horizontal() {
359 Direction::down()
360 } else {
361 Direction::right()
362 };
363
364 self.for_each(&self.strip, &mut |point, _| {
365 if self.is_point_covered(point) {
366 let word = self.get_whole_word(point, perp_direction);
367
368 if word.len() > 1 {
369 branching_words.push(word)
370 }
371 }
372 true
373 });
374
375 (main_line_word, branching_words)
376 }
377
378 pub fn apply_to_board(mut self) -> Board {
379 let mut i = 0usize;
380 let board_cells = &self.board_cells;
381
382 self.board.for_each_mut(&self.strip, &mut |_, board_cell| {
383 if let Some(ref new_board_cell) = board_cells[i] {
384 *board_cell = new_board_cell.clone();
385 }
386 i += 1;
387 true
388 });
389
390 self.board
391 }
392}
393
394impl ReadableBoard for BoardWithOverlay {
395 fn is_in_bounds(&self, point: Point) -> bool {
396 self.board.is_in_bounds(point)
397 }
398
399 fn get(&self, point: Point) -> Option<&BoardCell> {
400 if !self.is_in_bounds(point) {
401 return None;
402 }
403
404 if !self.strip.contains(point) {
405 return self.board.get(point);
407 }
408
409 if let Some(ref cell) = self.get_overlay_at(point) {
413 Some(cell)
414 } else {
415 self.board.get(point)
416 }
417 }
418}
419
420trait IterableBoard {
421 fn for_each_until(
422 &self,
423 start: Point,
424 dir: Direction,
425 f: &mut dyn FnMut(Point, &BoardCell) -> bool,
426 );
427
428 fn for_each(&self, strip: &Strip, f: &mut dyn FnMut(Point, &BoardCell) -> bool);
429}
430
431impl<T: ReadableBoard> IterableBoard for T {
432 fn for_each_until(
439 &self,
440 start: Point,
441 dir: Direction,
442 f: &mut dyn FnMut(Point, &BoardCell) -> bool,
443 ) {
444 let mut loc = start;
445
446 loop {
447 if let Some(bc) = self.get(loc) {
448 if !f(loc, bc) {
449 return;
450 }
451 } else {
452 return;
453 }
454
455 loc += dir;
456 }
457 }
458
459 fn for_each(&self, strip: &Strip, f: &mut dyn FnMut(Point, &BoardCell) -> bool) {
460 let mut loc = strip.start;
461
462 for _ in 0..strip.len {
463 if let Some(bc) = self.get(loc) {
464 if !f(loc, bc) {
465 return;
466 }
467 } else {
468 return;
469 }
470
471 loc += strip.dir;
472 }
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479
480 #[test]
481 fn at_no_tile_test() {
482 let board = Board::new();
483
484 assert_eq!(board.get(Point::new(0, 0)).unwrap(), &BoardCell::TripleWord);
485 assert_eq!(
486 board.get(Point::new(3, 0)).unwrap(),
487 &BoardCell::DoubleLetter
488 );
489 assert_eq!(board.get(Point::new(1, 1)).unwrap(), &BoardCell::DoubleWord);
490 assert_eq!(
491 board.get(Point::new(7, 7)).unwrap(),
492 &BoardCell::StartingSpot
493 );
494 assert_eq!(
495 board.get(Point::new(BOARD_SIZE as i32, BOARD_SIZE as i32)),
496 None
497 );
498 }
499
500 #[test]
501 fn set_and_get_tiles() {
502 let mut board = Board::new();
503
504 assert_eq!(
505 board
506 .set(Point::new(0, 0), BoardCell::Tile(Tile::Letter('A')))
507 .is_ok(),
508 true
509 );
510 assert_eq!(
511 board.get(Point::new(0, 0)).unwrap(),
512 &BoardCell::Tile(Tile::Letter('A'))
513 );
514
515 assert_eq!(
516 board
517 .set(
518 Point::new(BOARD_SIZE as i32, BOARD_SIZE as i32),
519 BoardCell::Empty
520 )
521 .is_err(),
522 true
523 );
524 }
525
526 #[test]
527 fn pieces_for_place() {
528 let mut board = Board::new();
529 let mut board_with_overlay =
530 BoardWithOverlay::try_overlay(board, Point::new(0, 0), Direction::new(1, 0), "HI")
531 .unwrap();
532
533 assert_eq!(
534 board_with_overlay.get_overlaid_letters(),
535 vec![Tile::Letter('H'), Tile::Letter('I')]
536 );
537
538 board = Board::new();
539 board
540 .set(Point::new(1, 0), BoardCell::Tile(Tile::Letter('E')))
541 .unwrap();
542 board
543 .set(Point::new(3, 0), BoardCell::Tile(Tile::Letter('L')))
544 .unwrap();
545 board_with_overlay =
546 BoardWithOverlay::try_overlay(board, Point::new(0, 0), Direction::new(1, 0), "HELLO")
547 .unwrap();
548
549 assert_eq!(
550 board_with_overlay.get_overlaid_letters(),
551 vec![Tile::Letter('H'), Tile::Letter('L'), Tile::Letter('O')]
552 );
553 }
554
555 #[test]
556 fn pieces_for_place_err() {
557 let mut board = Board::new();
558 let mut board_with_overlay = BoardWithOverlay::try_overlay(
559 board,
560 Point::new(0, 0),
561 Direction::new(1, 0),
562 "REALLY LONG WORD THAT OVERFLOWS THE ENTIRE BOARD",
563 );
564
565 assert_eq!(board_with_overlay.is_err(), true);
566
567 board = Board::new();
568 board_with_overlay = BoardWithOverlay::try_overlay(
569 board,
570 Point::new(10, 0),
571 Direction::new(1, 0),
572 "LONGWORD",
573 );
574
575 assert_eq!(board_with_overlay.is_err(), true);
576 }
577
578 fn make_board_with_overlay() -> Result<BoardWithOverlay> {
579 let mut board = Board::new();
588 board.set(Point::new(1, 0), BoardCell::Tile(Tile::from('P')))?;
589 board.set(Point::new(1, 1), BoardCell::Tile(Tile::from('R')))?;
590 board.set(Point::new(1, 3), BoardCell::Tile(Tile::from('M')))?;
592 board.set(Point::new(1, 4), BoardCell::Tile(Tile::from('E')))?;
593
594 board.set(Point::new(3, 0), BoardCell::Tile(Tile::from('C')))?;
595 board.set(Point::new(3, 1), BoardCell::Tile(Tile::from('R')))?;
596 board.set(Point::new(3, 3), BoardCell::Tile(Tile::from('E')))?;
598 board.set(Point::new(3, 4), BoardCell::Tile(Tile::from('K')))?;
599
600 board.set(Point::new(4, 2), BoardCell::Tile(Tile::from('D')))?;
601
602 let board_overlay =
603 BoardWithOverlay::try_overlay(board, Point::new(0, 2), Direction::right(), "MINED")?;
604
605 Ok(board_overlay)
606 }
607
608 #[test]
609 fn get_overlay_at() -> Result<()> {
610 let board = make_board_with_overlay()?;
611
612 assert_eq!(
613 board.get_overlay_at(Point::new(0, 2)),
614 Some(&BoardCell::Tile(Tile::from('M')))
615 );
616
617 assert_eq!(
618 board.get_overlay_at(Point::new(1, 2)),
619 Some(&BoardCell::Tile(Tile::from('I')))
620 );
621
622 assert_eq!(
623 board.get_overlay_at(Point::new(3, 2)),
624 Some(&BoardCell::Tile(Tile::from('E')))
625 );
626
627 assert_eq!(board.get_overlay_at(Point::new(0, 1)), None);
628 assert_eq!(board.get_overlay_at(Point::new(4, 3)), None);
629 assert_eq!(board.get_overlay_at(Point::new(2, 4)), None);
630 Ok(())
631 }
632
633 #[test]
634 fn full_overlay_test() -> Result<()> {
635 let board_overlay = make_board_with_overlay()?;
636
637 let (main_word, perp_words) = board_overlay.get_formed_words();
638
639 assert_eq!(main_word.len(), 5);
640 assert_eq!(perp_words.len(), 2);
641
642 Ok(())
643 }
644
645 #[test]
646 fn apply_to_board() -> Result<()> {
647 let board_overlay = make_board_with_overlay()?;
648
649 let board = board_overlay.apply_to_board();
650
651 assert_eq!(
652 board.get(Point::new(0, 2)).unwrap(),
653 &BoardCell::Tile(Tile::from('M'))
654 );
655 assert_eq!(
656 board.get(Point::new(1, 2)).unwrap(),
657 &BoardCell::Tile(Tile::from('I'))
658 );
659 assert_eq!(
660 board.get(Point::new(2, 2)).unwrap(),
661 &BoardCell::Tile(Tile::from('N'))
662 );
663 assert_eq!(
664 board.get(Point::new(3, 2)).unwrap(),
665 &BoardCell::Tile(Tile::from('E'))
666 );
667 assert_eq!(
668 board.get(Point::new(4, 2)).unwrap(),
669 &BoardCell::Tile(Tile::from('D'))
670 );
671
672 Ok(())
673 }
674}