Camicia

Camicia

Médio

Introdução

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

Instruções

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

Regras

  • O baralho é dividido entre dois jogadores. As cartas do jogador leem-se da esquerda para a direita, sendo a carta mais à esquerda o topo do baralho.
  • Uma ronda consiste em ambos os jogadores jogarem pelo menos uma carta.
  • Os jogadores colocam alternadamente a carta do topo do seu baralho numa pilha central.
  • Se a carta for uma carta numérica (2-10), a jogada passa simplesmente para o outro jogador.
  • Se a carta for uma carta de pagamento, é preciso pagar uma penalização:
    • J → o adversário tem de pagar 1 carta
    • Q → o adversário tem de pagar 2 cartas
    • K → o adversário tem de pagar 3 cartas
    • A → o adversário tem de pagar 4 cartas
  • Se o jogador que está a pagar uma penalização revelar outra carta de pagamento, esse jogador deixa de pagar a penalização. O outro jogador tem então de pagar uma penalização com base na nova carta de pagamento.
  • Se a penalização for paga na totalidade sem interrupção, o jogador que colocou a última carta de pagamento recolhe a pilha central e coloca-a no fundo do seu baralho. Esse jogador começa então a ronda seguinte.
  • Se um jogador ficar sem cartas e não conseguir jogar uma carta (quer durante o pagamento de uma penalização, quer quando é a sua vez), o outro jogador recolhe a pilha central.
  • O momento em que um jogador recolhe as cartas da pilha central chama-se uma vaza.
  • Se, depois de uma vaza, um jogador tiver todas as cartas na sua posse, o jogo termina.
  • O jogo entra num ciclo assim que os baralhos forem idênticos ao que eram anteriormente durante o jogo, sem contar as cartas numéricas!

Exemplos

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

A tua tarefa

  • Com base nos dados de entrada, simula o jogo seguindo as regras acima.
  • Determina as seguintes informações sobre o jogo:
    • Status: "finished" ou "loop"
    • Cards: número total de cartas jogadas ao longo do jogo
    • Tricks: número de vezes que a pilha central foi recolhida
Advanced

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!

Prós e contras de 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.

Dicas

Mostrar dicas

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.

Editar via GitHub A ligação abre numa nova janela ou separador
Roc Exercism

Estás pronto para começar Camicia?

Inscreve-te no Exercism para aprenderes e dominares Roc com 135 exercícios, e mentoria humana real, tudo grátis.