瀏覽代碼

Working displaying of 4x4 and 5x5 boards.

This is the new AnyBoard code.
Steve Thielemann 4 月之前
父節點
當前提交
5e62d5a3d6
共有 2 個文件被更改,包括 374 次插入50 次删除
  1. 37 0
      sudoku/src/bits.rs
  2. 337 50
      sudoku/src/sudoku.rs

+ 37 - 0
sudoku/src/bits.rs

@@ -221,4 +221,41 @@ mod tests {
         }
         assert_eq!(p.get(6), false);
     }
+
+    #[test]
+    fn check_u32() {
+        let mut p = GenBits::<u32>(0);
+
+        p.clear();
+
+        for i in 0..29 {
+            let mut result = p.get(i);
+            assert_eq!(result, false);
+            p.set(i, true);
+            result = p.get(i);
+            assert_eq!(result, true);
+        }
+
+        p = GenBits::<u32>(0);
+        p.set(13, true);
+        p.set(15, true);
+        p.set(26, true);
+        assert_eq!(3, p.count_set());
+        let values: Vec<u8> = p.iter().collect();
+        assert_eq!(values, vec!(13, 15, 26));
+        assert_eq!(3, p.count_set());
+        p.set(0, true);
+        assert_eq!(4, p.count_set());
+
+        p = GenBits::<u32>(0);
+        p.set_bits(10..26);
+
+        for i in 10..26 {
+            let result = p.get(i);
+            assert_eq!(result, true);
+        }
+        assert_eq!(p.get(9), false);
+        assert_eq!(p.get(27), false);
+    }
+
 }

+ 337 - 50
sudoku/src/sudoku.rs

@@ -1,22 +1,26 @@
 // pub mod group;
-use crate::group::*;
 use crate::bits::*;
+use crate::group::*;
 
 // use std::collections::HashSet;
 use std::string::String;
 
 const SIZE: u8 = 3;
 /// Width of the sudoku board.
-const WIDTH: u8 = SIZE*SIZE;
+const WIDTH: u8 = SIZE * SIZE;
 /// Size (width * height) of the board.
 const MAX_SIZE: u8 = WIDTH * WIDTH; // 81;
 
 extern crate rand_chacha;
 // use rand::prelude::*;
+use rand::distributions::{Distribution, Uniform};
 use rand::seq::SliceRandom;
 use rand_chacha::rand_core::SeedableRng;
 use rand_chacha::ChaCha20Rng;
-use rand::distributions::{Distribution, Uniform};
+
+// For custom error
+use std::error;
+use std::fmt;
 
 // Used bo calculate_possible to return the solutions.
 pub type SudokuBoard = [u8; MAX_SIZE as usize];
@@ -29,9 +33,6 @@ pub type SudokuPossible = [Bits; MAX_SIZE as usize];
 #[derive(Debug, Clone, Copy)]
 pub struct Board([u8; MAX_SIZE as usize]);
 
-
-
-
 impl Board {
     pub fn new() -> Self {
         let s = Self {
@@ -44,12 +45,12 @@ impl Board {
         self.0 = [0; MAX_SIZE as usize];
     }
 
-    pub fn set(&mut self, x:u8, y:u8, value:u8) {
-        self.0[pos(x,y) as usize] = value;
+    pub fn set(&mut self, x: u8, y: u8, value: u8) {
+        self.0[pos(x, y) as usize] = value;
     }
 
-    pub fn get(&mut self, x:u8, y:u8) -> u8 {
-        self.0[pos(x,y) as usize]
+    pub fn get(&mut self, x: u8, y: u8) -> u8 {
+        self.0[pos(x, y) as usize]
     }
 
     /// Load puzzle from a string.
@@ -61,7 +62,7 @@ impl Board {
 
         for ch in s.chars() {
             if ch != blank {
-                self.set(x,y, (ch as u8 - start_ch as u8)+1);
+                self.set(x, y, (ch as u8 - start_ch as u8) + 1);
             }
             y += 1;
             if y >= WIDTH {
@@ -77,31 +78,43 @@ impl Board {
     pub fn save_to_tld(&mut self, start_ch: char, blank: char) -> String {
         let mut result = String::new();
         result.reserve(MAX_SIZE as usize);
-        let start_ch = (start_ch as u8 -1) as char;
-        let mut x:u8=0;
-        let mut y:u8=0;
+        let start_ch = (start_ch as u8 - 1) as char;
+        let mut x: u8 = 0;
+        let mut y: u8 = 0;
         for _i in 0..MAX_SIZE {
-            if self.0[pos(x,y) as usize] == 0 {
+            if self.0[pos(x, y) as usize] == 0 {
                 result.push(blank);
             } else {
-                result.push((start_ch as u8 + self.0[pos(x,y) as usize]) as char); 
+                result.push((start_ch as u8 + self.0[pos(x, y) as usize]) as char);
             }
             y += 1;
             if y >= WIDTH {
-                y = 0; 
+                y = 0;
                 x += 1;
             }
         }
         result
     }
+}
 
+#[derive(Debug, Clone)]
+struct GameLoadError {
+    message: String,
 }
 
+impl fmt::Display for GameLoadError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Game load error: {}", self.message)
+    }
+}
+
+impl error::Error for GameLoadError {}
+
 // Vec doesn't implement Copy ...
 #[derive(Debug, Clone)]
 pub struct AnyBoard {
-    pub size : u8,
-    pub board : Vec<u8>,
+    pub size: u8,
+    pub board: Vec<u8>,
 }
 
 impl AnyBoard {
@@ -110,13 +123,286 @@ impl AnyBoard {
             panic!("Board size must be 3-5.");
         }
 
-        let s = AnyBoard{
+        let n = board_size as usize;
+        let s = AnyBoard {
             size: board_size,
-            board: vec![0, board_size*board_size*2],
+            board: vec![0; n * n * n * n],
         };
         s
     }
+
+    /// Max board width (size*size)
+    pub fn width(&self) -> u8 {
+        self.size * self.size
+    }
+
+    /// Max board index (width*width)
+    pub fn max_index(&self) -> usize {
+        self.width() as usize * self.width() as usize
+    }
+
+    /// Clear out the board
+    pub fn clear(&mut self) {
+        self.board.fill(0);
+    }
+
+    /// Calculate index position of (x,y)
+    pub fn pos(&self, x: u8, y: u8) -> usize {
+        x as usize + y as usize * self.width() as usize
+    }
+
+    /// Set a position in the board with a value
+    pub fn set(&mut self, x: u8, y: u8, value: u8) {
+        assert!(value <= self.size * self.size);
+        let index = self.pos(x, y);
+        assert!(index <= self.board.capacity());
+        self.board[index] = value;
+    }
+
+    /// Get value at position (x,y)
+    pub fn get(&self, x: u8, y: u8) -> u8 {
+        let index = self.pos(x, y);
+        assert!(index <= self.board.capacity());
+        self.board[index]
+    }
+
+    /// Load puzzle from string (top,left) going down.
+    pub fn load_from_tld(
+        &mut self,
+        start_ch: char,
+        blank: char,
+        s: &str,
+    ) -> Result<(), Box<dyn error::Error>> {
+        self.clear();
+        let mut x: u8 = 0;
+        let mut y: u8 = 0;
+
+        if s.len() != self.max_index() {
+            // self.size * self.size*self.size*self.size {
+            return Err(Box::new(GameLoadError {
+                message: format!(
+                    "String exceeds ({}) expected length {}.",
+                    s.len(),
+                    self.size * self.size
+                ),
+            }));
+        }
+
+        for ch in s.chars() {
+            if ch != blank {
+                let value = (ch as u8 - start_ch as u8) + 1;
+                if value == 0 || value > self.width() {
+                    return Err(Box::new(GameLoadError {
+                        message: format!(
+                            "String symbol ({}) represents value {}, expecting 1 to {}.",
+                            ch,
+                            value,
+                            self.size * self.size
+                        ),
+                    }));
+                }
+                self.set(x, y, value);
+            }
+            y += 1;
+            if y >= self.size * self.size {
+                y = 0;
+                x += 1;
+            }
+        }
+        Ok(())
+    }
+
+    /// Save puzzle to a string (top,left) going down.
+    pub fn save_to_tld(&self, start_ch: char, blank: char) -> String {
+        let mut result = String::new();
+        result.reserve(self.max_index());
+        let start_ch = (start_ch as u8 - 1) as char;
+        let mut x: u8 = 0;
+        let mut y: u8 = 0;
+
+        for _i in 0..self.max_index() {
+            let value = self.get(x, y);
+            if value == 0 {
+                result.push(blank);
+            } else {
+                result.push((start_ch as u8 + value) as char);
+            }
+            y += 1;
+            if y >= self.width() {
+                y = 0;
+                x += 1;
+            }
+        }
+        result
+    }
+
+    pub fn display(&self) {
+        let line = "═".repeat(self.size as usize);
+        let alpha_display = self.width() >= 10;
+        // println!("╔{}╦{}╦{}╗", line, line, line);
+
+        println!("alpha = {}", alpha_display);
+
+        // Top
+        for i in 0..self.size {
+            if i == 0 {
+                print!("╔{}", line);
+            } else {
+                print!("╦{}", line);
+            }
+        }
+        println!("╗");
+
+        for y in 0..self.width() {
+            print!("║");
+            for x in 0..self.width() {
+                let value = self.get(x, y);
+                if value == 0 {
+                    print!(" ");
+                } else {
+                    if alpha_display {
+                        print!("{}", (value - 1 + 'A' as u8) as char);
+                    } else {
+                        print!("{}", value);
+                    }
+                }
+                if x % self.size == self.size - 1 {
+                    print!("║");
+                }
+            }
+            println!("");
+            if y % self.size == self.size - 1 {
+                if y + 1 == self.width() {
+                    // Bottom
+                    for i in 0..self.size {
+                        if i == 0 {
+                            print!("╚{}", line);
+                        } else {
+                            print!("╩{}", line);
+                        }
+                    }
+                    println!("╝");
+
+                    // println("╚═══╩═══╩═══╝");
+                } else {
+                    // Middle
+                    for i in 0..self.size {
+                        if i == 0 {
+                            print!("╠{}", line);
+                        } else {
+                            print!("╬{}", line);
+                        }
+                    }
+                    println!("╣");
+                    // println("╠═══╬═══╬═══╣");
+                }
+            }
+        }
+    }
+}
+
+// Need to use u32, so 5*5=25, 25 bits can be accessed.
+// u16 is fine for 3*3=9.
+
+#[derive(Debug, Clone)]
+pub struct AnyPossible {
+    pub size: u8,
+    pub possible: Vec<GenBits<u32>>,
 }
+
+impl AnyPossible {
+    pub fn new(board_size: u8) -> Self {
+        let mut initial = GenBits::<u32>(0);
+        initial.set_bits(1..board_size);
+        Self {
+            size: board_size,
+            possible: vec![initial; board_size as usize * board_size as usize],
+        }
+    }
+
+    pub fn clear(&mut self) {
+        let mut initial = GenBits::<u32>(0);
+        initial.set_bits(1..self.size);
+        self.possible = vec![initial; self.size as usize * self.size as usize];
+    }
+
+    pub fn set(&mut self, index: usize, value: usize, state: bool) {
+        self.possible[index].set(value, state);
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct AnySolver {
+    pub board: AnyBoard,
+    pub possible: AnyPossible,
+}
+
+impl AnySolver {
+    pub fn new(initial_board: &AnyBoard) -> Self {
+        let mut s = AnySolver {
+            board: initial_board.clone(),
+            possible: AnyPossible::new(initial_board.max_index() as u8),
+        };
+        s.reset_possible();
+        s
+    }
+
+    /// Process a move
+    /// - Remove value from rows, columns, and cells.
+    /// - Clear possibles from (x,y) position, it's filled.
+    pub fn process_move(&mut self, x: u8, y: u8, value: u8) {
+        let mut g = for_row(y);
+        let value: usize = value as usize;
+        for g in g.0 {
+            self.possible.set(g, value, false);
+        }
+        g = for_column(x);
+        for g in g.0 {
+            self.possible.set(g, value, false);
+        }
+        g = for_cell(which_cell(x, y));
+        for g in g.0 {
+            self.possible.set(g, value, false);
+        }
+        self.possible.possible[self.board.pos(x, y)] = GenBits::<u32>(0); // .clear();
+    }
+
+    /// Reset the possible
+    /// - For when a new puzzle has been loaded.
+    /// - When something has changed, and the possibles are out of sync.
+    pub fn reset_possible(&mut self) {
+        self.possible.clear();
+        for y in 0..self.board.width() {
+            for x in 0..self.board.width() {
+                let value = self.board.get(x, y);
+                if value != 0 {
+                    self.process_move(x, y, value);
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+
+mod tests {
+    use crate::sudoku::*;
+
+    #[test]
+    fn display_board() {
+        let mut board: AnyBoard = AnyBoard::new(5);
+        println!("{:?}", board);
+        board.load_from_tld('b', '_', "_m_kzg____h_s_n____ftd_u_u_dh_____f_b_t_w_____yg_c_rl_o_tdhy__m__uvig_w_sk_eg___p_q_jikouys_r_d___mlq_t_sb_emcwg_dlzyo_kp_i_ng__ir_b_fp_vhz_ce_y_jm__w__m__o_k_xul_qbt_d_s__e____otv_dhegn___mfkpz_blr____s_dv_n_mjx_ckg_w_bo_p___kqyelwjcz_____nxumoisdh_z__fp_vbi_______dkx_eg__r_y_mlwf_u__q_i__o_chdv_j_i_he_r_____________p_zl_k_d_vbjh_y__e_p__s_tguc_q_s__qj_kpn_______ufw_hx__i_hvntirfxw_____lbckympjg___u_kz_m_bfn_yvx_h_ir_o____rgm_otlnx___ipfes_kwc____p__c_v_ugh_krj_m_w__x__x__ci_j_qk_mpo_dr_u_zb__ht_i_qe_wjvcy_bhkzx_ng_u_syv___u_c_hsfrlqo_t_e___pj_cn_h_slzr__j__mqgp_y_vd_m_bs_____t_o_n_h_____ez_f_e_ufd____p_g_z____cqr_x_");
+        println!("{:?}", board);
+        board.display();
+
+        board = AnyBoard::new(4);
+        board.load_from_tld('b', '_', "_bo_j_m_f__dp__ge_h_pcfdo___q__n___qio______df___f__l___hpnm_i___obig_p_qhl__k_m_dq_cn______o_g_p_____bi_kc__jn______fo____gi______eb____jd______jk__ml_bn_____i_m_b______oq_nj_d_n__jck_m_fgbq___i_medp___n__b___dg______qjk___j__p___fgohl_d_qo__mq__g_d_p_le_");
+        board.display();
+        assert!(false);
+    }
+}
+
 /*
 I probably should keep board and possible separate from one another.
 Possible is only used when solving the puzzles, and only used by the
@@ -135,7 +421,7 @@ funcs on just that, rather then having them in BoardPossible (wrong
 place for Possible's implementation).
  */
 
- /*
+/*
 #[derive(Debug, Clone, Copy)]
 pub struct SudokuPossible {
     pub possible: [Possible; MAX_SIZE as usize];
@@ -155,12 +441,12 @@ pub struct SudokuPossible {
 
 #[derive(Debug)]
 pub struct BoardPossible<'a> {
-    board: &'a mut Board, 
+    board: &'a mut Board,
     possible: [Bits; MAX_SIZE as usize],
 }
 
-impl <'a>BoardPossible<'a> {
-    pub fn new(board : &'a mut Board) -> Self {
+impl<'a> BoardPossible<'a> {
+    pub fn new(board: &'a mut Board) -> Self {
         // 10 = WIDTH + 1
         // This would need to be changed in order to handle 4x4 or 5x5.
         // NOTE: We ignore bit 0, we use bits 1-9 (for 3x3).
@@ -185,8 +471,8 @@ impl <'a>BoardPossible<'a> {
     /// - This updates the board.
     /// - this updates all the possible (row,column,cell).
     /// - Clears the possible for the (x,y).
-    pub fn set(&mut self, x:u8, y:u8, value:u8) {
-        self.board.0[pos(x,y)] = value;
+    pub fn set(&mut self, x: u8, y: u8, value: u8) {
+        self.board.0[pos(x, y)] = value;
 
         // update the possible
         let mut g: &Group = for_row(y);
@@ -197,16 +483,14 @@ impl <'a>BoardPossible<'a> {
         for g in g.0 {
             self.possible[g].set(value, false);
         }
-        g = for_cell(which_cell(x,y));
+        g = for_cell(which_cell(x, y));
         for g in g.0 {
             self.possible[g].set(value, false);
         }
-        self.possible[pos(x,y)].clear();
+        self.possible[pos(x, y)].clear();
     }
 
-    pub fn reset_possible(&mut self) {
-
-    }
+    pub fn reset_possible(&mut self) {}
 
     /// Display the possibles.
     /// This should be in the Possible struct, not here.
@@ -241,17 +525,16 @@ impl <'a>BoardPossible<'a> {
     }
 }
 
-
 #[derive(Debug, Clone, Copy)]
 pub struct Sudoku {
-    pub board: [u8; MAX_SIZE as usize], // Board
+    pub board: [u8; MAX_SIZE as usize],      // Board
     pub possible: [Bits; MAX_SIZE as usize], // BoardPossible
 }
 
 /// Translate x,y to position in board.
 /// This is used as usize, so return that instead of u8.
 pub const fn pos(x: u8, y: u8) -> usize {
-    x as usize + (y as usize * WIDTH as usize) 
+    x as usize + (y as usize * WIDTH as usize)
 }
 
 /// Translate x,y (with starting index of 1) to position in board.
@@ -348,7 +631,7 @@ impl Sudoku {
             if self.board[pos(x, y) as usize] == 0 {
                 result.push(blank);
             } else {
-                result.push((start_ch as u8 + self.board[pos(x,y) as usize]) as char);
+                result.push((start_ch as u8 + self.board[pos(x, y) as usize]) as char);
             }
             y += 1;
             if y >= WIDTH {
@@ -373,8 +656,8 @@ impl Sudoku {
         result
     }
 
-    pub fn get(&self, x:u8, y:u8) -> u8 {
-        self.board[pos(x,y) as usize]
+    pub fn get(&self, x: u8, y: u8) -> u8 {
+        self.board[pos(x, y) as usize]
     }
 
     /*
@@ -422,17 +705,17 @@ impl Sudoku {
                 if item != 0 {
                     let mut g: &Group = for_row(y);
                     for g in g.0 {
-                    self.possible[g as usize].set(item, false);
+                        self.possible[g as usize].set(item, false);
                     }
                     g = for_column(x);
                     for g in g.0 {
-                    self.possible[g as usize].set(item, false);
+                        self.possible[g as usize].set(item, false);
                     }
-                    g = for_cell(which_cell(x,y));
+                    g = for_cell(which_cell(x, y));
                     for g in g.0 {
-                    self.possible[g as usize].set(item, false);
+                        self.possible[g as usize].set(item, false);
                     }
-                    self.possible[pos(x,y) as usize].clear();
+                    self.possible[pos(x, y) as usize].clear();
                 }
             }
         }
@@ -513,7 +796,11 @@ impl Sudoku {
     /// Recursive brute-force solver
     /// - As possibilities are tried, it recursively calls itself to see
     ///   if there are any solutions with the given possibility.
-    fn calculate_possible(&mut self, total_solutions: &mut u16, solutions: &mut Vec<SudokuBoard>) -> bool {
+    fn calculate_possible(
+        &mut self,
+        total_solutions: &mut u16,
+        solutions: &mut Vec<SudokuBoard>,
+    ) -> bool {
         for idx in 0..MAX_SIZE as usize {
             if self.board[idx as usize] == 0 {
                 // Ok, there's a blank here
@@ -611,7 +898,7 @@ impl Sudoku {
     }
 
     /// Fill puzzle with random
-    /// - This is like the brute-force solver, it calls itself recursively 
+    /// - This is like the brute-force solver, it calls itself recursively
     ///   and backtraces if a solution can't be found.
     fn fill_board(&mut self, rng: &mut ChaCha20Rng) -> bool {
         let backup = Sudoku {
@@ -676,10 +963,10 @@ impl Sudoku {
         while value == 0 {
             x = puzrange.sample(&mut rng);
             y = puzrange.sample(&mut rng);
-            value = self.get(x,y);
+            value = self.get(x, y);
         }
 
-        self.set(x,y,0);
+        self.set(x, y, 0);
 
         // Clone, and solve by logic.
         let mut puzcopy = self.clone();
@@ -690,7 +977,7 @@ impl Sudoku {
         puzcopy.display_possible();
          */
         // If solvable, return true.
-        while puzcopy.solve(false) {};
+        while puzcopy.solve(false) {}
 
         /*
         puzcopy.display();
@@ -700,9 +987,9 @@ impl Sudoku {
         if puzcopy.puzzle_complete() {
             return true;
         }
-        
+
         // If not solvable, restore number, return false.
-        self.set(x,y,value);
+        self.set(x, y, value);
         return false;
     }