Feat: Add leave-lobby command #328
@@ -30,6 +30,8 @@ public class GameController {
|
|||||||
|
|
||||||
private int dealerIndex = 0;
|
private int dealerIndex = 0;
|
||||||
|
|
||||||
|
private volatile Runnable onGameEndedCallback;
|
||||||
|
|
||||||
private static final int DEALER_OFFSET = 1;
|
private static final int DEALER_OFFSET = 1;
|
||||||
private static final int SMALL_BLIND_OFFSET = 1;
|
private static final int SMALL_BLIND_OFFSET = 1;
|
||||||
private static final int BIG_BLIND_OFFSET = 2;
|
private static final int BIG_BLIND_OFFSET = 2;
|
||||||
@@ -45,6 +47,11 @@ public class GameController {
|
|||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set a callback to be invoked when the game ends. */
|
||||||
|
public void setOnGameEndedCallback(Runnable callback) {
|
||||||
|
this.onGameEndedCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a player to the game with the specified name and initial chip count.
|
* Adds a player to the game with the specified name and initial chip count.
|
||||||
*
|
*
|
||||||
@@ -304,5 +311,12 @@ public class GameController {
|
|||||||
*/
|
*/
|
||||||
public void endGame() {
|
public void endGame() {
|
||||||
engine.getState().setPhase(GamePhase.FINISHED);
|
engine.getState().setPhase(GamePhase.FINISHED);
|
||||||
|
if (onGameEndedCallback != null) {
|
||||||
|
try {
|
||||||
|
onGameEndedCallback.run();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Log or handle callback errors
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -26,7 +28,9 @@ public class Lobby {
|
|||||||
private final LobbyId id;
|
private final LobbyId id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<String> playerNames = new CopyOnWriteArrayList<>();
|
private final List<String> playerNames = new CopyOnWriteArrayList<>();
|
||||||
|
private final Set<String> absentPlayers = ConcurrentHashMap.newKeySet();
|
||||||
private volatile GameController gameController;
|
private volatile GameController gameController;
|
||||||
|
private volatile Runnable onGameEndedCallback;
|
||||||
|
|
||||||
public Lobby(LobbyId id, String name) {
|
public Lobby(LobbyId id, String name) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -96,12 +100,91 @@ public class Lobby {
|
|||||||
|
|
||||||
public void initGame(GameController controller) {
|
public void initGame(GameController controller) {
|
||||||
this.gameController = controller;
|
this.gameController = controller;
|
||||||
|
if (controller != null && onGameEndedCallback != null) {
|
||||||
|
controller.setOnGameEndedCallback(onGameEndedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a callback to be invoked when the game in this lobby ends. This will be propagated to the
|
||||||
|
* game controller when a game is started.
|
||||||
|
*/
|
||||||
|
public void setOnGameEndedCallback(Runnable callback) {
|
||||||
|
this.onGameEndedCallback = callback;
|
||||||
|
if (gameController != null) {
|
||||||
|
gameController.setOnGameEndedCallback(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameController getGameController() {
|
public GameController getGameController() {
|
||||||
return gameController;
|
return gameController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a player is currently active (not absent) in the lobby.
|
||||||
|
*
|
||||||
|
* @param playerName the player to check
|
||||||
|
* @return true if player is in the active player list
|
||||||
|
*/
|
||||||
|
public boolean isPlayerActive(String playerName) {
|
||||||
|
return playerNames.contains(playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a player as absent (left the lobby) while the game is still running. The player remains
|
||||||
|
* in the lobby's mappings but is moved to the absent set.
|
||||||
|
*
|
||||||
|
* @param playerName the player to mark as absent
|
||||||
|
* @return true if the player was active and is now absent
|
||||||
|
*/
|
||||||
|
public boolean leavePlayer(String playerName) {
|
||||||
|
if (playerName == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean removed = playerNames.remove(playerName);
|
||||||
|
if (removed) {
|
||||||
|
absentPlayers.add(playerName);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to restore an absent player back to active. Returns true if player was absent and is now
|
||||||
|
* active again.
|
||||||
|
*
|
||||||
|
* @param playerName the player to restore
|
||||||
|
* @return true if player was restored from absent
|
||||||
|
*/
|
||||||
|
public boolean restoreAbsentPlayer(String playerName) {
|
||||||
|
if (playerName == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean wasAbsent = absentPlayers.remove(playerName);
|
||||||
|
if (wasAbsent && !playerNames.contains(playerName)) {
|
||||||
|
playerNames.add(playerName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of absent (gone but not removed) players in this lobby.
|
||||||
|
*
|
||||||
|
* @return copy of absent players set
|
||||||
|
*/
|
||||||
|
public Set<String> getAbsentPlayers() {
|
||||||
|
return Set.copyOf(absentPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this lobby has any players (active or absent).
|
||||||
|
*
|
||||||
|
* @return true if playerNames or absentPlayers is non-empty
|
||||||
|
*/
|
||||||
|
public boolean hasAnyPlayers() {
|
||||||
|
return !playerNames.isEmpty() || !absentPlayers.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Atomically add a player and, if the lobby reached {@code autoStartPlayers} and no game exists
|
* Atomically add a player and, if the lobby reached {@code autoStartPlayers} and no game exists
|
||||||
* yet, create and start a game. The operation is synchronized on the internal player list to
|
* yet, create and start a game. The operation is synchronized on the internal player list to
|
||||||
@@ -139,6 +222,11 @@ public class Lobby {
|
|||||||
game.addPlayer(PlayerId.of(p), defaultStartChips);
|
game.addPlayer(PlayerId.of(p), defaultStartChips);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the callback before starting the game
|
||||||
|
if (onGameEndedCallback != null) {
|
||||||
|
game.setOnGameEndedCallback(onGameEndedCallback);
|
||||||
|
}
|
||||||
|
|
||||||
game.startGame();
|
game.startGame();
|
||||||
this.gameController = game;
|
this.gameController = game;
|
||||||
LOGGER.info(() -> "Auto-started game in lobby " + id.value());
|
LOGGER.info(() -> "Auto-started game in lobby " + id.value());
|
||||||
|
|||||||
+3
@@ -4,4 +4,7 @@ package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby;
|
|||||||
public interface LobbyEventListener {
|
public interface LobbyEventListener {
|
||||||
/** Called when a game is automatically started in the given lobby. */
|
/** Called when a game is automatically started in the given lobby. */
|
||||||
void onGameStarted(LobbyId lobbyId);
|
void onGameStarted(LobbyId lobbyId);
|
||||||
|
|
||||||
|
/** Called when a game in the given lobby ends (phase set to FINISHED). */
|
||||||
|
void onGameEnded(LobbyId lobbyId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,22 @@ public class LobbyManager {
|
|||||||
if (lobby == null) {
|
if (lobby == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if player was absent and try to restore
|
||||||
|
if (lobby.getAbsentPlayers().contains(username)) {
|
||||||
|
boolean restored = lobby.restoreAbsentPlayer(username);
|
||||||
|
if (restored) {
|
||||||
|
LOGGER.info(
|
||||||
|
() ->
|
||||||
|
"User '"
|
||||||
|
+ username
|
||||||
|
+ "' rejoined absent slot in lobby "
|
||||||
|
+ lobbyId.value());
|
||||||
|
playerToLobby.put(username, lobbyId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AddResult result =
|
AddResult result =
|
||||||
lobby.addPlayerAndMaybeStart(
|
lobby.addPlayerAndMaybeStart(
|
||||||
username, maxPlayersPerLobby, AUTO_START_PLAYERS, DEFAULT_START_CHIPS);
|
username, maxPlayersPerLobby, AUTO_START_PLAYERS, DEFAULT_START_CHIPS);
|
||||||
@@ -94,6 +110,8 @@ public class LobbyManager {
|
|||||||
+ AUTO_START_PLAYERS
|
+ AUTO_START_PLAYERS
|
||||||
+ " players; game started.");
|
+ " players; game started.");
|
||||||
notifyGameStarted(lobbyId);
|
notifyGameStarted(lobbyId);
|
||||||
|
// Set the game-ended callback
|
||||||
|
notifySetGameEndedCallback(lobbyId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -121,6 +139,29 @@ public class LobbyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notifySetGameEndedCallback(LobbyId lobbyId) {
|
||||||
|
Lobby lobby = activeLobbies.get(lobbyId);
|
||||||
|
if (lobby != null) {
|
||||||
|
lobby.setOnGameEndedCallback(
|
||||||
|
() -> {
|
||||||
|
notifyGameEnded(lobbyId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyGameEnded(LobbyId lobbyId) {
|
||||||
|
for (LobbyEventListener l : listeners) {
|
||||||
|
try {
|
||||||
|
l.onGameEnded(lobbyId);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOGGER.warning(
|
||||||
|
() ->
|
||||||
|
"Listener threw while handling game-end for lobby "
|
||||||
|
+ lobbyId.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean removePlayer(String username) {
|
public boolean removePlayer(String username) {
|
||||||
LobbyId id = playerToLobby.remove(username);
|
LobbyId id = playerToLobby.remove(username);
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
@@ -138,6 +179,33 @@ public class LobbyManager {
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a player as absent (left the lobby during a running game). The player is not removed but
|
||||||
|
* moved to the absent set, allowing them to rejoin later without losing their slot.
|
||||||
|
*
|
||||||
|
* @param username the player to mark as absent
|
||||||
|
* @param lobbyId the lobby they're leaving
|
||||||
|
* @return true if the player was marked absent successfully
|
||||||
|
*/
|
||||||
|
public boolean leavePlayerFromLobby(String username, LobbyId lobbyId) {
|
||||||
|
Lobby lobby = getLobby(lobbyId);
|
||||||
|
if (lobby == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean left = lobby.leavePlayer(username);
|
||||||
|
if (left) {
|
||||||
|
LOGGER.info(
|
||||||
|
() ->
|
||||||
|
"User '"
|
||||||
|
+ username
|
||||||
|
+ "' left lobby "
|
||||||
|
+ lobbyId.value()
|
||||||
|
+ " (marked absent)");
|
||||||
|
// Keep the playerToLobby mapping intact for rejoin
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<Lobby> getAllLobbies() {
|
public Collection<Lobby> getAllLobbies() {
|
||||||
return activeLobbies.values();
|
return activeLobbies.values();
|
||||||
}
|
}
|
||||||
@@ -169,6 +237,9 @@ public class LobbyManager {
|
|||||||
for (String username : removed.getPlayerNames()) {
|
for (String username : removed.getPlayerNames()) {
|
||||||
playerToLobby.remove(username);
|
playerToLobby.remove(username);
|
||||||
}
|
}
|
||||||
|
for (String username : removed.getAbsentPlayers()) {
|
||||||
|
playerToLobby.remove(username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Unit tests for LobbyManager leave/rejoin functionality. */
|
||||||
|
public class LobbyManagerTest {
|
||||||
|
private LobbyManager lobbyManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
lobbyManager = new LobbyManager(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLeavePlayerFromLobbyMarkingAbsent() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
assertNotNull(lobbyId);
|
||||||
|
|
||||||
|
// Add a player
|
||||||
|
boolean joined = lobbyManager.addPlayerToLobby("Player1", lobbyId);
|
||||||
|
assertTrue(joined);
|
||||||
|
|
||||||
|
Lobby lobby = lobbyManager.getLobby(lobbyId);
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().isEmpty());
|
||||||
|
|
||||||
|
// Mark player as absent
|
||||||
|
boolean left = lobbyManager.leavePlayerFromLobby("Player1", lobbyId);
|
||||||
|
assertTrue(left);
|
||||||
|
|
||||||
|
// Player should be in absent set
|
||||||
|
assertFalse(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().contains("Player1"));
|
||||||
|
|
||||||
|
// Player should still be in the playerToLobby mapping
|
||||||
|
Lobby retrievedLobby = lobbyManager.getLobbyByUsername("Player1");
|
||||||
|
assertEquals(lobbyId, retrievedLobby.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAbsentPlayerCanRejoin() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
|
||||||
|
// Add players to fill the lobby almost
|
||||||
|
lobbyManager.addPlayerToLobby("Player1", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("Player2", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("Player3", lobbyId);
|
||||||
|
|
||||||
|
Lobby lobby = lobbyManager.getLobby(lobbyId);
|
||||||
|
assertEquals(3, lobby.getPlayerNames().size());
|
||||||
|
|
||||||
|
// Player1 leaves
|
||||||
|
lobbyManager.leavePlayerFromLobby("Player1", lobbyId);
|
||||||
|
assertEquals(2, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(1, lobby.getAbsentPlayers().size());
|
||||||
|
|
||||||
|
// Player1 tries to rejoin - should succeed without error
|
||||||
|
boolean rejoined = lobbyManager.addPlayerToLobby("Player1", lobbyId);
|
||||||
|
assertTrue(rejoined);
|
||||||
|
|
||||||
|
assertEquals(3, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(0, lobby.getAbsentPlayers().size());
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAbsentPlayerDoesNotCountAsFullLobby() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
|
||||||
|
// Fill lobby completely
|
||||||
|
lobbyManager.addPlayerToLobby("P1", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P2", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P3", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P4", lobbyId);
|
||||||
|
|
||||||
|
Lobby lobby = lobbyManager.getLobby(lobbyId);
|
||||||
|
assertEquals(4, lobby.getPlayerNames().size());
|
||||||
|
|
||||||
|
// P1 leaves
|
||||||
|
lobbyManager.leavePlayerFromLobby("P1", lobbyId);
|
||||||
|
assertEquals(3, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(1, lobby.getAbsentPlayers().size());
|
||||||
|
|
||||||
|
// P1 can rejoin without "LOBBY_FULL" error
|
||||||
|
boolean rejoined = lobbyManager.addPlayerToLobby("P1", lobbyId);
|
||||||
|
assertTrue(rejoined);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlayerToLobbyMappingPreservedAfterLeave() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
lobbyManager.addPlayerToLobby("Player1", lobbyId);
|
||||||
|
|
||||||
|
// Verify mapping exists
|
||||||
|
Lobby beforeLeave = lobbyManager.getLobbyByUsername("Player1");
|
||||||
|
assertEquals(lobbyId, beforeLeave.getId());
|
||||||
|
|
||||||
|
// Player leaves
|
||||||
|
lobbyManager.leavePlayerFromLobby("Player1", lobbyId);
|
||||||
|
|
||||||
|
// Mapping should still exist for rejoin
|
||||||
|
Lobby afterLeave = lobbyManager.getLobbyByUsername("Player1");
|
||||||
|
assertEquals(lobbyId, afterLeave.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveLobbyCleanupsBothActiveAndAbsentPlayers() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
|
||||||
|
lobbyManager.addPlayerToLobby("P1", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P2", lobbyId);
|
||||||
|
lobbyManager.leavePlayerFromLobby("P1", lobbyId);
|
||||||
|
|
||||||
|
// Verify both are in lobby
|
||||||
|
Lobby lobby = lobbyManager.getLobby(lobbyId);
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("P2"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().contains("P1"));
|
||||||
|
|
||||||
|
// Remove lobby
|
||||||
|
lobbyManager.removeLobby(lobbyId);
|
||||||
|
|
||||||
|
// Both players should be removed from playerToLobby
|
||||||
|
assertNull(lobbyManager.getLobbyByUsername("P1"));
|
||||||
|
assertNull(lobbyManager.getLobbyByUsername("P2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLeaveNonExistentPlayerFromLobby() {
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
|
||||||
|
// Try to remove a player that was never added
|
||||||
|
boolean left = lobbyManager.leavePlayerFromLobby("NonExistent", lobbyId);
|
||||||
|
assertFalse(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLeavePlayerFromNonExistentLobby() {
|
||||||
|
LobbyId nonExistentId = LobbyId.of(999);
|
||||||
|
|
||||||
|
// Try to remove from non-existent lobby
|
||||||
|
boolean left = lobbyManager.leavePlayerFromLobby("Player1", nonExistentId);
|
||||||
|
assertFalse(left);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGameStartedWithAbsentPlayerTracking() {
|
||||||
|
// This test verifies that when a game starts with 4 players,
|
||||||
|
// the absent players tracking is initialized and works
|
||||||
|
LobbyId lobbyId = lobbyManager.createNewLobby("Test Lobby");
|
||||||
|
|
||||||
|
lobbyManager.addPlayerToLobby("P1", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P2", lobbyId);
|
||||||
|
lobbyManager.addPlayerToLobby("P3", lobbyId);
|
||||||
|
// Game should start when 4th player joins
|
||||||
|
lobbyManager.addPlayerToLobby("P4", lobbyId);
|
||||||
|
|
||||||
|
Lobby lobby = lobbyManager.getLobby(lobbyId);
|
||||||
|
assertNotNull(lobby.getGameController(), "Game should be started");
|
||||||
|
assertEquals(4, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(0, lobby.getAbsentPlayers().size());
|
||||||
|
|
||||||
|
// P1 leaves during game
|
||||||
|
boolean left = lobbyManager.leavePlayerFromLobby("P1", lobbyId);
|
||||||
|
assertTrue(left);
|
||||||
|
|
||||||
|
assertEquals(3, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(1, lobby.getAbsentPlayers().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.domain.lobby;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.GameController;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.GameEngine;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.RoundManager;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.engine.TurnManager;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.rules.RuleEngine;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.domain.game.state.GameState;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Unit tests for Lobby absent players tracking functionality. */
|
||||||
|
public class LobbyTest {
|
||||||
|
private Lobby lobby;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
lobby = new Lobby(LobbyId.of(1), "Test Lobby");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddPlayerSuccessfully() {
|
||||||
|
boolean added = lobby.addPlayer("Player1", 4);
|
||||||
|
assertTrue(added);
|
||||||
|
assertEquals(1, lobby.getPlayerNames().size());
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddPlayerToFullLobby() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
lobby.addPlayer("Player2", 4);
|
||||||
|
lobby.addPlayer("Player3", 4);
|
||||||
|
lobby.addPlayer("Player4", 4);
|
||||||
|
|
||||||
|
// Try to add 5th player to a 4-player lobby
|
||||||
|
boolean added = lobby.addPlayer("Player5", 4);
|
||||||
|
assertFalse(added);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLeavePlayerMovesToAbsent() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().isEmpty());
|
||||||
|
|
||||||
|
// Player leaves
|
||||||
|
boolean left = lobby.leavePlayer("Player1");
|
||||||
|
assertTrue(left);
|
||||||
|
|
||||||
|
// Player should be in absent set
|
||||||
|
assertFalse(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().contains("Player1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRestoreAbsentPlayerSuccessfully() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
lobby.leavePlayer("Player1");
|
||||||
|
|
||||||
|
// Restore the absent player
|
||||||
|
boolean restored = lobby.restoreAbsentPlayer("Player1");
|
||||||
|
assertTrue(restored);
|
||||||
|
|
||||||
|
// Player should be back in active list
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("Player1"));
|
||||||
|
assertFalse(lobby.getAbsentPlayers().contains("Player1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRestoreAbsentPlayerCannotRestoreTwice() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
lobby.leavePlayer("Player1");
|
||||||
|
|
||||||
|
boolean restored1 = lobby.restoreAbsentPlayer("Player1");
|
||||||
|
assertTrue(restored1);
|
||||||
|
|
||||||
|
// Try to restore again - should fail
|
||||||
|
boolean restored2 = lobby.restoreAbsentPlayer("Player1");
|
||||||
|
assertFalse(restored2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHasAnyPlayersWithActiveOnly() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
assertTrue(lobby.hasAnyPlayers());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHasAnyPlayersWithAbsentOnly() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
lobby.leavePlayer("Player1");
|
||||||
|
assertTrue(lobby.hasAnyPlayers());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHasAnyPlayersEmpty() {
|
||||||
|
assertFalse(lobby.hasAnyPlayers());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIsPlayerActive() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
assertTrue(lobby.isPlayerActive("Player1"));
|
||||||
|
|
||||||
|
lobby.leavePlayer("Player1");
|
||||||
|
assertFalse(lobby.isPlayerActive("Player1"));
|
||||||
|
|
||||||
|
lobby.restoreAbsentPlayer("Player1");
|
||||||
|
assertTrue(lobby.isPlayerActive("Player1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiplePlayersLeaveAndRejoin() {
|
||||||
|
// Add 4 players
|
||||||
|
lobby.addPlayer("P1", 4);
|
||||||
|
lobby.addPlayer("P2", 4);
|
||||||
|
lobby.addPlayer("P3", 4);
|
||||||
|
lobby.addPlayer("P4", 4);
|
||||||
|
|
||||||
|
// All active
|
||||||
|
assertEquals(4, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(0, lobby.getAbsentPlayers().size());
|
||||||
|
|
||||||
|
// P1 and P2 leave
|
||||||
|
lobby.leavePlayer("P1");
|
||||||
|
lobby.leavePlayer("P2");
|
||||||
|
|
||||||
|
assertEquals(2, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(2, lobby.getAbsentPlayers().size());
|
||||||
|
|
||||||
|
// P1 comes back
|
||||||
|
lobby.restoreAbsentPlayer("P1");
|
||||||
|
|
||||||
|
assertEquals(3, lobby.getPlayerNames().size());
|
||||||
|
assertEquals(1, lobby.getAbsentPlayers().size());
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("P1"));
|
||||||
|
assertTrue(lobby.getAbsentPlayers().contains("P2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGameEndedCallbackInvoked() {
|
||||||
|
// Create a simple game setup
|
||||||
|
GameState state = new GameState();
|
||||||
|
GameEngine engine =
|
||||||
|
new GameEngine(
|
||||||
|
state,
|
||||||
|
new RuleEngine(new ArrayList<>()),
|
||||||
|
new RoundManager(),
|
||||||
|
new TurnManager());
|
||||||
|
GameController gameController = new GameController(engine);
|
||||||
|
|
||||||
|
// Track if callback was invoked
|
||||||
|
boolean[] callbackInvoked = {false};
|
||||||
|
|
||||||
|
// Set callback
|
||||||
|
lobby.setOnGameEndedCallback(() -> callbackInvoked[0] = true);
|
||||||
|
|
||||||
|
// Simulate game ending
|
||||||
|
gameController.endGame();
|
||||||
|
// Callback should NOT be invoked yet because it's not registered in the
|
||||||
|
// controller
|
||||||
|
|
||||||
|
// Now register the callback in the controller
|
||||||
|
gameController.setOnGameEndedCallback(() -> callbackInvoked[0] = true);
|
||||||
|
gameController.endGame();
|
||||||
|
|
||||||
|
assertTrue(callbackInvoked[0], "Game ended callback should have been invoked");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRenamePlayerInActiveList() {
|
||||||
|
lobby.addPlayer("OldName", 4);
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("OldName"));
|
||||||
|
|
||||||
|
boolean renamed = lobby.renamePlayer("OldName", "NewName");
|
||||||
|
assertTrue(renamed);
|
||||||
|
|
||||||
|
assertFalse(lobby.getPlayerNames().contains("OldName"));
|
||||||
|
assertTrue(lobby.getPlayerNames().contains("NewName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRenamePlayerCannotRenameIfAbsent() {
|
||||||
|
lobby.addPlayer("Player1", 4);
|
||||||
|
lobby.leavePlayer("Player1");
|
||||||
|
|
||||||
|
// Should NOT be able to rename an absent player directly
|
||||||
|
boolean renamed = lobby.renamePlayer("Player1", "Player2");
|
||||||
|
assertFalse(renamed);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user