Camicia

Camicia

Médio

Introdução

Em uma tarde chuvosa, você está à mesa da cozinha jogando cartas com a sua avó. O jogo é a versão dela de Camicia.

No começo, parece só mais uma partida amistosa: cartas batendo na mesa, risadas ao redor e, de vez em quando, um sorriso vitorioso da Nonna. Mas, conforme o jogo se arrasta, algo estranho acontece. As mesmas cartas continuam voltando. Você joga carta após carta, mas o fim parece nunca chegar.

Você começa a se perguntar. Será que esse jogo algum dia vai acabar? Ou será que poderíamos continuar jogando para sempre?

Mais tarde, a curiosidade fala mais alto e você pesquisa na internet. Para a sua surpresa, descobre que o que aconteceu não foi só azar. Você e a sua avó podem ter esbarrado em uma das sequências mais longas possíveis! De repente, você não consegue mais parar. O que começou como um jogo casual virou uma missão: quanto tempo um jogo assim pode realmente durar? Você consegue encontrar uma sequência ainda mais longa do que a que jogou à mesa da cozinha? Talvez até longa o suficiente para bater um novo recorde mundial?

E assim, com nada além de um baralho e um pouco de engenhosidade algorítmica, você decide investigar...

Instruções

Neste exercício, você vai simular um jogo muito parecido com o clássico jogo de cartas Camicia. Seu programa vai receber a configuração inicial dos baralhos de dois jogadores e deve simular o jogo até que ele termine (ou detectar que ele nunca vai terminar).

Regras

  • O baralho é dividido entre dois jogadores. As cartas do jogador são lidas da esquerda para a direita, onde a carta mais à esquerda é o topo do baralho.
  • Uma rodada consiste em ambos os jogadores jogarem pelo menos uma carta.
  • Os jogadores se revezam colocando a carta do topo do seu baralho em uma pilha central.
  • Se a carta for uma carta numérica (2-10), o jogo simplesmente passa para o outro jogador.
  • Se a carta for uma carta de pagamento, uma penalidade deve ser paga:
    • J → o oponente deve pagar 1 carta
    • Q → o oponente deve pagar 2 cartas
    • K → o oponente deve pagar 3 cartas
    • A → o oponente deve pagar 4 cartas
  • Se o jogador que está pagando uma penalidade revelar outra carta de pagamento, esse jogador para de pagar a penalidade. O outro jogador deve então pagar uma penalidade com base na nova carta de pagamento.
  • Se a penalidade for totalmente paga sem interrupção, o jogador que colocou a última carta de pagamento recolhe a pilha central e a coloca na parte de baixo do seu baralho. Esse jogador então começa a próxima rodada.
  • Se um jogador ficar sem cartas e não conseguir jogar uma carta (seja enquanto paga uma penalidade ou quando for a sua vez), o outro jogador recolhe a pilha central.
  • O momento em que um jogador recolhe cartas da pilha central é chamado de trick.
  • Se um jogador tiver todas as cartas em sua posse depois de um trick, o jogo termina.
  • O jogo entra em loop assim que os baralhos estiverem idênticos ao que estavam anteriormente durante o jogo, não contando as cartas numéricas!

Exemplos

Um pequeno exemplo de uma partida que termina.

Rodada Jogador A Jogador B Pilha Penalidade 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 loop.

Rodada Jogador A Jogador B Pilha Penalidade 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 rodada 4 coincide com o início da rodada 2. Lembre-se, o valor das cartas numéricas não importa.

status: "loop", cards: 8, tricks: 3

Sua tarefa

  • Usando a entrada, simule o jogo seguindo as regras acima.
  • Determine 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 quer encarar um desafio mais empolgante, a busca por outros recordes para o jogo mais longo com final ainda está aberta. Existem 653.534.134.886.878.245.000 (aproximadamente 654 quintilhões) de possibilidades, e ainda não calculamos todas!

Prós e contras de from_quote

A função Card.from_quote converte uma string em um Card. Isso permite escrever um código como este:

card : Card
card = "Q"

Esse código chama Card.from_quote("Q") implicitamente em tempo de compilação. Executar código em tempo de compilação com essa abordagem (ou com outros métodos descritos abaixo) pode deixar o programa mais rápido, já que ele não precisa lidar com parsing e validação de dados em tempo de execução. Além disso, garante que dados inválidos (como um formato de data incorreto) sejam rejeitados em tempo de compilação, e não em tempo de execução, o que deixa seu app mais robusto. Por fim, deixa o código agradável e legível. Por exemplo, veja um dos testes deste exercício:

result = simulate_game({
    player_a: ["2", "A"],
    player_b: ["3", "4", "5", "6", "K"],
})

Porém, quem lê esse código pode supor que a função simulate_game recebe um registro contendo dois List(Str), quando na verdade é um registro contendo dois List(Card). Isso pode gerar confusão e erros. Por esse motivo, você pode preferir o formato um pouco mais longo, porém mais explícito: "Q".Card, que também chama from_quote em tempo de compilação.

Nos seus próprios programas, você pode preferir usar from_str:

Card := { ... }.{
    from_str : Str -> Try(Card, [InvalidCard])
    from_str = |card_str| ...
}

Você também pode chamar from_str (ou qualquer outra função pura) em tempo de compilação, assim:

Ok(card) = Card.from_str("Q")

Observação: colocar essa definição no nível superior faz com que ela seja executada em tempo de compilação (já que a função é pura e sua entrada é conhecida em tempo de compilação). Dentro de uma função, você pode usar from_str em tempo de execução.

Dicas

Mostrar dicas

Use match com [] e [card, .. as rest] para distinguir um baralho vazio da próxima carta e das cartas restantes. Atualizações de registro como { ..state, pile: new_pile } podem manter as transições do jogo explícitas sem estado mutável. Um Set pode armazenar tuplas ou listas como chaves ao rastrear estados do jogo já vistos.

Editar via GitHub O link abre em uma nova janela ou aba
Roc Exercism

Tudo pronto para começar Camicia?

Crie sua conta no Exercism para aprender e dominar Roc com 135 exercícios e mentoria humana de verdade, tudo de graça.