diff --git a/config.json b/config.json index 5918c82..385924c 100644 --- a/config.json +++ b/config.json @@ -551,6 +551,14 @@ "prerequisites": [], "difficulty": 6 }, + { + "slug": "dominoes", + "name": "Dominoes", + "uuid": "9a60bba5-0ac4-4621-aa23-1e8e7dfe9d2b", + "practices": [], + "prerequisites": [], + "difficulty": 6 + }, { "slug": "food-chain", "name": "Food Chain", diff --git a/exercises/practice/dominoes/.docs/instructions.append.md b/exercises/practice/dominoes/.docs/instructions.append.md new file mode 100644 index 0000000..f5eaf4d --- /dev/null +++ b/exercises/practice/dominoes/.docs/instructions.append.md @@ -0,0 +1,11 @@ +# Instruction append + +## Input format + +Each domino is stored as two consecutive bytes in linear memory, one byte for each half. + +For example, stones `[2|1]`, `[2|3]` and `[1|3]` are represented as the byte array `[ 0x02, 0x01, 0x02, 0x03, 0x01, 0x03 ]` + +## Reserved Memory + +The buffer for the input dominoes uses bytes 256-511 of linear memory. diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md new file mode 100644 index 0000000..75055b9 --- /dev/null +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -0,0 +1,15 @@ +# Instructions + +Make a chain of dominoes. + +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. + +For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something +like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. + +For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. +4 != 3 + +Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 0000000..df248c2 --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/dominoes/.eslintrc b/exercises/practice/dominoes/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/dominoes/.eslintrc @@ -0,0 +1,18 @@ +{ + "root": true, + "extends": "@exercism/eslint-config-javascript", + "env": { + "jest": true + }, + "overrides": [ + { + "files": [ + "*.spec.js" + ], + "excludedFiles": [ + "custom.spec.js" + ], + "extends": "@exercism/eslint-config-javascript/maintainers" + } + ] +} diff --git a/exercises/practice/dominoes/.meta/config.json b/exercises/practice/dominoes/.meta/config.json new file mode 100644 index 0000000..27e94e9 --- /dev/null +++ b/exercises/practice/dominoes/.meta/config.json @@ -0,0 +1,26 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "dominoes.wat" + ], + "test": [ + "dominoes.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "Make a chain of dominoes.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/dominoes/.meta/proof.ci.wat b/exercises/practice/dominoes/.meta/proof.ci.wat new file mode 100644 index 0000000..ad3d0f5 --- /dev/null +++ b/exercises/practice/dominoes/.meta/proof.ci.wat @@ -0,0 +1,118 @@ +(module + (memory (export "mem") 1) + + (global $parentTable i32 (i32.const 0)) + + (global $tallyTable i32 (i32.const 32)) + + (func $root (param $nibble i32) (result i32) + (local $current i32) + (local $parent i32) + (local.set $parent (local.get $nibble)) + + (loop $traverse + (local.set $current (local.get $parent)) + (local.set $parent (i32.load8_u (i32.add (global.get $parentTable) + (local.get $current)))) + (br_if $traverse (i32.ne (local.get $parent) + (local.get $current))) + ) + + (return (local.get $current)) + ) + + (func $updateTally (param $value i32) + (local $ptr i32) + + (local.set $ptr (i32.add (global.get $tallyTable) + (local.get $value))) + (i32.store8 (local.get $ptr) + (i32.add (i32.const 1) + (i32.load8_u (local.get $ptr)))) + ) + + ;; + ;; Determine if the dominoes form a chain. + ;; + ;; Each domino is stored as two consecutive bytes in linear memory (left, right). + ;; + ;; @param {i32} offset - offset of domino array in linear memory + ;; @param {i32} count - number of dominoes + ;; + ;; @returns {i32} 1 if the dominoes form a chain, 0 otherwise + ;; + (func (export "canChain") (param $offset i32) (param $count i32) (result i32) + (local $i i32) + (local $left i32) + (local $right i32) + (local $roots i32) + (local $tally i32) + + (if (i32.eqz (local.get $count)) (then + (return (i32.const 1)) + )) + + (memory.fill (global.get $tallyTable) (i32.const 0) (i32.const 32)) + + (local.set $i (i32.const 0)) + (loop $init + (i32.store8 (i32.add (global.get $parentTable) + (local.get $i)) + (local.get $i)) + (local.set $i (i32.add (local.get $i) + (i32.const 1))) + (br_if $init (i32.lt_u (local.get $i) + (i32.const 32))) + ) + + (loop $read + (local.set $left (i32.load8_u (local.get $offset))) + (local.set $right (i32.load8_u (i32.add (local.get $offset) + (i32.const 1)))) + (local.set $offset (i32.add (local.get $offset) + (i32.const 2))) + (local.set $count (i32.sub (local.get $count) + (i32.const 1))) + + (call $updateTally (local.get $left)) + (call $updateTally (local.get $right)) + + (local.set $left (call $root (local.get $left))) + (local.set $right (call $root (local.get $right))) + + (i32.store8 (i32.add (global.get $parentTable) + (local.get $left)) + (local.get $right)) + + (br_if $read (local.get $count)) + ) + + (local.set $roots (i32.const 0)) + (local.set $i (i32.const 0)) + (loop $count_roots + (local.set $tally (i32.load8_u (i32.add (global.get $tallyTable) + (local.get $i)))) + (if (local.get $tally) (then + (if (i32.and (local.get $tally) + (i32.const 1)) (then + (return (i32.const 0) + ))) + + (if (i32.eq (local.get $i) + (i32.load8_u (i32.add (global.get $parentTable) + (local.get $i)))) (then + (local.set $roots (i32.add (local.get $roots) + (i32.const 1))) + )) + )) + + (local.set $i (i32.add (local.get $i) + (i32.const 1))) + (br_if $count_roots (i32.lt_u (local.get $i) + (i32.const 32))) + ) + + (return (i32.eq (local.get $roots) + (i32.const 1))) + ) +) diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml new file mode 100644 index 0000000..08c8e08 --- /dev/null +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -0,0 +1,49 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[31a673f2-5e54-49fe-bd79-1c1dae476c9c] +description = "empty input = empty output" + +[4f99b933-367b-404b-8c6d-36d5923ee476] +description = "singleton input = singleton output" + +[91122d10-5ec7-47cb-b759-033756375869] +description = "singleton that can't be chained" + +[be8bc26b-fd3d-440b-8e9f-d698a0623be3] +description = "three elements" + +[99e615c6-c059-401c-9e87-ad7af11fea5c] +description = "can reverse dominoes" + +[51f0c291-5d43-40c5-b316-0429069528c9] +description = "can't be chained" + +[9a75e078-a025-4c23-8c3a-238553657f39] +description = "disconnected - simple" + +[0da0c7fe-d492-445d-b9ef-1f111f07a301] +description = "disconnected - double loop" + +[b6087ff0-f555-4ea0-a71c-f9d707c5994a] +description = "disconnected - single isolated" + +[2174fbdc-8b48-4bac-9914-8090d06ef978] +description = "need backtrack" + +[167bb480-dfd1-4318-a20d-4f90adb4a09f] +description = "separate loops" + +[cd061538-6046-45a7-ace9-6708fe8f6504] +description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/.npmrc b/exercises/practice/dominoes/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/dominoes/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/dominoes/LICENSE b/exercises/practice/dominoes/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/dominoes/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/dominoes/babel.config.js b/exercises/practice/dominoes/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/dominoes/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/dominoes/dominoes.spec.js b/exercises/practice/dominoes/dominoes.spec.js new file mode 100644 index 0000000..b393ff6 --- /dev/null +++ b/exercises/practice/dominoes/dominoes.spec.js @@ -0,0 +1,179 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +function canChain(dominoes) { + const inputBufferOffset = 256; + const inputBufferCapacity = 256; + + const byteLength = dominoes.length * 2; + if (byteLength > inputBufferCapacity) { + throw new Error( + `Input is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + const mem = currentInstance.get_mem_as_u8(inputBufferOffset, inputBufferCapacity); + mem.set(dominoes.flat()); + + // Pass offset and number of dominoes to WebAssembly function + return currentInstance.exports.canChain( + inputBufferOffset, + dominoes.length + ); +} + +beforeAll(async () => { + try { + const watPath = new URL("./dominoes.wat", import.meta.url); + const { buffer } = await compileWat(watPath); + wasmModule = await WebAssembly.compile(buffer); + } catch (err) { + console.log(`Error compiling *.wat: \n${err}`); + process.exit(1); + } +}); + +describe("canChain()", () => { + beforeEach(async () => { + currentInstance = null; + if (!wasmModule) { + return Promise.reject(); + } + try { + currentInstance = await new WasmRunner(wasmModule); + return Promise.resolve(); + } catch (err) { + console.log(`Error instantiating WebAssembly module: ${err}`); + return Promise.reject(); + } + }); + + test("empty input = empty output", () => { + expect(canChain([])).toBe(1); + }); + + xtest("singleton input = singleton output", () => { + expect(canChain([[1, 1]])).toBe(1); + }); + + xtest("singleton that can't be chained", () => { + expect(canChain([[1, 2]])).toBe(0); + }); + + xtest("three elements", () => { + expect( + canChain([ + [1, 2], + [3, 1], + [2, 3], + ]) + ).toBe(1); + }); + + xtest("can reverse dominoes", () => { + expect( + canChain([ + [1, 2], + [1, 3], + [2, 3], + ]) + ).toBe(1); + }); + + xtest("can't be chained", () => { + expect( + canChain([ + [1, 2], + [4, 1], + [2, 3], + ]) + ).toBe(0); + }); + + xtest("disconnected - simple", () => { + expect( + canChain([ + [1, 1], + [2, 2], + ]) + ).toBe(0); + }); + + xtest("disconnected - double loop", () => { + expect( + canChain([ + [1, 2], + [2, 1], + [3, 4], + [4, 3], + ]) + ).toBe(0); + }); + + xtest("disconnected - single isolated", () => { + expect( + canChain([ + [1, 2], + [2, 3], + [3, 1], + [4, 4], + ]) + ).toBe(0); + }); + + xtest("need backtrack", () => { + expect( + canChain([ + [1, 2], + [2, 3], + [3, 1], + [2, 4], + [2, 4], + ]) + ).toBe(1); + }); + + xtest("separate loops", () => { + expect( + canChain([ + [1, 2], + [2, 3], + [3, 1], + [1, 1], + [2, 2], + [3, 3], + ]) + ).toBe(1); + }); + + xtest("nine elements", () => { + expect( + canChain([ + [1, 2], + [5, 3], + [3, 1], + [1, 2], + [2, 4], + [1, 6], + [2, 3], + [3, 4], + [5, 6], + ]) + ).toBe(1); + }); + + xtest("separate three-domino loops", () => { + expect( + canChain([ + [1, 2], + [2, 3], + [3, 1], + [4, 5], + [5, 6], + [6, 4], + ]) + ).toBe(0); + }); +}); diff --git a/exercises/practice/dominoes/dominoes.wat b/exercises/practice/dominoes/dominoes.wat new file mode 100644 index 0000000..2ae2de4 --- /dev/null +++ b/exercises/practice/dominoes/dominoes.wat @@ -0,0 +1,17 @@ +(module + (memory (export "mem") 1) + + ;; + ;; Determine if the dominoes form a chain. + ;; + ;; Each domino is stored as two consecutive bytes in linear memory (left, right). + ;; + ;; @param {i32} offset - offset of domino array in linear memory + ;; @param {i32} count - number of dominoes + ;; + ;; @returns {i32} 1 if the dominoes form a chain, 0 otherwise + ;; + (func (export "canChain") (param $offset i32) (param $count i32) (result i32) + (return (i32.const 1)) + ) +) diff --git a/exercises/practice/dominoes/package.json b/exercises/practice/dominoes/package.json new file mode 100644 index 0000000..cc41fb7 --- /dev/null +++ b/exercises/practice/dominoes/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/wasm-dominoes", + "description": "Exercism exercises in WebAssembly.", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/dominoes" + }, + "jest": { + "maxWorkers": 1 + }, + "devDependencies": { + "@babel/core": "^7.23.3", + "@exercism/babel-preset-javascript": "^0.4.0", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.1", + "babel-jest": "^29.7.0", + "core-js": "^3.33.2", + "eslint": "^8.54.0", + "jest": "^29.7.0" + }, + "dependencies": { + "@exercism/wasm-lib": "^0.2.0" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./*", + "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch ./*", + "lint": "eslint ." + } +}