numberplace-rs

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

board.rs (4252B)


      1 use rand::RngExt;
      2 
      3 pub const GRID_SIZE: usize = 9;
      4 
      5 #[derive(Debug, Clone, Copy, PartialEq)]
      6 pub struct Cell {
      7     pub value: Option<u8>,
      8     pub is_fixed: bool,
      9 }
     10 
     11 impl Cell {
     12     pub fn new(value: Option<u8>, is_fixed: bool) -> Self {
     13         Self { value, is_fixed }
     14     }
     15 }
     16 
     17 pub struct Board {
     18     pub cells: [[Cell; GRID_SIZE]; GRID_SIZE],
     19 }
     20 
     21 impl Board {
     22     pub fn new() -> Self {
     23         let cells = [[Cell::new(None, false); GRID_SIZE]; GRID_SIZE];
     24         Self { cells }
     25     }
     26 
     27     pub fn set_value(&mut self, row: usize, col: usize, value: Option<u8>) {
     28         if !self.cells[row][col].is_fixed {
     29             self.cells[row][col].value = value;
     30         }
     31     }
     32 
     33     pub fn is_valid(&self, row: usize, col: usize, val: u8) -> bool {
     34         // Check row
     35         for c in 0..GRID_SIZE {
     36             if c != col && self.cells[row][c].value == Some(val) {
     37                 return false;
     38             }
     39         }
     40 
     41         // Check column
     42         for r in 0..GRID_SIZE {
     43             if r != row && self.cells[r][col].value == Some(val) {
     44                 return false;
     45             }
     46         }
     47 
     48         // Check 3x3 box
     49         let start_row = (row / 3) * 3;
     50         let start_col = (col / 3) * 3;
     51         for r in start_row..start_row + 3 {
     52             for c in start_col..start_col + 3 {
     53                 if (r != row || c != col) && self.cells[r][c].value == Some(val) {
     54                     return false;
     55                 }
     56             }
     57         }
     58 
     59         true
     60     }
     61 
     62     pub fn is_conflict(&self, row: usize, col: usize) -> bool {
     63         if let Some(val) = self.cells[row][col].value {
     64             !self.is_valid(row, col, val)
     65         } else {
     66             false
     67         }
     68     }
     69 
     70     pub fn is_full(&self) -> bool {
     71         self.cells
     72             .iter()
     73             .all(|row| row.iter().all(|c| c.value.is_some()))
     74     }
     75 
     76     pub fn is_solved(&self) -> bool {
     77         if !self.is_full() {
     78             return false;
     79         }
     80         for r in 0..GRID_SIZE {
     81             for c in 0..GRID_SIZE {
     82                 if self.is_conflict(r, c) {
     83                     return false;
     84                 }
     85             }
     86         }
     87         true
     88     }
     89 
     90     pub fn solve(&mut self) -> bool {
     91         let mut empty_cell = None;
     92         for r in 0..GRID_SIZE {
     93             for c in 0..GRID_SIZE {
     94                 if self.cells[r][c].value.is_none() {
     95                     empty_cell = Some((r, c));
     96                     break;
     97                 }
     98             }
     99             if empty_cell.is_some() {
    100                 break;
    101             }
    102         }
    103 
    104         let (r, c) = match empty_cell {
    105             Some(pos) => pos,
    106             None => return true, // Solved
    107         };
    108 
    109         for val in 1..=9 {
    110             if self.is_valid(r, c, val) {
    111                 self.cells[r][c].value = Some(val);
    112                 if self.solve() {
    113                     return true;
    114                 }
    115                 self.cells[r][c].value = None;
    116             }
    117         }
    118 
    119         false
    120     }
    121 
    122     pub fn generate(&mut self) {
    123         // Clear board
    124         *self = Board::new();
    125 
    126         // Fill diagonal 3x3 boxes
    127         for i in (0..GRID_SIZE).step_by(3) {
    128             self.fill_box(i, i);
    129         }
    130 
    131         // Solve the rest
    132         self.solve();
    133 
    134         // Set all as fixed for now (as the base)
    135         for r in 0..GRID_SIZE {
    136             for c in 0..GRID_SIZE {
    137                 self.cells[r][c].is_fixed = true;
    138             }
    139         }
    140 
    141         // Randomly remove cells
    142         let mut rng = rand::rng();
    143         let cells_to_remove = 40;
    144         let mut count = 0;
    145         while count < cells_to_remove {
    146             let r = rng.random_range(0..GRID_SIZE);
    147             let c = rng.random_range(0..GRID_SIZE);
    148             if self.cells[r][c].value.is_some() {
    149                 self.cells[r][c].value = None;
    150                 self.cells[r][c].is_fixed = false;
    151                 count += 1;
    152             }
    153         }
    154     }
    155 
    156     fn fill_box(&mut self, row: usize, col: usize) {
    157         use rand::seq::SliceRandom;
    158         let mut nums: Vec<u8> = (1..=9).collect();
    159         let mut rng = rand::rng();
    160         nums.shuffle(&mut rng);
    161         for r in 0..3 {
    162             for c in 0..3 {
    163                 self.cells[row + r][col + c].value = Some(nums[r * 3 + c]);
    164             }
    165         }
    166     }
    167 }