Dieses Projekt dient dazu, meine Java-Kenntnisse zu vertiefen. Als Grundlage wird das Kartenspiel Love Letter für 2–4 Spieler verwendet.
- Objektorientierte Programmierung mit Java
- Aufbau eines modularen Datenmodells
- Client-Server-Kommunikation über TCP
- Nebenläufigkeit und asynchrone Kommunikation
- Entwicklung einer Benutzeroberfläche mit JavaFX
- Trennung von Model, View und ViewModel (MVVM)
- Versionsverwaltung mit Git und GitHub
- Schreiben von automatisierten Tests
- Java und IntelliJ IDEA einrichten
- Git-Repository einrichten
- Grundlegendes Datenmodell entwerfen
- UML-Klassendiagramm erstellen
- TCP-Server implementieren
- TCP-Client implementieren
- Anmeldung mit eindeutigem Nicknamen ermöglichen
- Chatnachrichten an alle Clients übertragen
- Verbindungsaufbau und Verbindungsende bekannt geben
- Verbindung mit dem Befehl
byebeenden - Asynchronen Nachrichtenempfang umsetzen
- Spiel erzeugen und Teilnehmer verwalten
- Spiel mit 2–4 Spielern starten
- Runden auswerten und Herzmarker vergeben
- Folgerunden mit unverändertem Markerstand starten
- Gesamtsieger anhand der Herzmarker ermitteln
- Alle acht Karteneffekte im Datenmodell implementieren
- Spielsteuerung über Chatbefehle integrieren
- Direktnachrichten unterstützen
- Eigene Karten privat und Spielereignisse öffentlich übertragen
- Score-Befehl und verständliche Fehlerantworten ergänzen
- Javadoc-Dokumentation vervollständigen und im Repository ablegen
- JavaFX einrichten und ein Anwendungsfenster erstellen
- Anmeldeformular in View und ViewModel aufteilen
- Asynchrone Serververbindung und Nickname-Anmeldung integrieren
- Vergebene Nicknamen und Verbindungsfehler anzeigen
- Verbindung beim Schließen des Fensters beenden
- Spielzustand zwischen Server und Oberfläche synchronisieren
- Eigene Handkarten anzeigen
- Karten durch Anklicken spielen
- Zielspieler und gegebenenfalls Kartentyp auswählen
- Aktuellen Spieler, ausgeschiedene Spieler und Punktestand anzeigen
- MVVM-Struktur auf die Spieloberfläche erweitern
- Strukturierte Nachrichten für Spielzustände, Aktionen und Fehler integrieren
- Strukturierte Serverfehler anzeigen, ohne die Verbindung zu beenden
Bei mehreren Rundengewinnern beginnt vorläufig der erste Gewinner in der Teilnehmerreihenfolge die nächste Runde.
Wenn ein Spielteilnehmer die Verbindung verlässt, wird das bestehende
Spiel geschlossen. Der bisherige Punktestand wird nicht übernommen.
Die übrigen Clients bleiben im Chat und können mit /create ein neues
Spiel erstellen und mit /join erneut beitreten.
Verlässt nur ein Zuschauer die Verbindung, bleibt das Spiel bestehen.
Aktuell laufen 153 automatisierte Tests erfolgreich. Davon entfallen 56 Tests auf ChatServerTest, 4 auf GameStateCodecTest, 4 auf GameActionCodecTest und 1 auf GameErrorCodecTest. Die übrigen 88 Tests prüfen das Datenmodell.
Die Tests prüfen unter anderem:
- Spiel- und Rundenverwaltung sowie alle acht Karteneffekte
- Ungültige Spielzüge, Schutz, Gleichstände und Reservekarten
- Verarbeitung von Spielbefehlen und verständliche Fehlerantworten
- Private Handkartenmeldungen und öffentliche Spielereignisse
- Zugwechsel, Rundenwertung, Punktevergabe und Gesamtsieg
- Verhalten beim Verlassen eines Spielteilnehmers oder Zuschauers
- Private Nachrichten, ungültige Empfänger und Nachrichten an sich selbst
- Serialisierung und Deserialisierung strukturierter Protokollnachrichten
- Strukturierte Fehler bei ungültigen Nachrichten und abgelehnten Aktionen
- Unveränderte Spielzustände nach abgelehnten Aktionen
- Private Priester-Aufdeckungen ohne Weitergabe an andere Spieler oder Zuschauer
Der Maven-Durchlauf mit clean verify javadoc:javadoc war erfolgreich.
Alle Tests bestanden; die Javadoc-Erzeugung meldete keine Warnungen.
Folgende Fälle wurden manuell geprüft:
- Erfolgreiche Anmeldung mit freiem Nicknamen
- Ablehnung eines bereits vergebenen Nicknamens
- Erfolgreicher erneuter Versuch mit einem anderen Namen
- Fehlermeldung bei nicht gestartetem Server
- Anzeige einer Verbindungstrennung und erneute Freigabe des Formulars
- Trennung des Clients beim Schließen des Fensters
Diese Prüfungen ergänzen die automatisierten Tests. Sie sind nicht in der oben genannten Testanzahl enthalten.
Folgende Fälle wurden manuell geprüft:
- Spiel erstellen, beitreten und starten
- Eigene Handkarten mit Namen und Werten anzeigen
- Karte durch Anklicken auswählen und ausspielen
- Auswahl abbrechen und dabei Ziel- und Rateauswahl zurücksetzen
- Zielspieler und bei GUARD einen Kartentyp auswählen
- Private Karteninformation durch PRIEST anzeigen
- Strukturierte GAME_ERROR-Meldungen anzeigen, Handkarten behalten und einen zweiten Spielversuch über dieselbe Verbindung senden
- BARON ohne verfügbaren Gegner ausspielen
- GUARD ohne verfügbaren Gegner und ohne Ratekarte ausspielen
- Ziel- und Rateauswahl bei GUARD ohne verfügbaren Gegner deaktivieren
- Nächste Runde über die Oberfläche starten
- Gräfin-Regel gezielt in der JavaFX-Oberfläche prüfen
Diese manuellen Prüfungen sind nicht in der Anzahl der automatisierten Tests enthalten.
Die Javadoc-Kommentare beschreiben das Datenmodell sowie Server und Client, einschließlich wichtiger Voraussetzungen, Rückgabewerte und Fehlerfälle.
Eine generierte HTML-Version liegt unter docs/javadoc.
Nach dem Klonen oder Herunterladen des Projekts kann
docs/javadoc/index.html lokal im Browser geöffnet werden.
Die HTML-Version zeigt standardmäßig öffentliche und geschützte Elemente. Weitere interne Methoden sind direkt im Quellcode dokumentiert.
Mit installiertem Maven im Projektverzeichnis ausführen:
mvn clean verify javadoc:javadocAlternativ in IntelliJ über „Execute Maven Goal“ diese Ziele ausführen:
clean verify javadoc:javadoc
Dieser Durchlauf prüft das Projekt einschließlich der Tests und erzeugt
anschließend die Dokumentation unter target/reports/apidocs.
Nach Änderungen an den Javadoc-Kommentaren:
- Die Dokumentation erneut erzeugen.
- Den bisherigen Inhalt von
docs/javadocvollständig durch den Inhalt vontarget/reports/apidocsersetzen. docs/javadoc/index.htmlim Browser öffnen und die Darstellung prüfen.- Die geänderten Quelldateien und die aktualisierte HTML-Version gemeinsam committen.
Der Ordner target wird bei clean gelöscht. Die Kopie unter
docs/javadoc bleibt erhalten und wird mit Git versioniert.
- JDK 22
- Maven oder die Maven-Integration von IntelliJ IDEA
JavaFX 22.0.2 wird über Maven eingebunden. Ein separater Download des JavaFX-SDKs ist nicht erforderlich.
In IntelliJ die main-Methode der Klasse
loveletter.server.ChatServer ausführen.
Der Server meldet:
Chat server started on port 5500
Den Server während der Nutzung der Clients weiterlaufen lassen.
In IntelliJ über „Execute Maven Goal“ ausführen:
compile javafx:run
Alternativ mit installiertem Maven im Projektverzeichnis:
mvn compile javafx:runDer grafische Client verbindet sich mit localhost auf Port 5500.
Er startet den Server nicht automatisch.
Einen freien Nicknamen eingeben und auf Connect klicken. Nach erfolgreicher Anmeldung erscheint eine Willkommensnachricht. Eingabefeld und Button bleiben während der aktiven Verbindung deaktiviert.
Ist der Name bereits vergeben, kann ein anderer Name eingegeben und ein neuer Verbindungsversuch gestartet werden. Bei einem Verbindungsfehler oder einer Trennung durch den Server wird das Formular wieder freigegeben.
Beim Schließen des Fensters wird die Verbindung beendet.
Die grafische Oberfläche unterstützt die Anmeldung sowie das Erstellen, Beitreten und Starten eines Spiels. Eigene Handkarten werden mit Namen und Werten angezeigt und können durch Anklicken gespielt werden.
Je nach Karte lassen sich ein Zielspieler und bei GUARD ein Kartentyp auswählen. Wenn kein gültiger Gegner verfügbar ist, kann GUARD ohne Ziel und Ratekarte ausgespielt werden. Die entsprechenden Auswahlfelder sind dann deaktiviert.
Der Server überträgt persönliche Spielzustände an die Oberfläche. Servermeldungen erscheinen als aktuelle Rückmeldung. Eine durch PRIEST aufgedeckte Karte wird dem ausführenden Spieler separat angezeigt. Nach dem Rundenende kann die nächste Runde über einen Button gestartet werden.
Eine vollständige Spieler- und Punkteübersicht sowie eine Chatansicht mit Nachrichtenverlauf sind noch nicht umgesetzt.
Der Konsolenclient bleibt weiterhin nutzbar.
Die main-Methode der Klasse loveletter.client.ChatClient ausführen.
Für mehrere Spieler mehrere Instanzen starten und unterschiedliche
Nicknamen verwenden.
Nach der Anmeldung können die unten beschriebenen Chat- und Spielbefehle eingegeben werden.
| Befehl | Bedeutung |
|---|---|
/help |
Hilfe anzeigen |
/msg RECIPIENT MESSAGE |
Eine private Nachricht senden |
/create |
Ein neues Spiel erstellen |
/join |
Dem Spiel beitreten |
/start |
Das Spiel mit mindestens zwei Teilnehmern starten |
/hand |
Die eigenen Handkarten privat anzeigen |
/score |
Den Punktestand anzeigen |
/subscribe-state |
Persönliche Spielzustände als GAME_STATE-Nachrichten mit JSON-Inhalt abonnieren |
/play CARD [TARGET] |
Eine Karte spielen, gegebenenfalls mit Zielspieler |
/play GUARD TARGET GUESS |
GUARD spielen und eine Karte vermuten |
/next |
Nach einer gewerteten Runde die nächste Runde starten |
bye |
Die Verbindung beenden |
Beispiele:
/play HANDMAID/play PRIEST nati/play GUARD nati KING
Nach dem Gesamtsieg ist /next gesperrt.
Private Nachrichten sind auch ohne Spielteilnahme möglich.
Beispiel: /msg nati Hallo Nati!
Nur der Empfänger erhält die Nachricht; der Absender bekommt eine Sendebestätigung. Nachrichten an sich selbst werden einmal angezeigt. Unbekannte oder nicht mehr verbundene Empfänger werden abgelehnt.
Die aktuelle /msg-Syntax unterstützt Empfängernamen ohne Leerzeichen.
- Mehrere Clients können sich mit dem Server verbinden.
- Jeder Client verwendet einen eindeutigen Nicknamen.
- Neue Benutzer erhalten eine Willkommensnachricht.
- Andere Benutzer werden über Beitritt und Verlassen informiert.
- Nachrichten werden an alle verbundenen Clients übertragen.
- Mit
byewird die Verbindung beendet.
src/
├── main/
│ ├── java/
│ │ └── loveletter/
│ │ ├── client/
│ │ ├── server/
│ │ ├── model/
│ │ ├── protocol/
│ │ ├── view/
│ │ └── viewmodel/
│ └── resources/
└── test/
└── java/
- Java 22
- JavaFX 22
- Gson zur Serialisierung und Deserialisierung von Spielzuständen, Spielaktionen und Fehlern als JSON
- TCP-Sockets
- Git und GitHub
- IntelliJ IDEA
- Maven
classDiagram
class CardType {
<<enumeration>>
GUARD
PRIEST
BARON
HANDMAID
PRINCE
KING
COUNTESS
PRINCESS
-int value
-CardType(int value)
+int getValue()
}
class Deck {
-List~CardType~ cards
+Deck()
-void addCopies(CardType cardType, int numberOfCopies)
+int size()
+CardType draw()
+boolean isEmpty()
+void shuffle()
}
class Player {
-String name
-int affectionTokens
-List~CardType~ hand
-List~CardType~ discardPile
-boolean eliminated
-boolean protectedFromEffects
+Player(String name)
+String getName()
+int getAffectionTokens()
+void awardAffectionTokens()
+List~CardType~ getHand()
+List~CardType~ getDiscardPile()
+void receiveCard(CardType card)
+void discardCard(CardType card)
+void eliminate()
+boolean isEliminated()
+void protectFromEffects()
+void removeProtection()
+boolean isProtectedFromEffects()
+void resetForNewRound()
+void swapHandWith(Player other)
}
class GameRound {
-List~Player~ players
-Deck deck
-CardType reserveCard
-List~CardType~ faceUpRemovedCards
-int currentPlayerIndex
-boolean turnInProgress
-boolean winnerTokensAwarded
+GameRound(List~Player~ players)
+GameRound(List~Player~ players, int startingPlayerIndex)
+List~Player~ getPlayers()
+List~Player~ getAvailableOpponents(Player player)
+void playCard(Player player, CardType card)
+Optional~CardType~ playCard(Player player, CardType card, Player target)
+Optional~CardType~ playCard(Player player, CardType card, Player target, CardType guess)
-void setupRound()
+int getRemainingDeckSize()
+boolean hasReserveCard()
+List~CardType~ getFaceUpRemovedCards()
+Player getCurrentPlayer()
+void startCurrentTurn()
+void endCurrentTurn()
-void moveToNextActivePlayer()
+boolean isRoundOver()
+List~Player~ determineWinners()
-int discardValueOf(Player player)
+List~Player~ awardWinnerTokens()
+boolean isWinnerTokensAwarded()
}
class Game {
-List~Player~ players
-boolean started
-GameRound currentRound
+Game()
+List~Player~ getPlayers()
+boolean isStarted()
+void join(Player player)
+void start()
+GameRound getCurrentRound()
+List~Player~ finishCurrentRound()
+void startNextRound()
+int getRequiredTokensToWin()
+boolean isGameOver()
+List~Player~ getWinners()
}
GameRound "1" *-- "1" Deck : owns
GameRound "1" --> "2..4" Player : participants
Deck "1" --> "0..16" CardType : contains
Player "1" --> "0..2" CardType : hand
Player "1" --> "0..*" CardType : discards
GameRound "1" --> "0..1" CardType : reserve
GameRound "1" --> "0..3" CardType : face-up removed
Game "1" --> "0..4" Player : participants
Game "1" --> "0..1" GameRound : current round
Die Protokollklassen beschreiben die Daten, die zwischen Server und grafischem Client übertragen werden. Sie führen selbst keine Spielregeln aus.
GameStateenthält den Spielzustand aus Sicht eines Empfängers.PlayerStateenthält öffentlich sichtbare Informationen über einen Spieler.GameActionbeschreibt eine angeforderte Aktion.GameErrorenthält einen Fehlercode und eine verständliche Meldung.
classDiagram
direction TB
class GameState {
<<record>>
GamePhase phase
String recipientName
List~PlayerState~ players
String currentPlayerName
List~CardType~ ownHand
int remainingDeckSize
List~CardType~ faceUpRemovedCards
List~String~ roundWinners
List~String~ gameWinners
}
class PlayerState {
<<record>>
String name
int affectionTokens
boolean eliminated
boolean protectedFromEffects
int handSize
List~CardType~ discardPile
}
class GamePhase {
<<enumeration>>
NO_GAME
WAITING_FOR_PLAYERS
ROUND_IN_PROGRESS
ROUND_OVER
GAME_OVER
}
class GameAction {
<<record>>
GameActionType type
CardType card
String targetName
CardType guess
}
class GameActionType {
<<enumeration>>
CREATE
JOIN
START
PLAY
NEXT_ROUND
}
class GameError {
<<record>>
GameErrorCode code
String message
}
class GameErrorCode {
<<enumeration>>
INVALID_MESSAGE
ACTION_REJECTED
}
GameState --> GamePhase : phase
GameState "1" --> "0..4" PlayerState : players
GameAction --> GameActionType : type
GameError --> GameErrorCode : code
GameStateCodec, GameActionCodec und GameErrorCodec wandeln
die jeweiligen Records in JSON um und lesen sie aus JSON ein.
Die Nachrichten verwenden die Präfixe GAME_STATE, GAME_ACTION
und GAME_ERROR, jeweils gefolgt von einem Leerzeichen und dem JSON-Inhalt.
Spielzustände und Fehler werden vom Server zum Client gesendet;
Spielaktionen werden vom grafischen Client zum Server gesendet.
Die Handkarten anderer Spieler sind nicht Bestandteil von PlayerState.
Jeder Empfänger erhält seine eigenen Handkarten über GameState.ownHand.
Private Priester-Aufdeckungen werden weiterhin separat als Textnachricht
an den berechtigten Spieler übertragen.