Par un après-midi pluvieux, tu es assis à la table de la cuisine et tu joues aux cartes avec ta grand-mère. Le jeu est sa version personnelle de Camicia.
Au début, cela ressemble à une simple partie amicale : des cartes abattues, des rires autour de la table, et de temps en temps un sourire victorieux de Nonna. Mais à mesure que la partie s'éternise, quelque chose d'étrange se produit. Les mêmes cartes ne cessent de revenir. Tu joues carte après carte, et pourtant la fin ne semble jamais arriver.
Tu commences à te demander. Cette partie va-t-elle un jour se terminer ? Ou pourrait-on jouer ainsi pour toujours ?
Plus tard, poussé par la curiosité, tu fais des recherches en ligne et, à ta grande surprise, tu découvres que ce qui s'est passé n'était pas qu'une question de malchance. Toi et ta grand-mère êtes peut-être tombés sur l'une des plus longues séquences possibles ! Soudain, te voilà accro. Ce qui avait commencé comme une simple partie s'est transformé en quête : combien de temps un tel jeu peut-il vraiment durer ? Peux-tu trouver une séquence encore plus longue que celle que tu as jouée à la table de la cuisine ? Peut-être même assez longue pour établir un nouveau record du monde ?
Et donc, armé de rien d'autre qu'un jeu de cartes et d'un peu d'ingéniosité algorithmique, tu décides de mener l'enquête...
Dans cet exercice, tu vas simuler un jeu très proche du jeu de cartes classique Camicia. Ton programme recevra la configuration initiale des paquets de deux joueurs et devra simuler la partie jusqu'à ce qu'elle se termine (ou détecter qu'elle ne se terminera jamais).
Un petit exemple de partie qui se termine.
| Tour | Joueur A | Joueur B | Tas | Pénalité à payer |
|---|---|---|---|---|
| 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 | Joueur B : 4 |
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Joueur B : 3 |
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Joueur B : 2 |
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Joueur B : 1 |
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Joueur A : 3 |
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Joueur A : 2 |
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Joueur A : 1 |
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Joueur B : 2 |
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Joueur B : 1 |
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Joueur 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
Voici un petit exemple de partie qui boucle.
| Tour | Joueur A | Joueur B | Tas | Pénalité à payer |
|---|---|---|---|---|
| 1 | J 2 3 | 4 J 5 | - | - |
| 1 | 2 3 | 4 J 5 | J | Joueur 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 | Joueur 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 | Joueur B : 1 |
| 3 | 4 | J 3 | 5 J 2 | - |
| 4 | 4 5 J 2 | J 3 | - | - |
Le début du tour 4 est identique au début du tour 2. Rappel : la valeur des cartes numérotées n'a pas d'importance.
status: "loop", cards: 8, tricks: 3
"finished" ou "loop"
Pour celles et ceux qui veulent s'attaquer à un défi plus palpitant, la chasse aux autres records de la plus longue partie qui se termine est encore ouverte. Il y a 653 534 134 886 878 245 000 possibilités (environ 654 trillions), et on ne les a pas encore toutes calculées !
from_quote
La fonction Card.from_quote analyse une string pour en faire une Card.
Cela permet d'écrire du code comme celui-ci :
card : Card
card = "Q"
Ce code appelle implicitement Card.from_quote("Q") à la compilation. Exécuter du code à la compilation avec cette approche (ou avec les autres méthodes décrites ci-dessous) peut potentiellement accélérer le programme, puisqu'il n'a alors plus besoin de gérer l'analyse et la validation des données à l'exécution. De plus, cela garantit que les données invalides (comme un format de date incorrect) sont rejetées à la compilation plutôt qu'à l'exécution, ce qui rend ton application plus robuste. Enfin, cela rend le code agréable à lire. Par exemple, voici l'un des tests de cet exercice :
result = simulate_game({
player_a: ["2", "A"],
player_b: ["3", "4", "5", "6", "K"],
})
Cependant, une personne qui lit ce code pourrait supposer que la fonction simulate_game prend un enregistrement contenant deux List(Str), alors qu'en réalité il s'agit d'un enregistrement contenant deux List(Card). Cela peut prêter à confusion et provoquer des erreurs. Pour cette raison, tu préféreras peut-être la syntaxe un peu plus longue mais plus explicite : "Q".Card, qui appelle elle aussi from_quote à la compilation.
Dans tes propres programmes, tu préféreras peut-être utiliser from_str à la place :
Card := { ... }.{
from_str : Str -> Try(Card, [InvalidCard])
from_str = |card_str| ...
}
Tu peux aussi appeler from_str (ou n'importe quelle autre fonction pure) à la compilation, comme ceci :
Ok(card) = Card.from_str("Q")
Remarque : placer cette définition au niveau supérieur fait qu'elle s'exécute à la compilation (puisque la fonction est pure et que son entrée est connue à la compilation). À l'intérieur d'une fonction, tu peux utiliser from_str à l'exécution.
Utilise match avec [] et [card, .. as rest] pour distinguer un paquet vide de sa carte suivante et des cartes restantes.
Les mises à jour d'enregistrement telles que { ..state, pile: new_pile } permettent de garder des transitions de jeu explicites, sans état mutable.
Un Set peut stocker des tuples ou des tableaux comme clés lorsqu'on suit les états de jeu déjà rencontrés.
Inscris-toi sur Exercism pour apprendre et maîtriser Roc avec 135 exercices, et un vrai mentorat humain, le tout gratuitement.