diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java index ae43009..fa39d8c 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManager.java @@ -2,20 +2,28 @@ package uulm.teamname.marvelous.server.lobbymanager; import org.java_websocket.WebSocket; import org.tinylog.Logger; +import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; +import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum; import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage; +import uulm.teamname.marvelous.gamelibrary.messages.server.GameAssignmentMessage; +import uulm.teamname.marvelous.server.Server; import uulm.teamname.marvelous.server.lobby.Lobby; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiConsumer; public class LobbyManager { private final HashMap lobbies; + private final HashMap resourceDescriptorToLobby; + private final BiConsumer sendMessageCallback; + private String localResourceDescriptor; - public LobbyManager() { + public LobbyManager(BiConsumer sendMessageCallback) { + this.sendMessageCallback = sendMessageCallback; this.lobbies = new HashMap<>(); + this.resourceDescriptorToLobby = new HashMap<>(); } /** @@ -30,10 +38,86 @@ public class LobbyManager { Logger.info("Assigning lobby to player '{}'", playerName); var resourceDescriptor = connection.getResourceDescriptor(); - if (resourceDescriptor.length() == 0) { - // TODO: generate new ResourceDescriptor + if (resourceDescriptor == null || resourceDescriptor.length() == 0) { + Logger.trace("Resource descriptor is null, getting local one"); + resourceDescriptor = getLocalResourceDescriptor(); } + LobbyConnection targetedLobby = resourceDescriptorToLobby.get(resourceDescriptor); + + if (targetedLobby == null || !targetedLobby.hasFreePlayerSpot()) { + Logger.info("Lobby '{}' is non-existent, initializing lobby...", resourceDescriptor); + targetedLobby = initializeNewLobby(resourceDescriptor); + Logger.info("Adding mapping from resourceDescriptor {} to new lobby...", resourceDescriptor); + synchronized (resourceDescriptorToLobby) { + resourceDescriptorToLobby.put(resourceDescriptor, targetedLobby); + } + } + + Logger.trace("Obtaining lock on targetedLobby '{}'", targetedLobby.gameID); + synchronized (targetedLobby) { + + Participant participant; + + Logger.debug("Assigning participant to lobby"); + if (message.role.equals(RoleEnum.SPECTATOR)) { + Logger.trace("Generating new participant with type spectator"); + participant = new Participant(connection, ParticipantType.Spectator, playerName); + targetedLobby.addSpectator(participant); + + } else { + Logger.trace("Checking whether Player1 or Player2 spot is free in lobby '{}'", + targetedLobby.gameID); + var participantType = + !targetedLobby.hasPlayer1() + ? ParticipantType.PlayerOne + : ParticipantType.PlayerTwo; + + Logger.trace("Generating new participant with type {}", participantType); + participant = new Participant(connection, participantType, playerName); + if (!targetedLobby.addPlayer(participant)) { + Logger.warn("Participant could not be added to lobby. This is probably a bug."); + + } + } + Logger.trace("Adding mapping from participant '{}' to lobby {}", + participant.name, targetedLobby.gameID); + synchronized (lobbies) { + lobbies.put(participant, targetedLobby); + } + Logger.trace("Relaying message to newly created Lobby"); + targetedLobby.receiveMessage(participant, message); + } + + } + + private LobbyConnection initializeNewLobby(String gameID) { + return new LobbyConnection(gameID, sendMessageCallback); + } + + /** + * Returns the local resource descriptor if it is pointing to a not yet filled lobby (joinable lobby), or + * generates a new one if the lobby described by the resourceDescriptor is full already + */ + private String getLocalResourceDescriptor() { + Logger.trace("Getting local resourceDescriptor. Currently this is '{}'", localResourceDescriptor); + if (localResourceDescriptor == null) { + Logger.trace("local resourceDescriptor is null. Initializing local resourceDescriptor..."); + localResourceDescriptor = RandomWordGenerator.generateTwoWords(); + Logger.debug("Local resoucrceDescriptor initialized as '{}'", localResourceDescriptor); + } + var lobby = resourceDescriptorToLobby.get(localResourceDescriptor); + if (lobby != null) { + if (!lobby.hasFreePlayerSpot()) { + Logger.debug("Lobby is full, generating new local resourceDescriptor"); + while (resourceDescriptorToLobby.get(localResourceDescriptor) != null) { + localResourceDescriptor = RandomWordGenerator.generateTwoWords(); + } + Logger.debug("New resourceDescriptor is '{}'", localResourceDescriptor); + } + } + Logger.trace("Returning local resourceDescriptor"); + return localResourceDescriptor; } @@ -42,4 +126,13 @@ public class LobbyManager { Map getLobbies() { return lobbies; } + + /** + * A method to obtain the resourceDescriptorToLobby HashMap. + * Meant for testing, and shouldn't be used anywhere else. + */ + @Deprecated + Map getResourceDescriptorToLobby() { + return resourceDescriptorToLobby; + } } diff --git a/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManagerTest.java b/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManagerTest.java new file mode 100644 index 0000000..c644b38 --- /dev/null +++ b/Server/src/test/java/uulm/teamname/marvelous/server/lobbymanager/LobbyManagerTest.java @@ -0,0 +1,195 @@ +package uulm.teamname.marvelous.server.lobbymanager; + +import org.java_websocket.WebSocket; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import uulm.teamname.marvelous.gamelibrary.config.FieldType; +import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; +import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; +import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum; +import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage; +import uulm.teamname.marvelous.server.Server; + +import java.util.stream.Collectors; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; + +class LobbyManagerTest { + + LobbyManager manager; + WebSocket player1; + WebSocket player2; + WebSocket player3; + WebSocket spectator; + + PlayerReadyMessage playerReady; + PlayerReadyMessage spectatorReady; + PlayerReadyMessage aiReady; + + Participant player1Participant; + Participant player2Participant; + Participant player3Participant; + + @BeforeEach + void beforeEach() { + manager = new LobbyManager(); + + player1 = mock(WebSocket.class); + player2 = mock(WebSocket.class); + player3 = mock(WebSocket.class); + spectator = mock(WebSocket.class); + + playerReady = new PlayerReadyMessage(); + playerReady.role = RoleEnum.PLAYER; + + spectatorReady = new PlayerReadyMessage(); + spectatorReady.role = RoleEnum.SPECTATOR; + + aiReady = new PlayerReadyMessage(); + aiReady.role = RoleEnum.KI; + + player1Participant = new Participant( + player1, + ParticipantType.PlayerOne, + "AwesomePlayer"); + player2Participant = new Participant( + player2, + ParticipantType.PlayerTwo, + "MoreAwesomePlayer"); + player3Participant = new Participant( + player3, + ParticipantType.PlayerOne, + "AwesomestAwesomePlayer"); + } + + @Test + void lobbyManagerGetsCreatedEmpty() { + assertThat(manager.getLobbies()).isEmpty(); + } + + @Test + @DisplayName("When a participant is added, a new Lobby is created") + void lobbyCreationTest() { + var message = new PlayerReadyMessage(); + message.role = RoleEnum.PLAYER; + + assertThat(manager.getLobbies()).isEmpty(); + + when(player1.getResourceDescriptor()).thenReturn("/ResourcesFTW"); + + manager.assignLobbyToParticipant(player1, "AwesomePlayer", message); + + Participant player1Participant = new Participant(player1, ParticipantType.PlayerOne, "AwesomePlayer"); + + assertThat(manager.getLobbies()).containsOnlyKeys(player1Participant); + assertThat(manager.getLobbies().get(player1Participant)).isNotNull(); + assertThat(manager.getLobbies().get(player1Participant).getPlayer1()).isEqualTo(player1Participant); + + assertThat(manager.getResourceDescriptorToLobby()).containsOnlyKeys("/ResourcesFTW"); + assertThat(manager.getResourceDescriptorToLobby().get("/ResourcesFTW")) + .isEqualTo(manager.getLobbies().get(player1Participant)) + .isNotNull(); + } + + @Test + @DisplayName("when two participants with same ResourceDescriptor connect, they get assigned to the same lobby") + void twoParticipantsSameLobbyTest() { + + + + when(player1.getResourceDescriptor()).thenReturn("/fancyResourceDescriptor"); + when(player2.getResourceDescriptor()).thenReturn("/fancyResourceDescriptor"); + + manager.assignLobbyToParticipant(player1, "AwesomePlayer", playerReady); + manager.assignLobbyToParticipant(player2, "MoreAwesomePlayer", playerReady); + + Participant player1Participant = new Participant( + player1, ParticipantType.PlayerOne, "AwesomePlayer"); + + Participant player2Participant = new Participant( + player2, ParticipantType.PlayerTwo, "MoreAwesomePlayer"); + + assertThat(manager.getLobbies()).containsOnlyKeys(player1Participant, player2Participant); + assertThat(manager.getLobbies().get(player1Participant)) + .isEqualTo(manager.getLobbies().get(player2Participant)) + .isNotNull(); + + assertThat(manager.getResourceDescriptorToLobby()).containsOnlyKeys("/fancyResourceDescriptor"); + assertThat(manager.getResourceDescriptorToLobby().get("/fancyResourceDescriptor")) + .isEqualTo(manager.getLobbies().get(player1Participant)) + .isEqualTo(manager.getLobbies().get(player2Participant)) + .isNotNull(); + } + + @Test + @DisplayName("On Participant without ResourceDescriptor random lobby gets generated") + void randomLobbyTest() { + when(player1.getResourceDescriptor()).thenReturn(null); + + manager.assignLobbyToParticipant(player1, "AwesomePlayer", playerReady); + + assertThat(manager.getLobbies()).hasSize(1); + assertThat(manager.getLobbies().get(player1Participant)).isNotNull(); + assertThat(manager.getLobbies().get(player1Participant).gameID).isNotNull(); + + assertThat(manager.getResourceDescriptorToLobby()).hasSize(1); + assertThat(manager.getResourceDescriptorToLobby().get(manager.getLobbies().get(player1Participant).gameID)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player1Participant)); + } + + @Test + @DisplayName("When two people without ResourceDescriptor join, they get allocated to the same lobby") + void randomLobbyTwoPlayerTest() { + when(player1.getResourceDescriptor()).thenReturn(null); + when(player2.getResourceDescriptor()).thenReturn(null); + manager.assignLobbyToParticipant(player1, "AwesomePlayer", playerReady); + manager.assignLobbyToParticipant(player2, "MoreAwesomePlayer", playerReady); + + assertThat(manager.getLobbies()).hasSize(2); + assertThat(manager.getLobbies().get(player1Participant)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player2Participant)); + + assertThat(manager.getResourceDescriptorToLobby()).hasSize(1); + assertThat(manager.getResourceDescriptorToLobby().get(manager.getLobbies().get(player1Participant).gameID)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player1Participant)) + .isEqualTo(manager.getLobbies().get(player2Participant)); + } + + @Test + @DisplayName("When three players connect, a new lobby gets created") + void randomLobbyThreePlayerTest() { + when(player1.getResourceDescriptor()).thenReturn(null); + when(player2.getResourceDescriptor()).thenReturn(null); + when(player3.getResourceDescriptor()).thenReturn(null); + manager.assignLobbyToParticipant(player1, "AwesomePlayer", playerReady); + manager.assignLobbyToParticipant(player2, "MoreAwesomePlayer", playerReady); + manager.assignLobbyToParticipant(player3, "AwesomestAwesomePlayer", playerReady); + + assertThat(manager.getLobbies()).hasSize(3); + assertThat(manager.getLobbies().get(player1Participant)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player2Participant)) + .isNotEqualTo(manager.getLobbies().get(player3Participant)); + + assertThat(manager.getLobbies().get(player3Participant)).isNotNull(); + + assertThat(manager.getResourceDescriptorToLobby()).hasSize(2); + assertThat(manager.getResourceDescriptorToLobby().get(manager.getLobbies().get(player1Participant).gameID)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player1Participant)) + .isEqualTo(manager.getLobbies().get(player2Participant)) + .isNotEqualTo(manager.getLobbies().get(player3Participant)); + assertThat(manager.getResourceDescriptorToLobby().get(manager.getLobbies().get(player3Participant).gameID)) + .isNotNull() + .isEqualTo(manager.getLobbies().get(player3Participant)); + } + +}