Browse Source

Changed flip, flip_x, and flip_y to swap-only.

Steve Thielemann 1 week ago
parent
commit
6ec49e0a32
1 changed files with 183 additions and 106 deletions
  1. 183 106
      sudoku/src/sudoku.rs

+ 183 - 106
sudoku/src/sudoku.rs

@@ -38,15 +38,58 @@ impl error::Error for GameLoadError {}
 // const DEBUG_OUTPUT: bool = false;
 
 // Vec doesn't implement Copy ...
+
+/// AnyBoard - Any sized sudoku board storage
+///   Currently board limited to < 8x8 boards because of u8 limit.  (8x8 say WAT?!)
+///   Also limited on display (we handle 5X5 boards A-Y).  Anything > 5, we would have
+///       figure out something else for display output.
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct AnyBoard {
+    /// Size of board (cell width)
     pub size: u8,
+    /// Size of board (size*size())
     pub width: u8,
+    /// Max index size (width*width)
     pub max_index: usize,
+    /// Actual board data
+    ///   0 = blank (not 1!)
+    ///   1..=width+1 = values
     pub board: Vec<u8>,
 }
 
+use std::ops::{Index, IndexMut};
+
+impl Index<usize> for AnyBoard {
+    type Output = u8;
+
+    fn index<'a>(&'a self, i: usize) -> &'a u8 {
+        &self.board[i]
+    }
+}
+
+impl Index<(u8, u8)> for AnyBoard {
+    type Output = u8;
+
+    fn index<'a>(&'a self, i: (u8, u8)) -> &'a u8 {
+        &self.board[self.pos(i.0, i.1)]
+    }
+}
+
+impl IndexMut<usize> for AnyBoard {
+    fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut u8 {
+        &mut self.board[i]
+    }
+}
+
+impl IndexMut<(u8, u8)> for AnyBoard {
+    fn index_mut<'a>(&'a mut self, i: (u8, u8)) -> &'a mut u8 {
+        let idx = self.pos(i.0, i.1);
+        &mut self.board[idx]
+    }
+}
+
 impl AnyBoard {
+    /// Construct board of given size.
     pub fn new(board_size: u8) -> Self {
         if (board_size < 3) || (board_size > 5) {
             panic!("Board size must be 3-5.");
@@ -67,8 +110,10 @@ impl AnyBoard {
         self.board.fill(0);
     }
 
+    /// Copy from another board.
+    ///   Panics if board sizes don't match.
     pub fn copy(&mut self, copy_from: &Self) {
-        debug_assert!(
+        assert!(
             self.size == copy_from.size,
             "Can't copy size {} into size {}",
             copy_from.size,
@@ -82,6 +127,7 @@ impl AnyBoard {
     #[inline]
     #[must_use]
     /// Calculate index position of (x,y)
+    ///   debug_assert if x or y values are out of range.
     pub fn pos(&self, x: u8, y: u8) -> usize {
         debug_assert!(
             x < self.width && y < self.width,
@@ -104,6 +150,7 @@ impl AnyBoard {
     }
 
     /// Set a position in the board with a value
+    ///   debug_assert if x or y values are out of range.
     pub fn set(&mut self, x: u8, y: u8, value: u8) {
         debug_assert!(
             x < self.width && y < self.width,
@@ -127,6 +174,7 @@ impl AnyBoard {
 
     #[must_use]
     /// Get value at position (x,y)
+    ///   debug_assert if x or y values are out of range.
     pub fn get(&self, x: u8, y: u8) -> u8 {
         debug_assert!(
             x < self.width && y < self.width,
@@ -155,6 +203,7 @@ impl AnyBoard {
 
     #[must_use]
     /// Load puzzle from string (top,left) going down.
+    ///   String is in columns, row format, (not row, column).
     pub fn load_from_tld(
         &mut self,
         start_ch: char,
@@ -200,6 +249,7 @@ impl AnyBoard {
 
     #[must_use]
     /// Save puzzle to a string (top,left) going down.
+    ///   We store as row, column, output is column, row.
     pub fn save_to_tld(&self, start_ch: char, blank: char) -> String {
         let mut result = String::new();
         result.reserve(self.max_index);
@@ -225,6 +275,7 @@ impl AnyBoard {
 
     #[must_use]
     /// Load puzzle from string (top,left) going right.
+    ///   Column, row translated to row, column.
     pub fn load_from_tlr(
         &mut self,
         start_ch: char,
@@ -390,7 +441,7 @@ impl AnyBoard {
     /// Solve by brute force
     /// - Returns up to max # of solutions.
     /// - Returns total solutions found, and vector of boards (up to max).
-    /// - Uses brute_force (recursive function) to solve.
+    /// - Uses brute_force (recursion) to solve.
     pub fn brute_force_solver(&self, max: u16) -> (u16, Vec<AnyBoard>) {
         let mut workset = self.clone();
         let mut total_solutions: u16 = 0;
@@ -403,6 +454,7 @@ impl AnyBoard {
     }
 
     /// Recursive brute force solver.
+    /// Called by brute_force_solver.
     fn brute_force(
         &mut self,
         total_solutions: &mut u16,
@@ -460,40 +512,111 @@ impl AnyBoard {
         false
     }
 
+    // Mess with your brain:
+    // The board manipulations below should be implemented without cloning the board.
+    // We should be able to just swap values.  FUTURE: TODO: no allocations.
+
+    /// Invert the index
+    ///
+    /// Convert 0..width to width..=0
+    pub fn invert(&self, index: u8) -> u8 {
+        self.width - 1 - index
+    }
+
+    /*
+    The flip process.
+    X values don't change or move.
+    Uppercase letter gets swapped with lower case.
+
+    ╔═══╦═══╦═══╗
+    ║Xab║dfi║lp ║
+    ║AXc║ehk║o  ║
+    ║BCX║gjn║   ║
+    ╠═══╬═══╬═══╣
+    ║DEG║Xm ║   ║
+    ║FHJ║MX ║   ║
+    ║IKN║  X║   ║
+    ╠═══╬═══╬═══╣
+    ║LO ║   ║X  ║
+    ║P  ║   ║ X ║
+    ║   ║   ║  X║
+    ╚═══╩═══╩═══╝
+     */
+
     /// Flip board X and Y
     pub fn flip(&mut self) {
+        let (mut x1, mut x2, mut y1, mut y2): (u8, u8, u8, u8);
+        // (x1,y1) = lower case
+        // (x2,y2) = upper case
+
+        for idx in 0..self.width-1 {
+            // println!("idx: {idx}");
+            for pass in 0..2 {
+                x1 = idx;
+                x2 = idx;
+                y1 = idx;
+                y2 = idx;
+
+                if pass == 0 {
+                    x1 += 1;
+                    y2 += 1;
+                } else {
+                    if idx == 0 {
+                        break;
+                    }
+                    x1 += 1;
+                    y1 -= 1;
+                    x2 -= 1;
+                    y2 += 1;
+                }
+
+                // println!("Idx {idx} Pass {pass} Starting ({x1},{y1}), ({x2},{y2})");
+                for _swap in 0..self.width {
+                    // println!("Swap ({x1},{y1}) <=> ({x2},{y2})");
+                    (self[(x1,y1)], self[(x2,y2)]) = (self[(x2,y2)], self[(x1,y1)]);
+
+                    if (y1 == 0) || (x2 == 0)  {
+                        break;
+                    }
+                    x1 += 1;
+                    y1 -= 1;
+                    x2 -= 1;
+                    y2 += 1;
+
+                    if (x1 == self.width) || (y2 == self.width) {
+                        break;
+                    }
+                }
+            }
+        }
+        
+        /*
+        // The memory allocation way of swapping.
         let temp = self.clone();
         for x in 0..self.width {
             for y in 0..self.width {
                 self[(x, y)] = temp[(y, x)]
             }
         }
+        */
     }
 
     /// Flip board around X
     pub fn flip_x(&mut self) {
-        let mut temp = vec![0; self.width as usize];
-
         for y in 0..self.width {
-            for x in 0..self.width {
-                temp[x as usize] = self[(x, y)];
-            }
-            for x in 0..self.width {
-                self[(x, y)] = temp[self.width as usize - 1 - x as usize];
+            for x in 0..self.width / 2 {
+                let invert_x = self.invert(x);
+                (self[(x, y)], self[(invert_x, y)]) = (self[(invert_x, y)], self[(x, y)]);
             }
         }
     }
 
     /// Flip board around Y
     pub fn flip_y(&mut self) {
-        let mut temp = vec![0; self.width as usize];
-
         for x in 0..self.width {
-            for y in 0..self.width {
-                temp[y as usize] = self[(x, y)];
-            }
-            for y in 0..self.width {
-                self[(x, y)] = temp[self.width as usize - 1 - y as usize];
+            for y in 0..self.width / 2 {
+                let invert_y = self.invert(y);
+                (self[(x, y)], self[(x, invert_y)]) = (self[(x, invert_y)], self[(x, y)]);
             }
         }
     }
@@ -531,49 +654,26 @@ impl AnyBoard {
 // This allows you to index the board by (u8,u8) or usize.
 //
 
-use std::ops::{Index, IndexMut};
-
-impl Index<usize> for AnyBoard {
-    type Output = u8;
-
-    fn index<'a>(&'a self, i: usize) -> &'a u8 {
-        &self.board[i]
-    }
-}
-
-impl Index<(u8, u8)> for AnyBoard {
-    type Output = u8;
-
-    fn index<'a>(&'a self, i: (u8, u8)) -> &'a u8 {
-        &self.board[self.pos(i.0, i.1)]
-    }
-}
-
-impl IndexMut<usize> for AnyBoard {
-    fn index_mut<'a>(&'a mut self, i: usize) -> &'a mut u8 {
-        &mut self.board[i]
-    }
-}
-
-impl IndexMut<(u8, u8)> for AnyBoard {
-    fn index_mut<'a>(&'a mut self, i: (u8, u8)) -> &'a mut u8 {
-        let idx = self.pos(i.0, i.1);
-        &mut self.board[idx]
-    }
-}
-
 // Need to use u32, so 5*5=25, 25 bits can be accessed.
 // u16 is fine for 3*3=9.
 
+/// AnyPossible - Track what values can possibly go in what positions.
 #[derive(Debug, Clone)]
 pub struct AnyPossible {
+    /// Board size
     pub size: u8,
+    /// Board width (size*size)
     pub width: u8,
+    /// Board max index (width*width)
     pub max_index: usize,
+    /// What values are possible at what index position.
+    ///   true = possible
+    ///   Note:  The flags values go from 0..width!
     pub possible: Vec<Flags>,
 }
 
 impl AnyPossible {
+    /// Construct AnyPossible for given board_size
     pub fn new(board_size: u8) -> Self {
         let width = board_size as usize * board_size as usize;
         let mut initial = Flags::new(width);
@@ -588,15 +688,17 @@ impl AnyPossible {
         }
     }
 
+    /// Reset Possible (all values, all positions)
     pub fn clear(&mut self) {
         let mut initial = Flags::new(self.width as usize);
-        // let width = self.size * self.size;
         initial.set_range(0..self.width as usize);
         self.possible.fill(initial);
     }
 
+    /// Copy possible
+    ///   assert! if sizes don't match.
     pub fn copy(&mut self, copy_from: &Self) {
-        debug_assert!(
+        assert!(
             self.size == copy_from.size,
             "Can't copy size {} into size {}",
             copy_from.size,
@@ -630,6 +732,7 @@ impl AnyPossible {
     #[inline]
     /// Get state for given index and value.
     ///   - value range is from 1..=width.
+    ///   - debug_assert if index is out of range.
     pub fn get(&self, index: usize, value: usize) -> bool {
         debug_assert!(
             index < self.max_index,
@@ -643,6 +746,7 @@ impl AnyPossible {
     #[must_use]
     #[inline]
     /// Return index for given (x,y).
+    ///   - debug_assert if x or y are out of range.
     pub fn pos(&self, x: u8, y: u8) -> usize {
         debug_assert!(
             x < self.width && y < self.width,
@@ -657,6 +761,7 @@ impl AnyPossible {
     #[must_use]
     #[inline]
     /// Return (x,y) position for given index.
+    ///   - debug_assert if x or y are out of range.
     pub fn xy(&self, idx: usize) -> (u8, u8) {
         debug_assert!(idx < self.max_index, "Index {} >= {}", idx, self.max_index);
         (
@@ -783,8 +888,6 @@ impl AnyPossible {
         (pairs, possibles_updated)
     }
 
-    // This is working, it was code elsewhere that was having issues.
-
     #[must_use]
     /// Find positions where each value is possible.
     /// - Vec index is the value - 1  !
@@ -852,29 +955,15 @@ impl IndexMut<(u8, u8)> for AnyPossible {
     }
 }
 
-/*
-An idea I have for AnySolver is to store the AnyPossible as
-sections.  row/column/cell.
-I think this would better allow me to judge how hard the puzzle
-is.  If it can be solved by looking at just one section --
-that would be one level.  If it takes 2, another level. All 3,
-yet another.  I think that's how I mentally judge the puzzles.
-
-"Not all hard puzzles are hard."
-
- */
-
-/*
-pub struct Pairs {
-    pub values: [u8; 2],
-    pub pos: [u8; 2],
-}
-*/
-
+/// AnySolver - a Sudoku puzzle solver
+///   - This uses logic.
 #[derive(Debug, Clone)]
 pub struct AnySolver {
+    /// The board
     pub board: AnyBoard,
+    /// Possibles
     pub possible: AnyPossible,
+    /// Groups (Rows, Columns, Cells)
     pub group: AnyGroup,
     // Is this value set in the given cell?
     pub cell_poss: Vec<Flags>,
@@ -919,10 +1008,17 @@ impl AnySolver {
     /// Clear out board and possible.
     /// - group is ok, unless they change the board size.
     pub fn clear(&mut self) {
+        self.board.clear();
+        self.possible.clear();
+        self.reset_possible();
+        /*
+        // I should be able to reset here, and not have to re-allocate.
+
         let board_size: u8 = self.board.size;
         self.board = AnyBoard::new(board_size);
         self.possible = AnyPossible::new(board_size);
         self.reset_possible();
+        */
     }
 
     /// Process a move
@@ -946,6 +1042,7 @@ impl AnySolver {
             self.board.width
         );
 
+        // Remove value from the row, column, and cell possibles.
         let mut g = self.group.row(y);
         let val: usize = value as usize;
         for g in g {
@@ -959,57 +1056,27 @@ impl AnySolver {
         for g in g {
             self.possible.set(*g, val, false);
         }
+
         let idx = self.possible.pos(x, y);
         self.possible.possible[idx] = Flags::new(self.board.width as usize);
         self.board.board[idx] = value;
 
-        //
-        // When working with GenBits, remember to use value -1
-        //
-
-        /*
-        let ok = self.cell_poss[cell_index as usize].get(value as usize-1);
-        if !ok {
-            println!("{:?}", self.cell_poss);
-            println!("Cell {} ({},{})={}", cell_index, x, y, value);
-        }
-        */
-
         // Tests are run as debug, and this kills things...
         // Like the invalid board in validated_board test.
         let cell_index = self.group.which_cell(x, y);
 
-        /*
-        Never use this code.  It asserts/panics on invalid boards.
-        (Which is what validate_board checks...)
-
-        debug_assert!(
-            self.cell_poss[cell_index as usize].get(value as usize - 1),
-            "Cell {} ({},{})={}",
-            cell_index,
-            x,
-            y,
-            value
-        );
-        */
         self.cell_poss[cell_index as usize].set(value as usize - 1, false);
 
         if finalize {
             self.finalize_possible();
             // self.finalize_cell(cell_index);
         }
-        // Should I do the checks here for the logic possible fix?
-        // Or, should I make it a different function?
-        // Put somewhere else, since it has to re-check the entire board!
-
-        // OR, just check row and columns?
     }
 
     /*
     This is very heavy to call (especially for 5X5 boards).
     Maybe call this when we're getting stuck? (before logic 2 pass?)
 
-    Still to do: columns.  :P
      */
 
     pub fn finalize_possible(&mut self) -> bool {
@@ -1835,10 +1902,13 @@ impl AnySolver {
                     }
                     self.process_move(x, y, value, false);
                 } else {
+                    // Below, we check to see if blanks have possible values.
+
                     has_blanks = true;
                 }
             }
         }
+
         // Ok, the pieces given fit correctly with the sudoku constraints.
         self.finalize_possible();
         // self.finalize_move();
@@ -1917,6 +1987,8 @@ impl AnySolver {
     }
 
     #[must_use]
+    /// get (x,y) value.
+    ///   - debug_assert if x or y are out of range.
     pub fn get(&self, x: u8, y: u8) -> u8 {
         debug_assert!(
             x < self.board.width && y < self.board.width,
@@ -1937,7 +2009,7 @@ impl AnySolver {
     }
 
     /// Recursively completely fill the board.
-    /// This takes - quite a bit of time - for a 25x25 board.
+    /// This takes - some time (2.5 minutes) - for a 25x25 board.
     fn fill_board(&mut self, rng: &mut ChaCha20Rng) -> bool {
         let backup = self.clone();
         let max_index = self.board.max_index;
@@ -2035,6 +2107,9 @@ impl AnySolver {
         return false;
     }
 
+    /// logic_pass1
+    ///   Look for single values in the possibles.  
+    ///   (If there's only one value possible, it must go there.)
     fn logic_pass1(&mut self) -> bool {
         // Pass 1: Look for singles in the possible sets.
         let mut pass1 = false;
@@ -3159,8 +3234,10 @@ mod tests {
         // Reset the board, flip it, and try this again!
 
         board.copy(&base_board);
+        board.display();
         board.flip();
         let strings = board.to_strings();
+        println!("board.flip() display:");
         board.display();
         /*
         ╔═══╦═══╦═══╗
@@ -3385,10 +3462,10 @@ mod tests {
         board[(width, width)] = 3;
         board[(0, width)] = 4;
         // Also check these as well: ?
-        board[(width/2, size-1)] = 1;
-        board[(width+1-size, width/2)] = 2;
-        board[(width/2, width+1-size)] = 3;
-        board[(size-1, width/2)] = 4;
+        board[(width / 2, size - 1)] = 1;
+        board[(width + 1 - size, width / 2)] = 2;
+        board[(width / 2, width + 1 - size)] = 3;
+        board[(size - 1, width / 2)] = 4;
         board.display();
         /*
                ╔═══╦═══╦═══╗