diff --git a/config.json b/config.json index 92e3682b2..348369b22 100644 --- a/config.json +++ b/config.json @@ -1270,6 +1270,19 @@ "prerequisites": [], "difficulty": 3 }, + { + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "6bcfe2b6-e555-4c90-8c80-168730dd5ad3", + "practices": [], + "prerequisites": [], + "topics": [ + "conditionals", + "loops", + "strings" + ], + "difficulty": 3 + }, { "slug": "rotational-cipher", "name": "Rotational Cipher", diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md new file mode 100644 index 000000000..febdfc863 --- /dev/null +++ b/exercises/practice/bottle-song/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +Recite the lyrics to that popular children's repetitive song: Ten Green Bottles. + +Note that not all verses are identical. + +```text +Ten green bottles hanging on the wall, +Ten green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be nine green bottles hanging on the wall. + +Nine green bottles hanging on the wall, +Nine green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be eight green bottles hanging on the wall. + +Eight green bottles hanging on the wall, +Eight green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be seven green bottles hanging on the wall. + +Seven green bottles hanging on the wall, +Seven green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be six green bottles hanging on the wall. + +Six green bottles hanging on the wall, +Six green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be five green bottles hanging on the wall. + +Five green bottles hanging on the wall, +Five green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be four green bottles hanging on the wall. + +Four green bottles hanging on the wall, +Four green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be three green bottles hanging on the wall. + +Three green bottles hanging on the wall, +Three green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be two green bottles hanging on the wall. + +Two green bottles hanging on the wall, +Two green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be one green bottle hanging on the wall. + +One green bottle hanging on the wall, +One green bottle hanging on the wall, +And if one green bottle should accidentally fall, +There'll be no green bottles hanging on the wall. +``` diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json new file mode 100644 index 000000000..cf27bc8f1 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "Budmin" + ], + "files": { + "solution": [ + "bottle-song.ts" + ], + "test": [ + "bottle-song.test.ts" + ], + "example": [ + ".meta/proof.ci.ts" + ] + }, + "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles" + } + \ No newline at end of file diff --git a/exercises/practice/bottle-song/.meta/proof.ci.ts b/exercises/practice/bottle-song/.meta/proof.ci.ts new file mode 100644 index 000000000..303b99aba --- /dev/null +++ b/exercises/practice/bottle-song/.meta/proof.ci.ts @@ -0,0 +1,39 @@ +const NUMBERS = [ + "no", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine", + "Ten", +] + + +export const recite = (initialBottleCount: number, takeDownCount = 1): string[] => { + const out: string[] = [] + + for(let i = initialBottleCount; i > initialBottleCount - takeDownCount; i--) { + const currentBottles = i === 1 ? 'bottle' : 'bottles' + const nextBottles = i-1 === 1 ? 'bottle' : 'bottles' + + out.push(...[ + ...(Array(2).fill(`${NUMBERS[i]} green ${currentBottles} hanging on the wall,`)), + `And if one green bottle should accidentally fall,`, + `There'll be ${NUMBERS[i-1].toLowerCase()} green ${nextBottles} hanging on the wall.` + ]) + + if (initialBottleCount - takeDownCount !== i -1) { + out.push('') + } + + } + + return out +} + + + \ No newline at end of file diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml new file mode 100644 index 000000000..1f6e40a37 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/tests.toml @@ -0,0 +1,31 @@ +# 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. + +[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03] +description = "verse -> single verse -> first generic verse" + +[0f0aded3-472a-4c64-b842-18d4f1f5f030] +description = "verse -> single verse -> last generic verse" + +[f61f3c97-131f-459e-b40a-7428f3ed99d9] +description = "verse -> single verse -> verse with 2 bottles" + +[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0] +description = "verse -> single verse -> verse with 1 bottle" + +[a4a28170-83d6-4dc1-bd8b-319b6abb6a80] +description = "lyrics -> multiple verses -> first two verses" + +[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db] +description = "lyrics -> multiple verses -> last three verses" + +[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28] +description = "lyrics -> multiple verses -> all verses" diff --git a/exercises/practice/bottle-song/.vscode/extensions.json b/exercises/practice/bottle-song/.vscode/extensions.json new file mode 100644 index 000000000..daaa5ee2e --- /dev/null +++ b/exercises/practice/bottle-song/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/exercises/practice/bottle-song/.vscode/settings.json b/exercises/practice/bottle-song/.vscode/settings.json new file mode 100644 index 000000000..761fb422a --- /dev/null +++ b/exercises/practice/bottle-song/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "cSpell.words": ["exercism"], + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + } +} diff --git a/exercises/practice/bottle-song/.yarnrc.yml b/exercises/practice/bottle-song/.yarnrc.yml new file mode 100644 index 000000000..23e4a6d3d --- /dev/null +++ b/exercises/practice/bottle-song/.yarnrc.yml @@ -0,0 +1,3 @@ +compressionLevel: mixed + +enableGlobalCache: true diff --git a/exercises/practice/bottle-song/babel.config.cjs b/exercises/practice/bottle-song/babel.config.cjs new file mode 100644 index 000000000..164552797 --- /dev/null +++ b/exercises/practice/bottle-song/babel.config.cjs @@ -0,0 +1,5 @@ +module.exports = { + // eslint-disable-next-line @typescript-eslint/no-require-imports + presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]], + plugins: [], +} diff --git a/exercises/practice/bottle-song/bottle-song.test.ts b/exercises/practice/bottle-song/bottle-song.test.ts new file mode 100644 index 000000000..c626545fe --- /dev/null +++ b/exercises/practice/bottle-song/bottle-song.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it, xit } from '@jest/globals' +import { recite } from './bottle-song.ts' + +describe('Bottle Song', () => { + describe('verse', () => { + describe('single verse', () => { + it('first generic verse', () => { + const expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ] + expect(recite(10, 1)).toEqual(expected) + }) + + xit('last generic verse', () => { + const expected = [ + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ] + expect(recite(3, 1)).toEqual(expected) + }) + + xit('verse with 2 bottles', () => { + const expected = [ + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ] + expect(recite(2, 1)).toEqual(expected) + }) + + xit('verse with 1 bottle', () => { + const expected = [ + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ] + expect(recite(1, 1)).toEqual(expected) + }) + }) + }) + + describe('lyrics', () => { + describe('multiple verses', () => { + xit('first two verses', () => { + const expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ``, + `Nine green bottles hanging on the wall,`, + `Nine green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be eight green bottles hanging on the wall.`, + ] + expect(recite(10, 2)).toEqual(expected) + }) + + xit('last three verses', () => { + const expected = [ + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ``, + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ``, + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ] + expect(recite(3, 3)).toEqual(expected) + }) + + xit('all verses', () => { + const expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ``, + `Nine green bottles hanging on the wall,`, + `Nine green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be eight green bottles hanging on the wall.`, + ``, + `Eight green bottles hanging on the wall,`, + `Eight green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be seven green bottles hanging on the wall.`, + ``, + `Seven green bottles hanging on the wall,`, + `Seven green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be six green bottles hanging on the wall.`, + ``, + `Six green bottles hanging on the wall,`, + `Six green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be five green bottles hanging on the wall.`, + ``, + `Five green bottles hanging on the wall,`, + `Five green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be four green bottles hanging on the wall.`, + ``, + `Four green bottles hanging on the wall,`, + `Four green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be three green bottles hanging on the wall.`, + ``, + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ``, + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ``, + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ] + expect(recite(10, 10)).toEqual(expected) + }) + }) + }) +}) diff --git a/exercises/practice/bottle-song/bottle-song.ts b/exercises/practice/bottle-song/bottle-song.ts new file mode 100644 index 000000000..80c56e1c9 --- /dev/null +++ b/exercises/practice/bottle-song/bottle-song.ts @@ -0,0 +1,4 @@ + +export const recite = (initialBottleCount: unknown, takeDownCount: unknown): unknown => { + throw new Error('Remove this statement and implement this function') +} diff --git a/exercises/practice/bottle-song/eslint.config.mjs b/exercises/practice/bottle-song/eslint.config.mjs new file mode 100644 index 000000000..1be39c53f --- /dev/null +++ b/exercises/practice/bottle-song/eslint.config.mjs @@ -0,0 +1,26 @@ +// @ts-check + +import tsEslint from 'typescript-eslint' +import config from '@exercism/eslint-config-typescript' +import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' + +export default [ + ...tsEslint.config(...config, { + files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], + extends: maintainersConfig, + }), + { + ignores: [ + // # Protected or generated + '.git/**/*', + '.vscode/**/*', + + //# When using npm + 'node_modules/**/*', + + // # Configuration files + 'babel.config.cjs', + 'jest.config.cjs', + ], + }, +] diff --git a/exercises/practice/bottle-song/jest.config.cjs b/exercises/practice/bottle-song/jest.config.cjs new file mode 100644 index 000000000..0aba1a59e --- /dev/null +++ b/exercises/practice/bottle-song/jest.config.cjs @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +} diff --git a/exercises/practice/bottle-song/package.json b/exercises/practice/bottle-song/package.json new file mode 100644 index 000000000..c7d9dbccd --- /dev/null +++ b/exercises/practice/bottle-song/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/typescript-practice-bottle-song", + "version": "1.0.0", + "description": "Exercism practice exercise on bottle-song", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/exercism/typescript" + }, + "type": "module", + "engines": { + "node": "^18.16.0 || >=20.0.0" + }, + "devDependencies": { + "@exercism/babel-preset-typescript": "^0.6.0", + "@exercism/eslint-config-typescript": "^0.8.0", + "@jest/globals": "^29.7.0", + "@types/node": "~22.7.6", + "babel-jest": "^29.7.0", + "core-js": "~3.38.1", + "eslint": "^9.12.0", + "expect": "^29.7.0", + "jest": "^29.7.0", + "prettier": "^3.3.3", + "tstyche": "^2.1.1", + "typescript": "~5.6.3", + "typescript-eslint": "^8.10.0" + }, + "scripts": { + "test": "corepack yarn node test-runner.mjs", + "test:types": "corepack yarn tstyche", + "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", + "lint": "corepack yarn lint:types && corepack yarn lint:ci", + "lint:types": "corepack yarn tsc --noEmit -p .", + "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" + }, + "packageManager": "yarn@4.5.1" +} diff --git a/exercises/practice/bottle-song/tsconfig.json b/exercises/practice/bottle-song/tsconfig.json new file mode 100644 index 000000000..574616245 --- /dev/null +++ b/exercises/practice/bottle-song/tsconfig.json @@ -0,0 +1,38 @@ +{ + "display": "Configuration for Exercism TypeScript Exercises", + "compilerOptions": { + // Allows you to use the newest syntax, and have access to console.log + // https://www.typescriptlang.org/tsconfig#lib + "lib": ["ES2020", "dom"], + // Make sure typescript is configured to output ESM + // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm + "module": "Node16", + // Since this project is using babel, TypeScript may target something very + // high, and babel will make sure it runs on your local Node version. + // https://babeljs.io/docs/en/ + "target": "ES2020", // ESLint doesn't support this yet: "es2022", + + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + + // Because jest-resolve isn't like node resolve, the absolute path must be .ts + "allowImportingTsExtensions": true, + "noEmit": true, + + // Because we'll be using babel: ensure that Babel can safely transpile + // files in the TypeScript project. + // + // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats + "isolatedModules": true + }, + "include": [ + "*.ts", + "*.tsx", + ".meta/*.ts", + ".meta/*.tsx", + "__typetests__/*.tst.ts" + ], + "exclude": ["node_modules"] +}