great stuff

This commit is contained in:
Daniel Ledda
2021-06-08 17:29:29 +02:00
parent e7b8ae6120
commit c8f37d0d98
65 changed files with 3309 additions and 3331 deletions

View File

@@ -1,113 +0,0 @@
import type VoxelSpace from "./VoxelSpace";
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;
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.getUniqueRotations()) {
let end = uniqueSolns.length;
for (let i = 0; i < end; i++) {
if (rotation.matches(uniqueSolns[i])) {
foundMatch = true;
}
}
}
if (!foundMatch) {
uniqueSolns.push(solution);
}
}
return uniqueSolns;
}
getUniqueRotations(): 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.dim);
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: VoxelSpace) {
this.solutionSpaces.push(space);
}
print() {
let accum = "";
console.log("---");
for (let x = 0; x < this.dim; x++) {
for (let y = 0; y < this.dim; y++) {
for (let z = 0; z < this.dim; z++) {
for (const space of this.solutionSpaces) {
if (space.at(x, y, z)) {
accum += space.getId();
}
}
}
console.log(accum);
accum = "";
}
if (x !== this.dim - 1) {
console.log("-");
}
}
console.log("---");
}
at(x: 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.dim);
clone.solutionSpaces = this.solutionSpaces.slice();
return clone;
}
getDims() {
return [this.dim, this.dim, this.dim];
}
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++) {
cb(this.at(x, y, z), x, y, z);
}
}
}
}
}

View File

@@ -1,302 +0,0 @@
export type DimensionDef = [number, number, number];
export default class VoxelSpace {
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)
}
this.id = id;
this.length = dims[0] * dims[1] * dims[2];
this.dims = dims;
this.space = space;
if (cullEmpty) {
this.cullEmptySpace();
}
}
private static boolArrayToBigInt(boolArray: boolean[]): bigint {
let result = 0n;
for (let i = 0; i < boolArray.length; i++) {
if (boolArray[i]) {
result |= BigInt(1 << i);
}
}
return result;
}
binaryRep() {
return this.space.toString(2);
}
getExtrema() {
const extrema = {
xMax: -Infinity,
xMin: Infinity,
yMax: -Infinity,
yMin: Infinity,
zMax: -Infinity,
zMin: Infinity,
};
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;
}
private cullEmptySpace() {
const extrema = this.getExtrema();
let index = 0n;
let newSpace = 0n;
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 |= 1n << index;
}
index++;
}
}
}
this.dims[0] = extrema.xMax - extrema.xMin + 1;
this.dims[1] = extrema.yMax - extrema.yMin + 1;
this.dims[2] = extrema.zMax - extrema.zMin + 1;
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: VoxelSpace[] = [];
const refSpace = this.clone();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
refSpace.rot90('y');
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
refSpace.rot90('y');
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
refSpace.rot90('y');
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
refSpace.rot90('z');
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
refSpace.rot90('z');
refSpace.rot90('z');
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
return rotations;
}
getAllRotations() {
const rotations: VoxelSpace[] = [];
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: VoxelSpace[], newSpaces: VoxelSpace[]) {
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);
}
}
}
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);
}
}
}
return cubePositions;
} else {
throw new Error("cubeDim must be a positive integer.");
}
}
matches(space: VoxelSpace) {
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();
}
clone() {
return new VoxelSpace(this.id, this.getDims(), this.getRaw());
}
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpace[] {
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;
}
// [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) {
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
return (this.space & mask) !== 0n;
}
toggle(x: number, y: number, z: number) {
const mask = BigInt(1 << 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);
if (val) {
this.space |= mask;
} else {
this.space &= ~mask;
}
}
rotated90(dim: 'x' | 'y' | 'z') {
let newSpace = 0n;
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 |= BigInt(1 << rotIndex(i, j, k));
}
})
return new VoxelSpace(this.id, newDims, newSpace);
}
rot90(dim: 'x' | 'y' | 'z') {
const rot = this.rotated90(dim);
this.space = rot.getRaw();
this.dims = rot.getDims();
}
plus(space: VoxelSpace): VoxelSpace | null {
const otherSpace = space.getRaw();
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
return new VoxelSpace(this.id, this.dims, otherSpace | this.space);
}
return null;
}
size() {
let size = 0;
this.forEachCell((val) => {
if (val) {
size++;
}
});
return size;
}
}

20
src/solver/asconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"targets": {
"debug": {
"binaryFile": "build/untouched.wasm",
"textFile": "build/untouched.wat",
"sourceMap": true,
"debug": true
},
"release": {
"binaryFile": "../../public/solver/main.wasm",
"textFile": "../../public/solver/main.wat",
"sourceMap": false,
"optimizeLevel": 3,
"shrinkLevel": 1,
"converge": true,
"noAssert": true
}
},
"options": {}
}

View File

@@ -0,0 +1,89 @@
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;
this.solutionSpaces = [];
}
static filterUnique(solutions: SomaSolution[]): SomaSolution[] {
const uniqueSolns = new Array<SomaSolution>();
if (solutions.length == 0) {
return uniqueSolns;
}
uniqueSolns.push(solutions[0]);
for (let iSoln = 0; iSoln < solutions.length; iSoln++) {
const rots = solutions[iSoln].getRotations();
let foundMatch = false;
for (let iRot = 0; iRot < rots.length; iRot++) {
let end = uniqueSolns.length;
for (let i = 0; i < end; i++) {
if (rots[iRot].matches(uniqueSolns[i])) {
foundMatch = true;
}
}
}
if (!foundMatch) {
uniqueSolns.push(solutions[iSoln]);
}
}
return uniqueSolns;
}
getRotations(): SomaSolution[] {
const result: SomaSolution[] = new Array<SomaSolution>();
if (this.solutionSpaces.length == 0) {
return result;
}
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);
for (let j = 0; j < allRots.length; j++) {
solnRot.addSpace(allRots[j][i]);
}
result.push(solnRot);
}
return result;
}
matches(solution: SomaSolution): boolean {
for (let i = 0; i < this.solutionSpaces.length; i++) {
if (!this.solutionSpaces[i].matches(solution.solutionSpaces[i])) {
return false;
}
}
return true;
}
addSpace(space: VoxelSpace): void {
this.solutionSpaces.push(space);
}
at(x: i32, y: i32, z: i32): i32 {
for (let i = 0; i < this.solutionSpaces.length; i++) {
if (this.solutionSpaces[i].at(x, y, z)) {
return this.solutionSpaces[i].getId();
}
}
return 0;
}
clone(): SomaSolution {
const clone = new SomaSolution(this.dim);
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;
}
}

View File

@@ -3,18 +3,18 @@ import SomaSolution from "./SomaSolution";
export default class SomaSolver {
private solutionCube: VoxelSpace;
private dim: number;
private solutions: SomaSolution[] = [];
private iterations: number = 0;
constructor(dimension: number) {
private dim: 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], Array(dimension**3).fill(0));
this.solutionCube = new VoxelSpace(0, dimension, dimension, dimension, 0);
}
async solve(polycubes: VoxelSpace[]) {
solve(polycubes: VoxelSpace[]): void {
if (polycubes.length === 0) {
throw new Error("You must pass at least one polycube to solve the puzzle.");
}
@@ -22,25 +22,30 @@ export default class SomaSolver {
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.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));
}
let combos: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
combos.push(polycubes[0].getAllPositionsInCube(this.dim));
combos = combos.concat(combosWithRots);
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
this.solutions = SomaSolution.filterUnique(this.solutions);
}
getSolutions() {
return this.solutions.slice();
getSolutions(): SomaSolution[] {
return this.solutions.slice(0, this.solutions.length);
}
private backtrackSolve(workingSolution: VoxelSpace, polycubes: VoxelSpace[][], currentSoln: SomaSolution, depth = 0) {
private backtrackSolve(workingSolution: VoxelSpace, polycubes: VoxelSpace[][], currentSoln: SomaSolution, depth: i32 = 0): void {
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) {
if (polycubes.length == 1) {
this.solutions.push(nextSoln);
currentSoln = new SomaSolution(this.dim);
return;

View File

@@ -0,0 +1,327 @@
class Extrema {
constructor(
public xMax: i32,
public xMin: i32,
public yMax: i32,
public yMin: i32,
public zMax: i32,
public zMin: i32,
) {}
}
export default class VoxelSpace {
private length: i32;
private space: i64;
private id: 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.space = space;
if (cullEmpty) {
this.cullEmptySpace();
}
}
getExtrema(): Extrema {
const extrema = new Extrema(
0,
i32.MAX_VALUE,
0,
i32.MAX_VALUE,
0,
i32.MAX_VALUE,
);
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;
extrema.xMin = Math.min(extrema.xMin, x) as i32;
extrema.yMax = Math.max(extrema.yMax, y) as i32;
extrema.yMin = Math.min(extrema.yMin, y) as i32;
extrema.zMax = Math.max(extrema.zMax, z) as i32;
extrema.zMin = Math.min(extrema.zMin, z) as i32;
}
}
}
}
return extrema;
}
private cullEmptySpace(): void {
const extrema = this.getExtrema();
let index = 0;
let newSpace = 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 |= 1 << index;
}
index++;
}
}
}
this.dimx = extrema.xMax - extrema.xMin + 1;
this.dimy = extrema.yMax - extrema.yMin + 1;
this.dimz = extrema.zMax - extrema.zMin + 1;
this.space = newSpace;
}
getId(): i32 {
return this.id;
}
getUniqueRotations(): VoxelSpace[] {
const rotations: VoxelSpace[] = new Array<VoxelSpace>();
const refSpace = this.clone();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
refSpace.rot90Y();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
refSpace.rot90Y();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
refSpace.rot90Y();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
refSpace.rot90Z();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
refSpace.rot90Z();
refSpace.rot90Z();
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getXAxisSpins());
return rotations;
}
getAllRotations(): VoxelSpace[] {
let rotations: VoxelSpace[] = new Array<VoxelSpace>();
const refSpace = this.clone();
rotations = rotations.concat(refSpace.getXAxisSpins());
refSpace.rot90Y();
rotations = rotations.concat(refSpace.getXAxisSpins());
refSpace.rot90Y();
rotations = rotations.concat(refSpace.getXAxisSpins());
refSpace.rot90Y();
rotations = rotations.concat(refSpace.getXAxisSpins());
refSpace.rot90Z();
rotations = rotations.concat(refSpace.getXAxisSpins());
refSpace.rot90Z();
refSpace.rot90Z();
rotations = rotations.concat(refSpace.getXAxisSpins());
return rotations;
}
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpace[], newSpaces: VoxelSpace[]): void {
for (let iNew = 0; iNew < newSpaces.length; iNew++) {
let matchFound = false;
for (let iExisting = 0; iExisting < existingSpaces.length; iExisting++) {
if (newSpaces[iNew].matches(existingSpaces[iExisting])) {
matchFound = true;
break;
}
}
if (!matchFound) {
existingSpaces.push(newSpaces[iNew]);
}
}
}
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));
}
}
}
cubePositions.push(cubePos);
}
}
}
return cubePositions;
} else {
throw new Error("cubeDim must be a positive integer.");
}
}
matches(space: VoxelSpace): boolean {
if (space.dimx !== this.dimx) {
return false;
}
if (space.dimy !== this.dimy) {
return false;
}
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());
}
private getXAxisSpins(): VoxelSpace[] {
const rotations: Array<VoxelSpace> = new Array<VoxelSpace>();
rotations.push(this.clone());
for (let i = 0; i < 3; i++) {
rotations.push(rotations[i].rotated90X());
}
return rotations;
}
getRaw(): i64 {
return this.space;
}
// [1, 0, 0] [x] [ x]
// [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;
}
// [ 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);
}
// [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;
}
at(x: i32, y: i32, z: i32): boolean {
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;
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;
if (val) {
this.space |= mask;
} else {
this.space &= ~mask;
}
}
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++) {
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);
}
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++) {
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);
}
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++) {
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);
}
rot90X(): void {
const rot = this.rotated90X();
this.space = rot.getRaw();
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;
}
rot90Z(): void {
const rot = this.rotated90Z();
this.space = rot.getRaw();
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 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++) {
if (this.at(x, y, z)) {
size++;
}
}
}
}
return size;
}
getAllPermutationsInCubeOfSize(dim: 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));
}
return result;
}
}

View File

@@ -0,0 +1,22 @@
import SomaSolver from "./SomaSolver";
import VoxelSpace from "./VoxelSpace";
export function solve(polycubes: Array<i64>, dim: i32): Int64Array[] {
const solver = new SomaSolver(dim);
const voxelSpaces = new Array<VoxelSpace>();
for (let i = 0; i < polycubes.length; i++) {
voxelSpaces.push(new VoxelSpace(i, dim, dim, dim, polycubes[i], true));
}
solver.solve(voxelSpaces);
const solutions = solver.getSolutions();
let output: Int64Array[] = new Array<Int64Array>();
for (let i = 0; i < solutions.length; i++) {
const pieces = solutions[i].getPieces();
output.push(new Int64Array(pieces.length));
for (let j = 0; j < pieces.length; j++) {
output[i][j] = pieces[j].getRaw();
}
}
return output;
}

View File

@@ -0,0 +1,6 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": [
"./**/*.ts"
]
}

13
src/solver/index.js Normal file
View File

@@ -0,0 +1,13 @@
const AsBind = require("as-bind/dist/as-bind.cjs.js");
const fs = require("fs");
const wasm = fs.readFileSync("./build/untouched.wasm");
const asyncTask = async () => {
const asBindInstance = await AsBind.instantiate(wasm);
// You can now use your wasm / as-bind instance!
const response = asBindInstance.exports.solve(
[16875584n, 16810176n, 65688n, 77952n, 12296n, 2109456n, 4184n], 3
);
console.log(response); // AsBind: Hello World!
};
asyncTask();

View File

@@ -1,12 +0,0 @@
import SomaSolver from "./SomaSolver";
import VoxelSpace from "./VoxelSpace";
type SolveStartMessageData = {polycubes: bigint[], dims: number};
self.addEventListener('message', (event) => {
const {polycubes, dims} = event.data as SolveStartMessageData;
const solver = new SomaSolver(event.data.dims);
solver.solve(polycubes.map((cubeRep, i) => new VoxelSpace(i, [dims, dims, dims], cubeRep)));
(self as unknown as Worker).postMessage(solver.getSolutions());
});

60
src/solver/package-lock.json generated Normal file
View File

@@ -0,0 +1,60 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@assemblyscript/loader": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.1.tgz",
"integrity": "sha512-3vpqYxOY7o8SNj2riGNF3wSZsqWippWTs7YwyTPPyxvjbrT1ZJnMMoGm4HSpbZ0QmKphzsaM4trR+BtxLFynDA=="
},
"as-bind": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/as-bind/-/as-bind-0.7.1.tgz",
"integrity": "sha512-x/tfZZcyObwvAohhVYaKqLSvroMHqop3l9gkUO5JM0bBEdhI3BWXjkG3DZIuWj0YFzhQ8OiWG3FCvQQq/5yB3A==",
"requires": {
"visitor-as": "^0.5.0"
}
},
"assemblyscript": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.19.1.tgz",
"integrity": "sha512-unWcmJsw5H0H2GrTf25GlDJCaNzAveeFYPH5XhP54m540+26KJIurTEHN+xf/EI3MdK7IhThpGCE+pNqiNuLmA==",
"dev": true,
"requires": {
"binaryen": "101.0.0-nightly.20210527",
"long": "^4.0.0"
}
},
"binaryen": {
"version": "101.0.0-nightly.20210527",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-101.0.0-nightly.20210527.tgz",
"integrity": "sha512-dbKentJwA6H0LfI+pRuzNNzAooJwYFNrg1L8rRw8j6rlfkU815ytNLO+uDzGNcltYehUa5ERZFJHPIdqX12n0w==",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"dev": true
},
"ts-mixer": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-5.4.1.tgz",
"integrity": "sha512-Zo9HgPCtNouDgJ+LGtrzVOjSg8+7WGQktIKLwAfaNrlOK1mWGlz1ejsAF/YqUEqAGjUTeB5fEg8gH9Aui6w9xA=="
},
"visitor-as": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/visitor-as/-/visitor-as-0.5.0.tgz",
"integrity": "sha512-U2P13pa7BAnfj6IEbP4feS1Rci6NT4GlDcwpqkk90u7LGalc5jH9aMuWnxTC8RJJ92iZzDQ8Lea5/OnLDsgzlw==",
"requires": {
"lodash.clonedeep": "^4.5.0",
"ts-mixer": "^5.1.0"
}
}
}
}

15
src/solver/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"scripts": {
"asbuild:untouched": "asc assembly/index.ts --target debug",
"asbuild:optimized": "asc assembly/index.ts --target release",
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
"test": "node tests"
},
"dependencies": {
"@assemblyscript/loader": "^0.19.1",
"as-bind": "^0.7.1"
},
"devDependencies": {
"assemblyscript": "^0.19.1"
}
}