Una tarde lluviosa, estás en la mesa de la cocina jugando a las cartas con tu abuela. El juego es su propia versión de Camicia.
Al principio se siente como una partida amistosa más: cartas lanzadas sobre la mesa, risas de un lado a otro y, de vez en cuando, una sonrisa victoriosa de Nonna. Pero a medida que el juego se alarga, pasa algo extraño. Las mismas cartas no dejan de volver. Juegas carta tras carta, pero el final nunca parece llegar.
Empiezas a preguntarte. ¿Terminará alguna vez este juego? ¿O podríamos seguir jugando para siempre?
Más tarde, la curiosidad te lleva a buscar en internet y, para tu sorpresa, descubres que lo que pasó no fue solo mala suerte. ¡Es posible que tú y tu abuela hayan dado con una de las secuencias más largas posibles! De repente, esto te atrapa. Lo que empezó como un juego casual se ha convertido en una búsqueda: ¿cuánto puede durar realmente un juego así? ¿Puedes encontrar una secuencia aún más larga que la que jugaron en la mesa de la cocina? ¿Quizás lo bastante larga como para establecer un nuevo récord mundial?
Y así, con nada más que una baraja de cartas y algo de ingenio algorítmico, decides investigar...
En este ejercicio, vas a simular un juego muy parecido al clásico juego de cartas Camicia. Tu programa recibirá la configuración inicial de los mazos de dos jugadores y debe simular la partida hasta que termine (o detectar que nunca terminará).
Un pequeño ejemplo de una partida que termina.
| Ronda | Jugador A | Jugador B | Pila | Penalización pendiente |
|---|---|---|---|---|
| 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 | Jugador B: 4 |
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Jugador B: 3 |
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Jugador B: 2 |
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Jugador B: 1 |
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Jugador A: 3 |
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Jugador A: 2 |
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Jugador A: 1 |
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Jugador B: 2 |
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Jugador B: 1 |
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Jugador 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 | - | - |
estado: "finished", cartas: 13, bazas: 1
Este es un pequeño ejemplo de una partida que entra en un bucle.
| Ronda | Jugador A | Jugador B | Pila | Penalización pendiente |
|---|---|---|---|---|
| 1 | J 2 3 | 4 J 5 | - | - |
| 1 | 2 3 | 4 J 5 | J | Jugador 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 | Jugador 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 | Jugador B: 1 |
| 3 | 4 | J 3 | 5 J 2 | - |
| 4 | 4 5 J 2 | J 3 | - | - |
El comienzo de la ronda 4 coincide con el comienzo de la ronda 2. Recuerda: el valor de las cartas numéricas no importa.
estado: "loop", cartas: 8, bazas: 3
"finished" o "loop"
Para quienes quieran asumir un desafío más emocionante, la búsqueda de otros récords de la partida más larga con un final sigue abierta. Hay 653,534,134,886,878,245,000 (aproximadamente 654 trillones) posibilidades, ¡y todavía no las hemos calculado todas!
from_quote
La función Card.from_quote analiza un string y lo convierte en un Card.
Esto hace posible escribir código como el siguiente:
card : Card
card = "Q"
Este código llama implícitamente a Card.from_quote("Q") en tiempo de compilación.
Ejecutar código en tiempo de compilación con este enfoque (u otros métodos que se describen más abajo) puede acelerar el programa, ya que no necesita encargarse del análisis ni de la validación de datos en tiempo de ejecución.
Además, garantiza que los datos inválidos (como un formato de fecha incorrecto) se rechacen en tiempo de compilación en lugar de en tiempo de ejecución, lo que hace que tu aplicación sea más robusta.
Por último, hace que el código sea agradable y legible.
Por ejemplo, aquí tienes uno de los test de este ejercicio:
result = simulate_game({
player_a: ["2", "A"],
player_b: ["3", "4", "5", "6", "K"],
})
Sin embargo, alguien que lea este código podría suponer que la función simulate_game recibe un registro que contiene dos List(Str), cuando en realidad es un registro que contiene dos List(Card).
Esto puede generar confusión y errores.
Por eso, puede que prefieras el formato un poco más largo pero más explícito: "Q".Card, que también llama a from_quote en tiempo de compilación.
En tus propios programas, puede que prefieras usar from_str en su lugar:
Card := { ... }.{
from_str : Str -> Try(Card, [InvalidCard])
from_str = |card_str| ...
}
También puedes llamar a from_str (o a cualquier otra función pura) en tiempo de compilación, así:
Ok(card) = Card.from_str("Q")
Nota: si colocas esta definición en el nivel superior, se ejecuta en tiempo de compilación (ya que la función es pura y su argumento se conoce en tiempo de compilación).
Dentro de una función, puedes usar from_str en tiempo de ejecución.
Usa match con [] y [card, .. as rest] para distinguir un mazo vacío de su siguiente carta y de las cartas restantes.
Las actualizaciones de registro como { ..state, pile: new_pile } pueden mantener explícitas las transiciones del juego sin necesidad de estado mutable.
Un Set puede almacenar tuplas o listas como claves cuando haces seguimiento de estados del juego ya vistos.
Regístrate en Exercism para aprender y dominar Roc con 135 ejercicios y mentoría humana real, todo gratis.