diff --git a/src/graphql/sudoku.ts b/src/graphql/sudoku.ts index 671f1d3..add567d 100644 --- a/src/graphql/sudoku.ts +++ b/src/graphql/sudoku.ts @@ -2,9 +2,6 @@ import { gql } from "../mods"; import { generate, GenerateArguments } from "../sudoku/index"; export const typeDefs = gql` - type Cell { - value: Int - } """ A sudoku """ @@ -19,7 +16,7 @@ export const typeDefs = gql` size: Int! "The rows of the board, from top to bottom." - cells: [[Cell!]!]! + cells: [Int!]! } type Query { diff --git a/src/sudoku/index.ts b/src/sudoku/index.ts index d18b60d..00e0e20 100644 --- a/src/sudoku/index.ts +++ b/src/sudoku/index.ts @@ -1,9 +1,10 @@ import { StaticPool, isTimeoutError } from "node-worker-threads-pool"; import WORKERS from "physical-cpu-count"; +import { prettyPrint } from "./util"; const TIMEOUT = 20000; -export type Cell = { value: number }; +export type Cell = number; export type GenerateArguments = { regionWidth: number; @@ -15,10 +16,10 @@ export type Sudoku = { regionWidth: number; regionHeight: number; size: number; - cells: Cell[][]; + cells: Cell[]; }; -const pool = new StaticPool({ +const pool = new StaticPool({ size: WORKERS, task: "./src/sudoku/worker.js", }); @@ -44,6 +45,8 @@ export async function generate( TIMEOUT ); + prettyPrint(regionWidth, regionHeight, puzzle); + return { regionWidth, regionHeight, diff --git a/src/sudoku/math.ts b/src/sudoku/math.ts index 7e2746c..0e08db3 100644 --- a/src/sudoku/math.ts +++ b/src/sudoku/math.ts @@ -11,6 +11,7 @@ import { shuffle, range } from "./util"; import { Cell } from "./index"; type NodeMeta = { + index: number; row: number; col: number; region: number; @@ -73,11 +74,11 @@ export class SudokuMath { Math.floor(row / this.regionHeight) * this.regionHeight + Math.floor(col / this.regionWidth); const val = candidate % this.values; - return [candidate, row, col, region, val]; + return [boardIndex, row, col, region, val]; } - _checkInput(cells: Cell[][]) { - if (cells.length !== this.values || cells[0].length !== this.values) { + _checkInput(cells: Cell[]) { + if (cells.length !== this.values2) { throw new Error( "Given cells array does not match regionWidth & regionHeight" ); @@ -86,7 +87,7 @@ export class SudokuMath { // this takes a bit of time and the value may need to be cached getDLXHeader( - cells: undefined | Cell[][] = undefined, + cells: undefined | Cell[] = undefined, randomSearch = false ): [CNode, DNode[]] { if (cells) this._checkInput(cells); @@ -111,9 +112,9 @@ export class SudokuMath { : this.candidates; const dlxRows: DNode[] = []; - candidates.forEach(([i, row, col, region, val]) => { + candidates.forEach(([boardIndex, row, col, region, val]) => { if (cells) { - const exist = cells[row][col].value; + const exist = cells[boardIndex]; if (exist && exist - 1 !== val) { // skip candidates matching this constraint's position, but not its value // the effect is the exisitng value is preserved in the output @@ -121,7 +122,7 @@ export class SudokuMath { } } - const meta = { row, col, region, value: val + 1 }; + const meta = { index: boardIndex, row, col, region, value: val + 1 }; const dlxRow = linkNodesLR( this.getConstraintIDs(val, row, col, region).map((id) => addNodeToColumn(constraints[id], meta) @@ -133,20 +134,11 @@ export class SudokuMath { return [header, dlxRows]; } - _baseBoard(): Cell[][] { + _baseBoard(): Cell[] { // return a sudoku board with a random set of values in the first row // used in generateComplete for small speedup const firstRow = shuffle(range(1, this.values + 1)); - return [ - firstRow.map((value) => ({ value })), - ...Array(this.values - 1) - .fill(null) - .map(() => - Array(this.values) - .fill(0) - .map((value) => ({ value })) - ), - ]; + return [...firstRow, ...Array(this.values2 - this.values).fill(0)]; } generateComplete() { @@ -156,7 +148,7 @@ export class SudokuMath { const callback = (solution: DNode[]) => { solution.forEach((node) => { const meta: NodeMeta = node.meta; - result[meta.row][meta.col] = { value: meta.value }; + result[meta.index] = meta.value; }); // return the first solution return true; @@ -176,12 +168,12 @@ export class SudokuMath { let solutions = 0; const dlx = new DLX(header, () => ++solutions >= 2); - const candidates: DNode[][][] = Array.from(Array(this.values), () => - Array.from(Array(this.values), () => Array(this.values)) + const candidates: DNode[][] = Array.from(Array(this.values2), () => + Array.from(Array(this.values)) ); dlxRows.forEach((node) => { const meta = node.meta; - candidates[meta.row][meta.col][meta.value - 1] = node; + candidates[meta.index][meta.value - 1] = node; }); // board positions which have been removed @@ -200,12 +192,9 @@ export class SudokuMath { if (removed.has(n)) { continue; } - const row = Math.floor(n / this.values); - const col = n % this.values; - const existValue = completed[row][col].value; - const nodes = candidates[row][col]; + const nodes = candidates[n]; nodes.forEach((node) => { - if (node.meta.value !== existValue) { + if (node.meta.value !== completed[n]) { masked.push(node); maskRow(node); } @@ -257,17 +246,17 @@ export class SudokuMath { } removed.forEach((index) => { - completed[Math.floor(index / this.values)][index % this.values].value = 0; + completed[index] = 0; }); return completed; } - solve(existing: Cell[][]): void { + solve(existing: Cell[]): void { const [header] = this.getDLXHeader(existing); const callback = (solution: DNode[]) => { solution.forEach((node) => { const meta: NodeMeta = node.meta; - existing[meta.row][meta.col] = { value: meta.value }; + existing[meta.index] = meta.value; }); // return the first solution return true; diff --git a/src/sudoku/util.ts b/src/sudoku/util.ts index 7846663..0920b48 100644 --- a/src/sudoku/util.ts +++ b/src/sudoku/util.ts @@ -48,39 +48,43 @@ export function mapArray(arr: any[], indexes: number[]) { } export function clone(puzz: Cell[]): Cell[] { - return puzz.map(({ value }) => ({ value })); + return Array.from(puzz); } export function addCellValuesToSet(set: Set, cells: Cell[]) { cells.forEach((cell) => { - if (!!cell.value) set.add(cell.value); + if (cell > 0) set.add(cell); }); return set; } -export function prettyPrint(puzzle: Cell[][]) { - let width = Math.sqrt(puzzle[0].length); - let height = Math.sqrt(puzzle.length); +export function prettyPrint(width: number, height: number, cells: Cell[]) { + const side = width * height; + let line = ""; + cells.forEach((cell, i) => { + if (i > 0 && i % side == 0) { + console.log(line); + line = ""; - puzzle.forEach((row, i) => { - let line = ""; - row.forEach(({ value: cell }, j) => { - if (j > 0 && j % width == 0) { - line += "| "; + if (i > 0 && Math.floor(i / side) % height == 0) { + let divider = ""; + Array(width * height) + .fill(null) + .forEach((_, j) => { + if (j > 0 && j % width == 0) { + divider += " "; + } + divider += "---"; + }); + console.log(divider); } - line += ((cell ? cell : " ") + " ").padStart(3, " "); - }); - - if (i > 0 && i % height == 0) { - let divider = ""; - row.forEach((_, j) => { - if (j > 0 && j % width == 0) { - divider += " "; - } - divider += "-- "; - }); - console.log(divider); } - console.log(line); + + if (i % side !== 0 && i % width === 0) { + line += "| "; + } + + line += (cell > 0 ? `${cell}` : "").padStart(2, " ") + " "; }); + console.log(line); }