crossword_puzzle/
lib.rs

1//! This crate provides functionality for generating crossword puzzles.
2//! It includes data structures for representing words, grid, and algorithms
3//! for placing words and solving the puzzle.
4
5use std::collections::VecDeque;
6use std::fmt::Debug;
7
8use crate::error::{Error, GridError, WordError};
9use crate::word::{Direction, Position, Word};
10
11pub mod error;
12pub mod word;
13
14/// `Neighbor` represents the characters and their positions in the cells immediately adjacent to a given position on the crossword grid.
15/// It is used internally to check for conflicts or valid placements when adding words.
16#[derive(Debug, Default)]
17pub struct Neighbor {
18    /// The character and `Position` of the cell directly above the current position.
19    /// `None` if the cell is out of bounds or empty.
20    pub up: Option<(Position, char)>,
21    /// The character and `Position` of the cell directly to the right of the current position.
22    /// `None` if the cell is out of bounds or empty.
23    pub right: Option<(Position, char)>,
24    /// The character and `Position` of the cell directly below the current position.
25    /// `None` if the cell is out of bounds or empty.
26    pub down: Option<(Position, char)>,
27    /// The character and `Position` of the cell directly to the left of the current position.
28    /// `None` if the cell is out of bounds or empty.
29    pub left: Option<(Position, char)>,
30}
31
32/// `Grid` represents the crossword puzzle board and manages the placement and validation of words.
33/// It dynamically resizes to accommodate words and provides methods for adding words and finding valid placements.
34#[derive(Clone, Debug)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub struct Grid<'a> {
37    /// A collection of `Word`s that have been successfully placed on the grid.
38    pub words: Vec<Word<'a>>,
39    /// The 2D vector of characters representing the crossword board itself.
40    /// Empty cells are typically represented by a space character (' ').
41    pub board: Vec<Vec<char>>,
42}
43
44impl<'a> Default for Grid<'a> {
45    /// Creates a new empty `Grid`.
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51/// Type alias for a function that calculates the (x, y) position of a character within a word.
52/// It takes a reference to a `Word` and an index, returning the `(x, y)` coordinates.
53type GetPosFn<'a> = Box<dyn Fn(&Word<'a>, usize) -> (usize, usize)>;
54/// Type alias for a function that extracts the origin coordinate (x or y) of a word.
55/// It takes a reference to a `Word` and returns its origin coordinate as a `usize`.
56type GetOriginFn<'a> = Box<dyn Fn(&Word<'a>) -> usize>;
57/// Type alias representing a tuple used to help with word placement.
58/// It contains the `Direction` of placement, a `GetPosFn` for calculating character positions,
59/// and a `GetOriginFn` for determining the word's origin.
60type PlacementHelper<'a> = (Direction, GetPosFn<'a>, GetOriginFn<'a>);
61
62impl<'a> Grid<'a> {
63    /// Creates a new, empty `Grid` instance.
64    /// The grid is initialized with a single empty cell (`' '`).
65    ///
66    /// # Returns
67    ///
68    /// A new `Grid` with an empty `words` vector and a `board` containing one empty cell.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use crossword_puzzle::Grid;
74    ///
75    /// let grid = Grid::new();
76    /// assert!(grid.words.is_empty());
77    /// assert_eq!(grid.board, vec![vec![' ']]);
78    /// ```
79    pub fn new() -> Self {
80        Self {
81            words: vec![],
82            board: vec![vec![' ']],
83        }
84    }
85
86    /// Adds a `Word` to the grid.
87    ///
88    /// This function first ensures the grid is large enough to accommodate the new word,
89    /// then updates the word's internal position, fills the corresponding cells on the board
90    /// with the word's characters, and finally adds the word to the grid's list of placed words.
91    ///
92    /// # Arguments
93    ///
94    /// * `word` - A mutable `Word` instance to be added to the grid.
95    ///
96    /// # Returns
97    ///
98    /// - `Ok(())` if the word was successfully added.
99    /// - `Err(GridError)` if there was an issue with resizing the grid or filling the word.
100    ///
101    /// # Errors
102    ///
103    /// Returns a `GridError` if:
104    /// - The grid cannot be resized to fit the word.
105    /// - The word's direction is `NotSet` during filling.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use crossword_puzzle::{Grid, word::{Word, Direction}};
111    ///
112    /// let mut grid = Grid::new();
113    /// let word = Word::value("", 'R', "UST").unwrap().direction(Direction::Horizontal);
114    /// assert!(grid.add_word(word).is_ok());
115    /// ```
116    pub fn add_word(&mut self, mut word: Word<'a>) -> Result<(), GridError> {
117        self.ensure_grid_size(&mut word)?;
118        word.update_position();
119        self.fill_word(&word)?;
120
121        self.words.push(word);
122
123        for word in self.words.iter_mut() {
124            word.update_position();
125        }
126
127        Ok(())
128    }
129
130    /// Recursively resizes the grid by adding empty cells in the specified direction.
131    ///
132    /// This function expands the grid by `amount` in the given `direction`.
133    /// If `is_prepend` is `true`, cells are added at the beginning (top or left);
134    /// otherwise, they are added at the end (bottom or right).
135    /// Word positions are adjusted accordingly if cells are prepended.
136    ///
137    /// # Arguments
138    ///
139    /// * `amount` - The number of cells to add.
140    /// * `direction` - The `Direction` in which to resize the grid (Horizontal or Vertical).
141    /// * `is_prepend` - A boolean indicating whether to add cells at the beginning (`true`) or end (`false`).
142    ///
143    /// # Returns
144    ///
145    /// - `Ok(())` if the grid was successfully resized.
146    /// - `Err(GridError)` if an invalid direction is provided.
147    ///
148    /// # Errors
149    ///
150    /// Returns a `GridError::InvalidDirection` if `Direction::NotSet` is provided.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use crossword_puzzle::{Grid, word::Direction};
156    ///
157    /// let mut grid = Grid::new();
158    /// // Resize horizontally by 5 cells, prepending them
159    /// assert!(grid.resize_grid(5, Direction::Horizontal, true).is_ok());
160    /// // Resize vertically by 3 cells, appending them
161    /// assert!(grid.resize_grid(3, Direction::Vertical, false).is_ok());
162    /// ```
163    pub fn resize_grid(
164        &mut self,
165        amount: usize,
166        direction: Direction,
167        is_prepend: bool,
168    ) -> Result<(), GridError> {
169        if amount == 0 {
170            return Ok(());
171        }
172
173        match direction {
174            Direction::Horizontal => {
175                for row in self.board.iter_mut() {
176                    if is_prepend {
177                        row.insert(0, ' ');
178                    } else {
179                        row.push(' ');
180                    }
181                }
182
183                if is_prepend {
184                    for word in self.words.iter_mut() {
185                        word.position.x += 1;
186                    }
187                }
188            }
189            Direction::Vertical => {
190                let length = self.board[0].len();
191                if is_prepend {
192                    self.board.insert(0, [' '].repeat(length));
193                } else {
194                    self.board.push([' '].repeat(length));
195                }
196                if is_prepend {
197                    for word in self.words.iter_mut() {
198                        word.position.y += 1;
199                    }
200                }
201            }
202            Direction::NotSet => {
203                return Err(GridError::InvalidDirection(
204                    "Invalid direction for grid resize.".to_string(),
205                ))
206            }
207        }
208
209        self.resize_grid(amount - 1, direction, is_prepend)
210    }
211
212    /// Ensures the grid is large enough to accommodate a given `Word`.
213    ///
214    /// This function checks if the word, including its prefix and suffix, would extend
215    /// beyond the current grid boundaries. If necessary, it resizes the grid by calling
216    /// `resize_grid` and adjusts the word's position to reflect the new grid dimensions.
217    ///
218    /// # Arguments
219    ///
220    /// * `word` - A mutable reference to the `Word` to be placed.
221    ///
222    /// # Returns
223    ///
224    /// - `Ok(())` if the grid is successfully ensured to be large enough.
225    /// - `Err(GridError)` if there's an issue during grid resizing.
226    ///
227    /// # Errors
228    ///
229    /// Returns a `GridError` if `resize_grid` encounters an error (e.g., `InvalidDirection`).
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use crossword_puzzle::{Grid, word::{Word, Direction}};
235    ///
236    /// let mut grid = Grid::new();
237    /// let mut word = Word::value("", 'A', "BC").unwrap().position(0, 0).direction(Direction::Horizontal);
238    /// // Initially, the grid is 1x1. This will cause it to resize.
239    /// assert!(grid.ensure_grid_size(&mut word).is_ok());
240    /// ```
241    pub fn ensure_grid_size(&mut self, word: &mut Word<'a>) -> Result<(), GridError> {
242        let position = &mut word.position;
243        let segment = &word.segment;
244
245        let prefix_pos = if word.direction == Direction::Horizontal {
246            position.x as isize - segment.prefix.len() as isize
247        } else {
248            position.y as isize - segment.prefix.len() as isize
249        };
250
251        if prefix_pos < 0 {
252            let abs_prefix_pos = prefix_pos.unsigned_abs();
253            self.resize_grid(abs_prefix_pos, word.direction, true)?;
254
255            if word.direction == Direction::Horizontal {
256                position.x += abs_prefix_pos;
257            } else {
258                position.y += abs_prefix_pos;
259            }
260        }
261
262        let suffix_pos = if word.direction == Direction::Horizontal {
263            let length = position.x.saturating_add(segment.suffix.len()) + 1;
264            self.board[0].len() as isize - length as isize
265        } else {
266            let length = position.y.saturating_add(segment.suffix.len()) + 1;
267            self.board.len() as isize - length as isize
268        };
269        if suffix_pos < 0 {
270            self.resize_grid(suffix_pos.unsigned_abs(), word.direction, false)?;
271        }
272
273        Ok(())
274    }
275
276    /// Fills the grid with the characters of a given `Word`.
277    ///
278    /// This function iterates through the characters of the word and places them onto the `board`
279    /// at the word's calculated `position` and `direction`.
280    ///
281    /// # Arguments
282    ///
283    /// * `word` - A reference to the `Word` whose characters will fill the grid.
284    ///
285    /// # Returns
286    ///
287    /// - `Ok(())` if the word was successfully filled onto the grid.
288    /// - `Err(GridError)` if an invalid direction is provided.
289    ///
290    /// # Errors
291    ///
292    /// Returns a `GridError::InvalidDirection` if `Direction::NotSet` is provided.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use crossword_puzzle::{Grid, word::{Word, Direction}};
298    ///
299    /// let mut grid = Grid::new();
300    /// let word = Word::value("", 'T', "EST").unwrap().position(0, 0).direction(Direction::Horizontal);
301    /// assert!(grid.add_word(word).is_ok());
302    /// ```
303    pub fn fill_word(&mut self, word: &Word<'a>) -> Result<(), GridError> {
304        match word.direction {
305            Direction::Horizontal => {
306                for (ch, index) in word.segment.full_word().iter().zip(word.origin.x..) {
307                    self.board[word.position.y][index] = *ch;
308                }
309            }
310            Direction::Vertical => {
311                for (ch, index) in word.segment.full_word().iter().zip(word.origin.y..) {
312                    self.board[index][word.position.x] = *ch;
313                }
314            }
315            Direction::NotSet => {
316                return Err(GridError::InvalidDirection(
317                    "Invalid direction for filling word.".to_string(),
318                ))
319            }
320        }
321        Ok(())
322    }
323
324    /*
325     * VALIDATOR
326     */
327
328    /// Returns the character at the given `Position` on the grid, if it exists.
329    ///
330    /// This function safely retrieves a character from the `board` at the specified `Position`,
331    /// returning `None` if the position is out of bounds.
332    ///
333    /// # Arguments
334    ///
335    /// * `position` - The `Position` (x, y) on the grid to retrieve the character from.
336    ///
337    /// # Returns
338    ///
339    /// - `Some(char)` if a character exists at the given position.
340    /// - `None` if the position is out of bounds.
341    ///
342    /// # Examples
343    ///
344    /// ```
345    /// use crossword_puzzle::{Grid, word::{Word, Direction, Position}};
346    ///
347    /// let mut grid = Grid::new();
348    /// let word = Word::value("", 'A', "").unwrap().position(0, 0).direction(Direction::Horizontal);
349    /// grid.add_word(word).unwrap();
350    ///
351    /// assert_eq!(grid.get_char(Position { x: 0, y: 0 }), Some('A'));
352    /// assert_eq!(grid.get_char(Position { x: 1, y: 0 }), None);
353    /// ```
354    pub fn get_char(&self, position: Position) -> Option<char> {
355        self.board
356            .get(position.y)
357            .and_then(|col| col.get(position.x))
358            .copied()
359    }
360
361    /// Helper function to get a neighbor at a given offset from the current position.
362    ///
363    /// Returns `Some((Position, char))` if the neighbor exists and is within bounds,
364    /// otherwise `None`.
365    ///
366    /// # Arguments
367    ///
368    /// * `current_pos` - The starting `Position`.
369    /// * `dx` - The x-offset (change in column).
370    /// * `dy` - The y-offset (change in row).
371    ///
372    /// # Returns
373    ///
374    /// - `Some((Position, char))` if the neighbor exists and is within bounds.
375    /// - `None` if the new position is out of bounds.
376    ///
377    /// # Examples
378    ///
379    /// ```
380    /// use crossword_puzzle::{Grid, word::{Word, Direction, Position}};
381    ///
382    /// let mut grid = Grid::new();
383    /// let word = Word::value("", 'A', "").unwrap().position(0, 0).direction(Direction::Horizontal);
384    /// grid.add_word(word).unwrap();
385    ///
386    /// // Get the character at (0,0) (the current position)
387    /// assert_eq!(grid.get_neighbor_at_offset(Position { x: 0, y: 0 }, 0, 0), Some((Position { x: 0, y: 0 }, 'A')));
388    /// // Try to get a neighbor out of bounds
389    /// assert_eq!(grid.get_neighbor_at_offset(Position { x: 0, y: 0 }, -1, 0), None);
390    /// ```
391    pub fn get_neighbor_at_offset(
392        &self,
393        current_pos: Position,
394        dx: isize,
395        dy: isize,
396    ) -> Option<(Position, char)> {
397        let new_x = current_pos.x as isize + dx;
398        let new_y = current_pos.y as isize + dy;
399
400        if new_x >= 0 && new_y >= 0 {
401            let new_pos = Position {
402                x: new_x as usize,
403                y: new_y as usize,
404            };
405            self.get_char(new_pos).map(|ch| (new_pos, ch))
406        } else {
407            None
408        }
409    }
410
411    /// Returns the `Neighbor` struct for a given `Position` on the grid.
412    ///
413    /// This function checks the cells immediately above, right, below, and left of the given `position`.
414    /// It returns a `Neighbor` struct containing the character and position of each existing neighbor.
415    ///
416    /// # Arguments
417    ///
418    /// * `position` - The `Position` for which to find neighbors.
419    ///
420    /// # Returns
421    ///
422    /// A `Neighbor` struct populated with the characters and positions of adjacent cells.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// use crossword_puzzle::{Grid, word::{Word, Direction, Position}, Neighbor};
428    ///
429    /// let mut grid = Grid::new();
430    /// let word = Word::value("", 'A', "").unwrap().position(0, 0).direction(Direction::Horizontal);
431    /// grid.add_word(word).unwrap();
432    ///
433    /// let neighbors = grid.get_neighbor(Position { x: 0, y: 0 });
434    /// assert!(neighbors.up.is_none());
435    /// assert!(neighbors.right.is_none());
436    /// assert!(neighbors.down.is_none());
437    /// assert!(neighbors.left.is_none());
438    /// ```
439    pub fn get_neighbor(&self, position: Position) -> Neighbor {
440        Neighbor {
441            up: self.get_neighbor_at_offset(position, 0, -1),
442            right: self.get_neighbor_at_offset(position, 1, 0),
443            down: self.get_neighbor_at_offset(position, 0, 1),
444            left: self.get_neighbor_at_offset(position, -1, 0),
445        }
446    }
447
448    /// Calculates the next `Position` based on the current position, direction, and step.
449    ///
450    /// This helper function determines the new `Position` by moving `step` units from `current_pos`
451    /// in the specified `direction` (Horizontal or Vertical).
452    ///
453    /// # Arguments
454    ///
455    /// * `current_pos` - The starting `Position`.
456    /// * `direction` - The `Direction` of movement (Horizontal or Vertical).
457    /// * `step` - The number of units to move (can be negative for backward movement).
458    ///
459    /// # Returns
460    ///
461    /// - `Ok(Position)` representing the new calculated position.
462    /// - `Err(GridError)` if an invalid direction is provided.
463    ///
464    /// # Errors
465    ///
466    /// Returns a `GridError::InvalidDirection` if `Direction::NotSet` is provided.
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// use crossword_puzzle::{Grid, word::{Direction, Position}};
472    ///
473    /// let grid = Grid::new();
474    /// let pos = Position { x: 5, y: 5 };
475    ///
476    /// // Move horizontally by 2
477    /// assert_eq!(grid.get_next_pos(pos, Direction::Horizontal, 2).unwrap(), Position { x: 7, y: 5 });
478    /// // Move vertically by -3
479    /// assert_eq!(grid.get_next_pos(pos, Direction::Vertical, -3).unwrap(), Position { x: 5, y: 2 });
480    /// ```
481    pub fn get_next_pos(
482        &self,
483        current_pos: Position,
484        direction: Direction,
485        step: isize,
486    ) -> Result<Position, GridError> {
487        match direction {
488            Direction::Horizontal => Ok(Position {
489                x: (current_pos.x as isize + step) as usize,
490                y: current_pos.y,
491            }),
492            Direction::Vertical => Ok(Position {
493                x: current_pos.x,
494                y: (current_pos.y as isize + step) as usize,
495            }),
496            Direction::NotSet => Err(GridError::InvalidDirection("Invalid direction".to_string())),
497        }
498    }
499
500    /// Retrieves the coordinate value (x or y) based on the given direction.
501    ///
502    /// This helper function extracts either the `x` or `y` coordinate from `current_pos`
503    /// depending on the provided `direction` (Horizontal or Vertical).
504    ///
505    /// # Arguments
506    ///
507    /// * `current_pos` - The `Position` from which to extract the coordinate.
508    /// * `direction` - The `Direction` indicating which coordinate to retrieve (Horizontal for x, Vertical for y).
509    ///
510    /// # Returns
511    ///
512    /// - `Ok(usize)` representing the extracted coordinate value.
513    /// - `Err(GridError)` if an invalid direction is provided.
514    ///
515    /// # Errors
516    ///
517    /// Returns a `GridError::InvalidDirection` if `Direction::NotSet` is provided.
518    ///
519    /// # Examples
520    ///
521    /// ```
522    /// use crossword_puzzle::{Grid, word::{Direction, Position}};
523    ///
524    /// let grid = Grid::new();
525    /// let pos = Position { x: 10, y: 20 };
526    ///
527    /// assert_eq!(grid.get_coord_val(pos, Direction::Horizontal).unwrap(), 10);
528    /// assert_eq!(grid.get_coord_val(pos, Direction::Vertical).unwrap(), 20);
529    /// ```
530    pub fn get_coord_val(
531        &self,
532        current_pos: Position,
533        direction: Direction,
534    ) -> Result<usize, GridError> {
535        match direction {
536            Direction::Horizontal => Ok(current_pos.x),
537            Direction::Vertical => Ok(current_pos.y),
538            Direction::NotSet => Err(GridError::InvalidDirection("Invalid direction".to_string())),
539        }
540    }
541
542    /// Helper function to check if a character option is empty or contains a space.
543    ///
544    /// This function is used to determine if a cell on the grid is effectively empty.
545    ///
546    /// # Arguments
547    ///
548    /// * `char_option` - An `Option` containing a `(Position, char)` tuple.
549    ///
550    /// # Returns
551    ///
552    /// - `true` if `char_option` is `None` or if it contains a space character (`' '`).
553    /// - `false` otherwise.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// use crossword_puzzle::{Grid, word::Position};
559    ///
560    /// let grid = Grid::new();
561    ///
562    /// assert!(grid.is_char_empty_or_none(None));
563    /// assert!(grid.is_char_empty_or_none(Some((Position { x: 0, y: 0 }, ' '))));
564    /// assert!(!grid.is_char_empty_or_none(Some((Position { x: 0, y: 0 }, 'A'))));
565    /// ```
566    pub fn is_char_empty_or_none(&self, char_option: Option<(Position, char)>) -> bool {
567        match char_option {
568            Some((_, ch)) => ch == ' ',
569            None => true,
570        }
571    }
572
573    /// Checks if the neighboring cells in the specified direction are empty.
574    ///
575    /// This function is used to validate if a word can be placed without conflicting with
576    /// existing characters in adjacent cells (not directly part of the word's path).
577    /// It examines the cells perpendicular to the word's direction at `current_pos`.
578    /// For `Direction::Horizontal`, it checks `up` and `down` neighbors. For `Direction::Vertical`,
579    /// it checks `left` and `right` neighbors.
580    ///
581    /// # Arguments
582    ///
583    /// * `current_pos` - The `Position` to check neighbors around.
584    /// * `direction` - The `Direction` of the word being placed (Horizontal or Vertical).
585    ///
586    /// # Returns
587    ///
588    /// - `Ok(true)` if the relevant neighboring cells are empty.
589    /// - `Ok(false)` if any relevant neighboring cell contains a character.
590    /// - `Err(GridError)` if an invalid direction is provided.
591    ///
592    /// # Errors
593    ///
594    /// Returns a `GridError::InvalidDirection` if `Direction::NotSet` is provided.
595    ///
596    /// # Examples
597    ///
598    /// ```
599    /// use crossword_puzzle::{Grid, word::{Word, Direction, Position}};
600    ///
601    /// let mut grid = Grid::new();
602    /// // Initially, all cells are empty
603    /// assert!(grid.is_neighbor_cell_empty(Position { x: 0, y: 0 }, Direction::Horizontal).unwrap());
604    ///
605    /// // Place a word to make some cells occupied
606    /// let word = Word::value("A", 'B', "C").unwrap().position(0, 0).direction(Direction::Vertical);
607    /// grid.add_word(word).unwrap();
608    ///
609    /// // Now, checking (0,0) horizontally should return false because (0,1) is occupied by 'B'
610    /// assert!(!grid.is_neighbor_cell_empty(Position { x: 0, y: 0 }, Direction::Horizontal).unwrap());
611    /// ```
612    pub fn is_neighbor_cell_empty(
613        &self,
614        current_pos: Position,
615        direction: Direction,
616    ) -> Result<bool, GridError> {
617        let neighbor = self.get_neighbor(current_pos);
618
619        match direction {
620            Direction::Horizontal => Ok(self.is_char_empty_or_none(neighbor.up)
621                && self.is_char_empty_or_none(neighbor.down)),
622            Direction::Vertical => Ok(self.is_char_empty_or_none(neighbor.left)
623                && self.is_char_empty_or_none(neighbor.right)),
624            Direction::NotSet => Err(GridError::InvalidDirection("Invalid direction".to_string())),
625        }
626    }
627
628    /// Checks if a `Word` can be validly placed on the grid.
629    ///
630    /// This function performs a series of checks to ensure that placing the given `word`
631    /// on the grid at its specified position and direction does not violate any crossword rules.
632    /// It verifies that the word does not overlap with existing characters incorrectly and that
633    /// adjacent cells are empty where required.
634    ///
635    /// # Arguments
636    ///
637    /// * `word` - A reference to the `Word` to validate for placement.
638    ///
639    /// # Returns
640    ///
641    /// - `Ok(true)` if the word can be validly placed.
642    /// - `Ok(false)` if the placement is invalid due to conflicts or rule violations.
643    /// - `Err(GridError)` if an invalid direction is provided during internal checks.
644    ///
645    /// # Errors
646    ///
647    /// Returns a `GridError` if any internal helper function (e.g., `get_next_pos`,
648    /// `is_neighbor_cell_empty`) encounters an `InvalidDirection` error.
649    ///
650    /// # Examples
651    ///
652    /// ```
653    /// use crossword_puzzle::{Grid, word::{Word, Direction}};
654    ///
655    /// let mut grid = Grid::new();
656    /// let word1 = Word::value("", 'T', "EST").unwrap().position(0, 0).direction(Direction::Horizontal);
657    /// grid.add_word(word1).unwrap();
658    ///
659    /// // A word that overlaps correctly (e.g., 'T' from TEST and TEAM)
660    /// let word2 = Word::value("", 'T', "EAM").unwrap().position(0, 0).direction(Direction::Vertical);
661    /// assert!(grid.is_valid_placement(&word2).unwrap());
662    ///
663    /// // A word that conflicts (e.g., 'B' from BEST conflicts with 'T' from TEST)
664    /// let word3 = Word::value("BES", 'T', "").unwrap().position(4, 0).direction(Direction::Horizontal);
665    /// assert!(!grid.is_valid_placement(&word3).unwrap());
666    /// ```
667    pub fn is_valid_placement(&self, word: &Word<'a>) -> Result<bool, GridError> {
668        let mut pos = word.position;
669
670        // Check prefix
671        if !self.check_segment_placement(word, &mut pos, word.segment.prefix.chars().rev(), -1)? {
672            return Ok(false);
673        }
674
675        // Check suffix
676        pos = word.position;
677        if !self.check_segment_placement(word, &mut pos, word.segment.suffix.chars(), 1)? {
678            return Ok(false);
679        }
680
681        Ok(true)
682    }
683
684    /// Helper function to check the placement of a word segment (prefix or suffix).
685    ///
686    /// This function iterates through the characters of a word segment and validates their
687    /// placement on the grid. It checks for conflicts with existing characters and ensures
688    /// that perpendicular neighbors are empty where required.
689    ///
690    /// # Arguments
691    ///
692    /// * `word` - A reference to the `Word` being checked.
693    /// * `pos` - A mutable reference to the current `Position` on the grid, which is updated during iteration.
694    /// * `chars` - An iterator over the characters of the segment (prefix or suffix).
695    /// * `step` - The step size and direction (e.g., `1` for forward, `-1` for backward).
696    ///
697    /// # Returns
698    ///
699    /// - `Ok(true)` if the segment can be validly placed.
700    /// - `Ok(false)` if a conflict is found.
701    /// - `Err(GridError)` if an invalid direction is provided during internal checks.
702    ///
703    /// # Errors
704    ///
705    /// Returns a `GridError` if any internal helper function (e.g., `get_next_pos`,
706    /// `is_neighbor_cell_empty`) encounters an `InvalidDirection` error.
707    pub fn check_segment_placement(
708        &self,
709        word: &Word<'a>,
710        pos: &mut Position,
711        chars: impl Iterator<Item = char>,
712        step: isize,
713    ) -> Result<bool, GridError> {
714        for ch in chars {
715            *pos = self.get_next_pos(*pos, word.direction, step)?;
716            if let Some(board_ch) = self.get_char(*pos) {
717                if board_ch == ch {
718                    continue;
719                }
720                if board_ch != ' ' && board_ch != ch {
721                    return Ok(false);
722                }
723                if !self.is_neighbor_cell_empty(*pos, word.direction)? {
724                    return Ok(false);
725                }
726                if self.get_coord_val(*pos, word.direction)? != 0 {
727                    continue;
728                }
729            }
730            break;
731        }
732        // After the loop, check the character at the next position if the iterator is exhausted
733        *pos = self.get_next_pos(*pos, word.direction, step)?;
734        if let Some(board_ch) = self.get_char(*pos) {
735            if board_ch != ' ' {
736                return Ok(false);
737            }
738        }
739        Ok(true)
740    }
741
742    /// Finds valid placements for a word segment based on a crossed character.
743    ///
744    /// This function searches for existing words on the grid that can be crossed by a new word segment.
745    /// It generates potential `Word` candidates based on the `prefix`, `crossed` character, `suffix`,
746    /// and `direction`, and then validates each potential placement using `is_valid_placement`.
747    ///
748    /// # Arguments
749    ///
750    /// * `prefix` - The string segment before the crossed character.
751    /// * `crossed` - The character that crosses an existing word.
752    /// * `suffix` - The string segment after the crossed character.
753    /// * `direction` - The `Direction` of the new word (Horizontal or Vertical).
754    ///
755    /// # Returns
756    ///
757    /// - `Ok(Vec<Word>)` containing a list of valid `Word` placements.
758    /// - `Err(GridError)` if an invalid direction is provided or a `WordError` occurs during `Word::value` creation.
759    ///
760    /// # Errors
761    ///
762    /// Returns a `GridError` if `Direction::NotSet` is provided or if `Word::value` returns a `WordError`.
763    pub fn find_valid_placements_for_segment(
764        &self,
765        prefix: &'a str,
766        crossed: char,
767        suffix: &'a str,
768        direction: Direction,
769    ) -> Result<Vec<Word<'a>>, GridError> {
770        let mut placements = Vec::new();
771        let (opposite_direction, get_pos, get_origin): PlacementHelper<'a> = match direction {
772            Direction::Horizontal => (
773                Direction::Vertical,
774                Box::new(|w, i| (w.position.x, i)),
775                Box::new(|w| w.origin.y),
776            ),
777            Direction::Vertical => (
778                Direction::Horizontal,
779                Box::new(|w, i| (i, w.position.y)),
780                Box::new(|w| w.origin.x),
781            ),
782            Direction::NotSet => {
783                return Err(GridError::InvalidDirection("Invalid direction".to_string()))
784            }
785        };
786
787        for word in self
788            .words
789            .iter()
790            .filter(|p| p.direction == opposite_direction)
791        {
792            let full_word = word.segment.full_word();
793            for (ch, index) in full_word.iter().zip(get_origin(word)..) {
794                if *ch == crossed {
795                    let (x, y) = get_pos(word, index);
796                    let new_word = Word::value(prefix, crossed, suffix)?
797                        .position(x, y)
798                        .direction(direction);
799
800                    if self.is_valid_placement(&new_word)? {
801                        placements.push(new_word);
802                    }
803                }
804            }
805        }
806
807        Ok(placements)
808    }
809
810    /// Finds all valid placements for a given word string on the current grid.
811    ///
812    /// This function iterates through each character of the `word_str` to consider it as a potential
813    /// crossing point. For each potential crossing, it attempts to find valid horizontal and vertical
814    /// placements on the grid. If the grid is empty, it considers initial horizontal and vertical placements.
815    ///
816    /// # Arguments
817    ///
818    /// * `word_str` - The string slice representing the word to find placements for.
819    ///
820    /// # Returns
821    ///
822    /// - `Ok(Vec<Word>)` containing a list of all valid `Word` placements found.
823    /// - `Err(GridError)` if an invalid direction is provided or a `WordError` occurs during `Word` creation.
824    ///
825    /// # Errors
826    ///
827    /// Returns a `GridError` if `handle_initial_placements` or `find_valid_placements_for_segment`
828    /// return an error.
829    ///
830    /// # Examples
831    ///
832    /// ```
833    /// use crossword_puzzle::{Grid, word::{Word, Direction}};
834    ///
835    /// let mut grid = Grid::new();
836    /// let word1 = Word::value("", 'R', "UST").unwrap().direction(Direction::Horizontal);
837    /// grid.add_word(word1).unwrap();
838    ///
839    /// let placements = grid.find_valid_placements("TEST").unwrap();
840    /// assert!(!placements.is_empty());
841    /// ```
842    pub fn find_valid_placements(&self, word_str: &'a str) -> Result<Vec<Word<'a>>, GridError> {
843        let mut placements = Vec::new();
844
845        for index in 0..word_str.len() {
846            let (prefix, remain) = word_str.split_at(index);
847            let (mid, suffix) = remain.split_at(1);
848            let crossed = mid.chars().next().unwrap();
849
850            if self.words.is_empty() {
851                placements.extend(self.handle_initial_placements(prefix, crossed, suffix)?);
852            } else {
853                placements.extend(self.find_valid_placements_for_segment(
854                    prefix,
855                    crossed,
856                    suffix,
857                    Direction::Horizontal,
858                )?);
859                placements.extend(self.find_valid_placements_for_segment(
860                    prefix,
861                    crossed,
862                    suffix,
863                    Direction::Vertical,
864                )?);
865            }
866        }
867
868        Ok(placements)
869    }
870
871    /// Handles the initial placements when the grid is empty.
872    ///
873    /// It generates both horizontal and vertical `Word` placements for the given segment,
874    /// assuming an empty grid where the word can be placed at the origin (0,0).
875    ///
876    /// # Arguments
877    ///
878    /// * `prefix` - The string segment before the crossed character.
879    /// * `crossed` - The character that would be at the crossing point.
880    /// * `suffix` - The string segment after the crossed character.
881    ///
882    /// # Returns
883    ///
884    /// - `Ok(Vec<Word>)` containing two `Word` candidates: one horizontal and one vertical.
885    /// - `Err(GridError)` if a `WordError` occurs during `Word::value` creation.
886    ///
887    /// # Errors
888    ///
889    /// Returns a `GridError` if `Word::value` returns a `WordError` (e.g., invalid segment).
890    ///
891    /// # Examples
892    ///
893    /// ```
894    /// use crossword_puzzle::{Grid, word::Direction};
895    ///
896    /// let grid = Grid::new();
897    /// let placements = grid.handle_initial_placements("", 'A', "BC").unwrap();
898    /// assert_eq!(placements.len(), 2);
899    /// assert_eq!(placements[0].direction, Direction::Horizontal);
900    /// assert_eq!(placements[1].direction, Direction::Vertical);
901    /// ```
902    pub fn handle_initial_placements(
903        &self,
904        prefix: &'a str,
905        crossed: char,
906        suffix: &'a str,
907    ) -> Result<Vec<Word<'a>>, GridError> {
908        let mut placements = Vec::new();
909        let horizontal_word =
910            Word::value(prefix, crossed, suffix)?.direction(Direction::Horizontal);
911        placements.push(horizontal_word);
912
913        let vertical_word = Word::value(prefix, crossed, suffix)?.direction(Direction::Vertical);
914        placements.push(vertical_word);
915        Ok(placements)
916    }
917
918    /// Serializes the `Grid` into a JSON string.
919    ///
920    /// This function requires the `serde` feature to be enabled.
921    ///
922    /// # Returns
923    ///
924    /// - `Ok(String)` containing the JSON representation of the grid.
925    /// - `Err(serde_json::Error)` if serialization fails.
926    #[cfg(feature = "serde")]
927    pub fn to_json(&self) -> Result<String, serde_json::Error> {
928        serde_json::to_string(&self)
929    }
930
931    /// Serializes the `Grid` into a pretty-printed JSON string.
932    ///
933    /// This function requires the `serde` feature to be enabled.
934    ///
935    /// # Returns
936    ///
937    /// - `Ok(String)` containing the pretty-printed JSON representation of the grid.
938    /// - `Err(serde_json::Error)` if serialization fails.
939    #[cfg(feature = "serde")]
940    pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
941        serde_json::to_string_pretty(&self)
942    }
943}
944
945#[derive(Clone, Debug)]
946pub struct PossibleWord<'a> {
947    /// The string value of the word.
948    pub value: &'a str,
949    /// The number of remaining attempts to place this word on the grid.
950    pub remaining: usize,
951}
952
953impl<'a> PossibleWord<'a> {
954    /// Creates a new `PossibleWord` instance
955    ///
956    /// Initializes a `PossibleWord` with the given string `value` and sets
957    /// `remaining` attempts to `3` by default.
958    ///
959    /// # Arguments
960    ///
961    /// * `value` - The string slice representing the word.
962    ///
963    /// # Returns
964    ///
965    /// A new `PossibleWord` instance.
966    ///
967    /// # Examples
968    ///
969    /// ```
970    /// use crossword_puzzle::PossibleWord;
971    ///
972    /// let pw = PossibleWord::new("HELLO");
973    /// assert_eq!(pw.value, "HELLO");
974    /// assert_eq!(pw.remaining, 3);
975    /// ```
976    pub fn new(value: &'a str) -> Self {
977        Self {
978            value,
979            remaining: 3,
980        }
981    }
982}
983
984/// Calculates the squared Euclidean distance between two `Position`s.
985///
986/// This function is used to determine the distance between two points on the grid
987/// without computing the square root, which is often sufficient for comparisons.
988/// It's particularly useful for sorting placements by their proximity to a reference point.
989///
990/// # Arguments
991///
992/// * `a` - The first `Position`.
993/// * `b` - The second `Position`.
994///
995/// # Returns
996///
997/// The squared Euclidean distance as an `i32`.
998fn squared_euclidean(a: Position, b: Position) -> i32 {
999    let dx = a.x as i32 - b.x as i32;
1000    let dy = a.y as i32 - b.y as i32;
1001    (dx * dx) + (dy * dy)
1002}
1003
1004/// A backtracking function to generate the crossword puzzle.
1005///
1006/// This function attempts to place words one by one onto the grid using a recursive
1007/// backtracking approach. It explores possible placements for each word. If a word
1008/// cannot be placed in the current attempt and has remaining retries, it is re-queued
1009/// for a later attempt. If a placement leads to a dead end, the function backtracks
1010/// to try another path.
1011///
1012/// # Arguments
1013///
1014/// * `grid` - The current `Grid` state.
1015/// * `words_to_place` - A `VecDeque` containing `PossibleWord`s that still need to be placed,
1016///   along with their remaining placement attempts.
1017///
1018/// # Returns
1019///
1020/// - `Ok(Some(Grid))` if a complete and valid crossword puzzle grid is successfully generated.
1021/// - `Ok(None)` if no valid grid can be generated from the given words after all attempts.
1022/// - `Err(Error)` if an error occurs during grid operations (e.g., invalid word segments).
1023///
1024/// # Errors
1025///
1026/// Returns an `Error` if `Grid::find_valid_placements` or `Grid::add_word` return an error.
1027pub fn backtrack<'a>(
1028    grid: Grid<'a>,
1029    mut words_to_place: VecDeque<PossibleWord<'a>>,
1030) -> Result<Option<Grid<'a>>, Error> {
1031    if let Some(mut current_word) = words_to_place.pop_front() {
1032        let mut placements = grid.find_valid_placements(current_word.value)?;
1033        if placements.is_empty() && current_word.remaining > 1 {
1034            current_word.remaining = current_word.remaining.saturating_sub(1);
1035            words_to_place.push_back(current_word);
1036
1037            return backtrack(grid, words_to_place);
1038        }
1039
1040        let ref_position = Position {
1041            x: grid.board[0].len() / 2,
1042            y: grid.board.len() / 2,
1043        };
1044        placements.sort_by_key(|w| squared_euclidean(w.position, ref_position));
1045        for placement_word in placements {
1046            let mut new_grid = grid.clone();
1047            new_grid.add_word(placement_word)?;
1048
1049            if let Some(final_grid) = backtrack(new_grid, words_to_place.clone())? {
1050                return Ok(Some(final_grid));
1051            }
1052        }
1053    }
1054
1055    Ok((!grid.words.is_empty() || words_to_place.is_empty()).then_some(grid))
1056}
1057
1058/// Eliminates words that do not share any common characters with other words.
1059///
1060/// This function filters the initial list of words, keeping only those that have at least
1061/// one common character with another word in the list. This helps in reducing the search space
1062/// for the crossword generation by focusing on words that can actually intersect.
1063/// The words are then sorted by length in reverse order (longest first).
1064///
1065/// # Arguments
1066///
1067/// * `words_to_place` - A slice of string slices (`&[&'a str]`) representing the initial list of words.
1068///
1069/// # Returns
1070///
1071/// A `VecDeque<PossibleWord>` containing the filtered and sorted words, wrapped in `PossibleWord` structs.
1072///
1073/// # Examples
1074///
1075/// ```
1076/// use crossword_puzzle::{eliminate_words, PossibleWord};
1077/// use std::collections::VecDeque;
1078///
1079/// let words = &["RUST", "TEST", "CODE", "ZIP"];
1080/// let filtered_words = eliminate_words(words);
1081///
1082/// // "ZIP" does not share any common characters with "RUST", "TEST", or "CODE"
1083/// // So it should be eliminated.
1084/// assert_eq!(filtered_words.len(), 3);
1085/// ```
1086pub fn eliminate_words<'a>(words_to_place: &[&'a str]) -> VecDeque<PossibleWord<'a>> {
1087    let mut filtered_words_set = std::collections::HashSet::new();
1088
1089    for i in 0..words_to_place.len() {
1090        let word1 = words_to_place[i];
1091        let chars1: std::collections::HashSet<char> = word1.chars().collect();
1092
1093        let mut has_common_char_with_other_word = false;
1094        for (j, word2) in words_to_place.iter().enumerate() {
1095            if i == j {
1096                continue;
1097            }
1098            let chars2: std::collections::HashSet<char> = word2.chars().collect();
1099
1100            if chars1.intersection(&chars2).next().is_some() {
1101                has_common_char_with_other_word = true;
1102                break;
1103            }
1104        }
1105
1106        if has_common_char_with_other_word {
1107            filtered_words_set.insert(word1);
1108        }
1109    }
1110
1111    let mut filtered_words: Vec<&'a str> = filtered_words_set.into_iter().collect();
1112    filtered_words.sort_by_key(|c| std::cmp::Reverse(c.len()));
1113    VecDeque::from(
1114        filtered_words
1115            .iter()
1116            .map(|w| PossibleWord::new(w))
1117            .collect::<Vec<_>>(),
1118    )
1119}
1120
1121/// Generates a crossword puzzle grid from a given list of words.
1122///
1123/// This function takes a slice of string slices, where each string represents a word
1124/// to be included in the crossword puzzle. It attempts to arrange these words
1125/// into a valid crossword grid using a backtracking algorithm.
1126///
1127/// The process involves:
1128/// 1. Eliminating words that do not share common characters with other words to
1129///    reduce the search space.
1130/// 2. Attempting to place words one by one onto the grid.
1131/// 3. If a word cannot be placed, the algorithm backtracks and tries a different
1132///    placement or a different word.
1133///
1134/// # Arguments
1135///
1136/// * `words` - A slice of string slices (`&[&'a str]`) representing the words
1137///   to be used in the crossword puzzle.
1138///
1139/// # Returns
1140///
1141/// A `Result` which is:
1142/// - `Ok(Some(Grid))` if a valid crossword puzzle grid is successfully generated.
1143/// - `Ok(None)` if no valid grid can be generated from the given words.
1144/// - `Err(Error)` if an error occurs during the generation process (e.g.,
1145///   invalid word segments).
1146///
1147/// # Examples
1148///
1149/// ```
1150/// use crossword_puzzle::{generate, Grid};
1151///
1152/// let words = &["LOREM", "IPSUM", "DOLOR", "SIT", "AMET"];
1153/// match generate(words) {
1154///     Ok(Some(grid)) => {
1155///         // Print the generated grid
1156///         for row in grid.board {
1157///             println!("{}", row.iter().collect::<String>());
1158///         }
1159///     },
1160///     Ok(None) => println!("Could not generate a crossword puzzle."),
1161///     Err(e) => eprintln!("Error generating puzzle: {}", e),
1162/// }
1163/// ```
1164pub fn generate<'a>(words: &[&'a str]) -> Result<Option<Grid<'a>>, Error> {
1165    for word in words.iter() {
1166        if word.chars().any(|c| c.is_lowercase()) {
1167            return Err(Error::WordError(WordError::LowercaseCharactersInSegment));
1168        }
1169    }
1170
1171    let words_queue = eliminate_words(words);
1172    let initial_grid = Grid::new();
1173    backtrack(initial_grid, words_queue)
1174}