Numa tarde chuvosa, estás sentado à mesa da cozinha a jogar às cartas com a tua avó. O jogo é a versão dela do Camicia.
Ao princípio, parece apenas mais um jogo amigável: cartas batidas na mesa, risos de um lado ao outro da mesa, e um ou outro sorriso vitorioso da Nonna. Mas, à medida que o jogo se arrasta, acontece algo estranho. As mesmas cartas não param de voltar. Jogas carta atrás de carta, mas o fim nunca parece chegar.
Começas a pensar. Será que este jogo acaba? Ou podíamos continuar a jogar para sempre?
Mais tarde, a curiosidade leva-te a procurar na internet e, para tua surpresa, descobres que o que aconteceu não foi só má sorte. Tu e a tua avó podem ter tropeçado numa das sequências mais longas possíveis! De repente, não consegues largar o assunto. O que começou como um jogo descontraído transformou-se numa busca: quanto tempo pode durar realmente um jogo destes? Consegues encontrar uma sequência ainda mais longa do que a que jogaram à mesa da cozinha? Talvez até longa o suficiente para bater um novo recorde mundial?
E assim, com nada mais do que um baralho de cartas e alguma engenhosidade algorítmica, decides investigar...
Neste exercício, vais simular um jogo muito semelhante ao clássico jogo de cartas Camicia. O teu programa vai receber a configuração inicial dos baralhos de dois jogadores e tem de simular o jogo até este terminar (ou detetar que nunca vai terminar).
Um pequeno exemplo de uma partida que termina.
| Ronda | Jogador A | Jogador B | Pilha | Penalização devida |
|---|---|---|---|---|
| 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 | Jogador B: 4 |
| 1 | 7 8 Q 10 | 5 6 K 9 J | 2 3 A 4 | Jogador B: 3 |
| 1 | 7 8 Q 10 | 6 K 9 J | 2 3 A 4 5 | Jogador B: 2 |
| 1 | 7 8 Q 10 | K 9 J | 2 3 A 4 5 6 | Jogador B: 1 |
| 1 | 7 8 Q 10 | 9 J | 2 3 A 4 5 6 K | Jogador A: 3 |
| 1 | 8 Q 10 | 9 J | 2 3 A 4 5 6 K 7 | Jogador A: 2 |
| 1 | Q 10 | 9 J | 2 3 A 4 5 6 K 7 8 | Jogador A: 1 |
| 1 | 10 | 9 J | 2 3 A 4 5 6 K 7 8 Q | Jogador B: 2 |
| 1 | 10 | J | 2 3 A 4 5 6 K 7 8 Q 9 | Jogador B: 1 |
| 1 | 10 | - | 2 3 A 4 5 6 K 7 8 Q 9 J | Jogador 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
Este é um pequeno exemplo de uma partida que entra em ciclo.
| Ronda | Jogador A | Jogador B | Pilha | Penalização devida |
|---|---|---|---|---|
| 1 | J 2 3 | 4 J 5 | - | - |
| 1 | 2 3 | 4 J 5 | J | Jogador 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 | Jogador 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 | Jogador B: 1 |
| 3 | 4 | J 3 | 5 J 2 | - |
| 4 | 4 5 J 2 | J 3 | - | - |
O início da ronda 4 é igual ao início da ronda 2. Lembra-te: o valor das cartas numéricas não importa.
status: "loop", cards: 8, tricks: 3
"finished" ou "loop"
Para quem quiser aceitar um desafio mais emocionante, a procura de outros recordes para o jogo mais longo com fim continua em aberto. Há 653 534 134 886 878 245 000 (aproximadamente 654 triliões) possibilidades, e ainda não as calculámos todas!
from_quote
A função Card.from_quote analisa uma string e transforma-a num Card.
Isto permite escrever código como este:
card : Card
card = "Q"
Este código chama implicitamente Card.from_quote("Q") em tempo de compilação.
Executar código em tempo de compilação com esta abordagem (ou com outros métodos descritos abaixo) pode acelerar o programa, uma vez que não é preciso tratar a análise e a validação dos dados em tempo de execução.
Além disso, garante que os dados inválidos (como um formato de data incorreto) são rejeitados em tempo de compilação, em vez de em tempo de execução, o que torna a tua aplicação mais robusta.
Por último, torna o código agradável e legível.
Por exemplo, este é um dos testes deste exercício:
result = simulate_game({
player_a: ["2", "A"],
player_b: ["3", "4", "5", "6", "K"],
})
No entanto, quem lê este código pode assumir que a função simulate_game recebe um registo com dois List(Str), quando na verdade é um registo com dois List(Card).
Isto pode dar origem a confusão e erros.
Por isso, podes preferir o formato um pouco mais longo mas mais explícito: "Q".Card, que também chama from_quote em tempo de compilação.
Nos teus próprios programas, podes preferir usar from_str:
Card := { ... }.{
from_str : Str -> Try(Card, [InvalidCard])
from_str = |card_str| ...
}
Também podes chamar from_str (ou qualquer outra função pura) em tempo de compilação, assim:
Ok(card) = Card.from_str("Q")
Nota: colocar esta definição ao nível superior faz com que seja executada em tempo de compilação (uma vez que a função é pura e o seu parâmetro de entrada é conhecido em tempo de compilação).
Dentro de uma função, podes usar from_str em tempo de execução.
Usa match com [] e [card, .. as rest] para distinguir um baralho vazio da sua próxima carta e das cartas restantes.
Atualizações de registos como { ..state, pile: new_pile } podem manter as transições do jogo explícitas sem estado mutável.
Um Set pode guardar tuplos ou listas como chaves para acompanhar estados do jogo já vistos.
Inscreve-te no Exercism para aprenderes e dominares Roc com 135 exercícios, e mentoria humana real, tudo grátis.