Camicia

Camicia

Intermedia

Introducción

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...

Instrucciones

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á).

Reglas

  • El mazo se reparte entre dos jugadores. Las cartas del jugador se leen de izquierda a derecha, donde la carta más a la izquierda es la parte superior del mazo.
  • Una ronda consiste en que ambos jugadores jueguen al menos una carta.
  • Los jugadores se turnan para poner la carta superior de su mazo en una pila central.
  • Si la carta es una carta numérica (2-10), el turno simplemente pasa al otro jugador.
  • Si la carta es una carta de pago, se debe pagar una penalización:
    • J → el oponente debe pagar 1 carta
    • Q → el oponente debe pagar 2 cartas
    • K → el oponente debe pagar 3 cartas
    • A → el oponente debe pagar 4 cartas
  • Si el jugador que está pagando una penalización revela otra carta de pago, ese jugador deja de pagar la penalización. Entonces, el otro jugador debe pagar una penalización basada en la nueva carta de pago.
  • Si la penalización se paga por completo sin interrupciones, el jugador que puso la última carta de pago recoge la pila central y la coloca en la parte inferior de su mazo. Ese jugador comienza la siguiente ronda.
  • Si un jugador se queda sin cartas y no puede jugar una carta (ya sea mientras paga una penalización o cuando es su turno), el otro jugador recoge la pila central.
  • El momento en que un jugador recoge cartas de la pila central se llama baza.
  • Si un jugador tiene todas las cartas en su posesión después de una baza, el juego termina.
  • El juego entra en un bucle tan pronto como los mazos sean idénticos a como eran antes durante la partida, ¡sin contar las cartas numéricas!

Ejemplos

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

Tu tarea

  • Usando los datos de entrada, simula el juego siguiendo las reglas anteriores.
  • Determina la siguiente información sobre el juego:
    • Estado: "finished" o "loop"
    • Cartas: número total de cartas jugadas a lo largo de la partida
    • Bazas: número de veces que se recogió la pila central
Advanced

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!

Pros y contras de 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.

Pistas

Mostrar pistas

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.

Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Roc Exercism

¿Todo listo para empezar Camicia?

Regístrate en Exercism para aprender y dominar Roc con 135 ejercicios y mentoría humana real, todo gratis.