From 75ee6c7557908393a4e9814266c610f2e1e29fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Algrain?= Date: Fri, 11 Oct 2024 17:39:25 +0200 Subject: [PATCH] feat: :tada: Init Goban test Setup the project using TS and NPM Add goban test with Readme, skeleton and tests --- .gitignore | 59 ++++++++++++ README.md | 76 ++++++++++++++- goban.spec.ts | 39 ++++++++ goban.ts | 41 ++++++++ package-lock.json | 239 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 29 ++++++ tsconfig.json | 19 ++++ 7 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 goban.spec.ts create mode 100644 goban.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b950c56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +### IDE ### +.vscode +.idea/ + +### direnv ### +.direnv +.envrc + +### macOS ### +.DS_Store +# Thumbnails +._* + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# unit tests code coverage reports +coverage/ + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# dotenv environment variables file +.env +.env*.local + +# Default build output +dist/ + +# Temporary folders +tmp/ +temp/ + + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable + +# Folder config file +[Dd]esktop.ini \ No newline at end of file diff --git a/README.md b/README.md index 02f13c0..5b31555 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ -# ts-backend-tech-test -TypeScript Backend technical test. +# Lumapps Goban Technical test + +## How to launch the project + +To setup and launch tests: + +```sh +git clone https://github.com/lumapps/test-goban-ts.git +cd test-goban-ts +npm install +npm test +``` + +## Technical Test + +The theme of this test is the game of go. + +The goal is to write a function that determines whether the stone at an x, y position on a goban is taken or not. + +Vocabulary: + +- Goban: the board on which stones are placed to play +- Shape: a group of one or more adjacent stones of the same color (adjacent: stones that are left, right, top, bottom of each other, diagonals do not count) +- Freedom: empty space adjacent to a shape + +Reminder of the rules: + +1. The goban has an indefinite size +2. There are two players and everyone plays a stone color: black or white +3. The stones are laid one after the other each turn +4. When a form has no more freedom it is taken + +The objective of the test is to write an `isTaken` function which takes in parameter `x`, `y` and which returns true if the stone with the position `x`, `y`, is taken there and false otherwise. To do this function we use a function `getStatus(x, y)` which returns: + +- Status.BLACK: when the stone at position `x`, `y` is black +- Status.WHITE: when the stone at the position `x`, `y` is white +- Status.EMPTY: when there is no stone at position `x`, `y` +- Status.OUT: when the position `x`, `y` is out of the goban + +Complete the `isTaken` function with your solution. You can add parameters to the method if needed. This one must respect the good practices of TypeScript. You can test your solution at any time with `npm test`. The tests are in the file goban.spec.ts. + +Examples: + +```text +# = black +o = white +. = empty + + +. #. +# o # <= o is taken because she has no freedom, she has no adjacent empty space +. #. + + +... +# o # <= o is not taken because she has a freedom over +. #. + + +o # <= o is taken because she has no freedom (the top and the left are out of the goban so they are not freedoms) +#. + + +oo. +## o <= the form # is taken because it has no freedom +o o # +.o. + + +oo. +##. <= the form # is not taken because it has a freedom in x = 2, y = 1 (0, 0 on the top left) +o o # +.o. +``` diff --git a/goban.spec.ts b/goban.spec.ts new file mode 100644 index 0000000..8ad4d5b --- /dev/null +++ b/goban.spec.ts @@ -0,0 +1,39 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +import { Goban } from "./goban"; + +describe("Goban", () => { + it("test_white_is_taken_when_surrounded_by_black", () => { + const goban = new Goban([".#.", "#o#", ".#."]); + assert.ok(goban.isTaken(1, 1)); + }); + + it("test_white_is_not_taken_when_it_has_a_liberty", () => { + const goban = new Goban(["...", "#o#", ".#."]); + assert.equal(goban.isTaken(1, 1), false); + }); + + it("test_black_shape_is_taken_when_surrounded", () => { + const goban = new Goban(["oo.", "##o", "o#o", ".o."]); + assert.ok(goban.isTaken(0, 1)); + assert.ok(goban.isTaken(1, 1)); + assert.ok(goban.isTaken(1, 2)); + }); + + it("test_black_shape_is_not_taken_when_it_has_a_liberty", () => { + const goban = new Goban(["oo.", "##.", "o#o", ".o."]); + + assert.equal(goban.isTaken(0, 1), false); + assert.equal(goban.isTaken(1, 1), false); + assert.equal(goban.isTaken(1, 2), false); + }); + + it("test_square_shape_is_taken", () => { + const goban = new Goban(["oo.", "##o", "##o", "oo."]); + assert.ok(goban.isTaken(0, 1)); + assert.ok(goban.isTaken(0, 2)); + assert.ok(goban.isTaken(1, 1)); + assert.ok(goban.isTaken(1, 2)); + }); +}); diff --git a/goban.ts b/goban.ts new file mode 100644 index 0000000..a52f405 --- /dev/null +++ b/goban.ts @@ -0,0 +1,41 @@ +export enum Status { + WHITE = 1, + BLACK = 2, + EMPTY = 3, + OUT = 4, +} + +export class Goban { + private board: string[]; + + constructor(board: string[]) { + this.board = board; + } + + public print() { + console.log(this.board.join("\n")); + } + + public getStatus(x: number, y: number): Status { + if ( + !this.board || + x < 0 || + y < 0 || + y >= this.board.length || + x >= this.board[0].length + ) { + return Status.OUT; + } else if (this.board[y][x] === ".") { + return Status.EMPTY; + } else if (this.board[y][x] === "o") { + return Status.WHITE; + } else if (this.board[y][x] === "#") { + return Status.BLACK; + } + throw new Error(`Unknown goban value ${this.board[y][x]}`); + } + + public isTaken(x: number, y: number): boolean { + throw new Error("Not implemented"); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8ec66f7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,239 @@ +{ + "name": "ts-backend-tech-test", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ts-backend-tech-test", + "version": "1.0.0", + "license": "UNLICENSED", + "devDependencies": { + "@types/node": "22.7.5", + "ts-node": "10.9.2", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..478a8b3 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "ts-backend-tech-test", + "version": "1.0.0", + "description": "Lumapps TypeScript Backend Technical Test", + "author": "Lumapps", + "license": "UNLICENSED", + "private": true, + "homepage": "https://github.com/lumapps/ts-backend-tech-test#readme", + "bugs": { + "url": "https://github.com/lumapps/ts-backend-tech-test/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/lumapps/ts-backend-tech-test.git" + }, + "engines": { + "node": ">=22" + }, + "scripts": { + "start": "NODE_OPTIONS='-r ts-node/register' node ./goban.ts", + "test": "NODE_OPTIONS='-r ts-node/register' node --test ./*.spec.ts", + "test-only": "NODE_OPTIONS='-r ts-node/register' node --test --test-only ./*.spec.ts" + }, + "devDependencies": { + "@types/node": "22.7.5", + "ts-node": "10.9.2", + "typescript": "5.6.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..486ac79 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + /* Language and Environment */ + "target": "es2023", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["es2022"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + + /* Interop Constraints */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + + /* Completeness */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} \ No newline at end of file