Skip to content

Commit 6c8c357

Browse files
authored
Merge pull request #197 from atk/robot-name
new exercise: robot-name
2 parents 0bf7064 + 2c8e1af commit 6c8c357

File tree

11 files changed

+352
-0
lines changed

11 files changed

+352
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@
431431
"prerequisites": [],
432432
"difficulty": 5
433433
},
434+
{
435+
"slug": "robot-name",
436+
"name": "Robot Name",
437+
"uuid": "e84ba6b8-0b1a-434a-bbc3-47cb936be558",
438+
"practices": [],
439+
"prerequisites": [],
440+
"difficulty": 5
441+
},
434442
{
435443
"slug": "robot-simulator",
436444
"name": "Robot Simulator",
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Instructions
2+
3+
Manage robot factory settings.
4+
5+
When a robot comes off the factory floor, it has no name.
6+
7+
The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811.
8+
9+
Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped.
10+
The next time you ask, that robot will respond with a new random name.
11+
12+
The names must be random: they should not follow a predictable sequence.
13+
Using random names means a risk of collisions.
14+
Your solution must ensure that every existing robot has a unique name.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"root": true,
3+
"extends": "@exercism/eslint-config-javascript",
4+
"env": {
5+
"jest": true
6+
},
7+
"overrides": [
8+
{
9+
"files": [
10+
"*.spec.js"
11+
],
12+
"excludedFiles": [
13+
"custom.spec.js"
14+
],
15+
"extends": "@exercism/eslint-config-javascript/maintainers"
16+
}
17+
]
18+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"authors": [
3+
"atk"
4+
],
5+
"files": {
6+
"solution": [
7+
"robot-name.wat"
8+
],
9+
"test": [
10+
"robot-name.spec.js"
11+
],
12+
"example": [
13+
".meta/proof.ci.wat"
14+
],
15+
"invalidator": [
16+
"package.json"
17+
]
18+
},
19+
"blurb": "Manage robot factory settings.",
20+
"source": "A debugging session with Paul Blackwell at gSchool.",
21+
"custom": {
22+
"version.tests.compatibility": "jest-27",
23+
"flag.tests.task-per-describe": false,
24+
"flag.tests.may-run-long": false,
25+
"flag.tests.includes-optional": false
26+
}
27+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
(module
2+
(import "math" "random" (func $random (result f64)))
3+
(memory (export "mem") 96)
4+
5+
(global $length (mut i32) (i32.const 0))
6+
(global $outputOffset i32 (i32.const 2704384))
7+
(global $outputLength i32 (i32.const 5))
8+
(global $letter i32 (i32.const 65))
9+
(global $number i32 (i32.const 48))
10+
;;
11+
;; Generate a new name for a robot, consisting of two uppercase letters and three numbers,
12+
;; avoiding already used names
13+
;;
14+
;; @results {(i32,i32)} - offset and length in linear memory
15+
;;
16+
(func (export "generateName") (result i32 i32)
17+
(local $pos i32)
18+
(local $id i32)
19+
(if (i32.eqz (global.get $length)) (then (return (i32.const 0) (i32.const 0))))
20+
(local.set $pos (i32.shl (i32.trunc_f64_u (f64.mul (call $random)
21+
(f64.convert_i32_u (global.get $length)))) (i32.const 2)))
22+
(local.set $id (i32.load (local.get $pos)))
23+
(global.set $length (i32.sub (global.get $length) (i32.const 1)))
24+
(i32.store (local.get $pos) (i32.load (i32.shl (global.get $length) (i32.const 2))))
25+
(i32.store8 (global.get $outputOffset)
26+
(i32.add (i32.div_u (local.get $id) (i32.const 26000)) (global.get $letter)))
27+
(i32.store8 (i32.add (global.get $outputOffset) (i32.const 1))
28+
(i32.add (i32.rem_u (i32.div_u (local.get $id) (i32.const 1000))
29+
(i32.const 26)) (global.get $letter)))
30+
(i32.store8 (i32.add (global.get $outputOffset) (i32.const 2))
31+
(i32.add (i32.rem_u (i32.div_u (local.get $id) (i32.const 100))
32+
(i32.const 10)) (global.get $number)))
33+
(i32.store8 (i32.add (global.get $outputOffset) (i32.const 3))
34+
(i32.add (i32.rem_u (i32.div_u (local.get $id) (i32.const 10))
35+
(i32.const 10)) (global.get $number)))
36+
(i32.store8 (i32.add (global.get $outputOffset) (i32.const 4))
37+
(i32.add (i32.rem_u (local.get $id) (i32.const 10)) (global.get $number)))
38+
(global.get $outputOffset) (global.get $outputLength)
39+
)
40+
41+
;;
42+
;; Releases already used names
43+
;;
44+
(func $releaseNames (export "releaseNames")
45+
(global.set $length (i32.const 0))
46+
(loop $release
47+
(i32.store (i32.shl (global.get $length) (i32.const 2)) (global.get $length))
48+
(global.set $length (i32.add (global.get $length) (i32.const 1)))
49+
(br_if $release (i32.lt_u (global.get $length) (i32.const 676000))))
50+
)
51+
)

exercises/practice/robot-name/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
audit=false

exercises/practice/robot-name/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Exercism
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
presets: ["@exercism/babel-preset-javascript"],
3+
plugins: [],
4+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@exercism/wasm-robot-name",
3+
"description": "Exercism exercises in WebAssembly.",
4+
"author": "Alex Lohr",
5+
"type": "module",
6+
"private": true,
7+
"license": "MIT",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/exercism/wasm",
11+
"directory": "exercises/practice/robot-name"
12+
},
13+
"jest": {
14+
"maxWorkers": 1
15+
},
16+
"devDependencies": {
17+
"@babel/core": "^7.23.3",
18+
"@exercism/babel-preset-javascript": "^0.4.0",
19+
"@exercism/eslint-config-javascript": "^0.6.0",
20+
"@types/jest": "^29.5.8",
21+
"@types/node": "^20.9.1",
22+
"babel-jest": "^29.7.0",
23+
"core-js": "^3.33.2",
24+
"eslint": "^8.54.0",
25+
"jest": "^29.7.0"
26+
},
27+
"dependencies": {
28+
"@exercism/wasm-lib": "^0.2.0"
29+
},
30+
"scripts": {
31+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./*",
32+
"watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch ./*",
33+
"lint": "eslint ."
34+
}
35+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { compileWat, WasmRunner } from "@exercism/wasm-lib";
2+
3+
let wasmModule;
4+
let currentInstance;
5+
6+
class Robot {
7+
#name = '';
8+
constructor() { this.reset(); }
9+
get name() { return this.#name; }
10+
reset() {
11+
const [outputOffset, outputLength] = currentInstance.exports.generateName();
12+
this.#name = currentInstance.get_mem_as_utf8(outputOffset, outputLength);
13+
}
14+
static releaseNames() { currentInstance.exports.releaseNames(); }
15+
}
16+
17+
beforeAll(async () => {
18+
try {
19+
const watPath = new URL("./robot-name.wat", import.meta.url);
20+
const { buffer } = await compileWat(watPath);
21+
wasmModule = await WebAssembly.compile(buffer);
22+
} catch (err) {
23+
console.log(`Error compiling *.wat: \n${err}`);
24+
process.exit(1);
25+
}
26+
if (!wasmModule) {
27+
return Promise.reject();
28+
}
29+
try {
30+
currentInstance = await new WasmRunner(wasmModule);
31+
currentInstance.exports.releaseNames();
32+
return Promise.resolve();
33+
} catch (err) {
34+
console.log(`Error instantiating WebAssembly module: ${err}`);
35+
return Promise.reject();
36+
}
37+
});
38+
39+
const areSequential = (name1, name2) => {
40+
const alpha1 = name1.substring(0, 2);
41+
const alpha2 = name2.substring(0, 2);
42+
const num1 = Number(name1.substring(2, 5));
43+
const num2 = Number(name2.substring(2, 5));
44+
45+
const numDiff = num2 - num1;
46+
const alphaDiff =
47+
(alpha2.charCodeAt(0) - alpha1.charCodeAt(0)) * 26 +
48+
(alpha2.charCodeAt(1) - alpha1.charCodeAt(1));
49+
50+
const totalDiff = alphaDiff * 1000 + numDiff;
51+
52+
return Math.abs(totalDiff) <= 1;
53+
};
54+
55+
const TOTAL_NUMBER_OF_NAMES =
56+
26 * // A-Z
57+
26 * // A-Z
58+
10 * // 0-9
59+
10 * // 0-9
60+
10; // 0-9
61+
62+
describe('Robot', () => {
63+
let robot;
64+
65+
beforeEach(() => {
66+
robot = new Robot();
67+
});
68+
afterEach(() => {
69+
Robot.releaseNames();
70+
});
71+
72+
test('has a name', () => {
73+
expect(robot.name).toMatch(/^[A-Z]{2}\d{3}$/);
74+
});
75+
76+
xtest('name is the same each time', () => {
77+
expect(robot.name).toEqual(robot.name);
78+
});
79+
80+
xtest('different robots have different names', () => {
81+
const differentRobot = new Robot();
82+
expect(differentRobot.name).not.toEqual(robot.name);
83+
});
84+
85+
xtest('is able to reset the name', () => {
86+
const originalName = robot.name;
87+
88+
robot.reset();
89+
const newName = robot.name;
90+
91+
expect(newName).toMatch(/^[A-Z]{2}\d{3}$/);
92+
expect(originalName).not.toEqual(newName);
93+
});
94+
95+
xtest('should set a unique name after reset', () => {
96+
const NUMBER_OF_ROBOTS = 10000;
97+
const usedNames = new Set();
98+
99+
usedNames.add(robot.name);
100+
for (let i = 0; i < NUMBER_OF_ROBOTS; i += 1) {
101+
robot.reset();
102+
usedNames.add(robot.name);
103+
}
104+
105+
expect(usedNames.size).toEqual(NUMBER_OF_ROBOTS + 1);
106+
});
107+
108+
xtest('internal name cannot be modified', () => {
109+
const modifyInternal = () => {
110+
robot.name += 'a modification';
111+
};
112+
expect(() => modifyInternal()).toThrow();
113+
});
114+
115+
xtest('new names should not be sequential', () => {
116+
const name1 = robot.name;
117+
const name2 = new Robot().name;
118+
const name3 = new Robot().name;
119+
expect(areSequential(name1, name1)).toBe(true);
120+
expect(areSequential(name1, name2)).toBe(false);
121+
expect(areSequential(name2, name3)).toBe(false);
122+
});
123+
124+
xtest('names from reset should not be sequential', () => {
125+
const name1 = robot.name;
126+
robot.reset();
127+
const name2 = robot.name;
128+
robot.reset();
129+
const name3 = robot.name;
130+
expect(areSequential(name1, name2)).toBe(false);
131+
expect(areSequential(name2, name3)).toBe(false);
132+
expect(areSequential(name3, name3)).toBe(true);
133+
});
134+
135+
// This test is optional.
136+
//
137+
// This test doesn't run on our online test runner because it will time-out
138+
// with most implementations. It's up to you to test your solution locally.
139+
test.skip(
140+
'all the names can be generated',
141+
() => {
142+
const usedNames = new Set();
143+
usedNames.add(robot.name);
144+
145+
for (let i = 0; i < TOTAL_NUMBER_OF_NAMES - 1; i += 1) {
146+
const newRobot = new Robot();
147+
usedNames.add(newRobot.name);
148+
}
149+
150+
expect(usedNames.size).toEqual(TOTAL_NUMBER_OF_NAMES);
151+
},
152+
8 * 1000,
153+
);
154+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(module
2+
(import "math" "random" (func $random (result f64)))
3+
(memory (export "mem") 1)
4+
5+
;;
6+
;; Generate a new name for a robot, consisting of two uppercase letters and three numbers,
7+
;; avoiding already used names
8+
;;
9+
;; @results {(i32,i32)} - offset and length in linear memory
10+
;;
11+
(func (export "generateName") (result i32 i32)
12+
(i32.const 0) (i32.const 0)
13+
)
14+
15+
;;
16+
;; Release already used names so that they can be re-used
17+
;;
18+
(func (export "releaseNames"))
19+
)

0 commit comments

Comments
 (0)