Wir haben Cards4.net gebaut, damit man ein Kartenspiel öffnen und ohne Konto oder Werbung losspielen kann. Läuft eine Partie, sollte man einen Zug rückgängig machen oder später zu ihr zurückkehren können, ohne sich zu fragen, ob sich das Spielfeld verändert hat. Eine gespeicherte Partie sollte die schlechten Entscheidungen genauso getreu bewahren wie die guten.
Diese Erwartung zu erfüllen hat geprägt, wie wir Phoenix und Elixir im Backend, React im Browser und PostgreSQL für die Persistenz einsetzen. Regeln, Steuerung und gespeicherte Partien müssen sich alle darüber einig sein, was der Spieler getan hat.
Den Spielzustand auf dem Server halten
In Klondike kann das Verschieben einer Karte die darunterliegende Karte freilegen und umdrehen. Drückt der Spieler auf Rückgängig, müssen beide Änderungen zurückgenommen werden. Verlässt er die Partie danach, muss die gespeicherte Stellung das Spielfeld wiedergeben, zu dem er zurückgekehrt ist. Wir halten den Spielzustand auf dem Server, damit Zugverarbeitung, Rückgängig und Persistenz alle mit derselben Darstellung dieser Stellung arbeiten.
Wenn ein Spieler eine Karte ablegt, sendet der Browser den beabsichtigten Zug an unser Phoenix-Backend, wo die Regeln ihn gegen den aktuellen Zustand prüfen. Ist der Zug zulässig, wendet der Server ihn an, speichert die vorherige Stellung für Rückgängig und gibt das aktualisierte Spielfeld zurück. Der Browser kann dann das Ergebnis anzeigen, einschließlich aller aufgedeckten Karten. Da der Server diese Änderungen gemeinsam verarbeitet, muss das Frontend frühere Stellungen nicht aus dem rekonstruieren, was auf dem Bildschirm zu sehen war.
Im Mehrspielermodus erlaubt uns diese Anordnung außerdem, einen Tisch zu koordinieren, ohne uns darauf zu verlassen, dass der Browser eines Spielers entscheidet, was passiert ist. Phoenix Channels tragen Aktionen zum Server und liefern die resultierenden Aktualisierungen an die Spieler. Da der Server den vollständigen Spielzustand hält, kann er jedem Platz die Informationen senden, die dieser Spieler sehen darf, und die anderen Hände aus der Antwort heraushalten.
Eine Partie über das Ende ihrer Sitzung hinaus bewahren
Während des Spiels hält jede Solitaire-Sitzung ihre Stellung in einem GenServer, einem Elixir-Prozess, der eingehende Aufrufe nacheinander abarbeitet. Die Sitzung delegiert Regelprüfungen an das passende Spielmodul, sodass Klondike, FreeCell und Spider den Code zur Verwaltung von Sitzungen und Spielständen gemeinsam nutzen können.
Dieser Prozess hält die Stellung nur im Arbeitsspeicher, daher schreiben wir außerdem Snapshots in PostgreSQL. Trifft eine Anfrage für eine beendete Sitzung ein, kann die Anwendung deren gespeicherten Zustand in eine neue Sitzung laden. Die Wiederherstellung geschieht über diesen Code; der Supervisor startet diese temporären Sitzungsprozesse nach einem Absturz nicht automatisch neu.
Wir planen Schreibvorgänge getrennt von der Zugverarbeitung, damit nicht bei jedem Zug auf die Datenbank gewartet werden muss. Dadurch kann die Stellung im Arbeitsspeicher der letzten erfolgreichen Speicherung voraus sein. Die Wiederherstellung zu testen bedeutet zu prüfen, was tatsächlich geschrieben wurde und was sich wiederherstellen lässt, einschließlich des Rückgängig-Verlaufs.
Die Steuerung reaktionsschnell halten
Liegen die Regeln auf dem Server, braucht jeder Zug einen Netzwerk-Roundtrip, bevor er angenommen wird. Das kann das Ergebnis bei einer langsamen Verbindung verzögern und verhindert Offline-Spiel. Ein Solitaire-Spiel, das vollständig im Browser liefe, würde diese Abhängigkeit vermeiden. Wir haben sie in Kauf genommen, damit Regelprüfung, Rückgängig und gespeicherter Fortschritt dieselbe Backend-Implementierung nutzen können.
Die Oberfläche muss trotzdem reagieren, während ein Spieler eine Karte
zieht. Unsere Spielfelder verwenden React und TypeScript, wobei Vite den
Frontend-Build übernimmt. React verwaltet Spielfeld und Interaktionszustand,
während eine requestAnimationFrame-Schleife die Drag-Vorschau direkt
bewegt. So folgt die Vorschau dem Zeiger ohne ein React-State-Update pro
Frame, auch wenn das Annehmen des Ablegens weiterhin eine Serverantwort
erfordert.
Auf dem Smartphone braucht auch die Geste selbst Aufmerksamkeit. Karten sind kleiner, Spalten können lang sein, und eine Berührung kann bedeuten, eine Karte auszuwählen oder ein Ziehen zu beginnen. Wir testen diese Interaktionen getrennt von den Regeln, denn zu wissen, dass ein Stapel regelkonform bewegt werden darf, sagt wenig darüber, wie leicht ihn jemand aufnehmen kann. Abgebrochene Gesten und unzulässige Ablagen müssen das Spielfeld außerdem in einem Zustand hinterlassen, den der Spieler versteht.
Nachvollziehen, was schiefgelaufen ist
Wenn ein Spieler meldet, dass sich das Spielfeld unerwartet verhalten hat, müssen wir die Stellung nachbilden, die er gespielt hat. Unterstützte Solitaire-Partien werden aus Seeds erzeugt, sodass dasselbe Spiel, dieselbe Variante und derselbe Seed bei derselben Implementierung dieselbe Anfangsauslage ergeben.
Für ein Problem, das später auftritt, brauchen wir außerdem die Züge, die dorthin geführt haben. Ein Screenshot kann zeigen, was falsch aussah, während Seed und Zugfolge es uns erlauben, die Partie bis zu diesem Punkt nachzuverfolgen. Bewahren wir diese Eingaben in einem Regressionstest auf, können wir das Verhalten nach einer Korrektur erneut prüfen. Es bedeutet auch, dass Änderungen am Misch- oder Austeilcode Kompatibilitätsprüfungen brauchen, wenn alte Seeds weiterhin dieselben Auslagen erzeugen sollen.
Wir nutzen außerdem Fehlerberichte und Leistungsmessungen, um Ausfälle und langsame Seiten zu untersuchen. Diese dienen einem anderen Zweck als die Besucherzahlen in Plausible, das wir erst nach Einwilligung in die Analyse laden. Seitenaufrufe können uns sagen, dass jemand die Seite besucht hat; sie können nicht belegen, dass eine Partie korrekt gespeichert oder wiederhergestellt wurde. Die Datenschutzerklärung beschreibt die Daten, die diese getrennten Systeme erfassen.
Was wir früher tun würden
Bei einem weiteren Kartenspielprojekt würden wir ein Spiel durch einen vollständigen Zyklus aus Spielen, Rückgängig, Speichern und Fortsetzen bringen, bevor wir das Angebot erweitern. Wir würden diese Abfolge als Test mit einer festen Partie aufbewahren, damit spätere Änderungen gegen denselben Ausgangspunkt geprüft werden können. Das gäbe uns einen wiederholbaren Weg, eine Änderung zu erkennen, bei der ein Zug auf dem Bildschirm funktioniert, aber die nach einem Neuladen wiederhergestellte Stellung kaputtmacht. Für eine Website, zu der man zwischen anderen Dingen zurückkehrt, verdient es früh in der Entwicklung Zeit, diesen Rückbesuch richtig hinzubekommen.
FAQ
Welchen Technologie-Stack verwendet Cards4.net?
Das Backend nutzt Phoenix und Elixir mit PostgreSQL. Das Frontend nutzt React, TypeScript und Vite. Solitaire-Aktionen laufen über REST, während Mehrspielertische Phoenix Channels nutzen. Die Spielfelder verwenden kein LiveView.
Kann ich ohne Konto spielen?
Ja. Gelegentliches Spielen steht Gästen offen. Wenn du ein Konto möchtest, meldest du dich über einen einmaligen E-Mail-Link an. Die Website verlangt von dir kein Passwort.
Funktionieren die Spiele offline?
Nein. Züge werden auf dem Server geprüft, daher brauchst du zum Spielen eine Internetverbindung.
Welche Analysewerkzeuge verwendet die Website?
Plausible wird erst nach deiner Einwilligung in die Analyse geladen. Die Anwendung hat außerdem eine separate Fehlerberichterstattung und Leistungsüberwachung. Einzelheiten findest du in der Datenschutzerklärung.