diff --git a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java index 762f75a..c5dd4e7 100644 --- a/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java +++ b/Server/src/main/java/uulm/teamname/marvelous/server/lobbymanager/LobbyConnection.java @@ -1,48 +1,196 @@ package uulm.teamname.marvelous.server.lobbymanager; import org.tinylog.Logger; -import uulm.teamname.marvelous.gamelibrary.config.CharacterConfig; -import uulm.teamname.marvelous.gamelibrary.config.PartyConfig; -import uulm.teamname.marvelous.gamelibrary.config.ScenarioConfig; +import uulm.teamname.marvelous.gamelibrary.Tuple; +import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties; import uulm.teamname.marvelous.gamelibrary.events.Event; +import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage; import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType; -import uulm.teamname.marvelous.gamelibrary.requests.Request; +import uulm.teamname.marvelous.gamelibrary.messages.client.CharacterSelectionMessage; +import uulm.teamname.marvelous.gamelibrary.messages.client.RequestMessage; +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.netconnector.UserManager; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; /** - * A class that handles the connection to the lobby. It contains distinct websockets for player1, player2 and spectators. + * A class that handles the connection to the lobby. It contains the participants inside of the lobby. * The class is meant to be used in conjecture with {@link MessageRelay}. */ public class LobbyConnection implements Runnable { - private final Lobby lobby; + private Lobby lobby; public final String gameID; private Participant player1, player2; + private boolean characterSelection; + + /** Whether the character selection phase is reached */ + private boolean inGame; private final HashSet spectators; - private final BlockingQueue incomingRequests; + private final BlockingQueue> incomingMessages; + + /** + * A callback executed to send a message, originating from the + * {@link uulm.teamname.marvelous.server.netconnector.UserManager} + */ + private final BiConsumer sendMessageCallback; // TODO: FIX THIS JAVADOC - /** - * Creates a new LobbyConnection, whereby a new {@link Lobby} is initialized. - */ - public LobbyConnection(String gameID, - PartyConfig partyConfig, - CharacterConfig characterConfig, - ScenarioConfig scenarioConfig) { + /** Creates a new LobbyConnection */ + public LobbyConnection(String gameID, BiConsumer sendMessageCallback) { + this.gameID = gameID; + this.sendMessageCallback = sendMessageCallback; + this.spectators = new HashSet<>(10); + this.incomingMessages = new LinkedBlockingQueue<>(); + this.characterSelection = false; + this.inGame = false; + } + + @Override + public void run() { + Logger.info("Starting Lobby thread for lobby '{}'", gameID); + Logger.trace("Initializing lobby..."); + initializeLobby(); + + Logger.trace("Activating characterSelection state"); + this.characterSelection = true; + + + Logger.info("Starting character selection process"); + Logger.trace("Finding twenty-four random characters"); + Tuple selectionPossibilities = + Server.getCharacterConfig().getDisjointSetsOfPropertiesOfSize(12); + + Logger.info("Sending GameAssignment message with random characters to players"); + var gameAssignmentMessage = new GameAssignmentMessage(); + gameAssignmentMessage.gameID = this.gameID; + + // Send to player one with characters for player one + gameAssignmentMessage.characterSelection = selectionPossibilities.item1; + UserManager.getInstance().sendMessage(player1.getConnection(), gameAssignmentMessage); + + // And send the others to player 2 + gameAssignmentMessage.characterSelection = selectionPossibilities.item2; + UserManager.getInstance().sendMessage(player2.getConnection(), gameAssignmentMessage); + + CharacterProperties[] playerOneSelection = null; + CharacterProperties[] playerTwoSelection = null; + + Logger.info("Entering GameAssignment state. Waiting for answer about selected characters from players."); + while (characterSelection) { + Tuple currentMessage = null; + + try { // TODO: Exact duplication. Maybe extract? + Logger.trace("Checking for messages. Currently the amount of messages is {}", + incomingMessages.size()); + + if (incomingMessages.isEmpty()) { + Logger.trace("LobbyConnection thread waiting for new messages..."); + Thread.currentThread().wait(); + Logger.trace("Lobby '{}' woken up", gameID); + } + + Logger.trace("Polling incoming message queue"); + currentMessage = incomingMessages.poll(100, TimeUnit.MILLISECONDS); + + } catch (InterruptedException e) { + Logger.warn("LobbyConnection thread got interrupted. Exception: " + e.getMessage()); + } + + if (currentMessage == null) { + continue; + } else if (currentMessage.item2 instanceof CharacterSelectionMessage) { + var origin = currentMessage.item1; + var message = (CharacterSelectionMessage) currentMessage.item2; + if (origin.type.equals(ParticipantType.Spectator)) { + Logger.info("Spectator sent CharacterSelectionMessage. Sending error..."); + UserManager.getInstance().sendError( + origin.getConnection(), + "Spectators can't select characters"); + } else if (origin.type.equals(ParticipantType.PlayerOne)) { + if (playerOneSelection == null) { // TODO: Extract this. This isn't beautiful at all. + ArrayList chosenCharacters = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + if (message.characters[i]) { + chosenCharacters.add(selectionPossibilities.item1[i]); + } + } + playerOneSelection = chosenCharacters.toArray(new CharacterProperties[0]); + } + Logger.info("Player 1 has selected their characters"); + } else { + if (playerTwoSelection == null) { + ArrayList chosenCharacters = new ArrayList<>(); + for (int i = 0; i < 12; i++) { + if (message.characters[i]) { + chosenCharacters.add(selectionPossibilities.item2[i]); + } + } + playerTwoSelection = chosenCharacters.toArray(new CharacterProperties[0]); + } + Logger.info("Player 2 has selected their characters"); + } + } + + } + + while(inGame) { + Tuple currentMessage = null; + try { + Logger.trace("Checking for messages. Currently the amount of messages is {}", + incomingMessages.size()); + + if (incomingMessages.isEmpty()) { + Logger.trace("LobbyConnection thread waiting for new messages..."); + Thread.currentThread().wait(); + Logger.trace("Lobby '{}' woken up", gameID); + } + + Logger.trace("Polling incoming message queue"); + currentMessage = incomingMessages.poll(100, TimeUnit.MILLISECONDS); + + } catch (InterruptedException e) { + Logger.warn("LobbyConnection thread got interrupted. Exception: " + e.getMessage()); + } + + if (currentMessage == null) { + Logger.trace("Message was null, continuing"); + continue; + } else if (currentMessage.item2 instanceof CharacterSelectionMessage) { + receiveCharacterSelection((CharacterSelectionMessage) currentMessage.item2); + } else if (currentMessage.item2 instanceof RequestMessage) { + receiveRequests((RequestMessage) currentMessage.item2); + } else { + } + } + } + + /** Send messages to the lobbyConnection */ + public void receiveMessage(Participant participant, BasicMessage message) { + this.incomingMessages.add(Tuple.of(participant, message)); + } + + private void receiveRequests(RequestMessage message) { + // TODO: implement this + } + + private void receiveCharacterSelection(CharacterSelectionMessage message) { + // TODO: Implement proper character selection + } + + private void initializeLobby() { + Logger.trace("Initializing lobby..."); this.lobby = new Lobby( gameID, this, - partyConfig, - characterConfig, - scenarioConfig); - this.gameID = gameID; - this.spectators = new HashSet<>(10); - this.incomingRequests = new LinkedBlockingQueue<>(); + Server.getPartyConfig(), + Server.getCharacterConfig(), + Server.getScenarioConfig()); } /** @@ -52,9 +200,7 @@ public class LobbyConnection implements Runnable { return player1 != null; } - /** - * @return whether there is a player2 - */ + /** @return whether there is a player2 */ public boolean hasPlayer2() { return player2 != null; } @@ -176,16 +322,35 @@ public class LobbyConnection implements Runnable { /** Kills all connections to client, as well as the lobby */ public void terminateConnection() { - MessageRelay.getInstance().terminate(this); + // TODO: implement this } - /** Send requests to the lobby that the LobbyConnection connects to */ - public void receiveRequests(Request... requests) { - this.incomingRequests.add(requests); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LobbyConnection that = (LobbyConnection) o; + return characterSelection == that.characterSelection && inGame == that.inGame && Objects.equals(lobby, that.lobby) && Objects.equals(gameID, that.gameID) && Objects.equals(player1, that.player1) && Objects.equals(player2, that.player2) && Objects.equals(spectators, that.spectators) && Objects.equals(incomingMessages, that.incomingMessages) && Objects.equals(sendMessageCallback, that.sendMessageCallback); } @Override - public void run() { + public int hashCode() { + return Objects.hash(lobby, gameID, player1, player2, characterSelection, inGame, spectators, incomingMessages, sendMessageCallback); + } + @Override + public String toString() { + return "LobbyConnection{" + + "lobby=" + lobby + + ", gameID='" + gameID + '\'' + + ", player1=" + player1 + + ", player2=" + player2 + + ", characterSelection=" + characterSelection + + ", inGame=" + inGame + + ", spectators=" + spectators + + ", incomingMessages=" + incomingMessages + + ", sendMessageCallback=" + sendMessageCallback + + '}'; } }