From 1c60c4f0d48d9df6f9ba4cf9014dd437e2eb2ec5 Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Thu, 20 May 2021 07:56:10 +0200 Subject: [PATCH] First commit --- .gitignore | 1 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/deno.xml | 6 + .idea/modules.xml | 8 + .idea/soma.iml | 12 + .idea/workspace.xml | 90 +++++ package-lock.json | 157 ++++++++ package.json | 15 + src/Polycube.js | 84 +++++ src/Polycube.ts | 70 ++++ src/SomaSolver.js | 58 +++ src/SomaSolver.ts | 55 +++ src/VoxelSpace.js | 239 ++++++++++++ src/VoxelSpace.ts | 246 +++++++++++++ src/main.js | 139 +++++++ src/main.ts | 158 ++++++++ src/test.html | 13 + src/test.js | 499 +++++++++++++++++++++++++ src/test.ts | 524 +++++++++++++++++++++++++++ tsconfig.json | 66 ++++ 20 files changed, 2445 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/deno.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/soma.iml create mode 100644 .idea/workspace.xml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/Polycube.js create mode 100644 src/Polycube.ts create mode 100644 src/SomaSolver.js create mode 100644 src/SomaSolver.ts create mode 100644 src/VoxelSpace.js create mode 100644 src/VoxelSpace.ts create mode 100644 src/main.js create mode 100644 src/main.ts create mode 100644 src/test.html create mode 100644 src/test.js create mode 100644 src/test.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/deno.xml b/.idea/deno.xml new file mode 100644 index 0000000..b03feb5 --- /dev/null +++ b/.idea/deno.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fde2be1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/soma.iml b/.idea/soma.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/soma.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..bd82429 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1621324574891 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2d14489 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,157 @@ +{ + "name": "soma", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@haensl/log": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@haensl/log/-/log-1.3.4.tgz", + "integrity": "sha512-6AZuT9NQVbl5Bh5FW31rN7IO7IctF4plPvENnUHFRCdBqPJ5+zJ3LzuXoRPy6ZHd4KSsbJP9sVU552BEppmIUg==", + "requires": { + "chalk": "^4.1.0" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "js-profiler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/js-profiler/-/js-profiler-2.4.2.tgz", + "integrity": "sha512-wbOwWUQKYjJJEym2z+OkpApFaIjUv9CKNPHGMTe/UkGz5lLYy1PrDCzH0goR0ocEWjFCew+nYgk9ioVHob9Bmw==", + "requires": { + "@haensl/log": "^1.2.2", + "chalk": "^4.1.0", + "glob": "^7.1.6", + "node-getopt": "^0.3.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "node-getopt": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/node-getopt/-/node-getopt-0.3.2.tgz", + "integrity": "sha512-yqkmYrMbK1wPrfz7mgeYvA4tBperLg9FQ4S3Sau3nSAkpOA0x0zC8nQ1siBwozy1f4SE8vq2n1WKv99r+PCa1Q==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "typescript": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c4b2445 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "soma", + "version": "1.0.0", + "description": "Custom Somaesque cube solver webapp", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "deno run ./src/main.ts" + }, + "author": "Daniel Ledda", + "license": "ISC", + "dependencies": { + "js-profiler": "^2.4.2", + "typescript": "^4.2.4" + } +} diff --git a/src/Polycube.js b/src/Polycube.js new file mode 100644 index 0000000..ac41718 --- /dev/null +++ b/src/Polycube.js @@ -0,0 +1,84 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var VoxelSpace_1 = __importDefault(require("./VoxelSpace")); +var Polycube = /** @class */ (function (_super) { + __extends(Polycube, _super); + function Polycube(dims, vals, id) { + var _this = _super.call(this, dims, vals.map(function (val) { return val ? id : 0; }), true) || this; + _this.id = id; + return _this; + } + Polycube.prototype.getId = function () { + return this.id; + }; + Polycube.prototype.print = function () { + var accum = ""; + console.log("---"); + for (var i = 0; i < this.dims[0]; i++) { + for (var j = 0; j < this.dims[1]; j++) { + for (var k = 0; k < this.dims[2]; k++) { + accum += this.at(i, j, k) === 0 ? "O" : "#"; + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + }; + Polycube.prototype.matches = function (cube) { + var otherDims = cube.getDims(); + for (var i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + var otherVals = cube.getVals(); + for (var i = 0; i < this.vals.length; i++) { + if (Number(this.vals[i]) !== Number(otherVals[i])) { + return false; + } + } + return true; + }; + Polycube.prototype.size = function () { + var size = 0; + this.forEachCell(function (val) { + if (val) { + size++; + } + }); + return size; + }; + Polycube.prototype.rotated90 = function (dim) { + var rotated = _super.prototype.rotated90.call(this, dim); + return new Polycube(rotated.getDims(), rotated.getVals(), this.id); + }; + Polycube.prototype.clone = function () { + return new Polycube(this.getDims(), this.getVals(), this.id); + }; + Polycube.prototype.getUniqueRotations = function () { + var _this = this; + return _super.prototype.getUniqueRotations.call(this).map(function (rot) { return new Polycube(rot.getDims(), rot.getVals(), _this.id); }); + }; + return Polycube; +}(VoxelSpace_1.default)); +exports.default = Polycube; diff --git a/src/Polycube.ts b/src/Polycube.ts new file mode 100644 index 0000000..df81443 --- /dev/null +++ b/src/Polycube.ts @@ -0,0 +1,70 @@ +import VoxelSpace, {DimensionDef} from "./VoxelSpace"; + +export default class Polycube extends VoxelSpace { + private id: number; + constructor(dims: DimensionDef, vals: boolean[], id: number) { + super(dims, vals.map(val => val ? id : 0), true); + this.id = id; + } + + 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) === 0 ? "O" : "#"; + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + } + + matches(cube: VoxelSpace) { + const otherDims = cube.getDims(); + for (let i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + const otherVals = cube.getVals(); + for (let i = 0; i < this.vals.length; i++) { + if (Number(this.vals[i]) !== Number(otherVals[i])) { + return false; + } + } + return true; + } + + size() { + let size = 0; + this.forEachCell((val) => { + if (val) { + size++; + } + }); + return size; + } + + rotated90(dim: "x" | "y" | "z"): Polycube { + const rotated = super.rotated90(dim); + return new Polycube(rotated.getDims(), rotated.getVals() as unknown as boolean[], this.id); + } + + clone(): Polycube { + return new Polycube(this.getDims(), this.getVals() as unknown as boolean[], this.id); + } + + getUniqueRotations(): Polycube[] { + return super.getUniqueRotations().map(rot => new Polycube(rot.getDims(), rot.getVals() as unknown as boolean[], this.id)); + } +} \ No newline at end of file diff --git a/src/SomaSolver.js b/src/SomaSolver.js new file mode 100644 index 0000000..fda6de3 --- /dev/null +++ b/src/SomaSolver.js @@ -0,0 +1,58 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var VoxelSpace_1 = __importDefault(require("./VoxelSpace")); +var SomaSolver = /** @class */ (function () { + function SomaSolver(dimension) { + this.solutions = []; + this.iterations = 0; + if (dimension % 1 !== 0 || dimension < 0) { + throw new Error("The argument 'dimension' must be a positive whole number"); + } + this.dim = dimension; + this.solutionCube = new VoxelSpace_1.default([dimension, dimension, dimension], Array(Math.pow(dimension, 3)).fill(0)); + } + SomaSolver.prototype.solve = function (polycubes) { + if (polycubes.length === 0) { + throw new Error("You must pass at least one polycube to solve the puzzle."); + } + var cumulativeSize = polycubes.reduce(function (prev, curr) { return prev + curr.size(); }, 0); + if (cumulativeSize !== Math.pow(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: " + Math.pow(this.dim, 3)); + } + this.iterations = 0; + this.backtrackSolve(this.solutionCube, polycubes); + this.solutions = VoxelSpace_1.default.filterUnique(this.solutions); + this.solutions.forEach(function (sol) { return sol.print(); }); + console.log(this.solutions.length); + }; + SomaSolver.prototype.backtrackSolve = function (workingSolution, polycubes, depth) { + if (depth === void 0) { depth = 0; } + var nextCube = polycubes[0]; + var rots = depth === 0 ? [nextCube] : nextCube.getUniqueRotations(); + for (var i = 0; i < rots.length; i++) { + var polyCubeDims = rots[i].getDims(); + for (var x = 0; x < this.dim - polyCubeDims[0] + 1; x++) { + for (var y = 0; y < this.dim - polyCubeDims[1] + 1; y++) { + for (var z = 0; z < this.dim - polyCubeDims[2] + 1; z++) { + var successfulFusion = workingSolution.plus(rots[i], x, y, z); + if (successfulFusion) { + if (polycubes.length === 1) { + console.log("soln", this.iterations++); + this.solutions.push(successfulFusion); + return; + } + else { + this.backtrackSolve(successfulFusion, polycubes.slice(1), depth + 1); + } + } + } + } + } + } + }; + return SomaSolver; +}()); +exports.default = SomaSolver; diff --git a/src/SomaSolver.ts b/src/SomaSolver.ts new file mode 100644 index 0000000..1a24af7 --- /dev/null +++ b/src/SomaSolver.ts @@ -0,0 +1,55 @@ +import Polycube from "./Polycube"; +import VoxelSpace from "./VoxelSpace"; + +export default class SomaSolver { + private solutionCube: VoxelSpace; + private dim: number; + private solutions: VoxelSpace[] = []; + 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([dimension, dimension, dimension], Array(dimension**3).fill(0)); + } + + solve(polycubes: Polycube[]) { + 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.iterations = 0; + this.backtrackSolve(this.solutionCube, polycubes); + this.solutions = VoxelSpace.filterUnique(this.solutions); + this.solutions.forEach(sol => sol.print()); + console.log(this.solutions.length); + } + + private backtrackSolve(workingSolution: VoxelSpace, polycubes: Polycube[], depth = 0) { + const nextCube = polycubes[0]; + const rots = depth === 0 ? [nextCube] : nextCube.getUniqueRotations(); + for (let i = 0; i < rots.length; i++) { + const polyCubeDims = rots[i].getDims(); + for (let x = 0; x < this.dim - polyCubeDims[0] + 1; x++) { + for (let y = 0; y < this.dim - polyCubeDims[1] + 1; y++) { + for (let z = 0; z < this.dim - polyCubeDims[2] + 1; z++) { + const successfulFusion = workingSolution.plus(rots[i], x, y, z); + if (successfulFusion) { + if (polycubes.length === 1) { + console.log("soln", this.iterations++); + this.solutions.push(successfulFusion); + return; + } else { + this.backtrackSolve(successfulFusion, polycubes.slice(1), depth + 1); + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/VoxelSpace.js b/src/VoxelSpace.js new file mode 100644 index 0000000..023a4b4 --- /dev/null +++ b/src/VoxelSpace.js @@ -0,0 +1,239 @@ +"use strict"; +var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var VoxelSpace = /** @class */ (function () { + function VoxelSpace(dims, vals, cullEmpty) { + if (vals.length !== dims[0] * dims[1] * dims[2]) { + throw new Error("Vals don't fit in given dimensions."); + } + this.dims = dims; + this.vals = vals; + if (cullEmpty) { + this.cullEmptySpace(); + } + } + VoxelSpace.prototype.cullEmptySpace = function () { + var extrema = { + xMax: -Infinity, + xMin: Infinity, + yMax: -Infinity, + yMin: Infinity, + zMax: -Infinity, + zMin: Infinity, + }; + var newVals = []; + this.forEachCell(function (val, i, j, k) { + if (val !== 0) { + extrema.xMax = Math.max(extrema.xMax, i); + extrema.xMin = Math.min(extrema.xMin, i); + extrema.yMax = Math.max(extrema.yMax, j); + extrema.yMin = Math.min(extrema.yMin, j); + extrema.zMax = Math.max(extrema.zMax, k); + extrema.zMin = Math.min(extrema.zMin, k); + } + }); + for (var i = extrema.xMin; i <= extrema.xMax; i++) { + for (var j = extrema.yMin; j <= extrema.yMax; j++) { + for (var k = extrema.zMin; k <= extrema.zMax; k++) { + newVals.push(this.at(i, j, k)); + } + } + } + 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.vals = newVals; + }; + VoxelSpace.prototype.forEachCell = function (cb) { + loopStart: for (var x = 0; x < this.dims[0]; x++) { + for (var y = 0; y < this.dims[1]; y++) { + for (var z = 0; z < this.dims[2]; z++) { + if (cb(this.at(x, y, z), x, y, z) === 0) { + break loopStart; + } + } + } + } + }; + VoxelSpace.prototype.print = function () { + var accum = ""; + console.log("---"); + for (var i = 0; i < this.dims[0]; i++) { + for (var j = 0; j < this.dims[1]; j++) { + for (var k = 0; k < this.dims[2]; k++) { + accum += this.at(i, j, k); + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + }; + VoxelSpace.prototype.getUniqueRotations = function () { + var rotations = []; + var 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; + }; + VoxelSpace.filterUnique = function (spaces) { + if (spaces.length === 0) { + return []; + } + var uniqueSpaces = [spaces[0]]; + for (var _i = 0, spaces_1 = spaces; _i < spaces_1.length; _i++) { + var space = spaces_1[_i]; + var foundMatch = false; + for (var _a = 0, _b = space.getUniqueRotations(); _a < _b.length; _a++) { + var rotation = _b[_a]; + var end = uniqueSpaces.length; + for (var i = 0; i < end; i++) { + if (rotation.matches(uniqueSpaces[i])) { + foundMatch = true; + } + } + } + if (!foundMatch) { + uniqueSpaces.push(space); + } + } + return uniqueSpaces; + }; + VoxelSpace.pushNewUniqueSpaces = function (existingSpaces, newSpaces) { + for (var _i = 0, newSpaces_1 = newSpaces; _i < newSpaces_1.length; _i++) { + var newSpace = newSpaces_1[_i]; + var matchFound = false; + for (var _a = 0, existingSpaces_1 = existingSpaces; _a < existingSpaces_1.length; _a++) { + var existingSpace = existingSpaces_1[_a]; + if (newSpace.matches(existingSpace)) { + matchFound = true; + break; + } + } + if (!matchFound) { + existingSpaces.push(newSpace); + } + } + }; + VoxelSpace.prototype.matches = function (space) { + var otherDims = space.getDims(); + for (var i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + var otherVals = space.getVals(); + for (var i = 0; i < this.vals.length; i++) { + if (this.vals[i] !== otherVals[i]) { + return false; + } + } + return true; + }; + VoxelSpace.prototype.clone = function () { + return new VoxelSpace(this.getDims(), this.getVals()); + }; + VoxelSpace.prototype.getAxisSpins = function (axis) { + var rotations = [this.clone()]; + for (var i = 0; i < 3; i++) { + rotations.push(rotations[i].rotated90(axis)); + } + return rotations; + }; + VoxelSpace.prototype.getDims = function () { + return this.dims.slice(); + }; + VoxelSpace.prototype.getVals = function () { + return this.vals.slice(); + }; + // [1, 0, 0] [x] [ x] + // [0, 0, -1] * [y] = [-z] + // [0, 1, 0] [z] [ y] + VoxelSpace.prototype.newIndexRotX = function (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] + VoxelSpace.prototype.newIndexRotY = function (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] + VoxelSpace.prototype.newIndexRotZ = function (x, y, z) { + return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z; + }; + VoxelSpace.prototype.at = function (x, y, z) { + return this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z]; + }; + VoxelSpace.prototype.set = function (x, y, z, val) { + this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z] = val; + }; + VoxelSpace.prototype.rotated90 = function (dim) { + var newVals = __spreadArrays(this.vals); + var newDims; + var 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(function (val, i, j, k) { + newVals[rotIndex(i, j, k)] = val; + }); + return new VoxelSpace(newDims, newVals); + }; + VoxelSpace.prototype.rot90 = function (dim) { + var rot = this.rotated90(dim); + this.vals = rot.getVals(); + this.dims = rot.getDims(); + }; + VoxelSpace.prototype.plus = function (space, startX, startY, startZ) { + var result = this.clone(); + var spaceDims = space.getDims(); + for (var i = 0; i < spaceDims[0]; i++) { + for (var j = 0; j < spaceDims[1]; j++) { + for (var k = 0; k < spaceDims[2]; k++) { + var sourceVal = space.at(i, j, k); + var targetEmpty = result.at(startX + i, startY + j, startZ + k) === 0; + if (sourceVal !== 0 && targetEmpty) { + result.set(startX + i, startY + j, startZ + k, sourceVal); + } + else if (sourceVal !== 0 && !targetEmpty) { + return null; + } + } + } + } + return result; + }; + return VoxelSpace; +}()); +exports.default = VoxelSpace; diff --git a/src/VoxelSpace.ts b/src/VoxelSpace.ts new file mode 100644 index 0000000..1f0a573 --- /dev/null +++ b/src/VoxelSpace.ts @@ -0,0 +1,246 @@ +import Polycube from "./Polycube"; + +export type DimensionDef = [number, number, number]; + +export default class VoxelSpace { + protected vals: number[]; + protected dims: DimensionDef; + constructor(dims: DimensionDef, vals: number[], cullEmpty?: boolean) { + if (vals.length !== dims[0] * dims[1] * dims[2]) { + throw new Error("Vals don't fit in given dimensions."); + } + this.dims = dims; + this.vals = vals; + if (cullEmpty) { + this.cullEmptySpace(); + } + } + + private cullEmptySpace() { + const extrema = { + xMax: -Infinity, + xMin: Infinity, + yMax: -Infinity, + yMin: Infinity, + zMax: -Infinity, + zMin: Infinity, + }; + const newVals: number[] = []; + this.forEachCell((val, i, j, k) => { + if (val !== 0) { + extrema.xMax = Math.max(extrema.xMax, i); + extrema.xMin = Math.min(extrema.xMin, i); + extrema.yMax = Math.max(extrema.yMax, j); + extrema.yMin = Math.min(extrema.yMin, j); + extrema.zMax = Math.max(extrema.zMax, k); + extrema.zMin = Math.min(extrema.zMin, k); + } + }); + for (let i = extrema.xMin; i <= extrema.xMax; i++) { + for (let j = extrema.yMin; j <= extrema.yMax; j++) { + for (let k = extrema.zMin; k <= extrema.zMax; k++) { + newVals.push(this.at(i, j, k)); + } + } + } + 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.vals = newVals; + } + + forEachCell(cb: (val: number, 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; + } + } + } + } + } + + 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); + } + 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; + } + + static filterUnique(spaces: T[]): T[] { + if (spaces.length === 0) { + return []; + } + const uniqueSpaces = [spaces[0]]; + for (const space of spaces) { + let foundMatch = false; + for (const rotation of space.getUniqueRotations()) { + let end = uniqueSpaces.length; + for (let i = 0; i < end; i++) { + if (rotation.matches(uniqueSpaces[i])) { + foundMatch = true; + } + } + } + if (!foundMatch) { + uniqueSpaces.push(space); + } + } + return uniqueSpaces; + } + + 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); + } + } + } + + matches(space: VoxelSpace) { + const otherDims = space.getDims(); + for (let i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + const otherVals = space.getVals(); + for (let i = 0; i < this.vals.length; i++) { + if (this.vals[i] !== otherVals[i]) { + return false; + } + } + return true; + } + + clone() { + return new VoxelSpace(this.getDims(), this.getVals()); + } + + 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; + } + + getVals() { + return this.vals.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.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z]; + } + + set(x: number, y: number, z: number, val: number) { + this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z] = val; + } + + rotated90(dim: 'x' | 'y' | 'z') { + const newVals = [...this.vals]; + 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) => { + newVals[rotIndex(i, j, k)] = val; + }) + return new VoxelSpace(newDims, newVals); + } + + rot90(dim: 'x' | 'y' | 'z') { + const rot = this.rotated90(dim); + this.vals = rot.getVals(); + this.dims = rot.getDims(); + } + + plus(space: VoxelSpace, startX: number, startY: number, startZ: number): VoxelSpace | null { + let result: VoxelSpace = this.clone(); + const spaceDims = space.getDims(); + for (let i = 0; i < spaceDims[0]; i++) { + for (let j = 0; j < spaceDims[1]; j++) { + for (let k = 0; k < spaceDims[2]; k++) { + const sourceVal = space.at(i, j, k); + const targetEmpty = result.at(startX + i, startY + j, startZ + k) === 0; + if (sourceVal !== 0 && targetEmpty) { + result.set(startX + i, startY + j, startZ + k, sourceVal); + } else if (sourceVal !== 0 && !targetEmpty) { + return null; + } + } + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..5291352 --- /dev/null +++ b/src/main.js @@ -0,0 +1,139 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var Polycube_1 = __importDefault(require("./Polycube")); +var SomaSolver_1 = __importDefault(require("./SomaSolver")); +// const testCube = new Cube([4, 2, 5], [ +// "000", "001", "002", "003", "004", +// "010", "011", "012", "013", "014", +// "100", "101", "102", "103", "104", +// "110", "111", "112", "113", "114", +// "200", "201", "202", "203", "204", +// "210", "211", "212", "213", "214", +// "300", "301", "302", "303", "304", +// "310", "311", "312", "313", "314", +// ]); +// const somaCube = new Polycube([3, 3, 3], [ +// false, false, false, +// false, false, false, +// false, false, false, +// +// true, true, true, +// true, true, true, +// false, false, false, +// +// false, false, false, +// false, false, false, +// false, false, false, +// ], 1); +var unitCube1 = new Polycube_1.default([1, 1, 1], [true], 1); +var unitCube2 = new Polycube_1.default([1, 1, 1], [true], 2); +var unitCube3 = new Polycube_1.default([1, 1, 1], [true], 3); +var unitCube4 = new Polycube_1.default([1, 1, 1], [true], 4); +var unitCube5 = new Polycube_1.default([1, 1, 1], [true], 5); +var unitCube6 = new Polycube_1.default([1, 1, 1], [true], 6); +var unitCube7 = new Polycube_1.default([1, 1, 1], [true], 7); +var unitCube8 = new Polycube_1.default([1, 1, 1], [true], 8); +var unitCube9 = new Polycube_1.default([1, 1, 1], [true], 9); +var unitCube10 = new Polycube_1.default([1, 1, 1], [true], 10); +var unitCube11 = new Polycube_1.default([1, 1, 1], [true], 11); +var unitCube12 = new Polycube_1.default([1, 1, 1], [true], 12); +var unitCube13 = new Polycube_1.default([1, 1, 1], [true], 13); +var unitCube14 = new Polycube_1.default([1, 1, 1], [true], 14); +var unitCube15 = new Polycube_1.default([1, 1, 1], [true], 15); +var unitCube16 = new Polycube_1.default([1, 1, 1], [true], 16); +var unitCube17 = new Polycube_1.default([1, 1, 1], [true], 17); +var unitCube18 = new Polycube_1.default([1, 1, 1], [true], 18); +var unitCube19 = new Polycube_1.default([1, 1, 1], [true], 19); +var unitCube20 = new Polycube_1.default([1, 1, 1], [true], 20); +var unitCube21 = new Polycube_1.default([1, 1, 1], [true], 21); +var unitCube22 = new Polycube_1.default([1, 1, 1], [true], 22); +var unitCube23 = new Polycube_1.default([1, 1, 1], [true], 23); +var unitCube24 = new Polycube_1.default([1, 1, 1], [true], 24); +var unitCube25 = new Polycube_1.default([1, 1, 1], [true], 25); +var unitCube26 = new Polycube_1.default([1, 1, 1], [true], 26); +var unitCube27 = new Polycube_1.default([1, 1, 1], [true], 27); +var tetromino1 = new Polycube_1.default([3, 3, 3], [ + true, true, true, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 1); +var tetromino2 = new Polycube_1.default([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, true, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, +], 2); +var tetromino3 = new Polycube_1.default([3, 3, 3], [ + true, false, false, + true, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 3); +var tetromino4 = new Polycube_1.default([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + true, false, false, + true, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 4); +var tetromino5 = new Polycube_1.default([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 5); +var tetromino6 = new Polycube_1.default([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, false, false, + false, true, false, + false, true, true, + false, false, false, + false, false, false, + false, false, false, +], 6); +var triomino1 = new Polycube_1.default([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, false, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, +], 7); +// const cube = new VoxelSpace([3, 3, 3], Array(3**3).fill(0)); +// cube.plus(triomino1)?.plus(tetromino2, {x: 1, y: 0, z: 1})?.print(); +var solver = new SomaSolver_1.default(3); +console.log("solving"); +solver.solve([triomino1, tetromino2, tetromino3, tetromino1, tetromino4, tetromino5, tetromino6]); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..aa2c480 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,158 @@ +import Polycube from "./Polycube"; +import SomaSolver from "./SomaSolver"; + +// const testCube = new Cube([4, 2, 5], [ +// "000", "001", "002", "003", "004", +// "010", "011", "012", "013", "014", +// "100", "101", "102", "103", "104", +// "110", "111", "112", "113", "114", +// "200", "201", "202", "203", "204", +// "210", "211", "212", "213", "214", +// "300", "301", "302", "303", "304", +// "310", "311", "312", "313", "314", +// ]); +// const somaCube = new Polycube([3, 3, 3], [ +// false, false, false, +// false, false, false, +// false, false, false, +// +// true, true, true, +// true, true, true, +// false, false, false, +// +// false, false, false, +// false, false, false, +// false, false, false, +// ], 1); +const unitCube1 = new Polycube([1, 1, 1], [true], 1); +const unitCube2 = new Polycube([1, 1, 1], [true], 2); +const unitCube3 = new Polycube([1, 1, 1], [true], 3); +const unitCube4 = new Polycube([1, 1, 1], [true], 4); +const unitCube5 = new Polycube([1, 1, 1], [true], 5); +const unitCube6 = new Polycube([1, 1, 1], [true], 6); +const unitCube7 = new Polycube([1, 1, 1], [true], 7); +const unitCube8 = new Polycube([1, 1, 1], [true], 8); +const unitCube9 = new Polycube([1, 1, 1], [true], 9); +const unitCube10 = new Polycube([1, 1, 1], [true], 10); +const unitCube11 = new Polycube([1, 1, 1], [true], 11); +const unitCube12 = new Polycube([1, 1, 1], [true], 12); +const unitCube13 = new Polycube([1, 1, 1], [true], 13); +const unitCube14 = new Polycube([1, 1, 1], [true], 14); +const unitCube15 = new Polycube([1, 1, 1], [true], 15); +const unitCube16 = new Polycube([1, 1, 1], [true], 16); +const unitCube17 = new Polycube([1, 1, 1], [true], 17); +const unitCube18 = new Polycube([1, 1, 1], [true], 18); +const unitCube19 = new Polycube([1, 1, 1], [true], 19); +const unitCube20 = new Polycube([1, 1, 1], [true], 20); +const unitCube21 = new Polycube([1, 1, 1], [true], 21); +const unitCube22 = new Polycube([1, 1, 1], [true], 22); +const unitCube23 = new Polycube([1, 1, 1], [true], 23); +const unitCube24 = new Polycube([1, 1, 1], [true], 24); +const unitCube25 = new Polycube([1, 1, 1], [true], 25); +const unitCube26 = new Polycube([1, 1, 1], [true], 26); +const unitCube27 = new Polycube([1, 1, 1], [true], 27); + +const tetromino1 = new Polycube([3, 3, 3], [ + true, true, true, + false, true, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 1); + +const tetromino2 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, true, false, + false, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, +], 2); + +const tetromino3 = new Polycube([3, 3, 3], [ + true, false, false, + true, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 3); + +const tetromino4 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + + true, false, false, + true, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 4); + +const tetromino5 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + + false, true, false, + false, true, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 5); + +const tetromino6 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, false, false, + false, true, false, + false, true, true, + + false, false, false, + false, false, false, + false, false, false, +], 6); + +const triomino1 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, false, false, + false, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, +], 7); + +// const cube = new VoxelSpace([3, 3, 3], Array(3**3).fill(0)); +// cube.plus(triomino1)?.plus(tetromino2, {x: 1, y: 0, z: 1})?.print(); + +const solver = new SomaSolver(3); +console.log("solving"); +solver.solve([triomino1, tetromino2, tetromino3, tetromino1, tetromino4, tetromino5, tetromino6]); diff --git a/src/test.html b/src/test.html new file mode 100644 index 0000000..f1d4c75 --- /dev/null +++ b/src/test.html @@ -0,0 +1,13 @@ + + + + + Title + + + + +Start + + + \ No newline at end of file diff --git a/src/test.js b/src/test.js new file mode 100644 index 0000000..47e8bd4 --- /dev/null +++ b/src/test.js @@ -0,0 +1,499 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; +var SomaSolver = /** @class */ (function () { + function SomaSolver(dimension) { + this.solutions = []; + this.iterations = 0; + if (dimension % 1 !== 0 || dimension < 0) { + throw new Error("The argument 'dimension' must be a positive whole number"); + } + this.dim = dimension; + this.solutionCube = new VoxelSpace([dimension, dimension, dimension], Array(Math.pow(dimension, 3)).fill(0)); + } + SomaSolver.prototype.solve = function (polycubes) { + if (polycubes.length === 0) { + throw new Error("You must pass at least one polycube to solve the puzzle."); + } + var cumulativeSize = polycubes.reduce(function (prev, curr) { return prev + curr.size(); }, 0); + if (cumulativeSize !== Math.pow(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: " + Math.pow(this.dim, 3)); + } + this.iterations = 0; + this.backtrackSolve(this.solutionCube, polycubes); + this.solutions = VoxelSpace.filterUnique(this.solutions); + this.solutions.forEach(function (sol) { return sol.print(); }); + console.log(this.solutions.length); + }; + SomaSolver.prototype.backtrackSolve = function (workingSolution, polycubes, depth) { + if (depth === void 0) { depth = 0; } + var nextCube = polycubes[0]; + var rots = depth === 0 ? [nextCube] : nextCube.getUniqueRotations(); + for (var i = 0; i < rots.length; i++) { + var polyCubeDims = rots[i].getDims(); + for (var x = 0; x < this.dim - polyCubeDims[0] + 1; x++) { + for (var y = 0; y < this.dim - polyCubeDims[1] + 1; y++) { + for (var z = 0; z < this.dim - polyCubeDims[2] + 1; z++) { + var successfulFusion = workingSolution.plus(rots[i], x, y, z); + if (successfulFusion) { + if (polycubes.length === 1) { + console.log("soln", this.iterations++); + this.solutions.push(successfulFusion); + return; + } + else { + this.backtrackSolve(successfulFusion, polycubes.slice(1), depth + 1); + } + } + } + } + } + } + }; + return SomaSolver; +}()); +var VoxelSpace = /** @class */ (function () { + function VoxelSpace(dims, vals, cullEmpty) { + if (vals.length !== dims[0] * dims[1] * dims[2]) { + throw new Error("Vals don't fit in given dimensions."); + } + this.dims = dims; + this.vals = vals; + if (cullEmpty) { + this.cullEmptySpace(); + } + } + VoxelSpace.prototype.cullEmptySpace = function () { + var extrema = { + xMax: -Infinity, + xMin: Infinity, + yMax: -Infinity, + yMin: Infinity, + zMax: -Infinity, + zMin: Infinity, + }; + var newVals = []; + this.forEachCell(function (val, i, j, k) { + if (val !== 0) { + extrema.xMax = Math.max(extrema.xMax, i); + extrema.xMin = Math.min(extrema.xMin, i); + extrema.yMax = Math.max(extrema.yMax, j); + extrema.yMin = Math.min(extrema.yMin, j); + extrema.zMax = Math.max(extrema.zMax, k); + extrema.zMin = Math.min(extrema.zMin, k); + } + }); + for (var i = extrema.xMin; i <= extrema.xMax; i++) { + for (var j = extrema.yMin; j <= extrema.yMax; j++) { + for (var k = extrema.zMin; k <= extrema.zMax; k++) { + newVals.push(this.at(i, j, k)); + } + } + } + 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.vals = newVals; + }; + VoxelSpace.prototype.forEachCell = function (cb) { + loopStart: for (var x = 0; x < this.dims[0]; x++) { + for (var y = 0; y < this.dims[1]; y++) { + for (var z = 0; z < this.dims[2]; z++) { + if (cb(this.at(x, y, z), x, y, z) === 0) { + break loopStart; + } + } + } + } + }; + VoxelSpace.prototype.print = function () { + var accum = ""; + console.log("---"); + for (var i = 0; i < this.dims[0]; i++) { + for (var j = 0; j < this.dims[1]; j++) { + for (var k = 0; k < this.dims[2]; k++) { + accum += this.at(i, j, k); + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + }; + VoxelSpace.prototype.getUniqueRotations = function () { + var rotations = []; + var 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; + }; + VoxelSpace.filterUnique = function (spaces) { + if (spaces.length === 0) { + return []; + } + var uniqueSpaces = [spaces[0]]; + for (var _i = 0, spaces_1 = spaces; _i < spaces_1.length; _i++) { + var space = spaces_1[_i]; + var foundMatch = false; + for (var _a = 0, _b = space.getUniqueRotations(); _a < _b.length; _a++) { + var rotation = _b[_a]; + var end = uniqueSpaces.length; + for (var i = 0; i < end; i++) { + if (rotation.matches(uniqueSpaces[i])) { + foundMatch = true; + } + } + } + if (!foundMatch) { + uniqueSpaces.push(space); + } + } + return uniqueSpaces; + }; + VoxelSpace.pushNewUniqueSpaces = function (existingSpaces, newSpaces) { + for (var _i = 0, newSpaces_1 = newSpaces; _i < newSpaces_1.length; _i++) { + var newSpace = newSpaces_1[_i]; + var matchFound = false; + for (var _a = 0, existingSpaces_1 = existingSpaces; _a < existingSpaces_1.length; _a++) { + var existingSpace = existingSpaces_1[_a]; + if (newSpace.matches(existingSpace)) { + matchFound = true; + break; + } + } + if (!matchFound) { + existingSpaces.push(newSpace); + } + } + }; + VoxelSpace.prototype.matches = function (space) { + var otherDims = space.getDims(); + for (var i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + var otherVals = space.getVals(); + for (var i = 0; i < this.vals.length; i++) { + if (this.vals[i] !== otherVals[i]) { + return false; + } + } + return true; + }; + VoxelSpace.prototype.clone = function () { + return new VoxelSpace(this.getDims(), this.getVals()); + }; + VoxelSpace.prototype.getAxisSpins = function (axis) { + var rotations = [this.clone()]; + for (var i = 0; i < 3; i++) { + rotations.push(rotations[i].rotated90(axis)); + } + return rotations; + }; + VoxelSpace.prototype.getDims = function () { + return this.dims.slice(); + }; + VoxelSpace.prototype.getVals = function () { + return this.vals.slice(); + }; + // [1, 0, 0] [x] [ x] + // [0, 0, -1] * [y] = [-z] + // [0, 1, 0] [z] [ y] + VoxelSpace.prototype.newIndexRotX = function (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] + VoxelSpace.prototype.newIndexRotY = function (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] + VoxelSpace.prototype.newIndexRotZ = function (x, y, z) { + return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z; + }; + VoxelSpace.prototype.at = function (x, y, z) { + return this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z]; + }; + VoxelSpace.prototype.set = function (x, y, z, val) { + this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z] = val; + }; + VoxelSpace.prototype.rotated90 = function (dim) { + var newVals = __spreadArrays(this.vals); + var newDims; + var 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(function (val, i, j, k) { + newVals[rotIndex(i, j, k)] = val; + }); + return new VoxelSpace(newDims, newVals); + }; + VoxelSpace.prototype.rot90 = function (dim) { + var rot = this.rotated90(dim); + this.vals = rot.getVals(); + this.dims = rot.getDims(); + }; + VoxelSpace.prototype.plus = function (space, startX, startY, startZ) { + var result = this.clone(); + var spaceDims = space.getDims(); + for (var i = 0; i < spaceDims[0]; i++) { + for (var j = 0; j < spaceDims[1]; j++) { + for (var k = 0; k < spaceDims[2]; k++) { + var sourceVal = space.at(i, j, k); + var targetEmpty = result.at(startX + i, startY + j, startZ + k) === 0; + if (sourceVal !== 0 && targetEmpty) { + result.set(startX + i, startY + j, startZ + k, sourceVal); + } + else if (sourceVal !== 0 && !targetEmpty) { + return null; + } + } + } + } + return result; + }; + return VoxelSpace; +}()); +var Polycube = /** @class */ (function (_super) { + __extends(Polycube, _super); + function Polycube(dims, vals, id) { + var _this = _super.call(this, dims, vals.map(function (val) { return val ? id : 0; }), true) || this; + _this.id = id; + return _this; + } + Polycube.prototype.getId = function () { + return this.id; + }; + Polycube.prototype.print = function () { + var accum = ""; + console.log("---"); + for (var i = 0; i < this.dims[0]; i++) { + for (var j = 0; j < this.dims[1]; j++) { + for (var k = 0; k < this.dims[2]; k++) { + accum += this.at(i, j, k) === 0 ? "O" : "#"; + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + }; + Polycube.prototype.matches = function (cube) { + var otherDims = cube.getDims(); + for (var i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + var otherVals = cube.getVals(); + for (var i = 0; i < this.vals.length; i++) { + if (Number(this.vals[i]) !== Number(otherVals[i])) { + return false; + } + } + return true; + }; + Polycube.prototype.size = function () { + var size = 0; + this.forEachCell(function (val) { + if (val) { + size++; + } + }); + return size; + }; + Polycube.prototype.rotated90 = function (dim) { + var rotated = _super.prototype.rotated90.call(this, dim); + return new Polycube(rotated.getDims(), rotated.getVals(), this.id); + }; + Polycube.prototype.clone = function () { + return new Polycube(this.getDims(), this.getVals(), this.id); + }; + Polycube.prototype.getUniqueRotations = function () { + var _this = this; + return _super.prototype.getUniqueRotations.call(this).map(function (rot) { return new Polycube(rot.getDims(), rot.getVals(), _this.id); }); + }; + return Polycube; +}(VoxelSpace)); +// const testCube = new Cube([4, 2, 5], [ +// "000", "001", "002", "003", "004", +// "010", "011", "012", "013", "014", +// "100", "101", "102", "103", "104", +// "110", "111", "112", "113", "114", +// "200", "201", "202", "203", "204", +// "210", "211", "212", "213", "214", +// "300", "301", "302", "303", "304", +// "310", "311", "312", "313", "314", +// ]); +// const somaCube = new Polycube([3, 3, 3], [ +// false, false, false, +// false, false, false, +// false, false, false, +// +// true, true, true, +// true, true, true, +// false, false, false, +// +// false, false, false, +// false, false, false, +// false, false, false, +// ], 1); +var unitCube1 = new Polycube([1, 1, 1], [true], 1); +var unitCube2 = new Polycube([1, 1, 1], [true], 2); +var unitCube3 = new Polycube([1, 1, 1], [true], 3); +var unitCube4 = new Polycube([1, 1, 1], [true], 4); +var unitCube5 = new Polycube([1, 1, 1], [true], 5); +var unitCube6 = new Polycube([1, 1, 1], [true], 6); +var unitCube7 = new Polycube([1, 1, 1], [true], 7); +var unitCube8 = new Polycube([1, 1, 1], [true], 8); +var unitCube9 = new Polycube([1, 1, 1], [true], 9); +var unitCube10 = new Polycube([1, 1, 1], [true], 10); +var unitCube11 = new Polycube([1, 1, 1], [true], 11); +var unitCube12 = new Polycube([1, 1, 1], [true], 12); +var unitCube13 = new Polycube([1, 1, 1], [true], 13); +var unitCube14 = new Polycube([1, 1, 1], [true], 14); +var unitCube15 = new Polycube([1, 1, 1], [true], 15); +var unitCube16 = new Polycube([1, 1, 1], [true], 16); +var unitCube17 = new Polycube([1, 1, 1], [true], 17); +var unitCube18 = new Polycube([1, 1, 1], [true], 18); +var unitCube19 = new Polycube([1, 1, 1], [true], 19); +var unitCube20 = new Polycube([1, 1, 1], [true], 20); +var unitCube21 = new Polycube([1, 1, 1], [true], 21); +var unitCube22 = new Polycube([1, 1, 1], [true], 22); +var unitCube23 = new Polycube([1, 1, 1], [true], 23); +var unitCube24 = new Polycube([1, 1, 1], [true], 24); +var unitCube25 = new Polycube([1, 1, 1], [true], 25); +var unitCube26 = new Polycube([1, 1, 1], [true], 26); +var unitCube27 = new Polycube([1, 1, 1], [true], 27); +var tetromino1 = new Polycube([3, 3, 3], [ + true, true, true, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 1); +var tetromino2 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, true, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, +], 2); +var tetromino3 = new Polycube([3, 3, 3], [ + true, false, false, + true, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 3); +var tetromino4 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + true, false, false, + true, false, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 4); +var tetromino5 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, + false, false, false, +], 5); +var tetromino6 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, false, false, + false, true, false, + false, true, true, + false, false, false, + false, false, false, + false, false, false, +], 6); +var triomino1 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + false, false, false, + false, true, false, + false, true, false, + false, false, false, + false, false, false, + false, false, false, +], 7); +// const cube = new VoxelSpace([3, 3, 3], Array(3**3).fill(0)); +// cube.plus(triomino1)?.plus(tetromino2, {x: 1, y: 0, z: 1})?.print(); +function start() { + var solver = new SomaSolver(3); + console.log("solving"); + solver.solve([triomino1, tetromino2, tetromino3, tetromino1, tetromino4, tetromino5, tetromino6]); +} diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..305a0ba --- /dev/null +++ b/src/test.ts @@ -0,0 +1,524 @@ +class SomaSolver { + private solutionCube: VoxelSpace; + private dim: number; + private solutions: VoxelSpace[] = []; + 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([dimension, dimension, dimension], Array(dimension**3).fill(0)); + } + + solve(polycubes: Polycube[]) { + 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.iterations = 0; + this.backtrackSolve(this.solutionCube, polycubes); + this.solutions = VoxelSpace.filterUnique(this.solutions); + this.solutions.forEach(sol => sol.print()); + console.log(this.solutions.length); + } + + private backtrackSolve(workingSolution: VoxelSpace, polycubes: Polycube[], depth = 0) { + const nextCube = polycubes[0]; + const rots = depth === 0 ? [nextCube] : nextCube.getUniqueRotations(); + for (let i = 0; i < rots.length; i++) { + const polyCubeDims = rots[i].getDims(); + for (let x = 0; x < this.dim - polyCubeDims[0] + 1; x++) { + for (let y = 0; y < this.dim - polyCubeDims[1] + 1; y++) { + for (let z = 0; z < this.dim - polyCubeDims[2] + 1; z++) { + const successfulFusion = workingSolution.plus(rots[i], x, y, z); + if (successfulFusion) { + if (polycubes.length === 1) { + console.log("soln", this.iterations++); + this.solutions.push(successfulFusion); + return; + } else { + this.backtrackSolve(successfulFusion, polycubes.slice(1), depth + 1); + } + } + } + } + } + } + } +} + +class VoxelSpace { + protected vals: number[]; + protected dims: DimensionDef; + constructor(dims: DimensionDef, vals: number[], cullEmpty?: boolean) { + if (vals.length !== dims[0] * dims[1] * dims[2]) { + throw new Error("Vals don't fit in given dimensions."); + } + this.dims = dims; + this.vals = vals; + if (cullEmpty) { + this.cullEmptySpace(); + } + } + + private cullEmptySpace() { + const extrema = { + xMax: -Infinity, + xMin: Infinity, + yMax: -Infinity, + yMin: Infinity, + zMax: -Infinity, + zMin: Infinity, + }; + const newVals: number[] = []; + this.forEachCell((val, i, j, k) => { + if (val !== 0) { + extrema.xMax = Math.max(extrema.xMax, i); + extrema.xMin = Math.min(extrema.xMin, i); + extrema.yMax = Math.max(extrema.yMax, j); + extrema.yMin = Math.min(extrema.yMin, j); + extrema.zMax = Math.max(extrema.zMax, k); + extrema.zMin = Math.min(extrema.zMin, k); + } + }); + for (let i = extrema.xMin; i <= extrema.xMax; i++) { + for (let j = extrema.yMin; j <= extrema.yMax; j++) { + for (let k = extrema.zMin; k <= extrema.zMax; k++) { + newVals.push(this.at(i, j, k)); + } + } + } + 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.vals = newVals; + } + + forEachCell(cb: (val: number, 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; + } + } + } + } + } + + 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); + } + 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; + } + + static filterUnique(spaces: T[]): T[] { + if (spaces.length === 0) { + return []; + } + const uniqueSpaces = [spaces[0]]; + for (const space of spaces) { + let foundMatch = false; + for (const rotation of space.getUniqueRotations()) { + let end = uniqueSpaces.length; + for (let i = 0; i < end; i++) { + if (rotation.matches(uniqueSpaces[i])) { + foundMatch = true; + } + } + } + if (!foundMatch) { + uniqueSpaces.push(space); + } + } + return uniqueSpaces; + } + + 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); + } + } + } + + matches(space: VoxelSpace) { + const otherDims = space.getDims(); + for (let i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + const otherVals = space.getVals(); + for (let i = 0; i < this.vals.length; i++) { + if (this.vals[i] !== otherVals[i]) { + return false; + } + } + return true; + } + + clone() { + return new VoxelSpace(this.getDims(), this.getVals()); + } + + 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; + } + + getVals() { + return this.vals.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.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z]; + } + + set(x: number, y: number, z: number, val: number) { + this.vals[this.dims[1] * this.dims[2] * x + this.dims[2] * y + z] = val; + } + + rotated90(dim: 'x' | 'y' | 'z') { + const newVals = [...this.vals]; + 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) => { + newVals[rotIndex(i, j, k)] = val; + }) + return new VoxelSpace(newDims, newVals); + } + + rot90(dim: 'x' | 'y' | 'z') { + const rot = this.rotated90(dim); + this.vals = rot.getVals(); + this.dims = rot.getDims(); + } + + plus(space: VoxelSpace, startX: number, startY: number, startZ: number): VoxelSpace | null { + let result: VoxelSpace = this.clone(); + const spaceDims = space.getDims(); + for (let i = 0; i < spaceDims[0]; i++) { + for (let j = 0; j < spaceDims[1]; j++) { + for (let k = 0; k < spaceDims[2]; k++) { + const sourceVal = space.at(i, j, k); + const targetEmpty = result.at(startX + i, startY + j, startZ + k) === 0; + if (sourceVal !== 0 && targetEmpty) { + result.set(startX + i, startY + j, startZ + k, sourceVal); + } else if (sourceVal !== 0 && !targetEmpty) { + return null; + } + } + } + } + return result; + } +} + +class Polycube extends VoxelSpace { + private id: number; + constructor(dims: DimensionDef, vals: boolean[], id: number) { + super(dims, vals.map(val => val ? id : 0), true); + this.id = id; + } + + 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) === 0 ? "O" : "#"; + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + } + + matches(cube: VoxelSpace) { + const otherDims = cube.getDims(); + for (let i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + const otherVals = cube.getVals(); + for (let i = 0; i < this.vals.length; i++) { + if (Number(this.vals[i]) !== Number(otherVals[i])) { + return false; + } + } + return true; + } + + size() { + let size = 0; + this.forEachCell((val) => { + if (val) { + size++; + } + }); + return size; + } + + rotated90(dim: "x" | "y" | "z"): Polycube { + const rotated = super.rotated90(dim); + return new Polycube(rotated.getDims(), rotated.getVals() as unknown as boolean[], this.id); + } + + clone(): Polycube { + return new Polycube(this.getDims(), this.getVals() as unknown as boolean[], this.id); + } + + getUniqueRotations(): Polycube[] { + return super.getUniqueRotations().map(rot => new Polycube(rot.getDims(), rot.getVals() as unknown as boolean[], this.id)); + } +} + +type DimensionDef = [number, number, number]; + +// const testCube = new Cube([4, 2, 5], [ +// "000", "001", "002", "003", "004", +// "010", "011", "012", "013", "014", +// "100", "101", "102", "103", "104", +// "110", "111", "112", "113", "114", +// "200", "201", "202", "203", "204", +// "210", "211", "212", "213", "214", +// "300", "301", "302", "303", "304", +// "310", "311", "312", "313", "314", +// ]); +// const somaCube = new Polycube([3, 3, 3], [ +// false, false, false, +// false, false, false, +// false, false, false, +// +// true, true, true, +// true, true, true, +// false, false, false, +// +// false, false, false, +// false, false, false, +// false, false, false, +// ], 1); +const unitCube1 = new Polycube([1, 1, 1], [true], 1); +const unitCube2 = new Polycube([1, 1, 1], [true], 2); +const unitCube3 = new Polycube([1, 1, 1], [true], 3); +const unitCube4 = new Polycube([1, 1, 1], [true], 4); +const unitCube5 = new Polycube([1, 1, 1], [true], 5); +const unitCube6 = new Polycube([1, 1, 1], [true], 6); +const unitCube7 = new Polycube([1, 1, 1], [true], 7); +const unitCube8 = new Polycube([1, 1, 1], [true], 8); +const unitCube9 = new Polycube([1, 1, 1], [true], 9); +const unitCube10 = new Polycube([1, 1, 1], [true], 10); +const unitCube11 = new Polycube([1, 1, 1], [true], 11); +const unitCube12 = new Polycube([1, 1, 1], [true], 12); +const unitCube13 = new Polycube([1, 1, 1], [true], 13); +const unitCube14 = new Polycube([1, 1, 1], [true], 14); +const unitCube15 = new Polycube([1, 1, 1], [true], 15); +const unitCube16 = new Polycube([1, 1, 1], [true], 16); +const unitCube17 = new Polycube([1, 1, 1], [true], 17); +const unitCube18 = new Polycube([1, 1, 1], [true], 18); +const unitCube19 = new Polycube([1, 1, 1], [true], 19); +const unitCube20 = new Polycube([1, 1, 1], [true], 20); +const unitCube21 = new Polycube([1, 1, 1], [true], 21); +const unitCube22 = new Polycube([1, 1, 1], [true], 22); +const unitCube23 = new Polycube([1, 1, 1], [true], 23); +const unitCube24 = new Polycube([1, 1, 1], [true], 24); +const unitCube25 = new Polycube([1, 1, 1], [true], 25); +const unitCube26 = new Polycube([1, 1, 1], [true], 26); +const unitCube27 = new Polycube([1, 1, 1], [true], 27); + +const tetromino1 = new Polycube([3, 3, 3], [ + true, true, true, + false, true, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 1); + +const tetromino2 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, true, false, + false, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, +], 2); + +const tetromino3 = new Polycube([3, 3, 3], [ + true, false, false, + true, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 3); + +const tetromino4 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + + true, false, false, + true, false, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 4); + +const tetromino5 = new Polycube([3, 3, 3], [ + true, true, false, + false, false, false, + false, false, false, + + false, true, false, + false, true, false, + false, false, false, + + false, false, false, + false, false, false, + false, false, false, +], 5); + +const tetromino6 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, false, false, + false, true, false, + false, true, true, + + false, false, false, + false, false, false, + false, false, false, +], 6); + +const triomino1 = new Polycube([3, 3, 3], [ + false, false, false, + false, false, false, + false, true, false, + + false, false, false, + false, true, false, + false, true, false, + + false, false, false, + false, false, false, + false, false, false, +], 7); + +// const cube = new VoxelSpace([3, 3, 3], Array(3**3).fill(0)); +// cube.plus(triomino1)?.plus(tetromino2, {x: 1, y: 0, z: 1})?.print(); + +function start() { + const solver = new SomaSolver(3); + console.log("solving"); + solver.solve([triomino1, tetromino2, tetromino3, tetromino1, tetromino4, tetromino5, tetromino6]); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9545063 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,66 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": ["dom", "ES6"], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}