From 8d4b85007cff29e6872552e9ddbafe517100c30c Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Sat, 4 Jan 2025 10:04:35 +1100 Subject: [PATCH] Add scrabble-score exercise (#141) --- config.json | 8 ++ .../.docs/instructions.append.md | 5 + .../scrabble-score/.docs/instructions.md | 25 +++++ .../scrabble-score/.docs/introduction.md | 7 ++ exercises/practice/scrabble-score/.eslintrc | 18 ++++ .../practice/scrabble-score/.meta/config.json | 28 ++++++ .../scrabble-score/.meta/proof.ci.wat | 44 +++++++++ .../practice/scrabble-score/.meta/tests.toml | 43 +++++++++ exercises/practice/scrabble-score/.npmrc | 1 + exercises/practice/scrabble-score/LICENSE | 21 ++++ .../practice/scrabble-score/babel.config.js | 4 + .../practice/scrabble-score/package.json | 34 +++++++ .../scrabble-score/scrabble-score.spec.js | 95 +++++++++++++++++++ .../scrabble-score/scrabble-score.wat | 15 +++ 14 files changed, 348 insertions(+) create mode 100644 exercises/practice/scrabble-score/.docs/instructions.append.md create mode 100644 exercises/practice/scrabble-score/.docs/instructions.md create mode 100644 exercises/practice/scrabble-score/.docs/introduction.md create mode 100644 exercises/practice/scrabble-score/.eslintrc create mode 100644 exercises/practice/scrabble-score/.meta/config.json create mode 100644 exercises/practice/scrabble-score/.meta/proof.ci.wat create mode 100644 exercises/practice/scrabble-score/.meta/tests.toml create mode 100644 exercises/practice/scrabble-score/.npmrc create mode 100644 exercises/practice/scrabble-score/LICENSE create mode 100644 exercises/practice/scrabble-score/babel.config.js create mode 100644 exercises/practice/scrabble-score/package.json create mode 100644 exercises/practice/scrabble-score/scrabble-score.spec.js create mode 100644 exercises/practice/scrabble-score/scrabble-score.wat diff --git a/config.json b/config.json index e0e5bf0..7e0a5f1 100644 --- a/config.json +++ b/config.json @@ -247,6 +247,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "scrabble-score", + "name": "Scrabble Score", + "uuid": "f626cdcd-12d0-4540-988d-ccae4c6c8299", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "square-root", "name": "Square Root", diff --git a/exercises/practice/scrabble-score/.docs/instructions.append.md b/exercises/practice/scrabble-score/.docs/instructions.append.md new file mode 100644 index 0000000..da31715 --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/instructions.append.md @@ -0,0 +1,5 @@ +# Instructions append + +## Reserved Memory + +The buffer for the input string uses bytes 64-191 of linear memory. diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md new file mode 100644 index 0000000..738f928 --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -0,0 +1,25 @@ +# Instructions + +Your task is to compute a word's Scrabble score by summing the values of its letters. + +The letters are valued as follows: + +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | + +For example, the word "cabbage" is worth 14 points: + +- 3 points for C +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A +- 2 points for G +- 1 point for E diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 0000000..8821f24 --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble diff --git a/exercises/practice/scrabble-score/.eslintrc b/exercises/practice/scrabble-score/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/scrabble-score/.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/scrabble-score/.meta/config.json b/exercises/practice/scrabble-score/.meta/config.json new file mode 100644 index 0000000..fc6676b --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "scrabble-score.wat" + ], + "test": [ + "scrabble-score.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "Given a word, compute the Scrabble score for that word.", + "source": "Inspired by the Extreme Startup game", + "source_url": "https://github.com/rchatley/extreme_startup", + "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/scrabble-score/.meta/proof.ci.wat b/exercises/practice/scrabble-score/.meta/proof.ci.wat new file mode 100644 index 0000000..dec404d --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/proof.ci.wat @@ -0,0 +1,44 @@ +(module + (memory (export "mem") 1) + + ;; ':' is the ASCII character after '9' and indicates a letter score of 10. + (data (i32.const 0) "1332142418513113:11114484:") + + (global $ZERO i32 (i32.const 48)) + (global $A i32 (i32.const 65)) + + ;; + ;; Given a word, compute the Scrabble score for that word + ;; + ;; @param {i32} offset - The offset of the input string in linear memory + ;; @param {i32} length - The length of the input string in linear memory + ;; + ;; @returns {i32} - the computed score + ;; + (func (export "score") (param $offset i32) (param $len i32) (result i32) + (local $source i32) + (local $result i32) + (local $letter i32) + + (local.set $source (i32.add (local.get $offset) + (local.get $len))) + (local.set $result (i32.const 0)) + + (loop $body + (if (i32.ne (local.get $source) (local.get $offset)) (then + (local.set $source (i32.sub (local.get $source) + (i32.const 1))) + (local.set $letter (i32.sub (i32.and (i32.load8_u (local.get $source)) + (i32.const -33)) + (global.get $A))) + (if (i32.lt_u (local.get $letter) (i32.const 26)) (then + (local.set $result (i32.add (local.get $result) + (i32.sub (i32.load8_u (local.get $letter)) + (global.get $ZERO)))) + )) + (br $body) + )) + ) + (return (local.get $result)) + ) +) diff --git a/exercises/practice/scrabble-score/.meta/tests.toml b/exercises/practice/scrabble-score/.meta/tests.toml new file mode 100644 index 0000000..33a873c --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/tests.toml @@ -0,0 +1,43 @@ +# 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. + +[f46cda29-1ca5-4ef2-bd45-388a767e3db2] +description = "lowercase letter" + +[f7794b49-f13e-45d1-a933-4e48459b2201] +description = "uppercase letter" + +[eaba9c76-f9fa-49c9-a1b0-d1ba3a5b31fa] +description = "valuable letter" + +[f3c8c94e-bb48-4da2-b09f-e832e103151e] +description = "short word" + +[71e3d8fa-900d-4548-930e-68e7067c4615] +description = "short, valuable word" + +[d3088ad9-570c-4b51-8764-c75d5a430e99] +description = "medium word" + +[fa20c572-ad86-400a-8511-64512daac352] +description = "medium, valuable word" + +[9336f0ba-9c2b-4fa0-bd1c-2e2d328cf967] +description = "long, mixed-case word" + +[1e34e2c3-e444-4ea7-b598-3c2b46fd2c10] +description = "english-like word" + +[4efe3169-b3b6-4334-8bae-ff4ef24a7e4f] +description = "empty input" + +[3b305c1c-f260-4e15-a5b5-cb7d3ea7c3d7] +description = "entire alphabet available" diff --git a/exercises/practice/scrabble-score/.npmrc b/exercises/practice/scrabble-score/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/scrabble-score/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/scrabble-score/LICENSE b/exercises/practice/scrabble-score/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/scrabble-score/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/scrabble-score/babel.config.js b/exercises/practice/scrabble-score/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/scrabble-score/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/scrabble-score/package.json b/exercises/practice/scrabble-score/package.json new file mode 100644 index 0000000..c6472e6 --- /dev/null +++ b/exercises/practice/scrabble-score/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/wasm-scrabble-score", + "description": "Exercism exercises in WebAssembly.", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/scrabble-score" + }, + "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 ." + } +} diff --git a/exercises/practice/scrabble-score/scrabble-score.spec.js b/exercises/practice/scrabble-score/scrabble-score.spec.js new file mode 100644 index 0000000..b2300d8 --- /dev/null +++ b/exercises/practice/scrabble-score/scrabble-score.spec.js @@ -0,0 +1,95 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./scrabble-score.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); + } +}); + +function score(word = "") { + const inputBufferOffset = 64; + const inputBufferCapacity = 128; + + const inputLengthEncoded = new TextEncoder().encode(word).length; + if (inputLengthEncoded > inputBufferCapacity) { + throw new Error( + `String is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, word); + + return currentInstance.exports.score( + inputBufferOffset, + inputLengthEncoded + ); +} + +describe("ScrabbleScore", () => { + 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("lowercase letter", () => { + expect(score("a")).toBe(1); + }); + + xtest("uppercase letter", () => { + expect(score("A")).toBe(1); + }); + + xtest("valuable letter", () => { + expect(score("f")).toBe(4); + }); + + xtest("short word", () => { + expect(score("at")).toBe(2); + }); + + xtest("short, valuable word", () => { + expect(score("zoo")).toBe(12); + }); + + xtest("medium word", () => { + expect(score("street")).toBe(6); + }); + + xtest("medium, valuable word", () => { + expect(score("quirky")).toBe(22); + }); + + xtest("long, mixed-case word", () => { + expect(score("OxyphenButazone")).toBe(41); + }); + + xtest("english-like word", () => { + expect(score("pinata")).toBe(8); + }); + + xtest("empty input", () => { + expect(score("")).toBe(0); + }); + + xtest("entire alphabet available", () => { + expect(score("abcdefghijklmnopqrstuvwxyz")).toBe(87); + }); +}); diff --git a/exercises/practice/scrabble-score/scrabble-score.wat b/exercises/practice/scrabble-score/scrabble-score.wat new file mode 100644 index 0000000..5f0d3ed --- /dev/null +++ b/exercises/practice/scrabble-score/scrabble-score.wat @@ -0,0 +1,15 @@ +(module + (memory (export "mem") 1) + + ;; + ;; Given a word, compute the Scrabble score for that word + ;; + ;; @param {i32} offset - The offset of the input string in linear memory + ;; @param {i32} length - The length of the input string in linear memory + ;; + ;; @returns {i32} - the computed score + ;; + (func (export "score") (param $offset i32) (param $len i32) (result i32) + (return (i32.const -1)) + ) +)