From d9c66bd8f6ec0fe585f2f40cc102d97fc54e522b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Fri, 23 Jan 2026 21:47:07 -0800 Subject: [PATCH] add `camicia` --- config.json | 10 + .../practice/camicia/.docs/instructions.md | 84 ++++ .../practice/camicia/.docs/introduction.md | 24 ++ exercises/practice/camicia/.gitignore | 5 + exercises/practice/camicia/.meta/config.json | 25 ++ exercises/practice/camicia/.meta/proof.ci.js | 73 ++++ exercises/practice/camicia/.meta/tests.toml | 94 +++++ exercises/practice/camicia/.npmrc | 1 + exercises/practice/camicia/LICENSE | 21 + exercises/practice/camicia/babel.config.js | 4 + exercises/practice/camicia/camicia.js | 8 + exercises/practice/camicia/camicia.spec.js | 375 ++++++++++++++++++ exercises/practice/camicia/eslint.config.mjs | 45 +++ exercises/practice/camicia/jest.config.js | 22 + exercises/practice/camicia/package.json | 34 ++ 15 files changed, 825 insertions(+) create mode 100644 exercises/practice/camicia/.docs/instructions.md create mode 100644 exercises/practice/camicia/.docs/introduction.md create mode 100644 exercises/practice/camicia/.gitignore create mode 100644 exercises/practice/camicia/.meta/config.json create mode 100644 exercises/practice/camicia/.meta/proof.ci.js create mode 100644 exercises/practice/camicia/.meta/tests.toml create mode 100644 exercises/practice/camicia/.npmrc create mode 100644 exercises/practice/camicia/LICENSE create mode 100644 exercises/practice/camicia/babel.config.js create mode 100644 exercises/practice/camicia/camicia.js create mode 100644 exercises/practice/camicia/camicia.spec.js create mode 100644 exercises/practice/camicia/eslint.config.mjs create mode 100644 exercises/practice/camicia/jest.config.js create mode 100644 exercises/practice/camicia/package.json diff --git a/config.json b/config.json index ddc35cb96d..850b677699 100644 --- a/config.json +++ b/config.json @@ -869,6 +869,16 @@ "integers" ] }, + { + "slug": "camicia", + "name": "Camicia", + "uuid": "96d482ab-effc-4082-b4a8-3165de8ff0eb", + "practices": [], + "prerequisites": [ + "arrays" + ], + "difficulty": 5 + }, { "slug": "clock", "name": "Clock", diff --git a/exercises/practice/camicia/.docs/instructions.md b/exercises/practice/camicia/.docs/instructions.md new file mode 100644 index 0000000000..5ce3c755d0 --- /dev/null +++ b/exercises/practice/camicia/.docs/instructions.md @@ -0,0 +1,84 @@ +# Instructions + +In this exercise, you will simulate a game very similar to the classic card game **Camicia**. +Your program will receive the initial configuration of two players' decks and must simulate the game until it ends (or detect that it will never end). + +## Rules + +- The deck is split between **two players**. + The player's cards are read from left to right, where the leftmost card is the top of the deck. +- A round consists of both players playing at least one card. +- Players take turns placing the **top card** of their deck onto a central pile. +- If the card is a **number card** (2-10), play simply passes to the other player. +- If the card is a **payment card**, a penalty must be paid: + - **J** → opponent must pay 1 card + - **Q** → opponent must pay 2 cards + - **K** → opponent must pay 3 cards + - **A** → opponent must pay 4 cards +- If the player paying a penalty reveals another payment card, that player stops paying the penalty. + The other player must then pay a penalty based on the new payment card. +- If the penalty is fully paid without interruption, the player who placed the **last payment card** collects the central pile and places it at the bottom of their deck. + That player then starts the next round. +- If a player runs out of cards and is unable to play a card (either while paying a penalty or when it is their turn), the other player collects the central pile. +- The moment when a player collects cards from the central pile is called a **trick**. +- If a player has all the cards in their possession after a trick, the game **ends**. +- The game **enters a loop** as soon as the decks are identical to what they were earlier during the game, **not** counting number cards! + +## Examples + +A small example of a match that ends. + +| Round | Player A | Player B | Pile | Penalty Due | +| :---- | :----------- | :------------------------- | :------------------------- | :---------- | +| 1 | 2 A 7 8 Q 10 | 3 4 5 6 K 9 J | | - | +| 1 | A 7 8 Q 10 | 3 4 5 6 K 9 J | 2 | - | +| 1 | A 7 8 Q 10 | 4 5 6 K 9 J | 2 3 | - | +| 1 | 7 8 Q 10 | 4 5 6 K 9 J | 2 3 A | Player B: 4 | +| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Player B: 3 | +| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Player B: 2 | +| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Player B: 1 | +| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Player A: 3 | +| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Player A: 2 | +| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Player A: 1 | +| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Player B: 2 | +| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Player B: 1 | +| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Player A: 1 | +| 1 | - | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | +| 2 | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | - | + +status: `"finished"`, cards: 13, tricks: 1 + +This is a small example of a match that loops. + +| Round | Player A | Player B | Pile | Penalty Due | +| :---- | :------- | :------- | :---- | :---------- | +| 1 | J 2 3 | 4 J 5 | - | - | +| 1 | 2 3 | 4 J 5 | J | Player B: 1 | +| 1 | 2 3 | J 5 | J 4 | - | +| 2 | 2 3 J 4 | J 5 | - | - | +| 2 | 3 J 4 | J 5 | 2 | - | +| 2 | 3 J 4 | 5 | 2 J | Player A: 1 | +| 2 | J 4 | 5 | 2 J 3 | - | +| 3 | J 4 | 5 2 J 3 | - | - | +| 3 | J 4 | 2 J 3 | 5 | - | +| 3 | 4 | 2 J 3 | 5 J | Player B: 1 | +| 3 | 4 | J 3 | 5 J 2 | - | +| 4 | 4 5 J 2 | J 3 | - | - | + +The start of round 4 matches the start of round 2. +Recall, the value of the number cards does not matter. + +status: `"loop"`, cards: 8, tricks: 3 + +## Your Task + +- Using the input, simulate the game following the rules above. +- Determine the following information regarding the game: + - **Status**: `"finished"` or `"loop"` + - **Cards**: total number of cards played throughout the game + - **Tricks**: number of times the central pile was collected + +```exercism/advanced +For those who want to take on a more exciting challenge, the hunt for other records for the longest game with an end is still open. +There are 653,534,134,886,878,245,000 (approximately 654 quintillion) possibilities, and we haven't calculated them all yet! +``` diff --git a/exercises/practice/camicia/.docs/introduction.md b/exercises/practice/camicia/.docs/introduction.md new file mode 100644 index 0000000000..761d8a82c5 --- /dev/null +++ b/exercises/practice/camicia/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +One rainy afternoon, you sit at the kitchen table playing cards with your grandmother. +The game is her take on [Camicia][bmn]. + +At first it feels like just another friendly match: cards slapped down, laughter across the table, the occasional victorious grin from Nonna. +But as the game stretches on, something strange happens. +The same cards keep cycling back. +You play card after card, yet the end never seems to come. + +You start to wonder. +_Will this game ever finish? +Or could we keep playing forever?_ + +Later, driven by curiosity, you search online and to your surprise you discover that what happened wasn't just bad luck. +You and your grandmother may have stumbled upon one of the longest possible sequences! +Suddenly, you're hooked. +What began as a casual game has turned into a quest: _how long can such a game really last?_ +_Can you find a sequence even longer than the one you played at the kitchen table?_ +_Perhaps even long enough to set a new world record?_ + +And so, armed with nothing but a deck of cards and some algorithmic ingenuity, you decide to investigate... + +[bmn]: https://en.wikipedia.org/wiki/Beggar-my-neighbour diff --git a/exercises/practice/camicia/.gitignore b/exercises/practice/camicia/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/camicia/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/camicia/.meta/config.json b/exercises/practice/camicia/.meta/config.json new file mode 100644 index 0000000000..4d851b07d7 --- /dev/null +++ b/exercises/practice/camicia/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "camicia.js" + ], + "test": [ + "camicia.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Simulate the card game and determine whether the match ends or enters an infinite loop.", + "source": "Beggar-My-Neighbour", + "source_url": "https://www.richardpmann.com/beggar-my-neighbour-records.html", + "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/camicia/.meta/proof.ci.js b/exercises/practice/camicia/.meta/proof.ci.js new file mode 100644 index 0000000000..60ddbe93e2 --- /dev/null +++ b/exercises/practice/camicia/.meta/proof.ci.js @@ -0,0 +1,73 @@ +export const simulateGame = (playerA, playerB) => { + const getCardValue = (card) => { + if (card === 'J') return 1; + if (card === 'Q') return 2; + if (card === 'K') return 3; + if (card === 'A') return 4; + return 0; + }; + + const handA = playerA.map(getCardValue); + const handB = playerB.map(getCardValue); + let turn = 'A'; + let pile = []; + const seen = new Set(); + let totalTricks = 0; + let cardsPlayed = 0; + let currentDebt = 0; + + while (true) { + if (pile.length === 0) { + const round = JSON.stringify([handA, handB, turn]); + if (seen.has(round)) { + return { status: 'loop', tricks: totalTricks, cards: cardsPlayed }; + } + seen.add(round); + } + + const activeHand = turn === 'A' ? handA : handB; + const otherHand = turn === 'A' ? handB : handA; + + if (activeHand.length === 0) { + const extraTrick = pile.length === 0 ? 0 : 1; + return { + status: 'finished', + tricks: totalTricks + extraTrick, + cards: cardsPlayed, + }; + } + + const cardVal = activeHand.shift(); + pile.push(cardVal); + cardsPlayed += 1; + + // payment card so debt is either forgiven for player or assigned to opponent + if (cardVal > 0) { + currentDebt = cardVal; + turn = turn === 'A' ? 'B' : 'A'; + } else { + // time to pay up! + if (currentDebt > 0) { + currentDebt -= 1; + if (currentDebt === 0) { + // penalty paid off + otherHand.push(...pile); + pile = []; + totalTricks += 1; + currentDebt = 0; + + if (handA.length === 0 || handB.length === 0) { + return { + status: 'finished', + tricks: totalTricks, + cards: cardsPlayed, + }; + } + turn = turn === 'A' ? 'B' : 'A'; + } + } else { + turn = turn === 'A' ? 'B' : 'A'; + } + } + } +}; diff --git a/exercises/practice/camicia/.meta/tests.toml b/exercises/practice/camicia/.meta/tests.toml new file mode 100644 index 0000000000..18d3fdd99f --- /dev/null +++ b/exercises/practice/camicia/.meta/tests.toml @@ -0,0 +1,94 @@ +# 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. + +[0b7f737c-3ecd-4a55-b34d-e65c62a85c28] +description = "two cards, one trick" + +[27c19d75-53a5-48e5-b33b-232c3884d4f3] +description = "three cards, one trick" + +[9b02dd49-efaf-4b71-adca-a05c18a7c5b0] +description = "four cards, one trick" + +[fa3f4479-466a-4734-a001-ab79bfe27260] +description = "the ace reigns supreme" + +[07629689-f589-4f54-a6d1-8ce22776ce72] +description = "the king beats ace" + +[54d4a1c5-76fb-4d1e-8358-0e0296ac0601] +description = "the queen seduces the king" + +[c875500c-ff3d-47a4-bd1e-b60b90da80aa] +description = "the jack betrays the queen" + +[436875da-96ca-4149-be22-0b78173b8125] +description = "the 10 just wants to put on a show" + +[5be39bb6-1b34-4ce6-a1cd-0fcc142bb272] +description = "simple loop with decks of 3 cards" + +[2795dc21-0a2a-4c38-87c2-5a42e1ff15eb] +description = "the story is starting to get a bit complicated" + +[6999dfac-3fdc-41e2-b64b-38f4be228712] +description = "two tricks" + +[83dcd4f3-e089-4d54-855a-73f5346543a3] +description = "more tricks" + +[3107985a-f43e-486a-9ce8-db51547a9941] +description = "simple loop with decks of 4 cards" + +[dca32c31-11ed-49f6-b078-79ab912c1f7b] +description = "easy card combination" + +[1f8488d0-48d3-45ae-b819-59cedad0a5f4] +description = "easy card combination, inverted decks" + +[98878d35-623a-4d05-b81a-7bdc569eb88d] +description = "mirrored decks" + +[3e0ba597-ca10-484b-87a3-31a7df7d6da3] +description = "opposite decks" + +[92334ddb-aaa7-47fa-ab36-e928a8a6a67c] +description = "random decks #1" + +[30477523-9651-4860-84a3-e1ac461bb7fa] +description = "random decks #2" + +[20967de8-9e94-4e0e-9010-14bc1c157432] +description = "Kleber 1999" + +[9f2fdfe8-27f3-4323-816d-6bce98a9c6f7] +description = "Collins 2006" + +[c90b6f8d-7013-49f3-b5cb-14ea006cca1d] +description = "Mann and Wu 2007" + +[a3f1fbc5-1d0b-499a-92a5-22932dfc6bc8] +description = "Nessler 2012" + +[9cefb1ba-e6d1-4ab7-9d8f-76d8e0976d5f] +description = "Anderson 2013" + +[d37c0318-5be6-48d0-ab72-a7aaaff86179] +description = "Rucklidge 2014" + +[4305e479-ba87-432f-8a29-cd2bd75d2f05] +description = "Nessler 2021" + +[252f5cc3-b86d-4251-87ce-f920b7a6a559] +description = "Nessler 2022" + +[b9efcfa4-842f-4542-8112-8389c714d958] +description = "Casella 2024, first infinite game found" diff --git a/exercises/practice/camicia/.npmrc b/exercises/practice/camicia/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/camicia/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/camicia/LICENSE b/exercises/practice/camicia/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/camicia/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/camicia/babel.config.js b/exercises/practice/camicia/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/camicia/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/camicia/camicia.js b/exercises/practice/camicia/camicia.js new file mode 100644 index 0000000000..afbb5a7d49 --- /dev/null +++ b/exercises/practice/camicia/camicia.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'BookStore' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const simulateGame = (playerA, playerB) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/camicia/camicia.spec.js b/exercises/practice/camicia/camicia.spec.js new file mode 100644 index 0000000000..238db65cb2 --- /dev/null +++ b/exercises/practice/camicia/camicia.spec.js @@ -0,0 +1,375 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { simulateGame } from './camicia'; + +describe('Camicia', () => { + test('two cards, one trick', () => { + const playerA = ['2']; + const playerB = ['3']; + const expected = { status: 'finished', cards: 2, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('three cards, one trick', () => { + const playerA = ['2', '4']; + const playerB = ['3']; + const expected = { status: 'finished', cards: 3, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('four cards, one trick', () => { + const playerA = ['2', '4']; + const playerB = ['3', '5', '6']; + const expected = { status: 'finished', cards: 4, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('the ace reigns supreme', () => { + const playerA = ['2', 'A']; + const playerB = ['3', '4', '5', '6', '7']; + const expected = { status: 'finished', cards: 7, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('the king beats ace', () => { + const playerA = ['2', 'A']; + const playerB = ['3', '4', '5', '6', 'K']; + const expected = { status: 'finished', cards: 7, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('the queen seduces the king', () => { + const playerA = ['2', 'A', '7', '8', 'Q']; + const playerB = ['3', '4', '5', '6', 'K']; + const expected = { status: 'finished', cards: 10, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('the jack betrays the queen', () => { + const playerA = ['2', 'A', '7', '8', 'Q']; + const playerB = ['3', '4', '5', '6', 'K', '9', 'J']; + const expected = { status: 'finished', cards: 12, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('the 10 just wants to put on a show', () => { + const playerA = ['2', 'A', '7', '8', 'Q', '10']; + const playerB = ['3', '4', '5', '6', 'K', '9', 'J']; + const expected = { status: 'finished', cards: 13, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('simple loop with decks of 3 cards', () => { + const playerA = ['J', '2', '3']; + const playerB = ['4', 'J', '5']; + const expected = { status: 'loop', cards: 8, tricks: 3 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('the story is starting to get a bit complicated', () => { + const playerA = [ + '2', '6', '6', 'J', '4', 'K', 'Q', '10', 'K', 'J', + 'Q', '2', '3', 'K', '5', '6', 'Q', 'Q', 'A', 'A', + '6', '9', 'K', 'A', '8', 'K', '2', 'A', '9', 'A', + 'Q', '4', 'K', 'K', 'K', '3', '5', 'K', '8', 'Q', + '3', 'Q', '7', 'J', 'K', 'J', '9', 'J', '3', '3', + 'K', 'K', 'Q', 'A', 'K', '7', '10', 'A', 'Q', '7', + '10', 'J', '4', '5', 'J', '9', '10', 'Q', 'J', 'J', + 'K', '6', '10', 'J', '6', 'Q', 'J', '5', 'J', 'Q', + 'Q', '8', '3', '8', 'A', '2', '6', '9', 'K', '7', + 'J', 'K', 'K', '8', 'K', 'Q', '6', '10', 'J', '10', + 'J', 'Q', 'J', '10', '3', '8', 'K', 'A', '6', '9', + 'K', '2', 'A', 'A', '10', 'J', '6', 'A', '4', 'J', + 'A', 'J', 'J', '6', '2', 'J', '3', 'K', '2', '5', + '9', 'J', '9', '6', 'K', 'A', '5', 'Q', 'J', '2', + 'Q', 'K', 'A', '3', 'K', 'J', 'K', '2', '5', '6', + 'Q', 'J', 'Q', 'Q', 'J', '2', 'J', '9', 'Q', '7', + '7', 'A', 'Q', '7', 'Q', 'J', 'K', 'J', 'A', '7', + '7', '8', 'Q', '10', 'J', '10', 'J', 'J', '9', '2', + 'A', '2', + ]; + const playerB = [ + '7', '2', '10', 'K', '8', '2', 'J', '9', 'A', '5', + '6', 'J', 'Q', '6', 'K', '6', '5', 'A', '4', 'Q', + '7', 'J', '7', '10', '2', 'Q', '8', '2', '2', 'K', + 'J', 'A', '5', '5', 'A', '4', 'Q', '6', 'Q', 'K', + '10', '8', 'Q', '2', '10', 'J', 'A', 'Q', '8', 'Q', + 'Q', 'J', 'J', 'A', 'A', '9', '10', 'J', 'K', '4', + 'Q', '10', '10', 'J', 'K', '10', '2', 'J', '7', 'A', + 'K', 'K', 'J', 'A', 'J', '10', '8', 'K', 'A', '7', + 'Q', 'Q', 'J', '3', 'Q', '4', 'A', '3', 'A', 'Q', + 'Q', 'Q', '5', '4', 'K', 'J', '10', 'A', 'Q', 'J', + '6', 'J', 'A', '10', 'A', '5', '8', '3', 'K', '5', + '9', 'Q', '8', '7', '7', 'J', '7', 'Q', 'Q', 'Q', + 'A', '7', '8', '9', 'A', 'Q', 'A', 'K', '8', 'A', + 'A', 'J', '8', '4', '8', 'K', 'J', 'A', '10', 'Q', + '8', 'J', '8', '6', '10', 'Q', 'J', 'J', 'A', 'A', + 'J', '5', 'Q', '6', 'J', 'K', 'Q', '8', 'K', '4', + 'Q', 'Q', '6', 'J', 'K', '4', '7', 'J', 'J', '9', + '9', 'A', 'Q', 'Q', 'K', 'A', '6', '5', 'K', + ]; + const expected = { status: 'finished', cards: 361, tricks: 1 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('two tricks', () => { + const playerA = ['J']; + const playerB = ['3', 'J']; + const expected = { status: 'finished', cards: 5, tricks: 2 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('more tricks', () => { + const playerA = ['J', '2', '4']; + const playerB = ['3', 'J', 'A']; + const expected = { status: 'finished', cards: 12, tricks: 4 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + xtest('simple loop with decks of 4 cards', () => { + const playerA = ['2', '3', 'J', '6']; + const playerB = ['K', '5', 'J', '7']; + const expected = { status: 'loop', cards: 16, tricks: 4 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('easy card combination', () => { + const playerA = [ + '4', '8', '7', '5', '4', '10', '3', '9', '7', '3', + '10', '10', '6', '8', '2', '8', '5', '4', '5', '9', + '6', '5', '2', '8', '10', '9', + ]; + const playerB = [ + '6', '9', '4', '7', '2', '2', '3', '6', '7', '3', + 'A', 'A', 'A', 'A', 'K', 'K', 'K', 'K', 'Q', 'Q', + 'Q', 'Q', 'J', 'J', 'J', 'J', + ]; + const expected = { status: 'finished', cards: 40, tricks: 4 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('easy card combination, inverted decks', () => { + const playerA = [ + '3', '3', '5', '7', '3', '2', '10', '7', '6', '7', + 'A', 'A', 'A', 'A', 'K', 'K', 'K', 'K', 'Q', 'Q', + 'Q', 'Q', 'J', 'J', 'J', 'J', + ]; + const playerB = [ + '5', '10', '8', '2', '6', '7', '2', '4', '9', '2', + '6', '10', '10', '5', '4', '8', '4', '8', '6', '9', + '8', '5', '9', '3', '4', '9', + ]; + const expected = { status: 'finished', cards: 40, tricks: 4 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('mirrored decks', () => { + const playerA = [ + '2', 'A', '3', 'A', '3', 'K', '4', 'K', '2', 'Q', + '2', 'Q', '10', 'J', '5', 'J', '6', '10', '2', '9', + '10', '7', '3', '9', '6', '9', + ]; + const playerB = [ + '6', 'A', '4', 'A', '7', 'K', '4', 'K', '7', 'Q', + '7', 'Q', '5', 'J', '8', 'J', '4', '5', '8', '9', + '10', '6', '8', '3', '8', '5', + ]; + const expected = { status: 'finished', cards: 59, tricks: 4 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('opposite decks', () => { + const playerA = [ + '4', 'A', '9', 'A', '4', 'K', '9', 'K', '6', 'Q', + '8', 'Q', '8', 'J', '10', 'J', '9', '8', '4', '6', + '3', '6', '5', '2', '4', '3', + ]; + const playerB = [ + '10', '7', '3', '2', '9', '2', '7', '8', '7', '5', + 'J', '7', 'J', '10', 'Q', '10', 'Q', '3', 'K', '5', + 'K', '6', 'A', '2', 'A', '5', + ]; + const expected = { status: 'finished', cards: 151, tricks: 21 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('random decks #1', () => { + const playerA = [ + 'K', '10', '9', '8', 'J', '8', '6', '9', '7', 'A', + 'K', '5', '4', '4', 'J', '5', 'J', '4', '3', '5', + '8', '6', '7', '7', '4', '9', + ]; + const playerB = [ + '6', '3', 'K', 'A', 'Q', '10', 'A', '2', 'Q', '8', + '2', '10', '10', '2', 'Q', '3', 'K', '9', '7', 'A', + '3', 'Q', '5', 'J', '2', '6', + ]; + const expected = { status: 'finished', cards: 542, tricks: 76 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('random decks #2', () => { + const playerA = [ + '8', 'A', '4', '8', '5', 'Q', 'J', '2', '6', '2', + '9', '7', 'K', 'A', '8', '10', 'K', '8', '10', '9', + 'K', '6', '7', '3', 'K', '9', + ]; + const playerB = [ + '10', '5', '2', '6', 'Q', 'J', 'A', '9', '5', '5', + '3', '7', '3', 'J', 'A', '2', 'Q', '3', 'J', 'Q', + '4', '10', '4', '7', '4', '6', + ]; + const expected = { status: 'finished', cards: 327, tricks: 42 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Kleber 1999', () => { + const playerA = [ + '4', '8', '9', 'J', 'Q', '8', '5', '5', 'K', '2', + 'A', '9', '8', '5', '10', 'A', '4', 'J', '3', 'K', + '6', '9', '2', 'Q', 'K', '7', + ]; + const playerB = [ + '10', 'J', '3', '2', '4', '10', '4', '7', '5', '3', + '6', '6', '7', 'A', 'J', 'Q', 'A', '7', '2', '10', + '3', 'K', '9', '6', '8', 'Q', + ]; + const expected = { status: 'finished', cards: 5790, tricks: 805 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Collins 2006', () => { + const playerA = [ + 'A', '8', 'Q', 'K', '9', '10', '3', '7', '4', '2', + 'Q', '3', '2', '10', '9', 'K', 'A', '8', '7', '7', + '4', '5', 'J', '9', '2', '10', + ]; + const playerB = [ + '4', 'J', 'A', 'K', '8', '5', '6', '6', 'A', '6', + '5', 'Q', '4', '6', '10', '8', 'J', '2', '5', '7', + 'Q', 'J', '3', '3', 'K', '9', + ]; + const expected = { status: 'finished', cards: 6913, tricks: 960 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Mann and Wu 2007', () => { + const playerA = [ + 'K', '2', 'K', 'K', '3', '3', '6', '10', 'K', '6', + 'A', '2', '5', '5', '7', '9', 'J', 'A', 'A', '3', + '4', 'Q', '4', '8', 'J', '6', + ]; + const playerB = [ + '4', '5', '2', 'Q', '7', '9', '9', 'Q', '7', 'J', + '9', '8', '10', '3', '10', 'J', '4', '10', '8', '6', + '8', '7', 'A', 'Q', '5', '2', + ]; + const expected = { status: 'finished', cards: 7157, tricks: 1007 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Nessler 2012', () => { + const playerA = [ + '10', '3', '6', '7', 'Q', '2', '9', '8', '2', '8', + '4', 'A', '10', '6', 'K', '2', '10', 'A', '5', 'A', + '2', '4', 'Q', 'J', 'K', '4', + ]; + const playerB = [ + '10', 'Q', '4', '6', 'J', '9', '3', 'J', '9', '3', + '3', 'Q', 'K', '5', '9', '5', 'K', '6', '5', '7', + '8', 'J', 'A', '7', '8', '7', + ]; + const expected = { status: 'finished', cards: 7207, tricks: 1015 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Anderson 2013', () => { + const playerA = [ + '6', '7', 'A', '3', 'Q', '3', '5', 'J', '3', '2', + 'J', '7', '4', '5', 'Q', '10', '5', 'A', 'J', '2', + 'K', '8', '9', '9', 'K', '3', + ]; + const playerB = [ + '4', 'J', '6', '9', '8', '5', '10', '7', '9', 'Q', + '2', '7', '10', '8', '4', '10', 'A', '6', '4', 'A', + '6', '8', 'Q', 'K', 'K', '2', + ]; + const expected = { status: 'finished', cards: 7225, tricks: 1016 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Rucklidge 2014', () => { + const playerA = [ + '8', 'J', '2', '9', '4', '4', '5', '8', 'Q', '3', + '9', '3', '6', '2', '8', 'A', 'A', 'A', '9', '4', + '7', '2', '5', 'Q', 'Q', '3', + ]; + const playerB = [ + 'K', '7', '10', '6', '3', 'J', 'A', '7', '6', '5', + '5', '8', '10', '9', '10', '4', '2', '7', 'K', 'Q', + '10', 'K', '6', 'J', 'J', 'K', + ]; + const expected = { status: 'finished', cards: 7959, tricks: 1122 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Nessler 2021', () => { + const playerA = [ + '7', '2', '3', '4', 'K', '9', '6', '10', 'A', '8', + '9', 'Q', '7', 'A', '4', '8', 'J', 'J', 'A', '4', + '3', '2', '5', '6', '6', 'J', + ]; + const playerB = [ + '3', '10', '8', '9', '8', 'K', 'K', '2', '5', '5', + '7', '6', '4', '3', '5', '7', 'A', '9', 'J', 'K', + '2', 'Q', '10', 'Q', '10', 'Q', + ]; + const expected = { status: 'finished', cards: 7972, tricks: 1106 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Nessler 2022', () => { + const playerA = [ + '2', '10', '10', 'A', 'J', '3', '8', 'Q', '2', '5', + '5', '5', '9', '2', '4', '3', '10', 'Q', 'A', 'K', + 'Q', 'J', 'J', '9', 'Q', 'K', + ]; + const playerB = [ + '10', '7', '6', '3', '6', 'A', '8', '9', '4', '3', + 'K', 'J', '6', 'K', '4', '9', '7', '8', '5', '7', + '8', '2', 'A', '7', '4', '6', + ]; + const expected = { status: 'finished', cards: 8344, tricks: 1164 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); + + // prettier-ignore + xtest('Casella 2024, first infinite game found', () => { + const playerA = [ + '2', '8', '4', 'K', '5', '2', '3', 'Q', '6', 'K', + 'Q', 'A', 'J', '3', '5', '9', '8', '3', 'A', 'A', + 'J', '4', '4', 'J', '7', '5', + ]; + const playerB = [ + '7', '7', '8', '6', '10', '10', '6', '10', '7', '2', + 'Q', '6', '3', '2', '4', 'K', 'Q', '10', 'J', '5', + '9', '8', '9', '9', 'K', 'A', + ]; + const expected = { status: 'loop', cards: 474, tricks: 66 }; + expect(simulateGame(playerA, playerB)).toEqual(expected); + }); +}); diff --git a/exercises/practice/camicia/eslint.config.mjs b/exercises/practice/camicia/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/camicia/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/camicia/jest.config.js b/exercises/practice/camicia/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/camicia/jest.config.js @@ -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/camicia/package.json b/exercises/practice/camicia/package.json new file mode 100644 index 0000000000..68fe60c323 --- /dev/null +++ b/exercises/practice/camicia/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-camicia", + "description": "Exercism exercises in Javascript.", + "author": "Katrina Owen", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/camicia" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^24.3.0", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.3.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +}