feat: add realtime codenames game

This commit is contained in:
Schramm Dominik
2026-04-22 15:31:38 +02:00
commit 6bd6b08044
31 changed files with 3080 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
package at.dslan.codenames.game;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import at.dslan.codenames.game.RoomSnapshot.GamePhase;
import at.dslan.codenames.game.RoomSnapshot.GameStatus;
import at.dslan.codenames.game.RoomSnapshot.SeatRole;
import at.dslan.codenames.game.RoomSnapshot.Team;
import java.util.List;
import java.util.Random;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class GameRoomServiceTest {
private GameRoomService service;
@BeforeEach
void setUp() {
WordBank wordBank = (random, count) -> List.of(
"Jahr", "Mensch", "Zeit", "Kind", "Tag",
"Leben", "Thema", "Frage", "Arbeit", "Welt",
"Buch", "Haus", "Spiel", "Herz", "Wasser",
"Berg", "Baum", "Fisch", "Lampe", "Karte",
"Ring", "Kamera", "Planet", "Turm", "Kompass"
).subList(0, count);
service = new GameRoomService(wordBank, new Random(7));
}
@Test
void startsGameWhenAllSeatsAreFilled() {
GameRoomService.RoomSession room = service.createRoom("Host");
GameRoomService.RoomSession redOperative = service.joinRoom(room.roomId(), "Roter Ermittler");
GameRoomService.RoomSession blueSpymaster = service.joinRoom(room.roomId(), "Blauer Hinweis");
GameRoomService.RoomSession blueOperative = service.joinRoom(room.roomId(), "Blauer Ermittler");
service.takeSeat(room.roomId(), room.participantId(), Team.RED, SeatRole.SPYMASTER);
service.takeSeat(room.roomId(), redOperative.participantId(), Team.RED, SeatRole.OPERATIVE);
service.takeSeat(room.roomId(), blueSpymaster.participantId(), Team.BLUE, SeatRole.SPYMASTER);
service.takeSeat(room.roomId(), blueOperative.participantId(), Team.BLUE, SeatRole.OPERATIVE);
service.startGame(room.roomId(), room.participantId());
RoomSnapshot snapshot = service.getSnapshot(room.roomId(), room.participantId());
assertThat(snapshot.game().status()).isEqualTo(GameStatus.IN_PROGRESS);
assertThat(snapshot.game().phase()).isEqualTo(GamePhase.CLUE);
assertThat(snapshot.game().cards()).hasSize(25);
assertThat(snapshot.game().redRemaining() + snapshot.game().blueRemaining()).isEqualTo(17);
}
@Test
void correctGuessConsumesAttemptAndCanBeEndedManually() {
ReadyRoom room = createReadyRoom();
RoomSnapshot hostView = service.getSnapshot(room.roomId(), room.redSpymasterId());
Team currentTeam = hostView.game().currentTeam();
String spymasterId = currentTeam == Team.RED ? room.redSpymasterId() : room.blueSpymasterId();
String operativeId = currentTeam == Team.RED ? room.redOperativeId() : room.blueOperativeId();
int matchingCardIndex = findHiddenCardFor(room.roomId(), spymasterId, currentTeam);
service.submitClue(room.roomId(), spymasterId, "Atlas", 1);
service.guessCard(room.roomId(), operativeId, matchingCardIndex);
RoomSnapshot afterGuess = service.getSnapshot(room.roomId(), operativeId);
assertThat(afterGuess.game().phase()).isEqualTo(GamePhase.GUESSING);
assertThat(afterGuess.game().remainingGuesses()).isEqualTo(1);
service.endTurn(room.roomId(), operativeId);
RoomSnapshot afterTurn = service.getSnapshot(room.roomId(), operativeId);
assertThat(afterTurn.game().phase()).isEqualTo(GamePhase.CLUE);
assertThat(afterTurn.game().currentTeam()).isEqualTo(currentTeam == Team.RED ? Team.BLUE : Team.RED);
}
@Test
void rejectsGameStartWithoutCompleteRoster() {
GameRoomService.RoomSession room = service.createRoom("Host");
service.takeSeat(room.roomId(), room.participantId(), Team.RED, SeatRole.SPYMASTER);
assertThatThrownBy(() -> service.startGame(room.roomId(), room.participantId()))
.isInstanceOf(GameRoomService.GameException.class)
.hasMessageContaining("vier besetzte Rollen");
}
private ReadyRoom createReadyRoom() {
GameRoomService.RoomSession redSpymaster = service.createRoom("Host");
GameRoomService.RoomSession redOperative = service.joinRoom(redSpymaster.roomId(), "Rot");
GameRoomService.RoomSession blueSpymaster = service.joinRoom(redSpymaster.roomId(), "Blau");
GameRoomService.RoomSession blueOperative = service.joinRoom(redSpymaster.roomId(), "Blau 2");
service.takeSeat(redSpymaster.roomId(), redSpymaster.participantId(), Team.RED, SeatRole.SPYMASTER);
service.takeSeat(redSpymaster.roomId(), redOperative.participantId(), Team.RED, SeatRole.OPERATIVE);
service.takeSeat(redSpymaster.roomId(), blueSpymaster.participantId(), Team.BLUE, SeatRole.SPYMASTER);
service.takeSeat(redSpymaster.roomId(), blueOperative.participantId(), Team.BLUE, SeatRole.OPERATIVE);
service.startGame(redSpymaster.roomId(), redSpymaster.participantId());
return new ReadyRoom(
redSpymaster.roomId(),
redSpymaster.participantId(),
redOperative.participantId(),
blueSpymaster.participantId(),
blueOperative.participantId()
);
}
private int findHiddenCardFor(String roomId, String viewerId, Team team) {
return service.getSnapshot(roomId, viewerId).game().cards().stream()
.filter(card -> !card.revealed())
.filter(card -> card.visibleAffiliation() == (team == Team.RED
? RoomSnapshot.CardAffiliation.RED
: RoomSnapshot.CardAffiliation.BLUE))
.findFirst()
.orElseThrow()
.index();
}
private record ReadyRoom(
String roomId,
String redSpymasterId,
String redOperativeId,
String blueSpymasterId,
String blueOperativeId
) {
}
}