It all works....
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
import type VoxelSpace from "./VoxelSpace";
|
||||
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
import type VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||
|
||||
export default class SomaSolution {
|
||||
private solutionSpaces: VoxelSpace[];
|
||||
private dim: number;
|
||||
constructor(dim: number) {
|
||||
if (dim < 0 || dim % 1 !== 0) {
|
||||
throw new Error("Dimension must be a whole positive integer!");
|
||||
}
|
||||
this.dim = dim;
|
||||
private solutionSpaces: (VoxelSpaceBoolean | VoxelSpaceBigInt)[];
|
||||
private dimX: number;
|
||||
private dimY: number;
|
||||
private dimZ: number;
|
||||
|
||||
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionSpaces = [];
|
||||
}
|
||||
|
||||
@@ -40,7 +43,7 @@ export default class SomaSolution {
|
||||
const result: SomaSolution[] = [];
|
||||
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
||||
for (let i = 0; i < allRots[0].length; i++) {
|
||||
const solnRot = new SomaSolution(this.dim);
|
||||
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||
result.push(solnRot);
|
||||
}
|
||||
@@ -56,16 +59,16 @@ export default class SomaSolution {
|
||||
return true;
|
||||
}
|
||||
|
||||
addSpace(space: VoxelSpace) {
|
||||
addSpace(space: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
for (const space of this.solutionSpaces) {
|
||||
if (space.at(x, y, z)) {
|
||||
accum += space.getId();
|
||||
@@ -75,7 +78,7 @@ export default class SomaSolution {
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (x !== this.dim - 1) {
|
||||
if (x !== this.dimX - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
@@ -92,19 +95,19 @@ export default class SomaSolution {
|
||||
}
|
||||
|
||||
clone() {
|
||||
const clone = new SomaSolution(this.dim);
|
||||
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||
return clone;
|
||||
}
|
||||
|
||||
getDims() {
|
||||
return [this.dim, this.dim, this.dim];
|
||||
return [this.dimX, this.dimY, this.dimZ];
|
||||
}
|
||||
|
||||
forEachCell(cb: (val: number, x: number, y: number, z: number) => any) {
|
||||
loopStart: for (let x = 0; x < this.dim; x++) {
|
||||
for (let y = 0; y < this.dim; y++) {
|
||||
for (let z = 0; z < this.dim; z++) {
|
||||
loopStart: for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
cb(this.at(x, y, z), x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import VoxelSpace from "./VoxelSpace";
|
||||
import SomaSolution from "./SomaSolution";
|
||||
|
||||
export default class SomaSolver {
|
||||
private solutionCube: VoxelSpace;
|
||||
private dim: number;
|
||||
private solutions: SomaSolution[] = [];
|
||||
private iterations: number = 0;
|
||||
constructor(dimension: number) {
|
||||
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: VoxelSpace[]) {
|
||||
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: VoxelSpace) => rot.getAllPositionsInCube(this.dim)).flat());
|
||||
const combos = [polycubes[0].getAllPositionsInCube(this.dim), ...combosWithRots];
|
||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
||||
}
|
||||
|
||||
getSolutions() {
|
||||
return this.solutions.slice();
|
||||
}
|
||||
|
||||
private backtrackSolve(workingSolution: VoxelSpace, polycubes: VoxelSpace[][], currentSoln: SomaSolution, depth = 0) {
|
||||
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, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
|
||||
export type DimensionDef = [number, number, number];
|
||||
|
||||
const enum NeighbourDirection {
|
||||
@@ -9,30 +11,28 @@ const enum NeighbourDirection {
|
||||
NEGZ,
|
||||
}
|
||||
|
||||
export default class VoxelSpace {
|
||||
export default class VoxelSpaceBigInt {
|
||||
private dims: DimensionDef;
|
||||
private length: number;
|
||||
private space: bigint;
|
||||
private id: number;
|
||||
constructor(id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty?: boolean) {
|
||||
if (!space) {
|
||||
space = 0n;
|
||||
} else if (Array.isArray(space)) {
|
||||
if (space.length !== dims[0] * dims[1] * dims[2]) {
|
||||
throw new Error("Vals don't fit in given dimensions.");
|
||||
}
|
||||
space = VoxelSpace.boolArrayToBigInt(space)
|
||||
private color: string;
|
||||
|
||||
constructor(options: {id: number, dims: DimensionDef, space?: bigint, cullEmpty: boolean, color?: string}) {
|
||||
if (!options.space) {
|
||||
options.space = 0n;
|
||||
}
|
||||
this.id = id;
|
||||
this.length = dims[0] * dims[1] * dims[2];
|
||||
this.dims = dims;
|
||||
this.space = space;
|
||||
if (cullEmpty) {
|
||||
this.id = options.id;
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
this.dims = options.dims;
|
||||
this.space = BigInt(options.space);
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolArrayToBigInt(boolArray: boolean[]): bigint {
|
||||
static boolArrayToBigInt(boolArray: boolean[]) {
|
||||
let result = 0n;
|
||||
for (let i = 0; i < boolArray.length; i++) {
|
||||
if (boolArray[i]) {
|
||||
@@ -42,18 +42,26 @@ export default class VoxelSpace {
|
||||
return result;
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
binaryRep() {
|
||||
return this.space.toString(2);
|
||||
}
|
||||
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: -Infinity,
|
||||
xMin: Infinity,
|
||||
yMax: -Infinity,
|
||||
yMin: Infinity,
|
||||
zMax: -Infinity,
|
||||
zMin: Infinity,
|
||||
xMax: 0,
|
||||
xMin: this.dims[0],
|
||||
yMax: 0,
|
||||
yMin: this.dims[1],
|
||||
zMax: 0,
|
||||
zMin: this.dims[2],
|
||||
};
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
if (val) {
|
||||
@@ -68,7 +76,7 @@ export default class VoxelSpace {
|
||||
return extrema;
|
||||
}
|
||||
|
||||
private cullEmptySpace() {
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
let index = 0n;
|
||||
let newSpace = 0n;
|
||||
@@ -123,25 +131,25 @@ export default class VoxelSpace {
|
||||
}
|
||||
|
||||
getUniqueRotations() {
|
||||
const rotations: VoxelSpace[] = [];
|
||||
const rotations: VoxelSpaceBigInt[] = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getAllRotations() {
|
||||
const rotations: VoxelSpace[] = [];
|
||||
const rotations: VoxelSpaceBigInt[] = [];
|
||||
const refSpace = this.clone();
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
@@ -158,7 +166,7 @@ export default class VoxelSpace {
|
||||
return rotations;
|
||||
}
|
||||
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpace[], newSpaces: VoxelSpace[]) {
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBigInt[], newSpaces: VoxelSpaceBigInt[]) {
|
||||
for (const newSpace of newSpaces) {
|
||||
let matchFound = false;
|
||||
for (const existingSpace of existingSpaces) {
|
||||
@@ -173,41 +181,44 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInCube(cubeDim: number): VoxelSpace[] {
|
||||
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
|
||||
const cubePositions: VoxelSpace[] = [];
|
||||
for (let x = 0; x < cubeDim - this.dims[0] + 1; x++) {
|
||||
for (let y = 0; y < cubeDim - this.dims[1] + 1; y++) {
|
||||
for (let z = 0; z < cubeDim - this.dims[2] + 1; z++) {
|
||||
const cubePos = new VoxelSpace(this.id, [cubeDim, cubeDim, cubeDim]);
|
||||
this.forEachCell((val, rotX, rotY, rotZ) => {
|
||||
cubePos.set(x + rotX, y + rotY, z + rotZ, val);
|
||||
});
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
getAllPositionsInPrism(cubeDimX: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBigInt[] {
|
||||
const cubePositions: VoxelSpaceBigInt[] = [];
|
||||
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
|
||||
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
|
||||
const cubePos = new VoxelSpaceBigInt({id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false});
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
|
||||
});
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
} else {
|
||||
throw new Error("cubeDim must be a positive integer.");
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
|
||||
matches(space: VoxelSpace) {
|
||||
matches(space: VoxelSpaceBigInt | VoxelSpaceBoolean) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
if (otherDims[i] !== this.dims[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.space === space.getRaw();
|
||||
if (typeof space.getRaw() === "bigint") {
|
||||
return this.space === space.getRaw();
|
||||
} else {
|
||||
return this.binaryRep() === space.binaryRep();
|
||||
}
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new VoxelSpace(this.id, this.getDims(), this.getRaw());
|
||||
return new VoxelSpaceBigInt({id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false});
|
||||
}
|
||||
|
||||
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpace[] {
|
||||
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBigInt[] {
|
||||
const rotations = [this.clone()];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
rotations.push(rotations[i].rotated90(axis));
|
||||
@@ -250,30 +261,33 @@ export default class VoxelSpace {
|
||||
}
|
||||
|
||||
toggle(x: number, y: number, z: number) {
|
||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
this.space ^= mask;
|
||||
}
|
||||
|
||||
set(x: number, y: number, z: number, val: boolean) {
|
||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
if (val) {
|
||||
this.space |= mask;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.space &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
rotated90(dim: 'x' | 'y' | 'z') {
|
||||
let newSpace = 0n;
|
||||
let newDims: DimensionDef;
|
||||
let rotIndex: (i: number, j: number, k: number) => number;
|
||||
let newDims;
|
||||
let rotIndex;
|
||||
if (dim === 'x') {
|
||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||
rotIndex = this.newIndexRotX.bind(this);
|
||||
} else if (dim === 'y') {
|
||||
}
|
||||
else if (dim === 'y') {
|
||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||
rotIndex = this.newIndexRotY.bind(this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||
rotIndex = this.newIndexRotZ.bind(this);
|
||||
}
|
||||
@@ -281,8 +295,8 @@ export default class VoxelSpace {
|
||||
if (val) {
|
||||
newSpace |= BigInt(1 << rotIndex(i, j, k));
|
||||
}
|
||||
})
|
||||
return new VoxelSpace(this.id, newDims, newSpace);
|
||||
});
|
||||
return new VoxelSpaceBigInt({ id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false });
|
||||
}
|
||||
|
||||
rot90(dim: 'x' | 'y' | 'z') {
|
||||
@@ -291,10 +305,10 @@ export default class VoxelSpace {
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
|
||||
plus(space: VoxelSpace): VoxelSpace | null {
|
||||
plus(space: VoxelSpaceBigInt): VoxelSpaceBigInt | null {
|
||||
const otherSpace = space.getRaw();
|
||||
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
|
||||
return new VoxelSpace(this.id, this.dims, otherSpace | this.space);
|
||||
return new VoxelSpaceBigInt({ id: this.id, dims: this.getDims(), space: otherSpace | this.space, color: this.color, cullEmpty: false });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -331,4 +345,13 @@ export default class VoxelSpace {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getAllPermutationsInPrism(prismDimX: number, prismDimY: number, prismDimZ: number): VoxelSpaceBigInt[] {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array<VoxelSpaceBigInt>();
|
||||
for (let i = 0; i < rotations.length; i++) {
|
||||
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
366
src/VoxelSpaceBoolean.ts
Normal file
366
src/VoxelSpaceBoolean.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import type VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||
|
||||
export type DimensionDef = [number, number, number];
|
||||
|
||||
const enum NeighbourDirection {
|
||||
POSX,
|
||||
POSY,
|
||||
POSZ,
|
||||
NEGX,
|
||||
NEGY,
|
||||
NEGZ,
|
||||
}
|
||||
|
||||
export default class VoxelSpaceBoolean {
|
||||
private dims: DimensionDef;
|
||||
private length: number;
|
||||
private space: boolean[];
|
||||
private id: number;
|
||||
private color: string;
|
||||
|
||||
constructor(options: {id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty: boolean, color?: string}) {
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
if (!options.space) {
|
||||
options.space = new Array<boolean>(options.dims[0] * options.dims[1] * options.dims[2]);
|
||||
options.space.fill(false);
|
||||
} else if (!Array.isArray(options.space)) {
|
||||
const newSpace = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const mask = 1n << BigInt(i);
|
||||
newSpace.push((options.space & mask) !== 0n);
|
||||
}
|
||||
options.space = newSpace;
|
||||
}
|
||||
this.id = options.id;
|
||||
this.dims = options.dims;
|
||||
this.space = options.space;
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
binaryRep() {
|
||||
return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), "");
|
||||
}
|
||||
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: 0,
|
||||
xMin: this.dims[0],
|
||||
yMax: 0,
|
||||
yMin: this.dims[1],
|
||||
zMax: 0,
|
||||
zMin: this.dims[2],
|
||||
};
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
if (val) {
|
||||
extrema.xMax = Math.max(extrema.xMax, x);
|
||||
extrema.xMin = Math.min(extrema.xMin, x);
|
||||
extrema.yMax = Math.max(extrema.yMax, y);
|
||||
extrema.yMin = Math.min(extrema.yMin, y);
|
||||
extrema.zMax = Math.max(extrema.zMax, z);
|
||||
extrema.zMin = Math.min(extrema.zMin, z);
|
||||
}
|
||||
});
|
||||
return extrema;
|
||||
}
|
||||
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
const newX = extrema.xMax - extrema.xMin + 1;
|
||||
const newY = extrema.yMax - extrema.yMin + 1;
|
||||
const newZ = extrema.zMax - extrema.zMin + 1;
|
||||
const newSpace = new Array<boolean>(newX * newY * newZ);
|
||||
newSpace.fill(false);
|
||||
let index = 0;
|
||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace[index] = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dims[0] = newX;
|
||||
this.dims[1] = newY;
|
||||
this.dims[2] = newZ;
|
||||
this.space = newSpace;
|
||||
}
|
||||
|
||||
forEachCell(cb: (val: boolean, x: number, y: number, z: number) => any) {
|
||||
loopStart: for (let x = 0; x < this.dims[0]; x++) {
|
||||
for (let y = 0; y < this.dims[1]; y++) {
|
||||
for (let z = 0; z < this.dims[2]; z++) {
|
||||
if (cb(this.at(x, y, z), x, y, z) === 0) {
|
||||
break loopStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
print() {
|
||||
let accum = "";
|
||||
console.log("---");
|
||||
for (let i = 0; i < this.dims[0]; i++) {
|
||||
for (let j = 0; j < this.dims[1]; j++) {
|
||||
for (let k = 0; k < this.dims[2]; k++) {
|
||||
accum += this.at(i, j, k) ? '#' : 'O';
|
||||
}
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (i !== this.dims[0] - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
console.log("---");
|
||||
}
|
||||
|
||||
getUniqueRotations() {
|
||||
const rotations: VoxelSpaceBoolean[] = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getAllRotations() {
|
||||
const rotations: VoxelSpaceBoolean[] = [];
|
||||
const refSpace = this.clone();
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBoolean[], newSpaces: VoxelSpaceBoolean[]) {
|
||||
for (const newSpace of newSpaces) {
|
||||
let matchFound = false;
|
||||
for (const existingSpace of existingSpaces) {
|
||||
if (newSpace.matches(existingSpace)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) {
|
||||
existingSpaces.push(newSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInPrism(cubeDimX: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBoolean[] {
|
||||
const cubePositions: VoxelSpaceBoolean[] = [];
|
||||
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
|
||||
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
|
||||
const cubePos = new VoxelSpaceBoolean({id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false});
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
|
||||
});
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
|
||||
matches(space: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
if (otherDims[i] !== this.dims[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const otherRaw = space.getRaw();
|
||||
if (typeof otherRaw === "bigint") {
|
||||
return space.binaryRep() === this.binaryRep();
|
||||
}
|
||||
return this.space.reduce((prev, unit, i) => (unit === otherRaw[i]) && prev, true);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false});
|
||||
}
|
||||
|
||||
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBoolean[] {
|
||||
const rotations = [this.clone()];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
rotations.push(rotations[i].rotated90(axis));
|
||||
}
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getDims(): DimensionDef {
|
||||
return this.dims.slice() as DimensionDef;
|
||||
}
|
||||
|
||||
getRaw() {
|
||||
return this.space.slice();
|
||||
}
|
||||
|
||||
// [1, 0, 0] [x] [ x]
|
||||
// [0, 0, -1] * [y] = [-z]
|
||||
// [0, 1, 0] [z] [ y]
|
||||
private newIndexRotX(x: number, y: number, z: number) {
|
||||
return this.dims[2] * this.dims[1] * x + this.dims[1] * (this.dims[2] - 1 - z) + y;
|
||||
}
|
||||
|
||||
// [ 0, 0, 1] [x] [ z]
|
||||
// [ 0, 1, 0] * [y] = [ y]
|
||||
// [-1, 0, 0] [z] [-x]
|
||||
private newIndexRotY(x: number, y: number, z: number) {
|
||||
return this.dims[1] * this.dims[0] * z + this.dims[0] * y + (this.dims[0] - 1 - x);
|
||||
}
|
||||
|
||||
// [0, -1, 0] [x] [-y]
|
||||
// [1, 0, 0] * [y] = [ x]
|
||||
// [0, 0, 1] [z] [ z]
|
||||
private newIndexRotZ(x: number, y: number, z: number) {
|
||||
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||
}
|
||||
|
||||
at(x: number, y: number, z: number) {
|
||||
return this.space[this.index(x, y, z)];
|
||||
}
|
||||
|
||||
private index(x: number, y: number, z: number) {
|
||||
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||
}
|
||||
|
||||
toggle(x: number, y: number, z: number) {
|
||||
const index = this.index(x, y, z);
|
||||
this.space[index] = !this.space[index];
|
||||
}
|
||||
|
||||
set(x: number, y: number, z: number, val: boolean) {
|
||||
this.space[this.index(x, y, z)] = val;
|
||||
}
|
||||
|
||||
rotated90(dim: 'x' | 'y' | 'z') {
|
||||
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let newDims: DimensionDef;
|
||||
let rotIndex: (i: number, j: number, k: number) => number;
|
||||
if (dim === 'x') {
|
||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||
rotIndex = this.newIndexRotX.bind(this);
|
||||
} else if (dim === 'y') {
|
||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||
rotIndex = this.newIndexRotY.bind(this);
|
||||
} else {
|
||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||
rotIndex = this.newIndexRotZ.bind(this);
|
||||
}
|
||||
this.forEachCell((val, i, j, k) => {
|
||||
if (val) {
|
||||
newSpace[rotIndex(i, j, k)] = true;
|
||||
}
|
||||
})
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false});
|
||||
}
|
||||
|
||||
rot90(dim: 'x' | 'y' | 'z') {
|
||||
const rot = this.rotated90(dim);
|
||||
this.space = rot.getRaw();
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
|
||||
plus(space: VoxelSpaceBoolean): VoxelSpaceBoolean | null {
|
||||
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let clash = false;
|
||||
space.forEachCell((val, x, y, z) => {
|
||||
if (this.at(x, y, z) !== val) {
|
||||
newSpace[this.index(x, y, z)] = true;
|
||||
} else {
|
||||
if (val) {
|
||||
clash = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clash) {
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: this.getDims(), space: newSpace, color: this.color, cullEmpty: false});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
size() {
|
||||
let size = 0;
|
||||
this.forEachCell((val) => {
|
||||
if (val) {
|
||||
size++;
|
||||
}
|
||||
});
|
||||
return size;
|
||||
}
|
||||
|
||||
getDirectNeighbourProfile(x: number, y: number, z: number): number {
|
||||
let result = 0;
|
||||
if (x < this.dims[0] - 1 && this.at(x + 1, y, z)) {
|
||||
result += 1;
|
||||
}
|
||||
if (y < this.dims[1] - 1 && this.at(x, y + 1, z)) {
|
||||
result += 2;
|
||||
}
|
||||
if (z < this.dims[2] - 1 && this.at(x, y, z + 1)) {
|
||||
result += 4;
|
||||
}
|
||||
if (x > 0 && this.at(x - 1, y, z)) {
|
||||
result += 8;
|
||||
}
|
||||
if (y > 0 && this.at(x, y - 1, z)) {
|
||||
result += 16;
|
||||
}
|
||||
if (z > 0 && this.at(x, y, z - 1)) {
|
||||
result += 32;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getAllPermutationsInPrism(prismDimX: number, prismDimY: number, prismDimZ: number): VoxelSpaceBoolean[] {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array<VoxelSpaceBoolean>();
|
||||
for (let i = 0; i < rotations.length; i++) {
|
||||
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
0
src/desktop/build.js
Normal file
0
src/desktop/build.js
Normal file
30
src/desktop/main.js
Normal file
30
src/desktop/main.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile(path.join(__dirname, '../../public/index.html'));
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
app.on('activate', function() {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.on('window-all-close', function() {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
12
src/desktop/preload.js
Normal file
12
src/desktop/preload.js
Normal file
@@ -0,0 +1,12 @@
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const replaceText = (selector, text) => {
|
||||
const element = document.getElementById(selector);
|
||||
if (element) {
|
||||
element.innerText = text;
|
||||
}
|
||||
|
||||
}
|
||||
for (const dependency of ['chrome', 'node', 'electron']) {
|
||||
replaceText(`${dependency}-version`, process.versions[dependency]);
|
||||
}
|
||||
});
|
||||
102
src/solver/js/SomaSolution.js
Normal file
102
src/solver/js/SomaSolution.js
Normal file
@@ -0,0 +1,102 @@
|
||||
export default class SomaSolution {
|
||||
constructor(dimX, dimY, dimZ) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
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.getRotations()) {
|
||||
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;
|
||||
}
|
||||
getRotations() {
|
||||
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.dimX, this.dimY, this.dimZ);
|
||||
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.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
for (const space of this.solutionSpaces) {
|
||||
if (space.at(x, y, z)) {
|
||||
accum += space.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (x !== this.dimX - 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.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||
return clone;
|
||||
}
|
||||
getDims() {
|
||||
return [this.dimX, this.dimY, this.dimZ];
|
||||
}
|
||||
forEachCell(cb) {
|
||||
loopStart: for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
cb(this.at(x, y, z), x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getPieces() {
|
||||
return this.solutionSpaces.slice();
|
||||
}
|
||||
}
|
||||
119
src/solver/js/SomaSolution.ts
Normal file
119
src/solver/js/SomaSolution.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
|
||||
export default class SomaSolution {
|
||||
private solutionSpaces: VoxelSpaceBoolean[];
|
||||
private dimX: number;
|
||||
private dimY: number;
|
||||
private dimZ: number;
|
||||
|
||||
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionSpaces = [];
|
||||
}
|
||||
|
||||
static filterUnique(solutions: SomaSolution[]): SomaSolution[] {
|
||||
if (solutions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const uniqueSolns = [solutions[0]];
|
||||
for (const solution of solutions) {
|
||||
let foundMatch = false;
|
||||
for (const rotation of solution.getRotations()) {
|
||||
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;
|
||||
}
|
||||
|
||||
getRotations(): SomaSolution[] {
|
||||
if (this.solutionSpaces.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result: SomaSolution[] = [];
|
||||
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
||||
for (let i = 0; i < allRots[0].length; i++) {
|
||||
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||
result.push(solnRot);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
matches(solution: SomaSolution) {
|
||||
for (let i = 0; i < this.solutionSpaces.length; i++) {
|
||||
if (!this.solutionSpaces[i].matches(solution.solutionSpaces[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
addSpace(space: VoxelSpaceBoolean) {
|
||||
this.solutionSpaces.push(space);
|
||||
}
|
||||
|
||||
print() {
|
||||
let accum = "";
|
||||
console.log("---");
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
for (const space of this.solutionSpaces) {
|
||||
if (space.at(x, y, z)) {
|
||||
accum += space.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (x !== this.dimX - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
console.log("---");
|
||||
}
|
||||
|
||||
at(x: number, y: number, z: number) {
|
||||
for (const space of this.solutionSpaces) {
|
||||
if (space.at(x, y, z)) {
|
||||
return space.getId();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
clone() {
|
||||
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||
return clone;
|
||||
}
|
||||
|
||||
getDims() {
|
||||
return [this.dimX, this.dimY, this.dimZ];
|
||||
}
|
||||
|
||||
forEachCell(cb: (val: number, x: number, y: number, z: number) => any) {
|
||||
loopStart: for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
cb(this.at(x, y, z), x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPieces() {
|
||||
return this.solutionSpaces.slice();
|
||||
}
|
||||
}
|
||||
60
src/solver/js/SomaSolver.js
Normal file
60
src/solver/js/SomaSolver.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
import SomaSolution from "./SomaSolution";
|
||||
export default class SomaSolver {
|
||||
constructor(dimX, dimY, dimZ) {
|
||||
this.visualiser = { async showSoln(soln) { }, async showSpace(cube) { } };
|
||||
this.solutions = new Array();
|
||||
this.iterations = 0;
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionCube = new VoxelSpaceBoolean({ id: 0, dims: [dimX, dimY, dimZ], cullEmpty: false });
|
||||
}
|
||||
setDebug(visualiser) {
|
||||
this.visualiser = visualiser;
|
||||
}
|
||||
async solve(polycubes) {
|
||||
if (polycubes.length === 0) {
|
||||
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
||||
}
|
||||
this.solutions.splice(0, this.solutions.length);
|
||||
const combosWithRots = new Array();
|
||||
for (let i = 1; i < polycubes.length; i++) {
|
||||
const rots = polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ);
|
||||
combosWithRots.push(rots);
|
||||
}
|
||||
let combos = new Array();
|
||||
combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||
combos = combos.concat(combosWithRots);
|
||||
for (const combo of combos) {
|
||||
for (const rot of combo) {
|
||||
await this.visualiser.showSpace(rot);
|
||||
}
|
||||
}
|
||||
await this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dimX, this.dimY, this.dimZ));
|
||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
||||
}
|
||||
getSolutions() {
|
||||
return this.solutions.slice();
|
||||
}
|
||||
async backtrackSolve(workingSolution, polycubes, currentSoln, depth = 0) {
|
||||
const nextCubeGroup = polycubes[0];
|
||||
for (let i = 0; i < nextCubeGroup.length; i++) {
|
||||
const fusionAttempt = workingSolution.plus(nextCubeGroup[i]);
|
||||
++this.iterations;
|
||||
if (fusionAttempt) {
|
||||
const nextSoln = currentSoln.clone();
|
||||
nextSoln.addSpace(nextCubeGroup[i]);
|
||||
await this.visualiser.showSoln(nextSoln);
|
||||
if (polycubes.length === 1) {
|
||||
this.solutions.push(nextSoln);
|
||||
currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
await this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/solver/js/SomaSolver.ts
Normal file
73
src/solver/js/SomaSolver.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
import SomaSolution from "./SomaSolution";
|
||||
|
||||
interface DebugVisualiser {
|
||||
showSoln(soln: SomaSolution): Promise<void>;
|
||||
showSpace(cube: VoxelSpaceBoolean): Promise<void>;
|
||||
}
|
||||
|
||||
export default class SomaSolver {
|
||||
private solutionCube: VoxelSpaceBoolean;
|
||||
private dimX: number;
|
||||
private dimY: number;
|
||||
private dimZ: number;
|
||||
private visualiser: DebugVisualiser = {async showSoln(soln: SomaSolution) {}, async showSpace(cube: VoxelSpaceBoolean) {}};
|
||||
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
||||
private iterations: number = 0;
|
||||
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionCube = new VoxelSpaceBoolean({id: 0, dims: [dimX, dimY, dimZ], cullEmpty: false});
|
||||
}
|
||||
|
||||
setDebug(visualiser: DebugVisualiser) {
|
||||
this.visualiser = visualiser;
|
||||
}
|
||||
|
||||
async solve(polycubes: VoxelSpaceBoolean[]) {
|
||||
if (polycubes.length === 0) {
|
||||
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
||||
}
|
||||
this.solutions.splice(0, this.solutions.length);
|
||||
const combosWithRots: VoxelSpaceBoolean[][] = new Array<Array<VoxelSpaceBoolean>>();
|
||||
for (let i = 1; i < polycubes.length; i++) {
|
||||
const rots = polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ);
|
||||
combosWithRots.push(rots);
|
||||
}
|
||||
let combos: VoxelSpaceBoolean[][] = new Array<Array<VoxelSpaceBoolean>>();
|
||||
combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||
combos = combos.concat(combosWithRots);
|
||||
for (const combo of combos) {
|
||||
for (const rot of combo) {
|
||||
await this.visualiser.showSpace(rot);
|
||||
}
|
||||
}
|
||||
await this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dimX, this.dimY, this.dimZ));
|
||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
||||
}
|
||||
|
||||
getSolutions() {
|
||||
return this.solutions.slice();
|
||||
}
|
||||
|
||||
private async backtrackSolve(workingSolution: VoxelSpaceBoolean, polycubes: VoxelSpaceBoolean[][], currentSoln: SomaSolution, depth = 0) {
|
||||
const nextCubeGroup = polycubes[0];
|
||||
for (let i = 0; i < nextCubeGroup.length; i++) {
|
||||
const fusionAttempt = workingSolution.plus(nextCubeGroup[i]);
|
||||
++this.iterations;
|
||||
if (fusionAttempt) {
|
||||
const nextSoln = currentSoln.clone();
|
||||
nextSoln.addSpace(nextCubeGroup[i]);
|
||||
await this.visualiser.showSoln(nextSoln);
|
||||
if (polycubes.length === 1) {
|
||||
this.solutions.push(nextSoln);
|
||||
currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
return;
|
||||
} else {
|
||||
await this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
321
src/solver/js/VoxelSpaceBoolean.js
Normal file
321
src/solver/js/VoxelSpaceBoolean.js
Normal file
@@ -0,0 +1,321 @@
|
||||
export default class VoxelSpaceBoolean {
|
||||
constructor(options) {
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
if (!options.space) {
|
||||
options.space = new Array(options.dims[0] * options.dims[1] * options.dims[2]);
|
||||
options.space.fill(false);
|
||||
}
|
||||
else if (!Array.isArray(options.space)) {
|
||||
const newSpace = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const mask = 1n << BigInt(i);
|
||||
newSpace.push((options.space & mask) !== 0n);
|
||||
}
|
||||
options.space = newSpace;
|
||||
}
|
||||
this.id = options.id;
|
||||
this.dims = options.dims;
|
||||
this.space = options.space;
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
setColor(color) {
|
||||
this.color = color;
|
||||
}
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
binaryRep() {
|
||||
return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), "");
|
||||
}
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: 0,
|
||||
xMin: this.dims[0],
|
||||
yMax: 0,
|
||||
yMin: this.dims[1],
|
||||
zMax: 0,
|
||||
zMin: this.dims[2],
|
||||
};
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
if (val) {
|
||||
extrema.xMax = Math.max(extrema.xMax, x);
|
||||
extrema.xMin = Math.min(extrema.xMin, x);
|
||||
extrema.yMax = Math.max(extrema.yMax, y);
|
||||
extrema.yMin = Math.min(extrema.yMin, y);
|
||||
extrema.zMax = Math.max(extrema.zMax, z);
|
||||
extrema.zMin = Math.min(extrema.zMin, z);
|
||||
}
|
||||
});
|
||||
return extrema;
|
||||
}
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
const newX = extrema.xMax - extrema.xMin + 1;
|
||||
const newY = extrema.yMax - extrema.yMin + 1;
|
||||
const newZ = extrema.zMax - extrema.zMin + 1;
|
||||
const newSpace = new Array(newX * newY * newZ);
|
||||
newSpace.fill(false);
|
||||
let index = 0;
|
||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace[index] = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dims[0] = newX;
|
||||
this.dims[1] = newY;
|
||||
this.dims[2] = newZ;
|
||||
this.space = newSpace;
|
||||
}
|
||||
forEachCell(cb) {
|
||||
loopStart: for (let x = 0; x < this.dims[0]; x++) {
|
||||
for (let y = 0; y < this.dims[1]; y++) {
|
||||
for (let z = 0; z < this.dims[2]; z++) {
|
||||
if (cb(this.at(x, y, z), x, y, z) === 0) {
|
||||
break loopStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
print() {
|
||||
let accum = "";
|
||||
console.log("---");
|
||||
for (let i = 0; i < this.dims[0]; i++) {
|
||||
for (let j = 0; j < this.dims[1]; j++) {
|
||||
for (let k = 0; k < this.dims[2]; k++) {
|
||||
accum += this.at(i, j, k) ? '#' : 'O';
|
||||
}
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (i !== this.dims[0] - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
console.log("---");
|
||||
}
|
||||
getUniqueRotations() {
|
||||
const rotations = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
getAllRotations() {
|
||||
const rotations = [];
|
||||
const refSpace = this.clone();
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
static pushNewUniqueSpaces(existingSpaces, newSpaces) {
|
||||
for (const newSpace of newSpaces) {
|
||||
let matchFound = false;
|
||||
for (const existingSpace of existingSpaces) {
|
||||
if (newSpace.matches(existingSpace)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) {
|
||||
existingSpaces.push(newSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
getAllPositionsInPrism(cubeDimX, cubeDimY, cubeDimZ) {
|
||||
const cubePositions = [];
|
||||
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
|
||||
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
|
||||
const cubePos = new VoxelSpaceBoolean({ id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false });
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
|
||||
});
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
matches(space) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
if (otherDims[i] !== this.dims[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const otherRaw = space.getRaw();
|
||||
if (typeof otherRaw === "bigint") {
|
||||
return space.binaryRep() === this.binaryRep();
|
||||
}
|
||||
return this.space.reduce((prev, unit, i) => (unit === otherRaw[i]) && prev, true);
|
||||
}
|
||||
clone() {
|
||||
return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false });
|
||||
}
|
||||
getAxisSpins(axis) {
|
||||
const rotations = [this.clone()];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
rotations.push(rotations[i].rotated90(axis));
|
||||
}
|
||||
return rotations;
|
||||
}
|
||||
getDims() {
|
||||
return this.dims.slice();
|
||||
}
|
||||
getRaw() {
|
||||
return this.space.slice();
|
||||
}
|
||||
// [1, 0, 0] [x] [ x]
|
||||
// [0, 0, -1] * [y] = [-z]
|
||||
// [0, 1, 0] [z] [ y]
|
||||
newIndexRotX(x, y, z) {
|
||||
return this.dims[2] * this.dims[1] * x + this.dims[1] * (this.dims[2] - 1 - z) + y;
|
||||
}
|
||||
// [ 0, 0, 1] [x] [ z]
|
||||
// [ 0, 1, 0] * [y] = [ y]
|
||||
// [-1, 0, 0] [z] [-x]
|
||||
newIndexRotY(x, y, z) {
|
||||
return this.dims[1] * this.dims[0] * z + this.dims[0] * y + (this.dims[0] - 1 - x);
|
||||
}
|
||||
// [0, -1, 0] [x] [-y]
|
||||
// [1, 0, 0] * [y] = [ x]
|
||||
// [0, 0, 1] [z] [ z]
|
||||
newIndexRotZ(x, y, z) {
|
||||
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||
}
|
||||
at(x, y, z) {
|
||||
return this.space[this.index(x, y, z)];
|
||||
}
|
||||
index(x, y, z) {
|
||||
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||
}
|
||||
toggle(x, y, z) {
|
||||
const index = this.index(x, y, z);
|
||||
this.space[index] = !this.space[index];
|
||||
}
|
||||
set(x, y, z, val) {
|
||||
this.space[this.index(x, y, z)] = val;
|
||||
}
|
||||
rotated90(dim) {
|
||||
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let newDims;
|
||||
let rotIndex;
|
||||
if (dim === 'x') {
|
||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||
rotIndex = this.newIndexRotX.bind(this);
|
||||
}
|
||||
else if (dim === 'y') {
|
||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||
rotIndex = this.newIndexRotY.bind(this);
|
||||
}
|
||||
else {
|
||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||
rotIndex = this.newIndexRotZ.bind(this);
|
||||
}
|
||||
this.forEachCell((val, i, j, k) => {
|
||||
if (val) {
|
||||
newSpace[rotIndex(i, j, k)] = true;
|
||||
}
|
||||
});
|
||||
return new VoxelSpaceBoolean({ id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false });
|
||||
}
|
||||
rot90(dim) {
|
||||
const rot = this.rotated90(dim);
|
||||
this.space = rot.getRaw();
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
plus(space) {
|
||||
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let clash = false;
|
||||
space.forEachCell((val, x, y, z) => {
|
||||
if (this.at(x, y, z) !== val) {
|
||||
newSpace[this.index(x, y, z)] = true;
|
||||
}
|
||||
else {
|
||||
if (val) {
|
||||
clash = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clash) {
|
||||
return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: newSpace, color: this.color, cullEmpty: false });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
size() {
|
||||
let size = 0;
|
||||
this.forEachCell((val) => {
|
||||
if (val) {
|
||||
size++;
|
||||
}
|
||||
});
|
||||
return size;
|
||||
}
|
||||
getDirectNeighbourProfile(x, y, z) {
|
||||
let result = 0;
|
||||
if (x < this.dims[0] - 1 && this.at(x + 1, y, z)) {
|
||||
result += 1;
|
||||
}
|
||||
if (y < this.dims[1] - 1 && this.at(x, y + 1, z)) {
|
||||
result += 2;
|
||||
}
|
||||
if (z < this.dims[2] - 1 && this.at(x, y, z + 1)) {
|
||||
result += 4;
|
||||
}
|
||||
if (x > 0 && this.at(x - 1, y, z)) {
|
||||
result += 8;
|
||||
}
|
||||
if (y > 0 && this.at(x, y - 1, z)) {
|
||||
result += 16;
|
||||
}
|
||||
if (z > 0 && this.at(x, y, z - 1)) {
|
||||
result += 32;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
getAllPermutationsInPrism(prismDimX, prismDimY, prismDimZ) {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array();
|
||||
for (let i = 0; i < rotations.length; i++) {
|
||||
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
364
src/solver/js/VoxelSpaceBoolean.ts
Normal file
364
src/solver/js/VoxelSpaceBoolean.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
export type DimensionDef = [number, number, number];
|
||||
|
||||
const enum NeighbourDirection {
|
||||
POSX,
|
||||
POSY,
|
||||
POSZ,
|
||||
NEGX,
|
||||
NEGY,
|
||||
NEGZ,
|
||||
}
|
||||
|
||||
export default class VoxelSpaceBoolean {
|
||||
private dims: DimensionDef;
|
||||
private length: number;
|
||||
private space: boolean[];
|
||||
private id: number;
|
||||
private color: string;
|
||||
|
||||
constructor(options: {id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty: boolean, color?: string}) {
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
if (!options.space) {
|
||||
options.space = new Array<boolean>(options.dims[0] * options.dims[1] * options.dims[2]);
|
||||
options.space.fill(false);
|
||||
} else if (!Array.isArray(options.space)) {
|
||||
const newSpace = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const mask = 1n << BigInt(i);
|
||||
newSpace.push((options.space & mask) !== 0n);
|
||||
}
|
||||
options.space = newSpace;
|
||||
}
|
||||
this.id = options.id;
|
||||
this.dims = options.dims;
|
||||
this.space = options.space;
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
binaryRep() {
|
||||
return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), "");
|
||||
}
|
||||
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: 0,
|
||||
xMin: this.dims[0],
|
||||
yMax: 0,
|
||||
yMin: this.dims[1],
|
||||
zMax: 0,
|
||||
zMin: this.dims[2],
|
||||
};
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
if (val) {
|
||||
extrema.xMax = Math.max(extrema.xMax, x);
|
||||
extrema.xMin = Math.min(extrema.xMin, x);
|
||||
extrema.yMax = Math.max(extrema.yMax, y);
|
||||
extrema.yMin = Math.min(extrema.yMin, y);
|
||||
extrema.zMax = Math.max(extrema.zMax, z);
|
||||
extrema.zMin = Math.min(extrema.zMin, z);
|
||||
}
|
||||
});
|
||||
return extrema;
|
||||
}
|
||||
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
const newX = extrema.xMax - extrema.xMin + 1;
|
||||
const newY = extrema.yMax - extrema.yMin + 1;
|
||||
const newZ = extrema.zMax - extrema.zMin + 1;
|
||||
const newSpace = new Array<boolean>(newX * newY * newZ);
|
||||
newSpace.fill(false);
|
||||
let index = 0;
|
||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace[index] = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dims[0] = newX;
|
||||
this.dims[1] = newY;
|
||||
this.dims[2] = newZ;
|
||||
this.space = newSpace;
|
||||
}
|
||||
|
||||
forEachCell(cb: (val: boolean, x: number, y: number, z: number) => any) {
|
||||
loopStart: for (let x = 0; x < this.dims[0]; x++) {
|
||||
for (let y = 0; y < this.dims[1]; y++) {
|
||||
for (let z = 0; z < this.dims[2]; z++) {
|
||||
if (cb(this.at(x, y, z), x, y, z) === 0) {
|
||||
break loopStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
print() {
|
||||
let accum = "";
|
||||
console.log("---");
|
||||
for (let i = 0; i < this.dims[0]; i++) {
|
||||
for (let j = 0; j < this.dims[1]; j++) {
|
||||
for (let k = 0; k < this.dims[2]; k++) {
|
||||
accum += this.at(i, j, k) ? '#' : 'O';
|
||||
}
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (i !== this.dims[0] - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
console.log("---");
|
||||
}
|
||||
|
||||
getUniqueRotations() {
|
||||
const rotations: VoxelSpaceBoolean[] = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getAllRotations() {
|
||||
const rotations: VoxelSpaceBoolean[] = [];
|
||||
const refSpace = this.clone();
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
refSpace.rot90('z');
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBoolean[], newSpaces: VoxelSpaceBoolean[]) {
|
||||
for (const newSpace of newSpaces) {
|
||||
let matchFound = false;
|
||||
for (const existingSpace of existingSpaces) {
|
||||
if (newSpace.matches(existingSpace)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) {
|
||||
existingSpaces.push(newSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInPrism(cubeDimX: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBoolean[] {
|
||||
const cubePositions: VoxelSpaceBoolean[] = [];
|
||||
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
|
||||
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
|
||||
const cubePos = new VoxelSpaceBoolean({id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false});
|
||||
this.forEachCell((val, x, y, z) => {
|
||||
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
|
||||
});
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
|
||||
matches(space: VoxelSpaceBoolean) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
if (otherDims[i] !== this.dims[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const otherRaw = space.getRaw();
|
||||
if (typeof otherRaw === "bigint") {
|
||||
return space.binaryRep() === this.binaryRep();
|
||||
}
|
||||
return this.space.reduce((prev, unit, i) => (unit === otherRaw[i]) && prev, true);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false});
|
||||
}
|
||||
|
||||
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBoolean[] {
|
||||
const rotations = [this.clone()];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
rotations.push(rotations[i].rotated90(axis));
|
||||
}
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getDims(): DimensionDef {
|
||||
return this.dims.slice() as DimensionDef;
|
||||
}
|
||||
|
||||
getRaw() {
|
||||
return this.space.slice();
|
||||
}
|
||||
|
||||
// [1, 0, 0] [x] [ x]
|
||||
// [0, 0, -1] * [y] = [-z]
|
||||
// [0, 1, 0] [z] [ y]
|
||||
private newIndexRotX(x: number, y: number, z: number) {
|
||||
return this.dims[2] * this.dims[1] * x + this.dims[1] * (this.dims[2] - 1 - z) + y;
|
||||
}
|
||||
|
||||
// [ 0, 0, 1] [x] [ z]
|
||||
// [ 0, 1, 0] * [y] = [ y]
|
||||
// [-1, 0, 0] [z] [-x]
|
||||
private newIndexRotY(x: number, y: number, z: number) {
|
||||
return this.dims[1] * this.dims[0] * z + this.dims[0] * y + (this.dims[0] - 1 - x);
|
||||
}
|
||||
|
||||
// [0, -1, 0] [x] [-y]
|
||||
// [1, 0, 0] * [y] = [ x]
|
||||
// [0, 0, 1] [z] [ z]
|
||||
private newIndexRotZ(x: number, y: number, z: number) {
|
||||
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||
}
|
||||
|
||||
at(x: number, y: number, z: number) {
|
||||
return this.space[this.index(x, y, z)];
|
||||
}
|
||||
|
||||
private index(x: number, y: number, z: number) {
|
||||
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||
}
|
||||
|
||||
toggle(x: number, y: number, z: number) {
|
||||
const index = this.index(x, y, z);
|
||||
this.space[index] = !this.space[index];
|
||||
}
|
||||
|
||||
set(x: number, y: number, z: number, val: boolean) {
|
||||
this.space[this.index(x, y, z)] = val;
|
||||
}
|
||||
|
||||
rotated90(dim: 'x' | 'y' | 'z') {
|
||||
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let newDims: DimensionDef;
|
||||
let rotIndex: (i: number, j: number, k: number) => number;
|
||||
if (dim === 'x') {
|
||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||
rotIndex = this.newIndexRotX.bind(this);
|
||||
} else if (dim === 'y') {
|
||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||
rotIndex = this.newIndexRotY.bind(this);
|
||||
} else {
|
||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||
rotIndex = this.newIndexRotZ.bind(this);
|
||||
}
|
||||
this.forEachCell((val, i, j, k) => {
|
||||
if (val) {
|
||||
newSpace[rotIndex(i, j, k)] = true;
|
||||
}
|
||||
})
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false});
|
||||
}
|
||||
|
||||
rot90(dim: 'x' | 'y' | 'z') {
|
||||
const rot = this.rotated90(dim);
|
||||
this.space = rot.getRaw();
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
|
||||
plus(space: VoxelSpaceBoolean): VoxelSpaceBoolean | null {
|
||||
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let clash = false;
|
||||
space.forEachCell((val, x, y, z) => {
|
||||
if (this.at(x, y, z) !== val) {
|
||||
newSpace[this.index(x, y, z)] = true;
|
||||
} else {
|
||||
if (val) {
|
||||
clash = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clash) {
|
||||
return new VoxelSpaceBoolean({id: this.id, dims: this.getDims(), space: newSpace, color: this.color, cullEmpty: false});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
size() {
|
||||
let size = 0;
|
||||
this.forEachCell((val) => {
|
||||
if (val) {
|
||||
size++;
|
||||
}
|
||||
});
|
||||
return size;
|
||||
}
|
||||
|
||||
getDirectNeighbourProfile(x: number, y: number, z: number): number {
|
||||
let result = 0;
|
||||
if (x < this.dims[0] - 1 && this.at(x + 1, y, z)) {
|
||||
result += 1;
|
||||
}
|
||||
if (y < this.dims[1] - 1 && this.at(x, y + 1, z)) {
|
||||
result += 2;
|
||||
}
|
||||
if (z < this.dims[2] - 1 && this.at(x, y, z + 1)) {
|
||||
result += 4;
|
||||
}
|
||||
if (x > 0 && this.at(x - 1, y, z)) {
|
||||
result += 8;
|
||||
}
|
||||
if (y > 0 && this.at(x, y - 1, z)) {
|
||||
result += 16;
|
||||
}
|
||||
if (z > 0 && this.at(x, y, z - 1)) {
|
||||
result += 32;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getAllPermutationsInPrism(prismDimX: number, prismDimY: number, prismDimZ: number): VoxelSpaceBoolean[] {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array<VoxelSpaceBoolean>();
|
||||
for (let i = 0; i < rotations.length; i++) {
|
||||
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
16
src/solver/js/main.js
Normal file
16
src/solver/js/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import SomaSolver from "./SomaSolver";
|
||||
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
export function solve(polycubes, dimX, dimY, dimZ) {
|
||||
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||
const voxelSpaces = new Array();
|
||||
for (let i = 0; i < polycubes.length; i++) {
|
||||
voxelSpaces.push(new VoxelSpaceBoolean({
|
||||
id: i,
|
||||
dims: [dimX, dimY, dimZ],
|
||||
space: polycubes[i],
|
||||
cullEmpty: true
|
||||
}));
|
||||
}
|
||||
solver.solve(voxelSpaces);
|
||||
return solver.getSolutions();
|
||||
}
|
||||
18
src/solver/js/main.ts
Normal file
18
src/solver/js/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import SomaSolver from "./SomaSolver";
|
||||
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||
import type SomaSolution from "./SomaSolution";
|
||||
|
||||
export function solve(polycubes: bigint[], dimX: number, dimY: number, dimZ: number): SomaSolution[] {
|
||||
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||
const voxelSpaces = new Array<VoxelSpaceBoolean>();
|
||||
for (let i = 0; i < polycubes.length; i++) {
|
||||
voxelSpaces.push(new VoxelSpaceBoolean({
|
||||
id: i,
|
||||
dims: [dimX, dimY, dimZ],
|
||||
space: polycubes[i],
|
||||
cullEmpty: true
|
||||
}));
|
||||
}
|
||||
solver.solve(voxelSpaces);
|
||||
return solver.getSolutions();
|
||||
}
|
||||
11
src/solver/js/package.json
Normal file
11
src/solver/js/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "soma-solve-js",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "npx tsc main.ts --target ES2020 --moduleResolution node"
|
||||
},
|
||||
"author": "Daniel Ledda",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"debug": true
|
||||
},
|
||||
"release": {
|
||||
"binaryFile": "../../public/solver/main.wasm",
|
||||
"textFile": "../../public/solver/main.wat",
|
||||
"binaryFile": "../../../public/solver/main.wasm",
|
||||
"textFile": "../../../public/solver/main.wat",
|
||||
"sourceMap": false,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 1,
|
||||
@@ -2,12 +2,14 @@ import VoxelSpace from "./VoxelSpace";
|
||||
|
||||
export default class SomaSolution {
|
||||
private solutionSpaces: VoxelSpace[];
|
||||
private dim: i32;
|
||||
constructor(dim: i32) {
|
||||
if (dim < 0 || dim % 1 !== 0) {
|
||||
throw new Error("Dimension must be a whole positive integer!");
|
||||
}
|
||||
this.dim = dim;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
|
||||
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionSpaces = [];
|
||||
}
|
||||
|
||||
@@ -42,7 +44,7 @@ export default class SomaSolution {
|
||||
}
|
||||
const allRots: VoxelSpace[][] = this.solutionSpaces.map<VoxelSpace[]>(space => space.getAllRotations());
|
||||
for (let i = 0; i < allRots[0].length; i++) {
|
||||
const solnRot = new SomaSolution(this.dim);
|
||||
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
for (let j = 0; j < allRots.length; j++) {
|
||||
solnRot.addSpace(allRots[j][i]);
|
||||
}
|
||||
@@ -74,15 +76,11 @@ export default class SomaSolution {
|
||||
}
|
||||
|
||||
clone(): SomaSolution {
|
||||
const clone = new SomaSolution(this.dim);
|
||||
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice(0, this.solutionSpaces.length);
|
||||
return clone;
|
||||
}
|
||||
|
||||
getDims(): i32[] {
|
||||
return [this.dim, this.dim, this.dim];
|
||||
}
|
||||
|
||||
getPieces(): VoxelSpace[] {
|
||||
return this.solutionSpaces;
|
||||
}
|
||||
@@ -3,34 +3,30 @@ import SomaSolution from "./SomaSolution";
|
||||
|
||||
export default class SomaSolver {
|
||||
private solutionCube: VoxelSpace;
|
||||
private dim: i32;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
||||
private iterations: i32 = 0;
|
||||
constructor(dimension: i32) {
|
||||
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, 0);
|
||||
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionCube = new VoxelSpace(0, dimX, dimY, dimZ, 0);
|
||||
}
|
||||
|
||||
solve(polycubes: VoxelSpace[]): void {
|
||||
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.splice(0, this.solutions.length);
|
||||
const combosWithRots: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
||||
for (let i = 1; i < polycubes.length; i++) {
|
||||
combosWithRots.push(polycubes[i].getAllPermutationsInCubeOfSize(this.dim));
|
||||
combosWithRots.push(polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||
}
|
||||
let combos: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
||||
combos.push(polycubes[0].getAllPositionsInCube(this.dim));
|
||||
combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||
combos = combos.concat(combosWithRots);
|
||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dimX, this.dimY, this.dimZ));
|
||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
||||
}
|
||||
|
||||
@@ -47,7 +43,7 @@ export default class SomaSolver {
|
||||
nextSoln.addSpace(nextCubeGroup[i]);
|
||||
if (polycubes.length == 1) {
|
||||
this.solutions.push(nextSoln);
|
||||
currentSoln = new SomaSolution(this.dim);
|
||||
currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
return;
|
||||
} else {
|
||||
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
||||
@@ -13,19 +13,16 @@ export default class VoxelSpace {
|
||||
private length: i32;
|
||||
private space: i64;
|
||||
private id: i32;
|
||||
private dimx: i32;
|
||||
private dimy: i32;
|
||||
private dimz: i32;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
|
||||
constructor(id: i32, dimx: i32, dimy: i32, dimz: i32, space: i64 = 0, cullEmpty: boolean = false) {
|
||||
if (!space) {
|
||||
space = 0;
|
||||
}
|
||||
this.id = id;
|
||||
this.length = dimx * dimy * dimz;
|
||||
this.dimx = dimx;
|
||||
this.dimy = dimy;
|
||||
this.dimz = dimz;
|
||||
this.dimX = dimx;
|
||||
this.dimY = dimy;
|
||||
this.dimZ = dimz;
|
||||
this.space = space;
|
||||
if (cullEmpty) {
|
||||
this.cullEmptySpace();
|
||||
@@ -35,15 +32,15 @@ export default class VoxelSpace {
|
||||
getExtrema(): Extrema {
|
||||
const extrema = new Extrema(
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimX,
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimY,
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimZ,
|
||||
);
|
||||
for (let x = 0; x < this.dimx; x++) {
|
||||
for (let y = 0; y < this.dimy; y++) {
|
||||
for (let z = 0; z < this.dimz; z++) {
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
const val = this.at(x, y, z);
|
||||
if (val) {
|
||||
extrema.xMax = Math.max(extrema.xMax, x) as i32;
|
||||
@@ -61,8 +58,8 @@ export default class VoxelSpace {
|
||||
|
||||
private cullEmptySpace(): void {
|
||||
const extrema = this.getExtrema();
|
||||
let index = 0;
|
||||
let newSpace = 0;
|
||||
let index: i32 = 0;
|
||||
let newSpace: i64 = 0;
|
||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||
@@ -73,9 +70,9 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dimx = extrema.xMax - extrema.xMin + 1;
|
||||
this.dimy = extrema.yMax - extrema.yMin + 1;
|
||||
this.dimz = extrema.zMax - extrema.zMin + 1;
|
||||
this.dimX = extrema.xMax - extrema.xMin + 1;
|
||||
this.dimY = extrema.yMax - extrema.yMin + 1;
|
||||
this.dimZ = extrema.zMax - extrema.zMin + 1;
|
||||
this.space = newSpace;
|
||||
}
|
||||
|
||||
@@ -134,45 +131,44 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInCube(cubeDim: i32): VoxelSpace[] {
|
||||
if ((cubeDim > 0) && (cubeDim % 1 == 0)) {
|
||||
const cubePositions: VoxelSpace[] = [];
|
||||
for (let x = 0; x < cubeDim - this.dimx + 1; x++) {
|
||||
for (let y = 0; y < cubeDim - this.dimy + 1; y++) {
|
||||
for (let z = 0; z < cubeDim - this.dimz + 1; z++) {
|
||||
const cubePos = new VoxelSpace(this.id, cubeDim, cubeDim, cubeDim);
|
||||
for (let rotX = 0; rotX < this.dimx; rotX++) {
|
||||
for (let rotY = 0; rotY < this.dimy; rotY++) {
|
||||
for (let rotZ = 0; rotZ < this.dimz; rotZ++) {
|
||||
cubePos.set(x + rotX, y + rotY, z + rotZ, this.at(rotX, rotY, rotZ));
|
||||
}
|
||||
getAllPositionsInPrism(cubeDimX: i32, cubeDimY: i32, cubeDimZ: i32): VoxelSpace[] {
|
||||
const cubePositions: VoxelSpace[] = [];
|
||||
if (this.dimX > cubeDimX || this.dimY > cubeDimY || this.dimZ > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let x = 0; x < (cubeDimX - this.dimX + 1); x++) {
|
||||
for (let y = 0; y < (cubeDimY - this.dimY + 1); y++) {
|
||||
for (let z = 0; z < (cubeDimZ - this.dimZ + 1); z++) {
|
||||
const cubePos = new VoxelSpace(this.id, cubeDimX, cubeDimY, cubeDimZ);
|
||||
for (let posX = 0; posX < this.dimX; posX++) {
|
||||
for (let posY = 0; posY < this.dimY; posY++) {
|
||||
for (let posZ = 0; posZ < this.dimZ; posZ++) {
|
||||
cubePos.set(x + posX, y + posY, z + posZ, this.at(posX, posY, posZ));
|
||||
}
|
||||
}
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
cubePositions.push(cubePos);
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
} else {
|
||||
throw new Error("cubeDim must be a positive integer.");
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
|
||||
matches(space: VoxelSpace): boolean {
|
||||
if (space.dimx !== this.dimx) {
|
||||
if (space.dimX !== this.dimX) {
|
||||
return false;
|
||||
}
|
||||
if (space.dimy !== this.dimy) {
|
||||
if (space.dimY !== this.dimY) {
|
||||
return false;
|
||||
}
|
||||
if (space.dimz !== this.dimz) {
|
||||
if (space.dimZ !== this.dimZ) {
|
||||
return false;
|
||||
}
|
||||
return this.space == space.getRaw();
|
||||
}
|
||||
|
||||
clone(): VoxelSpace {
|
||||
return new VoxelSpace(this.id, this.dimx, this.dimy, this.dimz, this.getRaw());
|
||||
return new VoxelSpace(this.id, this.dimX, this.dimY, this.dimZ, this.getRaw());
|
||||
}
|
||||
|
||||
private getXAxisSpins(): VoxelSpace[] {
|
||||
@@ -192,35 +188,35 @@ export default class VoxelSpace {
|
||||
// [0, 0, -1] * [y] = [-z]
|
||||
// [0, 1, 0] [z] [ y]
|
||||
private newIndexRotX(x: i32, y: i32, z: i32): i32 {
|
||||
return this.dimz * this.dimy * x + this.dimy * (this.dimz - 1 - z) + y;
|
||||
return this.dimZ * this.dimY * x + this.dimY * (this.dimZ - 1 - z) + y;
|
||||
}
|
||||
|
||||
// [ 0, 0, 1] [x] [ z]
|
||||
// [ 0, 1, 0] * [y] = [ y]
|
||||
// [-1, 0, 0] [z] [-x]
|
||||
private newIndexRotY(x: i32, y: i32, z: i32): i32 {
|
||||
return this.dimy * this.dimx * z + this.dimx * y + (this.dimx - 1 - x);
|
||||
return this.dimY * this.dimX * z + this.dimX * y + (this.dimX - 1 - x);
|
||||
}
|
||||
|
||||
// [0, -1, 0] [x] [-y]
|
||||
// [1, 0, 0] * [y] = [ x]
|
||||
// [0, 0, 1] [z] [ z]
|
||||
private newIndexRotZ(x: i32, y: i32, z: i32): i32 {
|
||||
return this.dimx * this.dimz * (this.dimy - 1 - y) + this.dimz * x + z;
|
||||
return this.dimX * this.dimZ * (this.dimY - 1 - y) + this.dimZ * x + z;
|
||||
}
|
||||
|
||||
at(x: i32, y: i32, z: i32): boolean {
|
||||
const mask = 1 << (this.dimy * this.dimz * x + this.dimz * y + z);
|
||||
const mask = 1 << (this.dimY * this.dimZ * x + this.dimZ * y + z);
|
||||
return (this.space & mask) !== 0;
|
||||
}
|
||||
|
||||
toggle(x: i32, y: i32, z: i32): void {
|
||||
const mask = 1 << this.dimy * this.dimz * x + this.dimz * y + z;
|
||||
const mask = 1 << this.dimY * this.dimZ * x + this.dimZ * y + z;
|
||||
this.space ^= mask;
|
||||
}
|
||||
|
||||
set(x: i32, y: i32, z: i32, val: boolean): void {
|
||||
const mask = 1 << this.dimy * this.dimz * x + this.dimz * y + z;
|
||||
const mask = 1 << this.dimY * this.dimZ * x + this.dimZ * y + z;
|
||||
if (val) {
|
||||
this.space |= mask;
|
||||
} else {
|
||||
@@ -230,83 +226,83 @@ export default class VoxelSpace {
|
||||
|
||||
rotated90X(): VoxelSpace {
|
||||
let newSpace = 0;
|
||||
for (let x = 0; x < this.dimx; x++) {
|
||||
for (let y = 0; y < this.dimy; y++) {
|
||||
for (let z = 0; z < this.dimz; z++) {
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace |= 1 << this.newIndexRotX(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new VoxelSpace(this.id, this.dimx, this.dimz, this.dimy, newSpace);
|
||||
return new VoxelSpace(this.id, this.dimX, this.dimZ, this.dimY, newSpace);
|
||||
}
|
||||
|
||||
rotated90Y(): VoxelSpace {
|
||||
let newSpace = 0;
|
||||
for (let x = 0; x < this.dimx; x++) {
|
||||
for (let y = 0; y < this.dimy; y++) {
|
||||
for (let z = 0; z < this.dimz; z++) {
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace |= 1 << this.newIndexRotY(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new VoxelSpace(this.id, this.dimz, this.dimy, this.dimx, newSpace);
|
||||
return new VoxelSpace(this.id, this.dimZ, this.dimY, this.dimX, newSpace);
|
||||
}
|
||||
|
||||
rotated90Z(): VoxelSpace {
|
||||
let newSpace = 0;
|
||||
for (let x = 0; x < this.dimx; x++) {
|
||||
for (let y = 0; y < this.dimy; y++) {
|
||||
for (let z = 0; z < this.dimz; z++) {
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace |= 1 << this.newIndexRotZ(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new VoxelSpace(this.id, this.dimy, this.dimx, this.dimz, newSpace);
|
||||
return new VoxelSpace(this.id, this.dimY, this.dimX, this.dimZ, newSpace);
|
||||
}
|
||||
|
||||
rot90X(): void {
|
||||
const rot = this.rotated90X();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
rot90Y(): void {
|
||||
const rot = this.rotated90Y();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
rot90Z(): void {
|
||||
const rot = this.rotated90Z();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
plus(space: VoxelSpace): VoxelSpace | null {
|
||||
const otherSpace = space.getRaw();
|
||||
if ((this.space | otherSpace) == (this.space ^ otherSpace)) {
|
||||
return new VoxelSpace(this.id, this.dimx, this.dimy, this.dimz, otherSpace | this.space);
|
||||
return new VoxelSpace(this.id, this.dimX, this.dimY, this.dimZ, otherSpace | this.space);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
size(): i32 {
|
||||
let size = 0;
|
||||
for (let x = 0; x < this.dimx; x++) {
|
||||
for (let y = 0; y < this.dimy; y++) {
|
||||
for (let z = 0; z < this.dimz; z++) {
|
||||
for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
size++;
|
||||
}
|
||||
@@ -316,11 +312,11 @@ export default class VoxelSpace {
|
||||
return size;
|
||||
}
|
||||
|
||||
getAllPermutationsInCubeOfSize(dim: i32): VoxelSpace[] {
|
||||
getAllPermutationsInPrism(prismDimX: i32, prismDimY: i32, prismDimZ: i32): VoxelSpace[] {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array<VoxelSpace>();
|
||||
for (let i = 0; i < rotations.length; i++) {
|
||||
result = result.concat(rotations[i].getAllPositionsInCube(dim));
|
||||
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import SomaSolver from "./SomaSolver";
|
||||
import VoxelSpace from "./VoxelSpace";
|
||||
|
||||
|
||||
export function solve(polycubes: Array<i64>, dim: i32): Int64Array[] {
|
||||
const solver = new SomaSolver(dim);
|
||||
export function solve(polycubes: Array<i64>, dimX: i32, dimY: i32, dimZ: i32): Int64Array[] {
|
||||
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||
const voxelSpaces = new Array<VoxelSpace>();
|
||||
for (let i = 0; i < polycubes.length; i++) {
|
||||
voxelSpaces.push(new VoxelSpace(i, dim, dim, dim, polycubes[i], true));
|
||||
voxelSpaces.push(new VoxelSpace(i, dimX, dimY, dimZ, polycubes[i], true));
|
||||
}
|
||||
solver.solve(voxelSpaces);
|
||||
const solutions = solver.getSolutions();
|
||||
@@ -6,7 +6,7 @@ const asyncTask = async () => {
|
||||
|
||||
// You can now use your wasm / as-bind instance!
|
||||
const response = asBindInstance.exports.solve(
|
||||
[16875584n, 16810176n, 65688n, 77952n, 12296n, 2109456n, 4184n], 3
|
||||
[16875584n, 16810176n, 65688n, 77952n, 12296n, 2109456n, 4184n], 3, 3, 3
|
||||
);
|
||||
console.log(response); // AsBind: Hello World!
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"asbuild:untouched": "asc assembly/index.ts --target debug",
|
||||
"asbuild:optimized": "asc assembly/index.ts --target release",
|
||||
"asbuild:untouched": "asc assembly/index.ts --exportRuntime --transform as-bind --target debug",
|
||||
"asbuild:optimized": "asc assembly/index.ts --exportRuntime --transform as-bind --target release",
|
||||
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
||||
"test": "node tests"
|
||||
},
|
||||
318
src/store.ts
318
src/store.ts
@@ -1,111 +1,119 @@
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { get } from 'svelte/store';
|
||||
import type SomaSolution from "./SomaSolution";
|
||||
import SomaSolution from "./SomaSolution";
|
||||
import VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||
import type {DimensionDef} from "./VoxelSpaceBoolean";
|
||||
|
||||
type PolycubeInput = {
|
||||
color: string,
|
||||
rep: bigint,
|
||||
}
|
||||
|
||||
const MAX_DIMS = 5;
|
||||
const MIN_DIMS = 2;
|
||||
|
||||
const store = {
|
||||
polycubes: writable<PolycubeInput[]>([{rep: BigInt(0), color: colorFromIndex(0)}]),
|
||||
somaDimension: writable(3),
|
||||
};
|
||||
const MAX_DIMS = 20;
|
||||
const MIN_DIMS = 1;
|
||||
|
||||
export const solving = writable(false);
|
||||
export const debug = writable(false);
|
||||
export const somaDimX = dimStore(3);
|
||||
export const somaDimY = dimStore(3);
|
||||
export const somaDimZ = dimStore(3);
|
||||
export const polycubes = polycubeStore();
|
||||
export const selectedCube = writable(0);
|
||||
export const isMaxDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension >= MAX_DIMS);
|
||||
export const isMinDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension <= MIN_DIMS);
|
||||
export const isMaxPolycubes = derived(
|
||||
[store.polycubes, store.somaDimension],
|
||||
([$polycubes, $somaDimension]: [PolycubeInput[], number]) => $polycubes.length >= $somaDimension ** 3);
|
||||
export const isMinPolycubes = derived(store.polycubes, ($polycubes: PolycubeInput[]) => $polycubes.length <= 1);
|
||||
export const solutions = writable([] as SomaSolution[]);
|
||||
export const activeSolution = writable<number | null>(null);
|
||||
export const showingSolution = writable(false);
|
||||
export const totalVolume = derived(
|
||||
[somaDimX, somaDimY, somaDimZ],
|
||||
([$dimX, $dimY, $dimZ]: [number, number, number]) => $dimX*$dimY*$dimZ
|
||||
);
|
||||
export const isMaxPolycubes = derived(
|
||||
[polycubes, totalVolume],
|
||||
([$cubes, $vol]: [VoxelSpaceBigInt[], number]) => $cubes.length >= $vol
|
||||
);
|
||||
export const isMinPolycubes = derived(
|
||||
polycubes,
|
||||
($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1
|
||||
);
|
||||
|
||||
export const somaDimension = {
|
||||
subscribe: store.somaDimension.subscribe,
|
||||
inc() {
|
||||
if (!get(isMaxDimension)) {
|
||||
store.somaDimension.update((dims: number) => {
|
||||
polycubes.reset(dims + 1);
|
||||
return dims + 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
dec() {
|
||||
if (!get(isMinDimension)) {
|
||||
store.somaDimension.update((dims: number) => {
|
||||
polycubes.reset(dims - 1);
|
||||
return dims - 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
set(dims: number) {
|
||||
if (dims <= MAX_DIMS && dims >= MIN_DIMS) {
|
||||
polycubes.reset(dims);
|
||||
store.somaDimension.set(dims);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const polycubes = {
|
||||
subscribe: store.polycubes.subscribe,
|
||||
addCube() {
|
||||
const isMaxPolycubes = get(store.polycubes).length >= get(store.somaDimension) ** 3;
|
||||
if (!isMaxPolycubes) {
|
||||
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.concat({
|
||||
rep: BigInt(0),
|
||||
color: colorFromIndex(polycubes.length),
|
||||
}));
|
||||
}
|
||||
},
|
||||
removeCube() {
|
||||
const isMinPolycubes = get(store.polycubes).length <= 1;
|
||||
if (!isMinPolycubes) {
|
||||
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.splice(0, polycubes.length - 1));
|
||||
}
|
||||
const newLength = get(store.polycubes).length;
|
||||
if (newLength <= get(selectedCube)) {
|
||||
selectedCube.set(newLength - 1);
|
||||
}
|
||||
},
|
||||
toggle(cubeIndex: number, x: number, y: number, z: number) {
|
||||
const dims = get(store.somaDimension);
|
||||
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
|
||||
const cubes = get(store.polycubes);
|
||||
cubes[cubeIndex].rep ^= mask;
|
||||
store.polycubes.set(cubes);
|
||||
},
|
||||
set(cubeIndex: number, val: boolean, x: number, y: number, z: number) {
|
||||
const dims = get(store.somaDimension);
|
||||
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
|
||||
const cubes = get(store.polycubes);
|
||||
if (val) {
|
||||
cubes[cubeIndex].rep |= mask
|
||||
} else {
|
||||
cubes[cubeIndex].rep &= ~mask
|
||||
}
|
||||
store.polycubes.set(cubes);
|
||||
},
|
||||
reset(dims: number) {
|
||||
store.polycubes.update((polycubes: PolycubeInput[]) => {
|
||||
const result: PolycubeInput[] = [];
|
||||
for (let i = 0; i < Math.min(polycubes.length, dims**3); i++) {
|
||||
result.push({
|
||||
rep: BigInt(0),
|
||||
color: colorFromIndex(i),
|
||||
});
|
||||
function dimStore(init: number) {
|
||||
const dimStore = writable(init);
|
||||
return {
|
||||
subscribe: dimStore.subscribe,
|
||||
set(dim: number) {
|
||||
if (dim > MAX_DIMS || dim < MIN_DIMS) {
|
||||
return;
|
||||
}
|
||||
return result;
|
||||
dimStore.set(dim);
|
||||
polycubes.reset();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function polycubeStore() {
|
||||
function freshCube(id: number) {
|
||||
return new VoxelSpaceBigInt({
|
||||
id: id,
|
||||
dims: [get(somaDimX), get(somaDimY), get(somaDimZ)],
|
||||
color: colorFromIndex(id),
|
||||
cullEmpty: false
|
||||
});
|
||||
}
|
||||
};
|
||||
const polycubeStore = writable<VoxelSpaceBigInt[]>([freshCube(0)]);
|
||||
return {
|
||||
subscribe: polycubeStore.subscribe,
|
||||
setColor(cubeIndex: number, color: string) {
|
||||
const cubes = get(polycubeStore);
|
||||
cubes[cubeIndex].setColor(color);
|
||||
polycubeStore.set(cubes);
|
||||
},
|
||||
addCube() {
|
||||
if (!get(isMaxPolycubes)) {
|
||||
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) =>
|
||||
polycubes.concat(freshCube(polycubes.length)));
|
||||
}
|
||||
},
|
||||
removeCube() {
|
||||
if (!get(isMinPolycubes)) {
|
||||
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => polycubes.splice(0, polycubes.length - 1));
|
||||
}
|
||||
const newLength = get(polycubeStore).length;
|
||||
if (newLength <= get(selectedCube)) {
|
||||
selectedCube.set(newLength - 1);
|
||||
}
|
||||
},
|
||||
toggle(cubeIndex: number, x: number, y: number, z: number) {
|
||||
const cubes = get(polycubeStore);
|
||||
cubes[cubeIndex].toggle(x, y, z);
|
||||
polycubeStore.set(cubes);
|
||||
},
|
||||
set(cubeIndex: number, val: boolean, x: number, y: number, z: number) {
|
||||
const cubes = get(polycubeStore);
|
||||
cubes[cubeIndex].set(x, y, z, val);
|
||||
polycubeStore.set(cubes);
|
||||
},
|
||||
reset() {
|
||||
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => {
|
||||
const result: VoxelSpaceBigInt[] = [];
|
||||
for (let i = 0; i < Math.min(polycubes.length, get(totalVolume)); i++) {
|
||||
result.push(freshCube(i));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function colorFromIndex(index: number) {
|
||||
function rgbToHex(rgbStr: string): string {
|
||||
const sep = rgbStr.indexOf(",") > -1 ? "," : " ";
|
||||
const rgb = rgbStr.substr(4).split(")")[0].split(sep);
|
||||
const r = (+rgb[0]).toString(16).padStart(2, "0");
|
||||
const g = (+rgb[1]).toString(16).padStart(2, "0");
|
||||
const b = (+rgb[2]).toString(16).padStart(2, "0");
|
||||
return "#" + r + g + b;
|
||||
}
|
||||
|
||||
function hslToRgb(hslStr: string): string {
|
||||
const opt = new Option();
|
||||
opt.style.color = hslStr;
|
||||
return opt.style.color;
|
||||
}
|
||||
|
||||
export function colorFromIndex(index: number): string {
|
||||
const colorWheelCycle = Math.floor(index / 6);
|
||||
const darknessCycle = Math.floor(index / 12);
|
||||
const spacing = (360 / 6);
|
||||
@@ -113,5 +121,121 @@ function colorFromIndex(index: number) {
|
||||
let hue = spacing * (index % 6) + offset;
|
||||
const saturation = 100;
|
||||
const lightness = 1 / (2 + darknessCycle) * 100;
|
||||
return `hsl(${hue},${saturation}%,${Math.round(lightness)}%)`;
|
||||
}
|
||||
return rgbToHex(hslToRgb(`hsl(${hue},${saturation}%,${Math.round(lightness)}%)`));
|
||||
}
|
||||
|
||||
const worker = new Worker('../solver/main.js', {type: "module"});
|
||||
async function respondWasm(event: MessageEvent) {
|
||||
solutions.set(event.data.map((wasmSolution) => {
|
||||
const solnObj = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ));
|
||||
const spaceReps = wasmSolution.split(",");
|
||||
for (let i = 0; i < spaceReps.length; i++) {
|
||||
solnObj.addSpace(new VoxelSpaceBigInt({
|
||||
id: i,
|
||||
dims: [get(somaDimX), get(somaDimY), get(somaDimZ)] as DimensionDef,
|
||||
space: BigInt(parseInt(spaceReps[i])),
|
||||
color: get(polycubes)[i].getColor(),
|
||||
cullEmpty: false,
|
||||
}));
|
||||
}
|
||||
return solnObj;
|
||||
}));
|
||||
if (event.data.length > 0) {
|
||||
activeSolution.set(0);
|
||||
showingSolution.set(true);
|
||||
} else {
|
||||
showingSolution.set(false);
|
||||
activeSolution.set(null);
|
||||
}
|
||||
solving.set(false);
|
||||
}
|
||||
|
||||
function respondJs(event: MessageEvent) {
|
||||
solutions.set(event.data.solns.map(solnSpaces => {
|
||||
const solnObj = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ));
|
||||
for (let i = 0; i < solnSpaces.length; i++) {
|
||||
solnObj.addSpace(new VoxelSpaceBigInt({
|
||||
id: i,
|
||||
dims: [get(somaDimX), get(somaDimY), get(somaDimZ)] as DimensionDef,
|
||||
space: BigInt(`0b${ solnSpaces[i] }`),
|
||||
color: get(polycubes)[i].getColor(),
|
||||
cullEmpty: false,
|
||||
}));
|
||||
}
|
||||
return solnObj;
|
||||
}));
|
||||
if (event.data.length > 0) {
|
||||
activeSolution.set(0);
|
||||
showingSolution.set(true);
|
||||
} else {
|
||||
showingSolution.set(false);
|
||||
activeSolution.set(null);
|
||||
}
|
||||
solving.set(false);
|
||||
}
|
||||
|
||||
export function solve() {
|
||||
const doWasm = get(totalVolume) <= 32;
|
||||
let inputCubes;
|
||||
if (doWasm) {
|
||||
worker.onmessage = (e) => respondWasm(e);
|
||||
} else {
|
||||
worker.onmessage = (e) => respondJs(e);
|
||||
}
|
||||
inputCubes = get(polycubes).map(cubeInput => cubeInput.getRaw());
|
||||
solving.set(true);
|
||||
worker.postMessage({
|
||||
type: doWasm ? 'wasm' : 'js',
|
||||
polycubes: inputCubes,
|
||||
dimX: get(somaDimX),
|
||||
dimY: get(somaDimY),
|
||||
dimZ: get(somaDimZ)
|
||||
});
|
||||
}
|
||||
|
||||
// async function solveSync() {
|
||||
// const solver = new SomaSolver(get(somaDimX), get(somaDimY), get(somaDimZ));
|
||||
// function showSolutionWaitUserFeedback(soln: SomaSolution) {
|
||||
// activeSolution.set(0);
|
||||
// solutions.set([soln]);
|
||||
// showingSolution.set(true);
|
||||
// return new Promise<void>((resolve) => {
|
||||
// const callback = (e: KeyboardEvent) => {
|
||||
// resolve();
|
||||
// window.removeEventListener("keydown", callback);
|
||||
// };
|
||||
// window.addEventListener("keydown", callback);
|
||||
// });
|
||||
// }
|
||||
// if (get(debug)) {
|
||||
// solver.setDebug({
|
||||
// showSoln(soln: SomaSolution) {
|
||||
// return showSolutionWaitUserFeedback(soln);
|
||||
// },
|
||||
// showSpace(cube: VoxelSpaceBoolean) {
|
||||
// const testSoln = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ));
|
||||
// testSoln.addSpace(cube);
|
||||
// return showSolutionWaitUserFeedback(testSoln);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// solving.set(true);
|
||||
// await solver.solve(get(polycubes).map(cubeInput => new VoxelSpaceBoolean({
|
||||
// id: cubeInput.getId(),
|
||||
// dims: cubeInput.getDims(),
|
||||
// space: cubeInput.getRaw(),
|
||||
// color: cubeInput.getColor(),
|
||||
// cullEmpty: true
|
||||
// })));
|
||||
// const solns = solver.getSolutions();
|
||||
//
|
||||
// if (solns.length > 0) {
|
||||
// activeSolution.set(0);
|
||||
// solutions.set(solns);
|
||||
// showingSolution.set(true);
|
||||
// } else {
|
||||
// showingSolution.set(false);
|
||||
// activeSolution.set(null);
|
||||
// }
|
||||
// solving.set(false);
|
||||
// }
|
||||
@@ -1,24 +1,24 @@
|
||||
<script lang="ts">
|
||||
import {somaDimension, polycubes, selectedCube, showingSolution} from "../store";
|
||||
import {somaDimX, somaDimY, somaDimZ, polycubes, selectedCube, showingSolution} from "../store";
|
||||
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||
export let cubeNo: number;
|
||||
|
||||
$: dimension = $somaDimension;
|
||||
$: cube = $polycubes[cubeNo];
|
||||
$: cubeColor = cube.color;
|
||||
$: cube = $polycubes[cubeNo] as VoxelSpaceBoolean;
|
||||
$: cubeColor = cube.getColor();
|
||||
$: currentlyVisualised = $selectedCube === cubeNo && !$showingSolution;
|
||||
let cellStartDragInitialVal: boolean = false;
|
||||
let cellStartDrag: number = 0;
|
||||
let cellDragStartPos: {x: number, y: number} = {x: 0, y: 0};
|
||||
let cellEndDrag: number = 0;
|
||||
let cellDragEndPos: {x: number, y: number} = {x: 0, y: 0};
|
||||
let picker: HTMLInputElement;
|
||||
|
||||
function cellNo(x: number, y: number, z: number) {
|
||||
return dimension ** 2 * x + dimension * y + z;
|
||||
return $somaDimY * $somaDimZ * x + $somaDimZ * y + z;
|
||||
}
|
||||
|
||||
function at(rep: bigint, x: number, y: number, z: number) {
|
||||
const mask = BigInt(1) << BigInt(cellNo(x, y, z));
|
||||
return (rep & mask) !== BigInt(0);
|
||||
function at(cube: VoxelSpaceBoolean, x: number, y: number, z: number) {
|
||||
return cube.at(x, y, z);
|
||||
}
|
||||
|
||||
function onMouseOverCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
function onMouseDownCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||
cellStartDrag = cellNo(x, y, z);
|
||||
cellStartDragInitialVal = at(cube.rep, x, y, z);
|
||||
cellStartDragInitialVal = at(cube, x, y, z);
|
||||
cellDragStartPos.x = event.clientX;
|
||||
cellDragStartPos.y = event.clientY;
|
||||
}
|
||||
@@ -56,29 +56,43 @@
|
||||
|
||||
function onClickCube() {
|
||||
showingSolution.set(false);
|
||||
selectedCube.set(cubeNo)
|
||||
selectedCube.set(cubeNo);
|
||||
}
|
||||
|
||||
function onColorChange(event: InputEvent) {
|
||||
polycubes.setColor(cubeNo, event.target.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="cube"
|
||||
class:active={currentlyVisualised}
|
||||
style="--color: {cubeColor}; --dimension: {dimension};"
|
||||
style="--color: {cubeColor};"
|
||||
on:contextmenu|preventDefault
|
||||
on:mousedown={onClickCube}
|
||||
>
|
||||
<h1>Cube: {cubeNo + 1}</h1>
|
||||
{#each {length: dimension} as _, x}
|
||||
<div class="header">
|
||||
<h1>Cube: {cubeNo + 1}</h1>
|
||||
<div class="colorPickerBtn" on:click={picker.click()}>
|
||||
<input
|
||||
bind:this={picker}
|
||||
class="colorPicker"
|
||||
type="color"
|
||||
value="{cubeColor}"
|
||||
on:change={(event) => onColorChange(event)}/>
|
||||
</div>
|
||||
</div>
|
||||
{#each {length: $somaDimX} as _, x}
|
||||
<div class="layer">
|
||||
{#each {length: dimension} as _, y}
|
||||
{#each {length: $somaDimY} as _, y}
|
||||
<div class="row">
|
||||
{#each {length: dimension} as _, z}
|
||||
{#each {length: $somaDimZ} as _, z}
|
||||
<div
|
||||
class="cell"
|
||||
class:filled={at(cube.rep, z, dimension-1-x, y)}
|
||||
on:mousemove={(event) => onMouseOverCell(event, z, dimension-1-x, y)}
|
||||
on:mousedown={(event) => onMouseDownCell(event, z, dimension-1-x, y)}
|
||||
on:mouseup={(event) => onMouseUpCell(event, z, dimension-1-x, y)}
|
||||
class:filled={at(cube, x, y, z)}
|
||||
on:mousemove={(event) => onMouseOverCell(event, x, y, z)}
|
||||
on:mousedown={(event) => onMouseDownCell(event, x, y, z)}
|
||||
on:mouseup={(event) => onMouseUpCell(event, x, y, z)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -91,6 +105,27 @@
|
||||
* {
|
||||
--cell-size: 30px;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header > * {
|
||||
display: inline-block;
|
||||
}
|
||||
.colorPicker {
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.colorPickerBtn {
|
||||
align-self: center;
|
||||
background-image: url("../resources/ColorWheel.png");
|
||||
background-size: cover;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.cube.active {
|
||||
border: 3px solid #ff3e00;
|
||||
}
|
||||
@@ -125,6 +160,8 @@
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
.layer {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script lang="ts">
|
||||
import {isMaxPolycubes, isMinPolycubes, somaDimension, polycubes, solutions} from "../store";
|
||||
import SomaSolution from "../SomaSolution";
|
||||
import {
|
||||
isMaxPolycubes,
|
||||
isMinPolycubes,
|
||||
polycubes,
|
||||
solutions,
|
||||
colorFromIndex,
|
||||
activeSolution, showingSolution, totalVolume, somaDimX, somaDimY, somaDimZ, debug
|
||||
} from "../store";
|
||||
import SolutionList from "./SolutionList.svelte";
|
||||
import VoxelSpace from "../VoxelSpace";
|
||||
|
||||
$: numCubes = $polycubes.length;
|
||||
$: cubes = $polycubes;
|
||||
@@ -11,57 +16,16 @@
|
||||
let readyToSolve: boolean;
|
||||
let size: number;
|
||||
$: {
|
||||
const dim = $somaDimension as number;
|
||||
const polycubes: VoxelSpace[] = cubes.map(cubeInput => new VoxelSpace(0, [dim, dim, dim], cubeInput.rep));
|
||||
size = polycubes.reduce((prev, cube) => cube.size() + prev, 0);
|
||||
noEmpties = polycubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
||||
enoughSubcubes = size === dim**3;
|
||||
readyToSolve = size === dim**3 && noEmpties;
|
||||
}
|
||||
let solving = false;
|
||||
const worker = new Worker('../solver/main.js', {type: "module"});
|
||||
|
||||
async function respondWasm(event: MessageEvent) {
|
||||
const dim = $somaDimension as number;
|
||||
solutions.set(event.data.map((wasmSolution) => {
|
||||
const solnObj = new SomaSolution(dim);
|
||||
const spaceReps = wasmSolution.split(",");
|
||||
for (let i = 0; i < spaceReps.length; i++) {
|
||||
solnObj.addSpace(new VoxelSpace(i, [dim, dim, dim], BigInt(parseInt(spaceReps[i]))));
|
||||
}
|
||||
return solnObj;
|
||||
}));
|
||||
solving = false;
|
||||
}
|
||||
|
||||
function respondJs(event: MessageEvent) {
|
||||
solutions.set(event.data.map(solnData => {
|
||||
const solution = new SomaSolution(solnData.dim);
|
||||
solnData.solutionSpaces.forEach((voxelSpace, i) => solution.addSpace(new VoxelSpace(i, [solnData.dim, solnData.dim, solnData.dim], voxelSpace.space)));
|
||||
return solution;
|
||||
}));
|
||||
solving = false;
|
||||
}
|
||||
|
||||
function solveJs() {
|
||||
worker.onmessage = (e) => respondJs(e);
|
||||
const polycubes = cubes.map(cubeInput => cubeInput.rep);
|
||||
solving = true;
|
||||
worker.postMessage({type: 'js', polycubes, dims: $somaDimension});
|
||||
}
|
||||
|
||||
function solveWasm() {
|
||||
worker.onmessage = (e) => respondWasm(e);
|
||||
const polycubes = cubes.map(cubeInput => cubeInput.rep);
|
||||
console.log(polycubes);
|
||||
solving = true;
|
||||
worker.postMessage({type: 'wasm', polycubes, dims: $somaDimension});
|
||||
size = cubes.reduce((prev, cube) => cube.size() + prev, 0);
|
||||
noEmpties = cubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
||||
enoughSubcubes = size === $totalVolume;
|
||||
readyToSolve = enoughSubcubes && noEmpties;
|
||||
}
|
||||
|
||||
function genTooltip() {
|
||||
let messages = [];
|
||||
if (!enoughSubcubes) {
|
||||
messages.push(`You have not input enough subcubes to form a cube with a side length of ${$somaDimension}. Needed: ${$somaDimension**3}, current: ${size}.`);
|
||||
messages.push(`You have not input enough subcubes to form a rectangular prism with side lengths ${$somaDimX}, ${$somaDimY}, and ${$somaDimZ}. Needed: ${$totalVolume}, current: ${size}.`);
|
||||
}
|
||||
if (!noEmpties) {
|
||||
messages.push("You have left some of the polycube inputs empty. Remove them to solve.");
|
||||
@@ -74,34 +38,34 @@
|
||||
<h1>Somaesque</h1>
|
||||
<div class="widgets">
|
||||
<div class="option">
|
||||
<p>Dimension:</p>
|
||||
<p>Dimensions:</p>
|
||||
<div class="choice">
|
||||
<button
|
||||
class:selected={$somaDimension === 2}
|
||||
on:click={() => somaDimension.set(2)}
|
||||
disabled={$somaDimension === 2}>
|
||||
2
|
||||
</button>
|
||||
<button
|
||||
class:selected={$somaDimension === 3}
|
||||
on:click={() => somaDimension.set(3)}
|
||||
disabled={$somaDimension === 3}>
|
||||
3
|
||||
</button>
|
||||
<button
|
||||
class:selected={$somaDimension === 4}
|
||||
on:click={() => somaDimension.set(4)}
|
||||
disabled={$somaDimension === 4}>
|
||||
4
|
||||
</button>
|
||||
X
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimX.set(e.target.valueAsNumber)}/>
|
||||
Y
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimY.set(e.target.valueAsNumber)}/>
|
||||
Z
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimZ.set(e.target.valueAsNumber)}/>
|
||||
{#if $totalVolume > 32}
|
||||
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
<p>Cubes:</p>
|
||||
<div class="choice">
|
||||
<p>{numCubes}</p>
|
||||
<button on:click={polycubes.removeCube} disabled={$isMinPolycubes}>-</button>
|
||||
<p>{numCubes}</p>
|
||||
<button on:click={polycubes.addCube} disabled={$isMaxPolycubes}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +73,7 @@
|
||||
<div class="option">
|
||||
<button
|
||||
class="solve"
|
||||
on:click={solveWasm}
|
||||
on:click={solve}
|
||||
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
|
||||
disabled="{solving || !readyToSolve}">
|
||||
{solving ? "Solving..." : "Solve!"}
|
||||
@@ -121,6 +85,9 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.warn {
|
||||
color: red;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
@@ -130,10 +97,10 @@
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
button {
|
||||
input {
|
||||
display: inline-block;
|
||||
background-color: #999999;
|
||||
width: 2em;
|
||||
width: 3em;
|
||||
height: 2em;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {polycubes, activeSolution, showingSolution, solutions} from "../store";
|
||||
import {activeSolution, showingSolution, solutions} from "../store";
|
||||
import SomaSolution from "../SomaSolution";
|
||||
|
||||
$: solutionDisplayed = $solutions[$activeSolution];
|
||||
$: dimension = (solutionDisplayed && solutionDisplayed.getDims?.()[0]) ?? 3;
|
||||
$: solnToShow = $solutions[$activeSolution];
|
||||
$: dims = (solnToShow?.getDims?.()) ?? [3, 3, 3];
|
||||
|
||||
function colorAt(soln: SomaSolution, x: number, y: number, z: number) {
|
||||
return $polycubes[soln.at(z, dimension-1-x, y)].color;
|
||||
return solnToShow.getPieces()[soln.at(x, y, z)]?.getColor?.() ?? "red";
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,19 +14,18 @@
|
||||
<div
|
||||
class="cube"
|
||||
class:active={$showingSolution}
|
||||
style="--dimension: {dimension};"
|
||||
on:click={() => showingSolution.set(true)}
|
||||
>
|
||||
<h1>Solution #{$activeSolution + 1}</h1>
|
||||
<div class="center">
|
||||
{#each {length: dimension} as _, x}
|
||||
{#each {length: dims[0]} as _, x}
|
||||
<div class="layer">
|
||||
{#each {length: dimension} as _, y}
|
||||
{#each {length: dims[1]} as _, y}
|
||||
<div class="row">
|
||||
{#each {length: dimension} as _, z}
|
||||
{#each {length: dims[2]} as _, z}
|
||||
<div
|
||||
class="cell"
|
||||
style="background-color:{colorAt(solutionDisplayed, x, y, z)}; border-color: {colorAt(solutionDisplayed, x, y, z)}"
|
||||
style="background-color:{colorAt(solnToShow, x, y, z)}; border-color: {colorAt(solnToShow, x, y, z)}"
|
||||
class:filled={true}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
<script lang="ts">
|
||||
import PolycubeScene from "./threedee/PolycubeScene.ts";
|
||||
import PolycubeScene from "./threedee/PolycubeScene";
|
||||
import {onMount} from "svelte";
|
||||
import {polycubes, somaDimension, selectedCube, solutions, activeSolution, showingSolution} from "../store";
|
||||
import {polycubes, selectedCube, solutions, activeSolution, showingSolution, somaDimX, somaDimY, somaDimZ} from "../store";
|
||||
import Solution2D from "./Solution2D.svelte";
|
||||
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||
|
||||
$: cube = $polycubes[$selectedCube];
|
||||
$: soln = $solutions[$activeSolution];
|
||||
let el: HTMLCanvasElement;
|
||||
let threeTest: PolycubeScene;
|
||||
let scene: PolycubeScene;
|
||||
let loaded: boolean = false;
|
||||
|
||||
onMount(() => {
|
||||
threeTest = new PolycubeScene(el, () => loaded = true, console.log);
|
||||
scene = new PolycubeScene(el, () => loaded = true, console.log);
|
||||
});
|
||||
|
||||
window.getPermutations = () => {
|
||||
const newCube: VoxelSpaceBoolean = cube.clone() as VoxelSpaceBoolean;
|
||||
(newCube as VoxelSpaceBoolean).cullEmptySpace();
|
||||
return (newCube as VoxelSpaceBoolean).getAllPermutationsInPrism($somaDimX, $somaDimY, $somaDimZ);
|
||||
}
|
||||
|
||||
window.showRot = (rot: VoxelSpaceBoolean) => {
|
||||
scene?.showPolycube(rot);
|
||||
}
|
||||
|
||||
$: {
|
||||
if (loaded) {
|
||||
if ($showingSolution) {
|
||||
const colorMap = {};
|
||||
$polycubes.forEach((polycube, i) => colorMap[i] = polycube.color);
|
||||
threeTest?.showSolution(soln, colorMap);
|
||||
scene?.showSolution(soln);
|
||||
} else {
|
||||
threeTest?.showPolycube(cube.rep, $somaDimension, cube.color);
|
||||
scene?.showPolycube(cube);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="top">
|
||||
<div class="soln2d-container">
|
||||
<Solution2D/>
|
||||
</div>
|
||||
{#if $activeSolution !== null}
|
||||
<div class="soln2d-container">
|
||||
<Solution2D/>
|
||||
</div>
|
||||
{/if}
|
||||
<canvas
|
||||
bind:this={el}
|
||||
width="640"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import * as THREE from "three";
|
||||
import type VoxelSpace from "../../VoxelSpace";
|
||||
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||
import type GeometryManager from "./GeometryManager";
|
||||
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||
|
||||
export default class PolycubeMesh {
|
||||
private static geometryManager: GeometryManager;
|
||||
private group: THREE.Group;
|
||||
private meshes: THREE.Mesh[] = [];
|
||||
private currentPolycube: bigint = 0n;
|
||||
private currentPolycube: boolean[] | bigint = [];
|
||||
private material: THREE.MeshPhongMaterial;
|
||||
private numActiveCubes: number = 0;
|
||||
private flyDirection: THREE.Vector3 = new THREE.Vector3();
|
||||
|
||||
constructor(polycube: VoxelSpace, color: string) {
|
||||
this.material = new THREE.MeshPhongMaterial({color: 'red', shininess: 100, reflectivity: 100});
|
||||
constructor(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
this.material = new THREE.MeshPhongMaterial({color: polycube.getColor(), shininess: 100, reflectivity: 100});
|
||||
this.group = new THREE.Group();
|
||||
this.swapColor(color);
|
||||
this.swapPolycube(polycube);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,7 @@ export default class PolycubeMesh {
|
||||
PolycubeMesh.geometryManager = manager;
|
||||
}
|
||||
|
||||
swapColor(color: string) {
|
||||
this.material.color.set(color);
|
||||
}
|
||||
|
||||
swapPolycube(polycube: VoxelSpace) {
|
||||
swapPolycube(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
if (polycube.getRaw() === this.currentPolycube) {
|
||||
return;
|
||||
}
|
||||
@@ -40,10 +36,11 @@ export default class PolycubeMesh {
|
||||
}
|
||||
});
|
||||
this.currentPolycube = polycube.getRaw();
|
||||
this.material.color.set(polycube.getColor());
|
||||
this.flyDirection = this.middlePosOfGroup().normalize();
|
||||
}
|
||||
|
||||
private addCube(refPolycube: VoxelSpace, x: number, y: number, z: number) {
|
||||
private addCube(refPolycube: VoxelSpaceBoolean | VoxelSpaceBigInt, x: number, y: number, z: number) {
|
||||
const dims = refPolycube.getDims();
|
||||
const neighbourProfile = refPolycube.getDirectNeighbourProfile(x, y, z);
|
||||
const mesh = new THREE.Mesh(
|
||||
@@ -51,9 +48,9 @@ export default class PolycubeMesh {
|
||||
this.material
|
||||
);
|
||||
mesh.position.set(
|
||||
-((dims[0] - 1)/2) + x,
|
||||
-((dims[1] - 1)/2) + y,
|
||||
-((dims[2] - 1)/2) + z,
|
||||
((dims[0] - 1)/2) - x,
|
||||
-((dims[1] - 1)/2) + y,
|
||||
);
|
||||
this.meshes.push(mesh);
|
||||
this.group.add(mesh);
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as THREE from 'three';
|
||||
import type SomaSolution from "../../SomaSolution";
|
||||
import RotationControl from "./RotationControl";
|
||||
import PolycubeMesh from "./PolycubeMesh";
|
||||
import VoxelSpace, {DimensionDef} from "../../VoxelSpace";
|
||||
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||
import GeometryManager from "./GeometryManager";
|
||||
|
||||
export default class PolycubeScene {
|
||||
@@ -44,20 +45,19 @@ export default class PolycubeScene {
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
}
|
||||
|
||||
private showPolycube(polycube: bigint, dims: number, color: string) {
|
||||
showPolycube(voxelSpace: VoxelSpaceBoolean) {
|
||||
this.controls.disableFly();
|
||||
const voxelSpace = new VoxelSpace(0, [dims, dims, dims], polycube, true);
|
||||
this.clearScene();
|
||||
this.addPolycube(voxelSpace, color);
|
||||
this.addPolycube(voxelSpace);
|
||||
this.polycubeMeshes[0].center();
|
||||
}
|
||||
|
||||
private showSolution(solution: SomaSolution, colorMap: Record<number, string>) {
|
||||
showSolution(solution: SomaSolution) {
|
||||
this.controls.enableFly();
|
||||
this.clearScene();
|
||||
const pieces = solution.getPieces();
|
||||
for (let i = 0; i < pieces.length; i++) {
|
||||
this.addPolycube(pieces[i], colorMap[i]);
|
||||
this.addPolycube(pieces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ export default class PolycubeScene {
|
||||
this.cubeScene.clear();
|
||||
}
|
||||
|
||||
private addPolycube(voxelSpace: VoxelSpace, color: string) {
|
||||
const newMesh = new PolycubeMesh(voxelSpace, color);
|
||||
private addPolycube(voxelSpace: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
const newMesh = new PolycubeMesh(voxelSpace);
|
||||
this.polycubeMeshes.push(newMesh);
|
||||
this.cubeScene.add(newMesh.asObj3D());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user