Tracks
/
Roc
Roc
/
Übungen
/
Camicia
Camicia

Camicia

Mittel

Einführung

An einem regnerischen Nachmittag sitzt du mit deiner Großmutter am Küchentisch und spielst Karten. Das Spiel ist ihre Variante von Camicia.

Zuerst fühlt es sich an wie ein ganz normales Freundschaftsspiel: Karten, die auf den Tisch geklatscht werden, Gelächter am Tisch, ab und zu ein siegessicheres Grinsen von Nonna. Doch je länger das Spiel dauert, desto seltsamer wird es. Dieselben Karten kommen immer wieder zurück. Du spielst Karte um Karte, und trotzdem will das Ende nicht kommen.

Langsam fragst du dich. Wird dieses Spiel jemals enden? Oder könnten wir für immer weiterspielen?

Später suchst du aus Neugier im Internet und entdeckst zu deiner Überraschung, dass das, was passiert ist, nicht nur Pech war. Du und deine Großmutter seid vielleicht auf eine der längsten möglichen Sequenzen gestoßen! Auf einmal hat es dich gepackt. Was als harmloses Spiel begann, ist zu einer Mission geworden: Wie lange kann so ein Spiel wirklich dauern? Findest du eine Sequenz, die noch länger ist als die, die du am Küchentisch gespielt hast? Vielleicht sogar lang genug für einen neuen Weltrekord?

Und so beschließt du, nur mit einem Kartendeck und etwas algorithmischem Einfallsreichtum gewappnet, der Sache auf den Grund zu gehen...

Anleitung

In dieser Übung simulierst du ein Spiel, das dem klassischen Kartenspiel Camicia sehr ähnlich ist. Dein Programm erhält die Anfangskonfiguration der Kartenstapel zweier Spieler und muss das Spiel simulieren, bis es endet (oder erkennen, dass es nie enden wird).

Regeln

  • Der Kartenstapel wird zwischen zwei Spielern aufgeteilt. Die Karten eines Spielers werden von links nach rechts gelesen, wobei die Karte ganz links die oberste Karte des Stapels ist.
  • Eine Runde besteht darin, dass beide Spieler mindestens eine Karte ausspielen.
  • Die Spieler legen abwechselnd die oberste Karte ihres Stapels auf den Mittelstapel.
  • Ist die Karte eine Zahlenkarte (2-10), geht das Ausspielen einfach an den anderen Spieler über.
  • Ist die Karte eine Zahlkarte, muss eine Strafe gezahlt werden:
    • J → der Gegner muss 1 Karte zahlen
    • Q → der Gegner muss 2 Karten zahlen
    • K → der Gegner muss 3 Karten zahlen
    • A → der Gegner muss 4 Karten zahlen
  • Deckt der Spieler, der eine Strafe zahlt, eine weitere Zahlkarte auf, hört dieser Spieler auf, die Strafe zu zahlen. Der andere Spieler muss dann eine Strafe zahlen, die auf der neuen Zahlkarte beruht.
  • Wird die Strafe ohne Unterbrechung vollständig gezahlt, nimmt der Spieler, der die letzte Zahlkarte gelegt hat, den Mittelstapel auf und legt ihn unter seinen Stapel. Dieser Spieler beginnt dann die nächste Runde.
  • Gehen einem Spieler die Karten aus und kann er keine Karte spielen (entweder beim Zahlen einer Strafe oder wenn er an der Reihe ist), nimmt der andere Spieler den Mittelstapel auf.
  • Der Moment, in dem ein Spieler die Karten aus dem Mittelstapel aufnimmt, wird als Trick bezeichnet.
  • Hat ein Spieler nach einem Trick alle Karten in seinem Besitz, endet das Spiel.
  • Das Spiel gerät in eine Schleife, sobald die Stapel genauso aussehen wie früher im Spiel, wobei Zahlenkarten nicht mitgezählt werden!

Beispiele

Ein kleines Beispiel für eine Partie, die endet.

Runde Spieler A Spieler B Mittelstapel Fällige Strafe
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 Spieler B: 4
1 7 8 Q 10 5 6 K 9 J 2 3 A 4 Spieler B: 3
1 7 8 Q 10 6 K 9 J 2 3 A 4 5 Spieler B: 2
1 7 8 Q 10 K 9 J 2 3 A 4 5 6 Spieler B: 1
1 7 8 Q 10 9 J 2 3 A 4 5 6 K Spieler A: 3
1 8 Q 10 9 J 2 3 A 4 5 6 K 7 Spieler A: 2
1 Q 10 9 J 2 3 A 4 5 6 K 7 8 Spieler A: 1
1 10 9 J 2 3 A 4 5 6 K 7 8 Q Spieler B: 2
1 10 J 2 3 A 4 5 6 K 7 8 Q 9 Spieler B: 1
1 10 - 2 3 A 4 5 6 K 7 8 Q 9 J Spieler 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

Dies ist ein kleines Beispiel für eine Partie, die in eine Schleife gerät.

Runde Spieler A Spieler B Mittelstapel Fällige Strafe
1 J 2 3 4 J 5 - -
1 2 3 4 J 5 J Spieler 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 Spieler 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 Spieler B: 1
3 4 J 3 5 J 2 -
4 4 5 J 2 J 3 - -

Der Beginn von Runde 4 entspricht dem Beginn von Runde 2. Zur Erinnerung: Der Wert der Zahlenkarten spielt keine Rolle.

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

Deine Aufgabe

  • Simuliere das Spiel mit der Eingabe nach den obigen Regeln.
  • Ermittle die folgenden Informationen zum Spiel:
    • Status: "finished" oder "loop"
    • Cards: Gesamtzahl der Karten, die während des Spiels gespielt wurden
    • Tricks: Anzahl der Male, in denen der Mittelstapel aufgenommen wurde
Advanced

Für alle, die eine spannendere Herausforderung suchen: Die Suche nach weiteren Rekorden für das längste Spiel mit einem Ende ist noch offen. Es gibt 653,534,134,886,878,245,000 (etwa 654 Trillionen) Möglichkeiten, und wir haben sie noch nicht alle berechnet!

Vor- und Nachteile von from_quote

Die Funktion Card.from_quote parst einen String zu einer Card. Damit kannst du Code wie diesen schreiben:

card : Card
card = "Q"

Dieser Code ruft Card.from_quote("Q") zur Kompilierzeit implizit auf. Code zur Kompilierzeit mit diesem Ansatz (oder den weiter unten beschriebenen Methoden) auszuführen, kann das Programm potenziell beschleunigen, weil es das Parsen und die Datenvalidierung dann nicht zur Laufzeit erledigen muss. Außerdem stellt es sicher, dass ungültige Daten (etwa ein falsches Datumsformat) schon zur Kompilierzeit abgelehnt werden und nicht erst zur Laufzeit, was deine App robuster macht. Schließlich wird der Code dadurch schön lesbar. Hier ist zum Beispiel einer der Tests in dieser Übung:

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

Wer diesen Code liest, könnte allerdings annehmen, dass die Funktion simulate_game ein Record mit zwei List(Str) entgegennimmt, obwohl es in Wirklichkeit ein Record mit zwei List(Card) ist. Das kann zu Verwirrung und Fehlern führen. Aus diesem Grund bevorzugst du vielleicht die etwas längere, aber explizitere Schreibweise: "Q".Card, die ebenfalls from_quote zur Kompilierzeit aufruft.

In deinen eigenen Programmen bevorzugst du vielleicht stattdessen from_str:

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

Du kannst from_str (oder jede andere reine Funktion) auch zur Kompilierzeit aufrufen, und zwar so:

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

Hinweis: Wenn du diese Definition auf die oberste Ebene setzt, wird sie zur Kompilierzeit ausgeführt (weil die Funktion rein ist und ihre Eingabe zur Kompilierzeit bekannt ist). Innerhalb einer Funktion kannst du from_str zur Laufzeit verwenden.

Hinweise

Hinweise anzeigen

Mit match und den Mustern [] und [card, .. as rest] unterscheidest du ein leeres Deck von seiner nächsten Karte und den restlichen Karten. Record-Updates wie { ..state, pile: new_pile } halten Spielübergänge explizit, ganz ohne veränderlichen Zustand. Ein Set kann Tupel oder Listen als Schlüssel speichern, wenn du bereits gesehene Spielzustände nachverfolgst.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Roc Exercism

Bereit, mit Camicia zu starten?

Melde dich bei Exercism an, um Roc mit 135 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.