From c79e69358c5237f3d39ccb9bd2db886824ba7ac9 Mon Sep 17 00:00:00 2001 From: resu-xuniL Date: Sat, 6 Jun 2026 17:47:49 +0200 Subject: [PATCH 1/5] Add `bottle-song` exercice --- .../bottle-song/.docs/instructions.md | 57 ++++++ .../practice/bottle-song/.meta/config.json | 19 ++ .../practice/bottle-song/.meta/example.php | 54 ++++++ .../practice/bottle-song/.meta/tests.toml | 31 ++++ exercises/practice/bottle-song/BottleSong.php | 43 +++++ .../practice/bottle-song/BottleSongTest.php | 174 ++++++++++++++++++ 6 files changed, 378 insertions(+) create mode 100644 exercises/practice/bottle-song/.docs/instructions.md create mode 100644 exercises/practice/bottle-song/.meta/config.json create mode 100644 exercises/practice/bottle-song/.meta/example.php create mode 100644 exercises/practice/bottle-song/.meta/tests.toml create mode 100644 exercises/practice/bottle-song/BottleSong.php create mode 100644 exercises/practice/bottle-song/BottleSongTest.php diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md new file mode 100644 index 00000000..febdfc86 --- /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 00000000..43796293 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "resu-xuniL" + ], + "files": { + "solution": [ + "BottleSong.php" + ], + "test": [ + "BottleSongTest.php" + ], + "example": [ + ".meta/example.php" + ] + }, + "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" +} diff --git a/exercises/practice/bottle-song/.meta/example.php b/exercises/practice/bottle-song/.meta/example.php new file mode 100644 index 00000000..e8675273 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/example.php @@ -0,0 +1,54 @@ + 1 ? "s" : ""; + $next = $number - 1 !== 1 ? "s" : ""; + + $song = sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current) . PHP_EOL; + $song .= sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current) . PHP_EOL; + $song .= self::FINAL . PHP_EOL; + $song .= sprintf(self::FINISH, self::NUMBERS[$number - 1], $next); + + return $song; + } + + public function verses(int $start, int $size): string + { + $song = ""; + + for ($i=0; $i < $size; $i++) { + $song .= $this->verse($start - $i); + if ($i < $size -1) + $song .= PHP_EOL . "" . PHP_EOL; + } + + return $song; + } + + public function lyrics(): string + { + return $this->verses(10, 10); + } +} diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml new file mode 100644 index 00000000..1f6e40a3 --- /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/BottleSong.php b/exercises/practice/bottle-song/BottleSong.php new file mode 100644 index 00000000..a11b74a3 --- /dev/null +++ b/exercises/practice/bottle-song/BottleSong.php @@ -0,0 +1,43 @@ +. + * + * To disable strict typing, comment out the directive below. + */ + +declare(strict_types=1); + +class BottleSong +{ + public function verse(int $number): string + { + throw new \BadMethodCallException("Implement the verse method"); + } + + public function verses(int $start, int $size): string + { + throw new \BadMethodCallException("Implement the verses method"); + } + + public function lyrics(): string + { + throw new \BadMethodCallException("Implement the lyrics method"); + } +} diff --git a/exercises/practice/bottle-song/BottleSongTest.php b/exercises/practice/bottle-song/BottleSongTest.php new file mode 100644 index 00000000..5779367b --- /dev/null +++ b/exercises/practice/bottle-song/BottleSongTest.php @@ -0,0 +1,174 @@ + single verse -> first generic verse')] + public function testVerseSingleVerseFirstGenericVerse(): void + { + $expected = "Ten green bottles hanging on the wall,\n" . + "Ten green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be nine green bottles hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verse(10)); + } + + /** + * uuid: 0f0aded3-472a-4c64-b842-18d4f1f5f030 + */ + #[TestDox('verse -> single verse -> last generic verse')] + public function testVerseSingleVerseLastGenericVerse(): void + { + $expected = "Three green bottles hanging on the wall,\n" . + "Three green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be two green bottles hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verse(3)); + } + + /** + * uuid: f61f3c97-131f-459e-b40a-7428f3ed99d9 + */ + #[TestDox('verse -> single verse -> verse with 2 bottles')] + public function testVerseSingleVerseVerseWithTwoBottles(): void + { + $expected = "Two green bottles hanging on the wall,\n" . + "Two green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be one green bottle hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verse(2)); + } + + /** + * uuid: 05eadba9-5dbd-401e-a7e8-d17cc9baa8e0 + */ + #[TestDox('verse -> single verse -> verse with 1 bottle')] + public function testVerseSingleVerseVerseWithOneBottle(): void + { + $expected = "One green bottle hanging on the wall,\n" . + "One green bottle hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be no green bottles hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verse(1)); + } + + /** + * uuid: a4a28170-83d6-4dc1-bd8b-319b6abb6a80 + */ + #[TestDox('verses -> multiple verses -> first two verses')] + public function testVersesMultipleVersesFirstTwoVerses(): void + { + $expected = "Ten green bottles hanging on the wall,\n" . + "Ten green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be nine green bottles hanging on the wall.\n" . + "\n" . + "Nine green bottles hanging on the wall,\n" . + "Nine green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be eight green bottles hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verses(10, 2)); + } + + /** + * uuid: 3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db + */ + #[TestDox('verses -> multiple verses -> last three verses')] + public function testVersesMultipleVersesLastThreeVerses(): void + { + $expected = "Three green bottles hanging on the wall,\n" . + "Three green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be two green bottles hanging on the wall.\n" . + "\n" . + "Two green bottles hanging on the wall,\n" . + "Two green bottles hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be one green bottle hanging on the wall.\n" . + "\n" . + "One green bottle hanging on the wall,\n" . + "One green bottle hanging on the wall,\n" . + "And if one green bottle should accidentally fall,\n" . + "There'll be no green bottles hanging on the wall."; + $song = new BottleSong(); + $this->assertEquals($expected, $song->verses(3, 3)); + } + + /** + * uuid: 28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28 + */ + #[TestDox('lyrics -> multiple verses -> all verses')] + public function testLyricsMultipleVersesAllVerses(): void + { + $expected = <<assertEquals($expected, $song->lyrics()); + } +} From e533a38cc9815c8b124c6002073c235f5089e5e2 Mon Sep 17 00:00:00 2001 From: resu-xuniL Date: Sat, 6 Jun 2026 17:52:18 +0200 Subject: [PATCH 2/5] Add exercice to `auto-sync.txt` --- bin/auto-sync.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/auto-sync.txt b/bin/auto-sync.txt index 16e02841..26781bc6 100644 --- a/bin/auto-sync.txt +++ b/bin/auto-sync.txt @@ -11,6 +11,7 @@ binary-search binary-search-tree bob book-store +bottle-song bowling change circular-buffer From e5543e5a8c65d2e06603950334eb9799dded2eb3 Mon Sep 17 00:00:00 2001 From: resu-xuniL Date: Sat, 6 Jun 2026 18:02:26 +0200 Subject: [PATCH 3/5] Fix PHPCS Linting --- exercises/practice/bottle-song/.meta/example.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/bottle-song/.meta/example.php b/exercises/practice/bottle-song/.meta/example.php index e8675273..10572a5a 100644 --- a/exercises/practice/bottle-song/.meta/example.php +++ b/exercises/practice/bottle-song/.meta/example.php @@ -38,10 +38,11 @@ public function verses(int $start, int $size): string { $song = ""; - for ($i=0; $i < $size; $i++) { + for ($i = 0; $i < $size; $i++) { $song .= $this->verse($start - $i); - if ($i < $size -1) + if ($i < $size - 1) { $song .= PHP_EOL . "" . PHP_EOL; + } } return $song; From b38237d8cf3df02019a2f472fb9074f9163f85bf Mon Sep 17 00:00:00 2001 From: resu-xuniL Date: Sat, 6 Jun 2026 21:03:07 +0200 Subject: [PATCH 4/5] Fix windows phpunit tests (\r\n with PHP_EOL) --- .../practice/bottle-song/.meta/example.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/exercises/practice/bottle-song/.meta/example.php b/exercises/practice/bottle-song/.meta/example.php index 10572a5a..7da5f509 100644 --- a/exercises/practice/bottle-song/.meta/example.php +++ b/exercises/practice/bottle-song/.meta/example.php @@ -4,8 +4,8 @@ class BottleSong { - private const START = "%s green bottle%s hanging on the wall,"; - private const FINAL = "And if one green bottle should accidentally fall,"; + private const START = "%s green bottle%s hanging on the wall,\n"; + private const FINAL = "And if one green bottle should accidentally fall,\n"; private const FINISH = "There'll be %s green bottle%s hanging on the wall."; private const NUMBERS = [ "no", @@ -26,9 +26,9 @@ public function verse(int $number): string $current = $number > 1 ? "s" : ""; $next = $number - 1 !== 1 ? "s" : ""; - $song = sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current) . PHP_EOL; - $song .= sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current) . PHP_EOL; - $song .= self::FINAL . PHP_EOL; + $song = sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current); + $song .= sprintf(self::START, ucfirst(self::NUMBERS[$number]), $current); + $song .= self::FINAL; $song .= sprintf(self::FINISH, self::NUMBERS[$number - 1], $next); return $song; @@ -39,13 +39,10 @@ public function verses(int $start, int $size): string $song = ""; for ($i = 0; $i < $size; $i++) { - $song .= $this->verse($start - $i); - if ($i < $size - 1) { - $song .= PHP_EOL . "" . PHP_EOL; - } + $song .= $this->verse($start - $i) . "\n" . "\n"; } - return $song; + return rtrim($song, "\n"); } public function lyrics(): string From 898aab32892067612b4f9780c30e18fff3ee323b Mon Sep 17 00:00:00 2001 From: resu-xuniL Date: Sat, 6 Jun 2026 21:22:28 +0200 Subject: [PATCH 5/5] Edit `config.json` --- config.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.json b/config.json index 3268f8fc..003b53e0 100644 --- a/config.json +++ b/config.json @@ -237,6 +237,14 @@ "math" ] }, + { + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "e63faaa7-d0a4-4442-881f-8c6ed9f986e9", + "practices": [], + "prerequisites": [], + "difficulty": 1 + }, { "slug": "bowling", "name": "Bowling",