139 lines
4.9 KiB
JavaScript
139 lines
4.9 KiB
JavaScript
import VoxelSpace from "./VoxelSpace.js";
|
|
import SomaSolution from "./SomaSolution.js";
|
|
export default class SomaSolver {
|
|
constructor(dimension) {
|
|
this.solutions = [];
|
|
this.iterations = 0;
|
|
if (dimension % 1 !== 0 || dimension < 0) {
|
|
throw new Error("The argument 'dimension' must be a positive whole number");
|
|
}
|
|
this.dim = dimension;
|
|
this.solutionCube = new VoxelSpace(0, [dimension, dimension, dimension], Array(dimension ** 3).fill(0));
|
|
}
|
|
async solve(polycubes) {
|
|
if (polycubes.length === 0) {
|
|
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
|
}
|
|
let cumulativeSize = polycubes.reduce((prev, curr) => prev + curr.size(), 0);
|
|
if (cumulativeSize !== this.dim ** 3) {
|
|
throw new Error(`The polycubes passed do not add up to exactly enough units to form a cube of dimension ${this.dim}! Got: ${cumulativeSize}, need: ${this.dim ** 3}`);
|
|
}
|
|
this.solutions = [];
|
|
const combosWithRots = polycubes.slice(1).map(polycube => polycube.getUniqueRotations().map((rot) => rot.getAllPositionsInCube(this.dim)).flat());
|
|
const combos = [polycubes[0].getAllPositionsInCube(this.dim), ...combosWithRots];
|
|
console.log(combos.flat().length);
|
|
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
|
this.solutions = SomaSolution.filterUnique(this.solutions);
|
|
}
|
|
getSolutions() {
|
|
return this.solutions.slice();
|
|
}
|
|
backtrackSolve(workingSolution, polycubes, currentSoln) {
|
|
const nextCubeGroup = polycubes[0];
|
|
for (let i = 0; i < nextCubeGroup.length; i++) {
|
|
const fusionAttempt = workingSolution.plus(nextCubeGroup[i]);
|
|
if (fusionAttempt) {
|
|
const nextSoln = currentSoln.clone();
|
|
nextSoln.addSpace(nextCubeGroup[i]);
|
|
if (polycubes.length === 1) {
|
|
this.solutions.push(nextSoln);
|
|
currentSoln = new SomaSolution(this.dim);
|
|
return;
|
|
}
|
|
else {
|
|
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SomaSolution {
|
|
constructor(dim) {
|
|
if (dim < 0 || dim % 1 !== 0) {
|
|
throw new Error("Dimension must be a whole positive integer!");
|
|
}
|
|
this.dim = dim;
|
|
this.solutionSpaces = [];
|
|
}
|
|
static filterUnique(solutions) {
|
|
if (solutions.length === 0) {
|
|
return [];
|
|
}
|
|
const uniqueSolns = [solutions[0]];
|
|
for (const solution of solutions) {
|
|
let foundMatch = false;
|
|
for (const rotation of solution.getUniqueRotations()) {
|
|
let end = uniqueSolns.length;
|
|
for (let i = 0; i < end; i++) {
|
|
if (rotation.matches(uniqueSolns[i])) {
|
|
foundMatch = true;
|
|
}
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
uniqueSolns.push(solution);
|
|
}
|
|
}
|
|
return uniqueSolns;
|
|
}
|
|
getUniqueRotations() {
|
|
if (this.solutionSpaces.length === 0) {
|
|
return [];
|
|
}
|
|
const result = [];
|
|
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
|
for (let i = 0; i < allRots[0].length; i++) {
|
|
const solnRot = new SomaSolution(this.dim);
|
|
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
|
result.push(solnRot);
|
|
}
|
|
return result;
|
|
}
|
|
matches(solution) {
|
|
for (let i = 0; i < this.solutionSpaces.length; i++) {
|
|
if (!this.solutionSpaces[i].matches(solution.solutionSpaces[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
addSpace(space) {
|
|
this.solutionSpaces.push(space);
|
|
}
|
|
print() {
|
|
let accum = "";
|
|
console.log("---");
|
|
for (let x = 0; x < this.dim; x++) {
|
|
for (let y = 0; y < this.dim; y++) {
|
|
for (let z = 0; z < this.dim; z++) {
|
|
for (const space of this.solutionSpaces) {
|
|
if (space.at(x, y, z)) {
|
|
accum += space.getId();
|
|
}
|
|
}
|
|
}
|
|
console.log(accum);
|
|
accum = "";
|
|
}
|
|
if (x !== this.dim - 1) {
|
|
console.log("-");
|
|
}
|
|
}
|
|
console.log("---");
|
|
}
|
|
at(x, y, z) {
|
|
for (const space of this.solutionSpaces) {
|
|
if (space.at(x, y, z)) {
|
|
return space.getId();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
clone() {
|
|
const clone = new SomaSolution(this.dim);
|
|
clone.solutionSpaces = this.solutionSpaces.slice();
|
|
return clone;
|
|
}
|
|
}
|