Skip to content

Commit 21e60d8

Browse files
authored
Implement Camicia Practice Exercise (#3105)
* Initial setup * Add Camicia class & Implement tests * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Implement solution * Move solution to the .meta package * Move to .meta/src/reference/java/ * Config Camicia exercise * End file with a new line * Update author * Make simulateGame static and return result record * Update solution and disable more tests * Update gradle version * Update gradle * Update result record, config, instructions and tests * Format config
1 parent 552bda8 commit 21e60d8

16 files changed

Lines changed: 1303 additions & 0 deletions

File tree

config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,19 @@
12751275
],
12761276
"difficulty": 6
12771277
},
1278+
{
1279+
"slug": "camicia",
1280+
"name": "Camicia",
1281+
"uuid": "b4f7c3b0-6d3c-45e2-a328-05e09c7467f4",
1282+
"practices": [],
1283+
"prerequisites": [
1284+
"strings",
1285+
"for-loops",
1286+
"arrays",
1287+
"if-else-statements"
1288+
],
1289+
"difficulty": 6
1290+
},
12781291
{
12791292
"slug": "etl",
12801293
"name": "ETL",
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Instructions
2+
3+
In this exercise, you will simulate a game very similar to the classic card game **Camicia**.
4+
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).
5+
6+
## Rules
7+
8+
- The deck is split between **two players**.
9+
The player's cards are read from left to right, where the leftmost card is the top of the deck.
10+
- A round consists of both players playing at least one card.
11+
- Players take turns placing the **top card** of their deck onto a central pile.
12+
- If the card is a **number card** (2-10), play simply passes to the other player.
13+
- If the card is a **payment card**, a penalty must be paid:
14+
- **J** → opponent must pay 1 card
15+
- **Q** → opponent must pay 2 cards
16+
- **K** → opponent must pay 3 cards
17+
- **A** → opponent must pay 4 cards
18+
- If the player paying a penalty reveals another payment card, that player stops paying the penalty.
19+
The other player must then pay a penalty based on the new payment card.
20+
- 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.
21+
That player then starts the next round.
22+
- 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.
23+
- The moment when a player collects cards from the central pile is called a **trick**.
24+
- If a player has all the cards in their possession after a trick, the game **ends**.
25+
- The game **enters a loop** as soon as the decks are identical to what they were earlier during the game, **not** counting number cards!
26+
27+
## Examples
28+
29+
A small example of a match that ends.
30+
31+
| Round | Player A | Player B | Pile | Penalty Due |
32+
| :---- | :----------- | :------------------------- | :------------------------- | :---------- |
33+
| 1 | 2 A 7 8 Q 10 | 3 4 5 6 K 9 J | | - |
34+
| 1 | A 7 8 Q 10 | 3 4 5 6 K 9 J | 2 | - |
35+
| 1 | A 7 8 Q 10 | 4 5 6 K 9 J | 2 3 | - |
36+
| 1 | 7 8 Q 10 | 4 5 6 K 9 J | 2 3 A | Player B: 4 |
37+
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Player B: 3 |
38+
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Player B: 2 |
39+
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Player B: 1 |
40+
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Player A: 3 |
41+
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Player A: 2 |
42+
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Player A: 1 |
43+
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Player B: 2 |
44+
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Player B: 1 |
45+
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Player A: 1 |
46+
| 1 | - | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - |
47+
| 2 | - | 2 3 A 4 5 6 K 7 8 Q 9 J 10 | - | - |
48+
49+
status: `"finished"`, cards: 13, tricks: 1
50+
51+
This is a small example of a match that loops.
52+
53+
| Round | Player A | Player B | Pile | Penalty Due |
54+
| :---- | :------- | :------- | :---- | :---------- |
55+
| 1 | J 2 3 | 4 J 5 | - | - |
56+
| 1 | 2 3 | 4 J 5 | J | Player B: 1 |
57+
| 1 | 2 3 | J 5 | J 4 | - |
58+
| 2 | 2 3 J 4 | J 5 | - | - |
59+
| 2 | 3 J 4 | J 5 | 2 | - |
60+
| 2 | 3 J 4 | 5 | 2 J | Player A: 1 |
61+
| 2 | J 4 | 5 | 2 J 3 | - |
62+
| 3 | J 4 | 5 2 J 3 | - | - |
63+
| 3 | J 4 | 2 J 3 | 5 | - |
64+
| 3 | 4 | 2 J 3 | 5 J | Player B: 1 |
65+
| 3 | 4 | J 3 | 5 J 2 | - |
66+
| 4 | 4 5 J 2 | J 3 | - | - |
67+
68+
The start of round 4 matches the start of round 2.
69+
Recall, the value of the number cards does not matter.
70+
71+
status: `"loop"`, cards: 8, tricks: 3
72+
73+
## Your Task
74+
75+
- Using the input, simulate the game following the rules above.
76+
- Determine the following information regarding the game:
77+
- **Status**: `"finished"` or `"loop"`
78+
- **Cards**: total number of cards played throughout the game
79+
- **Tricks**: number of times the central pile was collected
80+
81+
~~~~exercism/advanced
82+
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.
83+
There are 653,534,134,886,878,245,000 (approximately 654 quintillion) possibilities, and we haven't calculated them all yet!
84+
~~~~
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Introduction
2+
3+
One rainy afternoon, you sit at the kitchen table playing cards with your grandmother.
4+
The game is her take on [Camicia][bmn].
5+
6+
At first it feels like just another friendly match: cards slapped down, laughter across the table, the occasional victorious grin from Nonna.
7+
But as the game stretches on, something strange happens.
8+
The same cards keep cycling back.
9+
You play card after card, yet the end never seems to come.
10+
11+
You start to wonder.
12+
_Will this game ever finish?
13+
Or could we keep playing forever?_
14+
15+
Later, driven by curiosity, you search online and to your surprise you discover that what happened wasn't just bad luck.
16+
You and your grandmother may have stumbled upon one of the longest possible sequences!
17+
Suddenly, you're hooked.
18+
What began as a casual game has turned into a quest: _how long can such a game really last?_
19+
_Can you find a sequence even longer than the one you played at the kitchen table?_
20+
_Perhaps even long enough to set a new world record?_
21+
22+
And so, armed with nothing but a deck of cards and some algorithmic ingenuity, you decide to investigate...
23+
24+
[bmn]: https://en.wikipedia.org/wiki/Beggar-my-neighbour
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"authors": [
3+
"thibault2705"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/main/java/Camicia.java",
8+
"src/main/java/CamiciaResult.java"
9+
],
10+
"test": [
11+
"src/test/java/CamiciaTest.java"
12+
],
13+
"example": [
14+
".meta/src/reference/java/Camicia.java",
15+
".meta/src/reference/java/CamiciaResult.java"
16+
],
17+
"invalidator": [
18+
"build.gradle"
19+
]
20+
},
21+
"blurb": "Simulate the card game and determine whether the match ends or enters an infinite loop.",
22+
"source": "Beggar-My-Neighbour",
23+
"source_url": "https://www.richardpmann.com/beggar-my-neighbour-records.html"
24+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import java.util.ArrayDeque;
2+
import java.util.Deque;
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
7+
public class Camicia {
8+
9+
private Status status;
10+
private int cards;
11+
private int tricks;
12+
13+
private enum Player {
14+
PLAYER_A, PLAYER_B
15+
}
16+
17+
private enum Status {
18+
FINISHED, LOOP
19+
}
20+
21+
private static int penaltyOf(String card) {
22+
return switch (card) {
23+
case "J" -> 1;
24+
case "Q" -> 2;
25+
case "K" -> 3;
26+
case "A" -> 4;
27+
default -> 0;
28+
};
29+
}
30+
31+
private static boolean isPaymentCard(String card) {
32+
return penaltyOf(card) > 0;
33+
}
34+
35+
/**
36+
* Return a snapshot of the current state
37+
*/
38+
private static String stateKey(Deque<String> deckA, Deque<String> deckB) {
39+
StringBuilder sb = new StringBuilder();
40+
41+
for (String card : deckA) {
42+
sb.append(isPaymentCard(card) ? card : "N");
43+
}
44+
45+
sb.append('|');
46+
47+
for (String card : deckB) {
48+
sb.append(isPaymentCard(card) ? card : "N");
49+
}
50+
51+
return sb.toString();
52+
}
53+
54+
private static Player otherPlayer(Player player) {
55+
return player == Player.PLAYER_A ? Player.PLAYER_B : Player.PLAYER_A;
56+
}
57+
58+
private static Deque<String> deckOf(Player player, Deque<String> deckA, Deque<String> deckB) {
59+
return player == Player.PLAYER_A ? deckA : deckB;
60+
}
61+
62+
public static CamiciaResult simulateGame(List<String> playerA, List<String> playerB) {
63+
Deque<String> deckA = new ArrayDeque<>(playerA);
64+
Deque<String> deckB = new ArrayDeque<>(playerB);
65+
66+
int cardsPlayed = 0;
67+
int tricksCount = 0;
68+
Player current = Player.PLAYER_A;
69+
70+
Set<String> seenStates = new HashSet<>();
71+
72+
while (true) {
73+
String key = stateKey(deckA, deckB);
74+
// Key already exists, which means this is a loop
75+
if (!seenStates.add(key)) {
76+
return finishGame(Status.LOOP, cardsPlayed, tricksCount);
77+
}
78+
79+
// Otherwise, play next round
80+
RoundResult result = playRound(deckA, deckB, current);
81+
82+
cardsPlayed += result.pileSize();
83+
tricksCount++;
84+
85+
// Check if someone wins and finish the game
86+
if (hasWinner(deckA, deckB)) {
87+
return finishGame(Status.FINISHED, cardsPlayed, tricksCount);
88+
}
89+
90+
// Otherwise, play next round
91+
current = result.nextStarter();
92+
}
93+
}
94+
95+
private static RoundResult playRound(Deque<String> deckA, Deque<String> deckB, Player startingPlayer) {
96+
Deque<String> pile = new ArrayDeque<>(); // cards played in this round
97+
Player currentPlayer = startingPlayer;
98+
int pendingPenalty = 0;
99+
100+
while (true) {
101+
Deque<String> currentPlayerDeck = deckOf(currentPlayer, deckA, deckB);
102+
Player opponent = otherPlayer(currentPlayer);
103+
Deque<String> opponentDeck = deckOf(opponent, deckA, deckB);
104+
105+
// Current player deck is empty, opponent collects all pile, end this round
106+
if (currentPlayerDeck.isEmpty()) {
107+
opponentDeck.addAll(pile);
108+
return new RoundResult(opponent, pile.size());
109+
}
110+
111+
// Otherwise, current player plays 1 card, add to pile
112+
String card = currentPlayerDeck.poll();
113+
pile.addLast(card);
114+
115+
// Current player must pay off pending penalty
116+
if (pendingPenalty > 0) {
117+
// And player reveals a payment card
118+
if (isPaymentCard(card)) {
119+
// reset penalty based on new card, switch turn
120+
pendingPenalty = penaltyOf(card);
121+
currentPlayer = opponent;
122+
} else {
123+
// Otherwise, deduct penalty
124+
pendingPenalty--;
125+
// No pending penalty
126+
if (pendingPenalty == 0) {
127+
// Opponent collects all pile and win the round
128+
deckOf(opponent, deckA, deckB).addAll(pile);
129+
return new RoundResult(opponent, pile.size());
130+
}
131+
}
132+
} else {
133+
// Normal gameplay, without pending penalty
134+
// If player reveals a payment card, update penalty
135+
if (isPaymentCard(card)) {
136+
pendingPenalty = penaltyOf(card);
137+
}
138+
currentPlayer = opponent;
139+
}
140+
}
141+
}
142+
143+
private static CamiciaResult finishGame(Status status, int cardsPlayed, int tricksCount) {
144+
return new CamiciaResult(status == null ? null : status.toString().toLowerCase(), cardsPlayed, tricksCount);
145+
}
146+
147+
private static boolean hasWinner(Deque<String> deckA, Deque<String> deckB) {
148+
return deckA.isEmpty() || deckB.isEmpty();
149+
}
150+
151+
/**
152+
* Immutable round result
153+
*/
154+
private record RoundResult(Player nextStarter, int pileSize) {
155+
}
156+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* {@link CamiciaResult} shows the result of a Camicia game
3+
*/
4+
public record CamiciaResult(String status, int cards, int tricks) {
5+
6+
}

0 commit comments

Comments
 (0)