Compare commits
	
		
			3 Commits
		
	
	
		
			master
			...
			3c6d69c343
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3c6d69c343 | |||
| 1ff71d099e | |||
| ae9016fbf6 | 
@ -1,5 +1,5 @@
 | 
			
		||||
import { gql } from "../mods.js";
 | 
			
		||||
import { Sudoku, GenerateArguments, SolveArguments } from "../sudoku/index.js";
 | 
			
		||||
import { generate, GenerateArguments } from "../sudoku/index.js";
 | 
			
		||||
 | 
			
		||||
export const typeDefs = gql`
 | 
			
		||||
  """
 | 
			
		||||
@ -22,22 +22,16 @@ export const typeDefs = gql`
 | 
			
		||||
  type Query {
 | 
			
		||||
    "Generates a new sudoku."
 | 
			
		||||
    generate(regionWidth: Int = 3, regionHeight: Int = 3, clues: Int!): Sudoku!
 | 
			
		||||
 | 
			
		||||
    "Solves the given sudoku."
 | 
			
		||||
    solve(regionWidth: Int = 3, regionHeight: Int = 3, cells: [Int!]!): Sudoku!
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
interface SudokuFuncs {
 | 
			
		||||
  solve(args: SolveArguments): Promise<Sudoku> | Sudoku;
 | 
			
		||||
  generate(args: GenerateArguments): Promise<Sudoku> | Sudoku;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const resolvers = {
 | 
			
		||||
  Query: {
 | 
			
		||||
    generate: (obj: any, args: GenerateArguments, ctx: SudokuFuncs) =>
 | 
			
		||||
      ctx.generate(args),
 | 
			
		||||
    solve: (obj: any, args: SolveArguments, ctx: SudokuFuncs) =>
 | 
			
		||||
      ctx.solve(args),
 | 
			
		||||
    generate: (
 | 
			
		||||
      obj: any,
 | 
			
		||||
      { regionWidth, regionHeight, clues }: GenerateArguments
 | 
			
		||||
    ) => {
 | 
			
		||||
      return generate(regionWidth, regionHeight, clues);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								src/main.ts
									
									
									
									
									
								
							@ -1,13 +1,6 @@
 | 
			
		||||
import { Application, bodyParser } from "./mods.js";
 | 
			
		||||
import { applyGraphQL } from "./graphql.js";
 | 
			
		||||
import { typeDefs, resolvers } from "./graphql/index.js";
 | 
			
		||||
import {
 | 
			
		||||
  initializeWorkers,
 | 
			
		||||
  solve,
 | 
			
		||||
  generate,
 | 
			
		||||
  solveSync,
 | 
			
		||||
  generateSync,
 | 
			
		||||
} from "./sudoku/index.js";
 | 
			
		||||
import stoppable from "stoppable";
 | 
			
		||||
 | 
			
		||||
import cors from "@koa/cors";
 | 
			
		||||
@ -38,25 +31,10 @@ async function main() {
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let sudokuFuncs: any;
 | 
			
		||||
  if (process.env.USE_WORKER_THREADS) {
 | 
			
		||||
    initializeWorkers();
 | 
			
		||||
    sudokuFuncs = {
 | 
			
		||||
      solve,
 | 
			
		||||
      generate,
 | 
			
		||||
    };
 | 
			
		||||
  } else {
 | 
			
		||||
    sudokuFuncs = {
 | 
			
		||||
      solve: solveSync,
 | 
			
		||||
      generate: generateSync,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyGraphQL({
 | 
			
		||||
    app,
 | 
			
		||||
    typeDefs: typeDefs,
 | 
			
		||||
    resolvers: resolvers,
 | 
			
		||||
    context: () => sudokuFuncs,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  runtime.server = stoppable(
 | 
			
		||||
 | 
			
		||||
@ -114,10 +114,10 @@ export class DLX {
 | 
			
		||||
 | 
			
		||||
    let row = c as DNode;
 | 
			
		||||
    while ((row = row.down) !== c) {
 | 
			
		||||
      // traverse down the rows this column
 | 
			
		||||
      // traverse DOWN the rows this column contained
 | 
			
		||||
      let col = row;
 | 
			
		||||
      while ((col = col.right) !== row) {
 | 
			
		||||
        // traverse the columns of this row to the right
 | 
			
		||||
        // traverse the columns of this row (to the RIGHT)
 | 
			
		||||
 | 
			
		||||
        // remove this node from its column, and shrink its column's size
 | 
			
		||||
        this.updates++;
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
import { spawn, Thread, Worker } from "threads";
 | 
			
		||||
import type { ModuleThread } from "threads";
 | 
			
		||||
import { SudokuMath } from "./math.js";
 | 
			
		||||
import { prettyPrint } from "./util.js";
 | 
			
		||||
 | 
			
		||||
import WORKERS from "physical-cpu-count";
 | 
			
		||||
import { prettyPrint } from "./util.js";
 | 
			
		||||
 | 
			
		||||
const TIMEOUT = 20000;
 | 
			
		||||
 | 
			
		||||
@ -15,12 +13,6 @@ export type GenerateArguments = {
 | 
			
		||||
  clues: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type SolveArguments = {
 | 
			
		||||
  regionWidth: number;
 | 
			
		||||
  regionHeight: number;
 | 
			
		||||
  cells: Cell[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Sudoku = {
 | 
			
		||||
  regionWidth: number;
 | 
			
		||||
  regionHeight: number;
 | 
			
		||||
@ -28,41 +20,19 @@ export type Sudoku = {
 | 
			
		||||
  cells: Cell[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type SudokuWorker = {
 | 
			
		||||
  generate: (
 | 
			
		||||
    regionWidth: number,
 | 
			
		||||
    regionHeight: number,
 | 
			
		||||
    clues: number
 | 
			
		||||
  ) => Promise<number[]>;
 | 
			
		||||
  solve: (
 | 
			
		||||
    regionWidth: number,
 | 
			
		||||
    regionHeight: number,
 | 
			
		||||
    cells: number[]
 | 
			
		||||
  ) => Promise<[boolean, number[]]>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const available: ModuleThread<SudokuWorker>[] = [];
 | 
			
		||||
 | 
			
		||||
function spawnWorker() {
 | 
			
		||||
  spawn<SudokuWorker>(new Worker("./worker")).then((worker) =>
 | 
			
		||||
    available.push(worker)
 | 
			
		||||
  );
 | 
			
		||||
function getWorker() {
 | 
			
		||||
  return spawn(new Worker("./worker"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initializeWorkers() {
 | 
			
		||||
const available: any = [];
 | 
			
		||||
 | 
			
		||||
function initialize() {
 | 
			
		||||
  console.log(`Starting ${WORKERS} worker threads`);
 | 
			
		||||
  for (let n = 0; n < WORKERS; n++) {
 | 
			
		||||
    spawnWorker();
 | 
			
		||||
    getWorker().then((worker) => available.push(worker));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pickWorker() {
 | 
			
		||||
  const proxy = available.pop();
 | 
			
		||||
  if (!proxy) {
 | 
			
		||||
    throw new Error("No workers available right now. Please try again.");
 | 
			
		||||
  }
 | 
			
		||||
  return proxy;
 | 
			
		||||
}
 | 
			
		||||
initialize();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Awaits a promise with a timeout.
 | 
			
		||||
@ -82,33 +52,27 @@ function withTimeout<T>(promise: Promise<T>, ms: number, cb: () => any) {
 | 
			
		||||
  }).finally(() => clearTimeout(timeout!));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function workerTaskWithTimeout(
 | 
			
		||||
  task: (worker: ModuleThread) => Promise<any>,
 | 
			
		||||
  timeout: number
 | 
			
		||||
) {
 | 
			
		||||
  const worker = pickWorker();
 | 
			
		||||
  let timedOut = false;
 | 
			
		||||
  return withTimeout(task(worker), timeout, () => {
 | 
			
		||||
    timedOut = true;
 | 
			
		||||
    Thread.terminate(worker);
 | 
			
		||||
    spawnWorker();
 | 
			
		||||
    return new Error("Timed out.");
 | 
			
		||||
  }).finally(() => {
 | 
			
		||||
    if (!timedOut) {
 | 
			
		||||
      available.push(worker);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
export async function generate(
 | 
			
		||||
  regionWidth: number,
 | 
			
		||||
  regionHeight: number,
 | 
			
		||||
  clues: number
 | 
			
		||||
): Promise<Sudoku> {
 | 
			
		||||
  const proxy = available.pop();
 | 
			
		||||
  if (!proxy) {
 | 
			
		||||
    throw new Error("No workers available right now. Please try again.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
export async function generate({
 | 
			
		||||
  regionWidth,
 | 
			
		||||
  regionHeight,
 | 
			
		||||
  clues,
 | 
			
		||||
}: GenerateArguments): Promise<Sudoku> {
 | 
			
		||||
  const puzzle = await workerTaskWithTimeout(
 | 
			
		||||
    (worker) => worker.generate(regionWidth, regionHeight, clues),
 | 
			
		||||
    TIMEOUT
 | 
			
		||||
  const puzzle = await withTimeout<number[]>(
 | 
			
		||||
    proxy.generate(regionWidth, regionHeight, clues),
 | 
			
		||||
    TIMEOUT,
 | 
			
		||||
    () => {
 | 
			
		||||
      Thread.terminate(proxy);
 | 
			
		||||
      getWorker().then((worker) => available.push(worker));
 | 
			
		||||
      return new Error("Timed out. Try reducing the number of clues.");
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  available.push(proxy);
 | 
			
		||||
  prettyPrint(regionWidth, regionHeight, puzzle);
 | 
			
		||||
  return {
 | 
			
		||||
    regionWidth,
 | 
			
		||||
@ -117,67 +81,3 @@ export async function generate({
 | 
			
		||||
    cells: puzzle,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function generateSync({
 | 
			
		||||
  regionWidth,
 | 
			
		||||
  regionHeight,
 | 
			
		||||
  clues,
 | 
			
		||||
}: GenerateArguments): Sudoku {
 | 
			
		||||
  const math = SudokuMath.get(regionWidth, regionHeight);
 | 
			
		||||
  const cells = math.generate(clues, 1, TIMEOUT);
 | 
			
		||||
  prettyPrint(regionWidth, regionHeight, cells);
 | 
			
		||||
  return {
 | 
			
		||||
    regionWidth,
 | 
			
		||||
    regionHeight,
 | 
			
		||||
    size: (regionWidth * regionHeight) ** 2,
 | 
			
		||||
    cells,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function solve({
 | 
			
		||||
  regionWidth,
 | 
			
		||||
  regionHeight,
 | 
			
		||||
  cells,
 | 
			
		||||
}: SolveArguments): Promise<Sudoku> {
 | 
			
		||||
  const size = (regionWidth * regionHeight) ** 2;
 | 
			
		||||
  if (size !== cells.length) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "The given region dimensions do not align with the number of cells."
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  const [solved, result] = await workerTaskWithTimeout(
 | 
			
		||||
    (proxy) => proxy.solve(regionWidth, regionHeight, cells),
 | 
			
		||||
    TIMEOUT
 | 
			
		||||
  );
 | 
			
		||||
  if (!solved) {
 | 
			
		||||
    throw new Error("The given puzzle has no solution.");
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    regionWidth,
 | 
			
		||||
    regionHeight,
 | 
			
		||||
    size,
 | 
			
		||||
    cells: result,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function solveSync({
 | 
			
		||||
  regionWidth,
 | 
			
		||||
  regionHeight,
 | 
			
		||||
  cells,
 | 
			
		||||
}: SolveArguments): Sudoku {
 | 
			
		||||
  const size = (regionWidth * regionHeight) ** 2;
 | 
			
		||||
  if (size !== cells.length) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "The given region dimensions do not align with the number of cells."
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (!SudokuMath.get(regionWidth, regionHeight).solve(cells)) {
 | 
			
		||||
    throw new Error("The given puzzle has no solution.");
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    regionWidth,
 | 
			
		||||
    regionHeight,
 | 
			
		||||
    size,
 | 
			
		||||
    cells,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -141,28 +141,27 @@ export class SudokuMath {
 | 
			
		||||
    return [...firstRow, ...Array(this.values2 - this.values).fill(0)];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateComplete(): [number[], number] {
 | 
			
		||||
  generateComplete() {
 | 
			
		||||
    const result = this._baseBoard();
 | 
			
		||||
    const [header] = this.getDLXHeader(result, true);
 | 
			
		||||
    const dlx = new DLX(header, (solution: DNode[]) => {
 | 
			
		||||
 | 
			
		||||
    const callback = (solution: DNode[]) => {
 | 
			
		||||
      solution.forEach((node) => {
 | 
			
		||||
        const meta: NodeMeta = node.meta;
 | 
			
		||||
        result[meta.index] = meta.value;
 | 
			
		||||
      });
 | 
			
		||||
      // stop after the first solution
 | 
			
		||||
      // return the first solution
 | 
			
		||||
      return true;
 | 
			
		||||
    });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const dlx = new DLX(header, callback);
 | 
			
		||||
 | 
			
		||||
    dlx.search();
 | 
			
		||||
    return [result, dlx.updates];
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generate(clues: number, attempts = Infinity, totalTime = Infinity) {
 | 
			
		||||
    if (clues === 0) {
 | 
			
		||||
      return Array(this.values2).fill(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let [completed, updates] = this.generateComplete();
 | 
			
		||||
    const completed = this.generateComplete();
 | 
			
		||||
 | 
			
		||||
    const [header, dlxRows] = this.getDLXHeader(); // complete header - no candidates removed
 | 
			
		||||
 | 
			
		||||
@ -177,93 +176,61 @@ export class SudokuMath {
 | 
			
		||||
      candidates[meta.index][meta.value - 1] = node;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // board positions which have been removed
 | 
			
		||||
    const removed = new Set<number>();
 | 
			
		||||
    const masked: DNode[] = [];
 | 
			
		||||
 | 
			
		||||
    const hasOneSolution = () => {
 | 
			
		||||
      solutions = 0;
 | 
			
		||||
      dlx.search();
 | 
			
		||||
      updates += dlx.updates;
 | 
			
		||||
      return solutions === 1;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const mask = () => {
 | 
			
		||||
      // mask all DLX rows which are nullified by existing values
 | 
			
		||||
      for (let n = 0; n < this.values2; n++) {
 | 
			
		||||
        if (removed.has(n)) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        const nodes = candidates[n];
 | 
			
		||||
        nodes.forEach((node) => {
 | 
			
		||||
          if (node.meta.value !== completed[n]) {
 | 
			
		||||
            masked.push(node);
 | 
			
		||||
            maskRow(node);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const unmask = () => {
 | 
			
		||||
      // unmask all DLX rows
 | 
			
		||||
      while (masked.length > 0) {
 | 
			
		||||
        unmaskRow(masked.pop()!);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const start = Date.now();
 | 
			
		||||
    const elapsed = () => Date.now() - start;
 | 
			
		||||
 | 
			
		||||
    // masked rows in the order they were masked
 | 
			
		||||
    const masked: DNode[] = [];
 | 
			
		||||
 | 
			
		||||
    const maskAtIdx = (idx: number) => {
 | 
			
		||||
      const nodes = candidates[idx];
 | 
			
		||||
      nodes.forEach((node) => {
 | 
			
		||||
        if (node.meta.value !== completed[idx]) {
 | 
			
		||||
          masked.push(node);
 | 
			
		||||
          maskRow(node);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const removed = new Set<number>();
 | 
			
		||||
    const removeable = Array.from(this.indexes);
 | 
			
		||||
 | 
			
		||||
    const maskUpto = (n: number) => {
 | 
			
		||||
      for (let j = 0; j < n; j++) {
 | 
			
		||||
        // process up to what we've handled
 | 
			
		||||
        const idx = removeable[j];
 | 
			
		||||
        if (removed.has(idx)) {
 | 
			
		||||
          // if we've removed this cell, do not mask it
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        // otherwise, we had given up it; mask it leaving the original value
 | 
			
		||||
        // we won't try this cell again
 | 
			
		||||
        maskAtIdx(idx);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (let j = this.values2 - 1; j >= n; j--) {
 | 
			
		||||
        // for all those we haven't handled, mask leaving the original values
 | 
			
		||||
        // for us to to attempt to unmask one cell at a time
 | 
			
		||||
        maskAtIdx(removeable[j]);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const unmaskAll = () => {
 | 
			
		||||
      let node;
 | 
			
		||||
      while ((node = masked.pop())) {
 | 
			
		||||
        unmaskRow(node);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const unmaskNextCell = () => {
 | 
			
		||||
      for (let j = 0; j < this.values - 1; j++) {
 | 
			
		||||
        unmaskRow(masked.pop()!);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let best = 0;
 | 
			
		||||
    const attempt = () => {
 | 
			
		||||
      // attempt to remove cells until 'clues' cells remain
 | 
			
		||||
      attempts--;
 | 
			
		||||
      removed.clear();
 | 
			
		||||
      // attempt remove cells until 'clues' cells remain
 | 
			
		||||
      shuffle(removeable);
 | 
			
		||||
      maskUpto(0);
 | 
			
		||||
 | 
			
		||||
      for (let i = 0; i < this.values2; i++) {
 | 
			
		||||
      for (let n = 0; n < this.values2; n++) {
 | 
			
		||||
        if (elapsed() > totalTime || this.values2 - removed.size == clues) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        unmaskNextCell();
 | 
			
		||||
 | 
			
		||||
        let toRemove = removeable[n];
 | 
			
		||||
        removed.add(toRemove);
 | 
			
		||||
        mask();
 | 
			
		||||
 | 
			
		||||
        if (!hasOneSolution()) {
 | 
			
		||||
          // failed attempt. prepare for next
 | 
			
		||||
          unmaskAll();
 | 
			
		||||
          maskUpto(i + 1);
 | 
			
		||||
          continue;
 | 
			
		||||
          removed.delete(toRemove);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        removed.add(removeable[i]);
 | 
			
		||||
        unmask();
 | 
			
		||||
      }
 | 
			
		||||
      if (removed.size > best) {
 | 
			
		||||
        best = removed.size;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      unmaskAll();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    while (
 | 
			
		||||
@ -273,6 +240,8 @@ export class SudokuMath {
 | 
			
		||||
    ) {
 | 
			
		||||
      // try to reach the clue goal up to `attempts` times or as long as
 | 
			
		||||
      // elapsed time is less than `totalTime`
 | 
			
		||||
      attempts--;
 | 
			
		||||
      removed.clear();
 | 
			
		||||
      attempt();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -282,28 +251,16 @@ export class SudokuMath {
 | 
			
		||||
    return completed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  solve(puzzle: Cell[]) {
 | 
			
		||||
    const [header] = this.getDLXHeader(puzzle);
 | 
			
		||||
    let solved = false;
 | 
			
		||||
  solve(existing: Cell[]): void {
 | 
			
		||||
    const [header] = this.getDLXHeader(existing);
 | 
			
		||||
    const callback = (solution: DNode[]) => {
 | 
			
		||||
      solution.forEach((node) => {
 | 
			
		||||
        const meta: NodeMeta = node.meta;
 | 
			
		||||
        puzzle[meta.index] = meta.value;
 | 
			
		||||
        existing[meta.index] = meta.value;
 | 
			
		||||
      });
 | 
			
		||||
      solved = true;
 | 
			
		||||
      // return the first solution
 | 
			
		||||
      return true;
 | 
			
		||||
    };
 | 
			
		||||
    new DLX(header, callback).search();
 | 
			
		||||
    return solved;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static maths: { [key: string]: SudokuMath } = {};
 | 
			
		||||
  static get(regionWidth: number, regionHeight: number) {
 | 
			
		||||
    const key = `${regionWidth}:${regionHeight}`;
 | 
			
		||||
    return (
 | 
			
		||||
      SudokuMath.maths[key] ??
 | 
			
		||||
      (SudokuMath.maths[key] = new SudokuMath(regionWidth, regionHeight))
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,16 @@
 | 
			
		||||
import { SudokuMath } from "./math.js";
 | 
			
		||||
import { expose } from "threads/worker";
 | 
			
		||||
 | 
			
		||||
const maths = {};
 | 
			
		||||
 | 
			
		||||
expose({
 | 
			
		||||
  generate(regionWidth, regionHeight, clues) {
 | 
			
		||||
    return SudokuMath.get(regionWidth, regionHeight).generate(clues);
 | 
			
		||||
  },
 | 
			
		||||
  solve(regionWidth, regionHeight, cells) {
 | 
			
		||||
    const result = SudokuMath.get(regionWidth, regionHeight).solve(cells);
 | 
			
		||||
    return [result, cells];
 | 
			
		||||
    const math =
 | 
			
		||||
      maths[`${regionWidth}:${regionHeight}`] ||
 | 
			
		||||
      (maths[`${regionWidth}:${regionHeight}`] = new SudokuMath(
 | 
			
		||||
        regionWidth,
 | 
			
		||||
        regionHeight
 | 
			
		||||
      ));
 | 
			
		||||
    return math.generate(clues);
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user