bugfixes in server and cli-client
This commit is contained in:
@@ -9,19 +9,23 @@ import eu.jonahbauer.wizard.common.model.Configuration;
|
||||
import eu.jonahbauer.wizard.core.machine.Game;
|
||||
import eu.jonahbauer.wizard.core.messages.Observer;
|
||||
import eu.jonahbauer.wizard.core.model.Configurations;
|
||||
import eu.jonahbauer.wizard.core.model.GameConfiguration;
|
||||
import eu.jonahbauer.wizard.server.machine.Player;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@Getter
|
||||
@Log4j2
|
||||
@EqualsAndHashCode(of = "uuid")
|
||||
public class Session implements Observer {
|
||||
private static final int MIN_PLAYERS = 3;
|
||||
@@ -46,7 +50,7 @@ public class Session implements Observer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the given player with this session, removes him from the lobby, notifies all other players in the
|
||||
* Associates the given player with this session and notifies all other players in the
|
||||
* session with a {@link PlayerJoinedMessage}, the joining player with a {@link SessionJoinedMessage} and all
|
||||
* players in the lobby with a {@link SessionModifiedMessage}.
|
||||
*
|
||||
@@ -55,18 +59,19 @@ public class Session implements Observer {
|
||||
* @return the players uuid
|
||||
*/
|
||||
public synchronized UUID join(Player player, String name) {
|
||||
if (players.size() == MAX_PLAYERS) {
|
||||
if (game != null) {
|
||||
throw new NackException(NackMessage.GAME_ALREADY_STARTED, "Game has already started.");
|
||||
} else if (players.size() == MAX_PLAYERS) {
|
||||
throw new NackException(NackMessage.SESSION_FULL, "Session is full.");
|
||||
} else if (players.values().stream().anyMatch(p -> p.getName().equalsIgnoreCase(name))) {
|
||||
throw new NackException(NackMessage.NAME_TAKEN, "Name is already taken.");
|
||||
}
|
||||
|
||||
Lobby.getInstance().leave(player);
|
||||
|
||||
SessionPlayer sessionPlayer;
|
||||
do {
|
||||
sessionPlayer = new SessionPlayer(UUID.randomUUID(), name, player);
|
||||
sessionPlayer = new SessionPlayer(UUID.randomUUID(), name);
|
||||
} while (players.putIfAbsent(sessionPlayer.getUuid(), sessionPlayer) != null);
|
||||
sessionPlayer.setPlayer(player);
|
||||
|
||||
notifyJoined(sessionPlayer.toData());
|
||||
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
|
||||
@@ -75,54 +80,120 @@ public class Session implements Observer {
|
||||
}
|
||||
|
||||
public synchronized void leave(UUID player) {
|
||||
if (players.remove(player) != null) {
|
||||
if (players.size() == 0) {
|
||||
Lobby.getInstance().removeSession(uuid);
|
||||
} else {
|
||||
notifyPlayers(new PlayerLeftMessage(player));
|
||||
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
|
||||
if (game == null) {
|
||||
if (players.remove(player) != null) {
|
||||
if (players.size() == 0) {
|
||||
Lobby.getInstance().removeSession(uuid);
|
||||
} else {
|
||||
notifyPlayers(new PlayerLeftMessage(player));
|
||||
Lobby.getInstance().notifyPlayers(new SessionModifiedMessage(toData()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var sessionPlayer = players.get(player);
|
||||
if (sessionPlayer != null) {
|
||||
sessionPlayer.setPlayer(null);
|
||||
if (players.values().stream().noneMatch(SessionPlayer::isConnected)) {
|
||||
Lobby.getInstance().removeSession(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void ready(UUID player, boolean ready) {
|
||||
var sessionPlayer = players.get(player);
|
||||
if (sessionPlayer == null) {
|
||||
public synchronized void ready(UUID uuid, boolean ready) {
|
||||
var player = players.get(uuid);
|
||||
if (player == null) {
|
||||
throw new NackException(NackMessage.PLAYER_NOT_FOUND, "Who are you?");
|
||||
} else if (game != null) {
|
||||
throw new NackException(NackMessage.GAME_ALREADY_STARTED, "Game has already started.");
|
||||
}
|
||||
|
||||
if (sessionPlayer.isReady() != ready) {
|
||||
sessionPlayer.setReady(ready);
|
||||
sessionPlayer.getPlayer().send(new AckMessage());
|
||||
notifyPlayers(new PlayerModifiedMessage(sessionPlayer.toData()));
|
||||
player.send(new AckMessage());
|
||||
if (player.isReady() != ready) {
|
||||
player.setReady(ready);
|
||||
notifyPlayers(new PlayerModifiedMessage(player.toData()));
|
||||
}
|
||||
|
||||
if (players.size() >= MIN_PLAYERS && players.values().stream().allMatch(SessionPlayer::isReady)) {
|
||||
game = new Game(Configurations.get(configuration), this);
|
||||
notifyPlayers(new StartingGameMessage());
|
||||
game.start(players.keySet().stream().toList());
|
||||
startGame();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the message sent by the player to the game.
|
||||
*
|
||||
* @param uuid the player
|
||||
* @param message the players message
|
||||
*/
|
||||
public synchronized void handlePlayerMessage(UUID uuid, PlayerMessage message) {
|
||||
var player = players.get(uuid);
|
||||
if (player == null) {
|
||||
throw new NackException(NackMessage.PLAYER_NOT_FOUND, "Who are you?");
|
||||
} else if (game == null) {
|
||||
throw new NackException(NackMessage.GAME_NOT_YET_STARTED, "Game hat not yet started.");
|
||||
} else {
|
||||
try {
|
||||
game.execute(s -> {
|
||||
// start buffering while game is locked
|
||||
player.buffer();
|
||||
return s.onMessage(game, uuid, message);
|
||||
});
|
||||
player.send(new AckMessage());
|
||||
} catch (IllegalStateException e) {
|
||||
throw new NackException(NackMessage.ILLEGAL_STATE, e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new NackException(NackMessage.ILLEGAL_ARGUMENT, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
notifyPlayers(new StartingGameMessage());
|
||||
game = new Game(Configurations.get(configuration).withTimeout(timeout), this);
|
||||
game.start(List.copyOf(players.keySet()));
|
||||
CompletableFuture.runAsync(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
game.await();
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
} catch (ExecutionException e) {
|
||||
log.warn("Game completed with exception.", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
players.forEach((id, player) -> player.setReady(false));
|
||||
synchronized (this) {
|
||||
game = null;
|
||||
for (SessionPlayer player : List.copyOf(players.values())) {
|
||||
if (!player.isConnected()) {
|
||||
leave(player.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyJoined(PlayerData joined) {
|
||||
var message = new PlayerJoinedMessage(joined);
|
||||
for (SessionPlayer player : players.values()) {
|
||||
if (player.getUuid().equals(joined.getUuid())) {
|
||||
player.getPlayer().send(new SessionJoinedMessage(
|
||||
player.send(new SessionJoinedMessage(
|
||||
getUuid(),
|
||||
player.getUuid(),
|
||||
players.values().stream().map(SessionPlayer::toData).toList(),
|
||||
player.getSecret()
|
||||
));
|
||||
} else {
|
||||
player.getPlayer().send(message);
|
||||
player.send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyPlayers(ServerMessage message) {
|
||||
for (SessionPlayer player : players.values()) {
|
||||
player.getPlayer().send(message);
|
||||
player.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +201,6 @@ public class Session implements Observer {
|
||||
return new SessionData(uuid, name, players.size(), configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the message sent by the player to the game.
|
||||
*
|
||||
* @param player the player
|
||||
* @param message the players message
|
||||
*/
|
||||
public void handlePlayerMessage(UUID player, PlayerMessage message) {
|
||||
game.onMessage(player, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(ObserverMessage message) {
|
||||
notifyPlayers(new GameMessage(message));
|
||||
@@ -147,7 +208,7 @@ public class Session implements Observer {
|
||||
|
||||
@Override
|
||||
public void notify(UUID player, ObserverMessage message) {
|
||||
players.get(player).getPlayer().send(new GameMessage(message));
|
||||
players.get(player).send(new GameMessage(message));
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -155,8 +216,8 @@ public class Session implements Observer {
|
||||
private static class SessionPlayer {
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
private final Player player;
|
||||
private final String secret = generateSecret();
|
||||
private Player player;
|
||||
private boolean ready;
|
||||
|
||||
private static String generateSecret() {
|
||||
@@ -169,5 +230,21 @@ public class Session implements Observer {
|
||||
public PlayerData toData() {
|
||||
return new PlayerData(uuid, name, ready);
|
||||
}
|
||||
|
||||
public void send(ServerMessage message) {
|
||||
if (player != null) {
|
||||
player.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void buffer() {
|
||||
if (player != null) {
|
||||
player.buffer();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return player != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,18 +3,18 @@ package eu.jonahbauer.wizard.server.machine;
|
||||
import eu.jonahbauer.wizard.common.machine.State;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.server.NackException;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ClientState extends State<ClientState, Player> {
|
||||
default Optional<ClientState> onOpen(Player player) {
|
||||
throw new IllegalStateException(); // TODO nachdenken
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
default Optional<ClientState> onMessage(Player player, ClientMessage message) {
|
||||
player.send(new NackMessage(NackMessage.BAD_REQUEST, "Unexpected message."));
|
||||
return Optional.empty();
|
||||
throw new NackException(NackMessage.UNEXPECTED_MESSAGE, "Don't know what to do with " + message + ".");
|
||||
}
|
||||
|
||||
Optional<ClientState> onClose(Player player, CloseStatus status);
|
||||
|
@@ -2,17 +2,20 @@ package eu.jonahbauer.wizard.server.machine;
|
||||
|
||||
import eu.jonahbauer.wizard.common.machine.Context;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.Response;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.server.NackException;
|
||||
import eu.jonahbauer.wizard.server.machine.states.CreatedState;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Player extends Context<ClientState, Player> {
|
||||
private final WebSocketSession session;
|
||||
private final ArrayList<ServerMessage> buffer = new ArrayList<>();
|
||||
private Class<?> shouldBuffer = null;
|
||||
|
||||
public Player(WebSocketSession session) {
|
||||
super(new CreatedState());
|
||||
@@ -29,22 +32,32 @@ public class Player extends Context<ClientState, Player> {
|
||||
}
|
||||
|
||||
public void onMessage(ClientMessage message) {
|
||||
|
||||
try {
|
||||
execute(s -> s.onMessage(this, message));
|
||||
} catch (NackException e) {
|
||||
send(new NackMessage(e.getCode(), e.getMessage()));
|
||||
}
|
||||
execute(s -> s.onMessage(this, message));
|
||||
}
|
||||
|
||||
public void onClose(CloseStatus status) {
|
||||
execute(s -> s.onClose(this, status));
|
||||
}
|
||||
|
||||
public void buffer() {
|
||||
shouldBuffer = Response.class;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void send(ServerMessage message) {
|
||||
synchronized (session) {
|
||||
session.sendMessage(new TextMessage(message.toString()));
|
||||
if (shouldBuffer != null && shouldBuffer.isInstance(message)) {
|
||||
session.sendMessage(new TextMessage(message.toString()));
|
||||
shouldBuffer = null;
|
||||
for (ServerMessage serverMessage : buffer) {
|
||||
session.sendMessage(new TextMessage(serverMessage.toString()));
|
||||
}
|
||||
buffer.clear();
|
||||
} else if (shouldBuffer != null) {
|
||||
buffer.add(message);
|
||||
} else {
|
||||
session.sendMessage(new TextMessage(message.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,46 +0,0 @@
|
||||
package eu.jonahbauer.wizard.server.machine.states;
|
||||
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.StartingGameMessage;
|
||||
import eu.jonahbauer.wizard.server.machine.ClientState;
|
||||
import eu.jonahbauer.wizard.server.machine.Player;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class InGame implements ClientState {
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Player context) {
|
||||
context.send(new StartingGameMessage());
|
||||
|
||||
return ClientState.super.onEnter(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExit(Player context) {
|
||||
ClientState.super.onExit(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onOpen(Player player) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Player player, ClientMessage message) {
|
||||
if(message instanceof LeaveSessionMessage) {
|
||||
//?
|
||||
return Optional.empty();
|
||||
} else {
|
||||
player.send(new NackMessage(0, "Error: Invalid Message!"));
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onClose(Player player, CloseStatus status) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@@ -23,16 +23,33 @@ public class LobbyState implements ClientState {
|
||||
public Optional<ClientState> onMessage(Player player, ClientMessage message) {
|
||||
if (message instanceof CreateSessionMessage create) {
|
||||
Lobby.getInstance().leave(player);
|
||||
var session = Lobby.getInstance().createSession(create);
|
||||
var uuid = session.join(player, create.getPlayerName());
|
||||
return Optional.of(new SessionState(session.getUuid(), uuid, create.getPlayerName()));
|
||||
try {
|
||||
player.buffer();
|
||||
var session = Lobby.getInstance().createSession(create);
|
||||
var uuid = session.join(player, create.getPlayerName());
|
||||
return Optional.of(new SessionState(session.getUuid(), uuid, create.getPlayerName()));
|
||||
} catch (NackException nack) {
|
||||
// nack must be sent before joinlobby
|
||||
player.send(nack.toMessage());
|
||||
Lobby.getInstance().join(player);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else if (message instanceof JoinSessionMessage join) {
|
||||
var session = Lobby.getInstance().getSession(join.getSession());
|
||||
if (session == null) {
|
||||
throw new NackException(NackMessage.NOT_FOUND, "Session not found.");
|
||||
} else {
|
||||
var uuid = session.join(player, join.getPlayerName());
|
||||
return Optional.of(new SessionState(session.getUuid(), uuid, join.getPlayerName()));
|
||||
Lobby.getInstance().leave(player);
|
||||
try {
|
||||
player.buffer();
|
||||
var session = Lobby.getInstance().getSession(join.getSession());
|
||||
if (session == null) {
|
||||
throw new NackException(NackMessage.NOT_FOUND, "Session not found.");
|
||||
} else {
|
||||
var uuid = session.join(player, join.getPlayerName());
|
||||
return Optional.of(new SessionState(session.getUuid(), uuid, join.getPlayerName()));
|
||||
}
|
||||
} catch (NackException nack) {
|
||||
// nack must be sent before joinlobby
|
||||
player.send(nack.toMessage());
|
||||
Lobby.getInstance().join(player);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return ClientState.super.onMessage(player, message);
|
||||
|
@@ -4,7 +4,6 @@ import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ReadyMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.server.Lobby;
|
||||
import eu.jonahbauer.wizard.server.machine.ClientState;
|
||||
import eu.jonahbauer.wizard.server.machine.Player;
|
||||
@@ -42,8 +41,7 @@ public class SessionState implements ClientState {
|
||||
Lobby.getInstance().getSession(session).handlePlayerMessage(self, playerMessage);
|
||||
return Optional.empty();
|
||||
} else {
|
||||
player.send(new NackMessage(NackMessage.UNEXPECTED_MESSAGE, "Invalid Message!"));
|
||||
return Optional.empty();
|
||||
return ClientState.super.onMessage(player, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package eu.jonahbauer.wizard.server.socket;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.server.NackException;
|
||||
import eu.jonahbauer.wizard.server.machine.Player;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -29,7 +32,14 @@ public class WizardSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(@NotNull WebSocketSession session, @NotNull TextMessage text) {
|
||||
players.get(session.getId()).onMessage(ClientMessage.parse(text.getPayload()));
|
||||
var player = players.get(session.getId());
|
||||
try {
|
||||
player.onMessage(ClientMessage.parse(text.getPayload()));
|
||||
} catch (NackException e) {
|
||||
player.send(e.toMessage());
|
||||
} catch (JsonParseException e) {
|
||||
player.send(new NackMessage(NackMessage.MALFORMED_MESSAGE, "Could not parse " + text.getPayload() + "."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user