feat: implemented lobby creation and general management
This commit is contained in:
parent
8b2805fcf1
commit
e7a8f0e1e4
@ -2,20 +2,28 @@ package uulm.teamname.marvelous.server.lobbymanager;
|
|||||||
|
|
||||||
import org.java_websocket.WebSocket;
|
import org.java_websocket.WebSocket;
|
||||||
import org.tinylog.Logger;
|
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.client.PlayerReadyMessage;
|
||||||
|
import uulm.teamname.marvelous.gamelibrary.messages.server.GameAssignmentMessage;
|
||||||
|
import uulm.teamname.marvelous.server.Server;
|
||||||
import uulm.teamname.marvelous.server.lobby.Lobby;
|
import uulm.teamname.marvelous.server.lobby.Lobby;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
public class LobbyManager {
|
public class LobbyManager {
|
||||||
private final HashMap<Participant, LobbyConnection> lobbies;
|
private final HashMap<Participant, LobbyConnection> lobbies;
|
||||||
|
private final HashMap<String, LobbyConnection> resourceDescriptorToLobby;
|
||||||
|
private final BiConsumer<Participant, BasicMessage> sendMessageCallback;
|
||||||
|
private String localResourceDescriptor;
|
||||||
|
|
||||||
public LobbyManager() {
|
public LobbyManager(BiConsumer<Participant, BasicMessage> sendMessageCallback) {
|
||||||
|
this.sendMessageCallback = sendMessageCallback;
|
||||||
this.lobbies = new HashMap<>();
|
this.lobbies = new HashMap<>();
|
||||||
|
this.resourceDescriptorToLobby = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,10 +38,86 @@ public class LobbyManager {
|
|||||||
Logger.info("Assigning lobby to player '{}'", playerName);
|
Logger.info("Assigning lobby to player '{}'", playerName);
|
||||||
|
|
||||||
var resourceDescriptor = connection.getResourceDescriptor();
|
var resourceDescriptor = connection.getResourceDescriptor();
|
||||||
if (resourceDescriptor.length() == 0) {
|
if (resourceDescriptor == null || resourceDescriptor.length() == 0) {
|
||||||
// TODO: generate new ResourceDescriptor
|
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<Participant, LobbyConnection> getLobbies() {
|
Map<Participant, LobbyConnection> getLobbies() {
|
||||||
return lobbies;
|
return lobbies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method to obtain the resourceDescriptorToLobby HashMap.
|
||||||
|
* Meant for testing, and shouldn't be used anywhere else.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Map<String, LobbyConnection> getResourceDescriptorToLobby() {
|
||||||
|
return resourceDescriptorToLobby;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user