algx 0.2.0

Implementation of Knuth's Algorithm X
Documentation
use std::collections::{BTreeSet, VecDeque};

#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

use js_sys::Array;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
#[derive(Default)]
pub struct SolverBuilder {
    rows: Vec<Vec<usize>>,
    initial_columns: Vec<usize>,
}

#[wasm_bindgen]
impl SolverBuilder {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_row(&mut self, row: Vec<usize>) {
        self.rows.push(row);
    }

    pub fn set_initial_columns(&mut self, initial_columns: Vec<usize>) {
        self.initial_columns = initial_columns;
    }

    pub fn build(self) -> Solver {
        Solver {
            solver: crate::Solver::new(self.rows, self.initial_columns),
        }
    }
}

#[wasm_bindgen]
pub struct Solver {
    solver: crate::Solver,
}

#[wasm_bindgen]
impl Solver {
    pub fn next_solution(&mut self) -> Array {
        let next_solution = self.solver.next();
        into_js_array(next_solution.unwrap_or_default())
    }

    pub async fn all_solutions(self) -> Array {
        self.solver.map(into_js_array).collect()
    }
}

fn into_js_array<T>(vec: Vec<T>) -> Array
where
    JsValue: From<T>,
{
    vec.into_iter().map(JsValue::from).collect()
}

#[wasm_bindgen]
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct Pos {
    pub x: i32,
    pub y: i32,
}

impl Pos {
    pub fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }
}

#[wasm_bindgen]
pub fn generate_polyamino_rows(square_count: usize) -> Array {
    let mut shapes = BTreeSet::new();

    let mut stack: VecDeque<Vec<(i32, i32)>> = VecDeque::new();
    stack.push_back(vec![(0, 0)]);

    while let Some(shape) = stack.pop_front() {
        if shape.len() == square_count {
            let mut ret = vec![];
            let min_x = shape.iter().map(|(x, _)| x).min().copied().unwrap();
            let min_y = shape.iter().map(|(_, y)| y).min().copied().unwrap();

            for (x, y) in shape {
                ret.push(Pos::new(x - min_x, y - min_y));
            }

            shapes.insert(ret);
        } else {
            for (i, j) in [(1, 0), (0, 1), (0, -1), (-1, 0)] {
                let mut shape = shape.clone();
                let mut pos = shape.last().copied().unwrap();
                pos.0 += i;
                pos.1 += j;

                if !shape.contains(&pos) {
                    shape.push(pos);
                    stack.push_back(shape);
                }
            }
        }
    }

    into_js_array(shapes.into_iter().map(into_js_array).collect())
}