1

I am fairly new to Rust, creating this sudoku generator as one of my first projects. I am following algorithm - How to generate Sudoku boards with unique solutions, but it keeps generating boards that contain multiple of the same number per 3X3 area.

Here's the code I have for generating the boards:

use std::{fmt::{Debug, Formatter, Error, Display}, ops::Deref};

use rand::Rng;
use termion::color;

const POSITIONS: [i32; 10] = [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    0
];


#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Slot {
    pub val: i32
}

#[derive(Clone, Debug)]
pub struct SlotGrid {
    pub data: Vec<Vec<Slot>>
}

pub struct Board {
    data: SlotGrid,
    solved: SlotGrid,
    pub game_data: SlotGrid
}

impl Slot {
    pub fn new(val: i32) -> Self {
        if !POSITIONS.to_vec().contains(&val) {
            panic!("Invalid value");
        }

        Self { val: val }
    }
}

// TODO: custom size support
impl SlotGrid {
    pub fn new() -> Self {
        let d1: Vec<Vec<i32>> = vec![
            vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
            vec![4, 5, 6, 7, 8, 9, 1, 2, 3],
            vec![7, 8, 9, 1, 2, 3, 4, 5, 6],
            vec![2, 3, 1, 5, 6, 4, 8, 9, 7],
            vec![5, 6, 4, 8, 9, 7, 2, 3, 1],
            vec![8, 9, 7, 2, 3, 1, 5, 6, 4],
            vec![3, 1, 2, 6, 4, 5, 9, 7, 8],
            vec![6, 4, 5, 9, 7, 8, 3, 1, 2],
            vec![9, 7, 8, 3, 1, 2, 6, 4, 5]
        ];

        let mut d2: Vec<Vec<Slot>> = Vec::new();

        for row in d1 {
            let mut row2: Vec<Slot> = Vec::new();
                
            for item in row {
                row2.push(Slot::new(item));
            }

            d2.push(row2);
        }

        Self { data: d2 }
    }

    pub fn set(&mut self, x: usize, y: usize, val: i32) {
        self.data[y][x].val = val;
    }

    pub fn get(&self, x: usize, y: usize) -> Slot {
        self.data[y][x]
    }

    fn swap_slots(&mut self, n1: i32, n2: i32) {
        for y in 0 .. 8 {
            for x in 0 .. 8 {
                if self.get(x, y).val == n1 {
                    self.set(x, y, n2);
                    continue;
                }
                
                if self.get(x, y).val == n2 {
                    self.set(x, y, n1);
                }
            }
        }   
    }

    fn swap_rows(&mut self, y1: usize, y2: usize) {
        let first: Vec<Slot> = self.data[y1].clone();
        let second: Vec<Slot> = self.data[y2].clone();

        self.data[y2] = first;
        self.data[y1] = second;
    }

    fn swap_cols(&mut self, x1: usize, x2: usize) {
        for y in 0 .. 8 {
            let first = self.data[y][x1];
            let second = self.data[y][x2];

            self.data[y][x1].val = second.val;
            self.data[y][x2].val = first.val;
        }
    }

    fn swap_3x3_rows(&mut self, y1: usize, y2: usize) {
        for i in 0 .. 2 {
            self.swap_rows(y1 * 3 + i, y2 * 3 + i);
        }
    }

    fn swap_3x3_cols(&mut self, x1: usize, x2:usize) {
        for i in 0 .. 2 {
            self.swap_cols(x1 * 3 + i, x2 * 3 + i);
        }
    }

    pub fn shuffle_rows(&mut self) {
        let mut rng = rand::thread_rng();
        let mut num: usize;
                    
        for i in 0 .. 8 {
            let rand = rng.gen::<usize>() % 3;
            num = i / 3;
            self.swap_rows(i, num * 3 + rand);
        }
    }

    pub fn shuffle_cols(&mut self) {
        let mut rng = rand::thread_rng();
        let mut num: usize;

        for i in 0 .. 0 {
            let rand = rng.gen::<usize>() % 3;
            num = i / 3;
            self.swap_cols(i, num * 3 + rand);
        }
    }

    pub fn shuffle_nums(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 8 {
            let num = rng.gen::<usize>() % 9;

            self.swap_slots(i, num.try_into().unwrap());
        }
    }

    pub fn shuffle_3x3_rows(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 2 {
            let num = rng.gen::<usize>() % 3;
            
            self.swap_3x3_rows(i, num);
        }
    }

    pub fn shuffle_3x3_cols(&mut self) {
        let mut rng = rand::thread_rng();
        
        for i in 0 .. 2 {
            let num = rng.gen::<usize>() % 3;

            self.swap_3x3_cols(i, num);
        }
    }
}

impl Board {
    pub fn new(cmplx: usize) -> Self {
        let mut data = SlotGrid::new();
        let mut rng = rand::thread_rng();

        data.shuffle_rows();
        data.shuffle_cols();
        data.shuffle_3x3_rows();
        data.shuffle_3x3_cols();

        let old_data = data.clone();
        let mut out: SlotGrid = data.clone(); 

        let mut i = 0;

        loop {
            // remove until usolvable and go back 1

            let x = rng.gen::<usize>() % 9;
            let y = rng.gen::<usize>() % 9;

            data.set(x, y, 0);
        
            if !back_track(&mut (data.clone()), 0, 0) && i >= cmplx {
                break;
            }
            
            i += 1;
            out = data.clone();
        }

        Self { data: out.clone(), solved: old_data, game_data: out }
    }

    pub fn is_solved(&self) -> bool {
        for i in 0 .. self.data.data.len() - 1 {
            for j in 0 .. self.data.data[i].len() - 1 {
                if self.data.data[i][j] != self.solved.data[i][j] {
                    return false;
                }
            }
        }

        true
    }
}

impl Display for Board {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        let mut s = String::new();

        let mut i = 10;
        for row in &self.data.data {
            i -= 1;
            
            s.push_str(color::Fg(color::Magenta).to_string().deref());
            s.push_str(i.to_string().deref());
            s.push_str(" | ");
            s.push_str(color::Fg(color::Reset).to_string().deref());
            
            let mut j = 0;
            for slot in row {
                j += 1;

                let val = if slot.val == 0 {
                    "_".to_string()
                } else {
                    slot.val.to_string()
                };
                
                s.push_str(colorify(j, 9 - i, val).deref());
                s.push(' ');
            }

            s.push_str("\n");
            print!("\n");
        }
        
        s.push_str(color::Fg(color::Red).to_string().deref());
        s.push_str("    A B C D E F G H I");
        s.push_str(color::Fg(color::Reset).to_string().deref());

        write!(f, "{}", s)
    }
}

fn colorify(x: usize, y: usize, s: String) -> String {
    let x2 = x - 1;

    let corners: String = if (0 .. 3).contains(&x2) || (6 .. 9).contains(&x2) {
        color::Fg(color::LightCyan).to_string()
    } else {
        color::Fg(color::Green).to_string()
    };

    let mut out = String::new();

    if (0 .. 3).contains(&y) {
        out.push_str(corners.deref());
    }
    else if (3 .. 6).contains(&y) {
        if (3 .. 6).contains(&x2) {
            out.push_str(color::Fg(color::LightCyan).to_string().deref());
        }
        else {
            out.push_str(color::Fg(color::Green).to_string().deref());
        }
    }
    else if (6 .. 9).contains(&y) {
        out.push_str(corners.deref());
    }

    out.push_str(s.deref());
    out.push_str(color::Fg(color::Reset).to_string().deref());

    out
}

fn back_track(data: &mut SlotGrid, mut x: usize, mut y: usize) -> bool {
    if x == 8 && y == 8 {
        return true;
    }

    if x == 8 {
        y += 1;
        x = 0;
    }

    if data.get(x, y).val > 0 {
        return back_track(data, x + 1, y);
    }

    for i in 1 .. 8 {
        if is_safe(&data, x, y, i) {
            data.set(x, y, i.try_into().unwrap());
        
            if back_track(data, x + 1, y) {
                return true;
            }
        }

        data.set(x, y, 0);
    }

    return false;
}

pub fn is_safe(data: &SlotGrid, x: usize, y: usize, val: usize) -> bool {
    println!("ran is_safe");
    for y2 in 0 .. 8 {
        if data.get(x, y2).val == val.try_into().unwrap() {
            drop(data);
            println!("A");
            return false;
        }
    }

    for x2 in 0 .. 8 {
        if data.get(x2, y).val == val.try_into().unwrap() {
            drop(data);
            println!("B");
            return false;
        }
    }

    let start_row = y - (y % 3);
    let start_col = x - (x % 3);

    for i in 0 .. 2 {
        for j in 0 .. 2 {
            if data.get(i + start_row, j + start_col).val == val.try_into().unwrap() {
                drop(data);
                println!("C");
                return false;
            }
        }
    }

    drop(data);
    return true;
}

It appears that there may be something wrong with is_safe, but having multiple numbers per area means there is something wrong with the way the numbers are originally mixed. I have looked around a bit but have found nothing out of the ordinary (although I might just be blind). Can someone help me figure out what I did wrong here?

  • 2
    Looks like you used the [normal (exclusive) range operator](https://stackoverflow.com/q/31797599/555045) but wrote down your bounds as if you were using the [inclusive range operator](https://stackoverflow.com/q/52932572/555045) so `is_safe` doesn't look at the whole board – harold Jan 18 '23 at 12:47
  • Since you are learning Rust, you should definitively take advantage of this situation to try and learn how to use a debugger with Rust. It's the kind of tools that would really help you with this kind of issues, since you could just see the execution of your algorithm and witness as it goes wrong, so you understand exactly *where* that is happening. – jthulhu Jan 18 '23 at 12:51
  • @jthulhu any recommendations for specific debuggers? – a human being Jan 18 '23 at 13:33
  • @ahumanbeing personally, I use gdb, which is a generic debugger (ie. it works with any binary), which is very powerful. In addition to that, I have rust-gdb, which is a layer that adds pretty-printing Rust values (otherwise gdb will just print every pointer and such). This is a terminal-based debugger, so maybe it's not what you might prefer. But usually it has great integration with editors, as it's a very popular debugger in general. – jthulhu Jan 18 '23 at 16:19

1 Answers1

0

Like @harold said, the solution was to use inclusive operators in my code. There are still some errors in my main file to fix, but this specific issue is solved so I will be closing the question.