feat: implemented lobby creation and general management

This commit is contained in:
Yannik Bretschneider 2021-06-06 03:49:11 +02:00
parent 8b2805fcf1
commit e7a8f0e1e4
2 changed files with 294 additions and 6 deletions

View File

@ -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;
}
} }

View File

@ -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));
}
}