diff --git a/config.json b/config.json
index 05193524a..49da76d02 100644
--- a/config.json
+++ b/config.json
@@ -2300,6 +2300,22 @@
],
"difficulty": 5
},
+ {
+ "slug": "camicia",
+ "name": "Camicia",
+ "uuid": "949a708f-4379-4c96-9fec-71885e8164d9",
+ "practices": [
+ "discriminated-unions",
+ "records",
+ "recursion"
+ ],
+ "prerequisites": [
+ "discriminated-unions",
+ "records",
+ "recursion"
+ ],
+ "difficulty": 5
+ },
{
"slug": "satellite",
"name": "Satellite",
diff --git a/exercises/Exercises.slnx b/exercises/Exercises.slnx
index c25398833..11b6d9c4a 100644
--- a/exercises/Exercises.slnx
+++ b/exercises/Exercises.slnx
@@ -36,6 +36,7 @@
+
diff --git a/exercises/practice/camicia/.docs/instructions.md b/exercises/practice/camicia/.docs/instructions.md
new file mode 100644
index 000000000..db62fcef2
--- /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 000000000..761d8a82c
--- /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/.meta/Example.fs b/exercises/practice/camicia/.meta/Example.fs
new file mode 100644
index 000000000..c12cc657f
--- /dev/null
+++ b/exercises/practice/camicia/.meta/Example.fs
@@ -0,0 +1,99 @@
+module Camicia
+
+open System
+open System.Collections.Generic
+
+type GameStatus =
+ | Finished
+ | Loop
+
+type GameResult = { Status: GameStatus; Tricks: int; Cards: int }
+
+type Player =
+ | A
+ | B
+
+let private cardValue =
+ function
+ | "J" -> 1
+ | "Q" -> 2
+ | "K" -> 3
+ | "A" -> 4
+ | _ -> 0
+
+let private otherPlayer =
+ function
+ | A -> B
+ | B -> A
+
+let private handFor player handA handB =
+ match player with
+ | A -> handA
+ | B -> handB
+
+let private positionKey (handA: Queue) (handB: Queue) activePlayer =
+ let handAKey = String.Join(",", handA)
+ let handBKey = String.Join(",", handB)
+ $"{handAKey}|{handBKey}|{activePlayer}"
+
+let private hasBeenSeen handA handB activePlayer (seen: HashSet) =
+ positionKey handA handB activePlayer |> seen.Add |> not
+
+let private finishedGame tricks cards pile =
+ let extraTrick = if Seq.isEmpty pile then 0 else 1
+ { Status = Finished; Tricks = tricks + extraTrick; Cards = cards }
+
+let private loopedGame tricks cards = { Status = Loop; Tricks = tricks; Cards = cards }
+
+let private awardPile pile (winner: Queue) =
+ for card in pile do
+ winner.Enqueue card
+
+ (pile: List).Clear()
+
+let simulateGame (playerA: string array) (playerB: string array) =
+ let handA = Queue(playerA |> Seq.map cardValue)
+ let handB = Queue(playerB |> Seq.map cardValue)
+ let pile = List()
+ let seen = HashSet()
+ let mutable activePlayer = A
+ let mutable tricks = 0
+ let mutable cards = 0
+ let mutable debt = 0
+
+ let rec play () =
+ if Seq.isEmpty pile && hasBeenSeen handA handB activePlayer seen then
+ loopedGame tricks cards
+ else
+ let activeHand = handFor activePlayer handA handB
+ let otherHand = handFor (otherPlayer activePlayer) handA handB
+
+ if activeHand.Count = 0 then
+ finishedGame tricks cards pile
+ else
+ let card = activeHand.Dequeue()
+ pile.Add card
+ cards <- cards + 1
+
+ if card > 0 then
+ debt <- card
+ activePlayer <- otherPlayer activePlayer
+ play ()
+ elif debt > 1 then
+ debt <- debt - 1
+ play ()
+ elif debt = 1 then
+ awardPile pile otherHand
+ tricks <- tricks + 1
+ debt <- 0
+
+ if handA.Count = 0 || handB.Count = 0 then
+ finishedGame tricks cards pile
+ else
+ activePlayer <- otherPlayer activePlayer
+ play ()
+ else
+ activePlayer <- otherPlayer activePlayer
+ play ()
+
+ play ()
diff --git a/exercises/practice/camicia/.meta/config.json b/exercises/practice/camicia/.meta/config.json
new file mode 100644
index 000000000..efe05d9a9
--- /dev/null
+++ b/exercises/practice/camicia/.meta/config.json
@@ -0,0 +1,19 @@
+{
+ "authors": [
+ "BNAndras"
+ ],
+ "files": {
+ "solution": [
+ "Camicia.fs"
+ ],
+ "test": [
+ "CamiciaTests.fs"
+ ],
+ "example": [
+ ".meta/Example.fs"
+ ]
+ },
+ "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"
+}
diff --git a/exercises/practice/camicia/.meta/tests.toml b/exercises/practice/camicia/.meta/tests.toml
new file mode 100644
index 000000000..18d3fdd99
--- /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/Camicia.fs b/exercises/practice/camicia/Camicia.fs
new file mode 100644
index 000000000..9be39c49e
--- /dev/null
+++ b/exercises/practice/camicia/Camicia.fs
@@ -0,0 +1,9 @@
+module Camicia
+
+type GameStatus =
+ | Finished
+ | Loop
+
+type GameResult = { Status: GameStatus; Tricks: int; Cards: int }
+
+let simulateGame (playerA: string array) (playerB: string array) = failwith "You need to implement this function."
diff --git a/exercises/practice/camicia/Camicia.fsproj b/exercises/practice/camicia/Camicia.fsproj
new file mode 100644
index 000000000..2538048e3
--- /dev/null
+++ b/exercises/practice/camicia/Camicia.fsproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+ Exercism
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exercises/practice/camicia/CamiciaTests.fs b/exercises/practice/camicia/CamiciaTests.fs
new file mode 100644
index 000000000..a4cb77002
--- /dev/null
+++ b/exercises/practice/camicia/CamiciaTests.fs
@@ -0,0 +1,337 @@
+module CamiciaTests
+
+open FsUnit.Xunit
+open Xunit
+
+open Camicia
+
+[]
+let ``Two cards, one trick`` () =
+ let playerA = [|"2"|]
+ let playerB = [|"3"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 2 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Three cards, one trick`` () =
+ let playerA = [|"2"; "4"|]
+ let playerB = [|"3"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 3 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Four cards, one trick`` () =
+ let playerA = [|"2"; "4"|]
+ let playerB = [|"3"; "5"; "6"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 4 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The ace reigns supreme`` () =
+ let playerA = [|"2"; "A"|]
+ let playerB = [|"3"; "4"; "5"; "6"; "7"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 7 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The king beats ace`` () =
+ let playerA = [|"2"; "A"|]
+ let playerB = [|"3"; "4"; "5"; "6"; "K"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 7 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The queen seduces the king`` () =
+ let playerA = [|"2"; "A"; "7"; "8"; "Q"|]
+ let playerB = [|"3"; "4"; "5"; "6"; "K"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 10 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The jack betrays the queen`` () =
+ let playerA = [|"2"; "A"; "7"; "8"; "Q"|]
+ let playerB = [|"3"; "4"; "5"; "6"; "K"; "9"; "J"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 12 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The 10 just wants to put on a show`` () =
+ let playerA = [|"2"; "A"; "7"; "8"; "Q"; "10"|]
+ let playerB = [|"3"; "4"; "5"; "6"; "K"; "9"; "J"|]
+ let expected = { Status = Finished; Tricks = 1; Cards = 13 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Simple loop with decks of 3 cards`` () =
+ let playerA = [|"J"; "2"; "3"|]
+ let playerB = [|"4"; "J"; "5"|]
+ let expected = { Status = Loop; Tricks = 3; Cards = 8 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``The story is starting to get a bit complicated`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1; Cards = 361 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Two tricks`` () =
+ let playerA = [|"J"|]
+ let playerB = [|"3"; "J"|]
+ let expected = { Status = Finished; Tricks = 2; Cards = 5 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``More tricks`` () =
+ let playerA = [|"J"; "2"; "4"|]
+ let playerB = [|"3"; "J"; "A"|]
+ let expected = { Status = Finished; Tricks = 4; Cards = 12 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Simple loop with decks of 4 cards`` () =
+ let playerA = [|"2"; "3"; "J"; "6"|]
+ let playerB = [|"K"; "5"; "J"; "7"|]
+ let expected = { Status = Loop; Tricks = 4; Cards = 16 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Easy card combination`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 4; Cards = 40 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Easy card combination, inverted decks`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 4; Cards = 40 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Mirrored decks`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 4; Cards = 59 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Opposite decks`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 21; Cards = 151 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Random decks #1`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 76; Cards = 542 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Random decks #2`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 42; Cards = 327 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Kleber 1999`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 805; Cards = 5790 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Collins 2006`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 960; Cards = 6913 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Mann and Wu 2007`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1007; Cards = 7157 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Nessler 2012`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1015; Cards = 7207 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Anderson 2013`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1016; Cards = 7225 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Rucklidge 2014`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1122; Cards = 7959 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Nessler 2021`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1106; Cards = 7972 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Nessler 2022`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Finished; Tricks = 1164; Cards = 8344 }
+ simulateGame playerA playerB |> should equal expected
+
+[]
+let ``Casella 2024, first infinite game found`` () =
+ let 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" |]
+ let 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" |]
+ let expected = { Status = Loop; Tricks = 66; Cards = 474 }
+ simulateGame playerA playerB |> should equal expected
+
diff --git a/generators/Generators.fs b/generators/Generators.fs
index 59fccf3f4..b53f0fbc7 100644
--- a/generators/Generators.fs
+++ b/generators/Generators.fs
@@ -240,6 +240,54 @@ type Change() =
| _ -> None
| _ -> None
+type Camicia() =
+ inherit ExerciseGenerator()
+
+ let cardsPerLine = 8
+ let continuationIndent = String.replicate 18 " "
+
+ let renderHandRows rows =
+ let lastRow = (List.length rows) - 1
+
+ rows
+ |> List.mapi (fun index row ->
+ let prefix =
+ if index = 0 then "[| " else continuationIndent
+
+ let suffix =
+ if index = lastRow then " |]" else ";"
+
+ $"%s{prefix}%s{row}%s{suffix}")
+ |> String.concat "\n"
+
+ let renderHand value =
+ let cards =
+ value
+ |> Seq.map Obj.render
+ |> Seq.toList
+
+ if cards.Length <= cardsPerLine then
+ Array.renderStrings cards
+ else
+ cards
+ |> List.chunkBySize cardsPerLine
+ |> List.map (String.concat "; ")
+ |> renderHandRows
+
+ override _.RenderExpected(_, _, value) =
+ let status =
+ value.["status"].ToObject()
+ |> String.upperCaseFirst
+
+ let tricks = value.["tricks"].ToObject()
+ let cards = value.["cards"].ToObject()
+
+ $"{{ Status = %s{status}; Tricks = %i{tricks}; Cards = %i{cards} }}"
+
+ override _.RenderInput(_, _, value) = renderHand value
+
+ override this.PropertiesWithIdentifier testCase = this.Properties testCase
+
type CircularBuffer() =
inherit ExerciseGenerator()