bugfixes in server and cli-client
This commit is contained in:
parent
80864f0d44
commit
53992fc4cf
@ -85,4 +85,18 @@ object JavaWebSocket {
|
||||
const val version = "1.5.2"
|
||||
const val group = "org.java-websocket"
|
||||
const val id = "$group:Java-WebSocket:$version"
|
||||
}
|
||||
|
||||
object SpringBoot {
|
||||
const val version = "2.5.6"
|
||||
const val group = "org.springframework.boot"
|
||||
const val plugin = group
|
||||
const val starterWebsocket = "$group:spring-boot-starter-websocket"
|
||||
const val starterTomcat = "$group:spring-boot-starter-tomcat"
|
||||
const val starterLog4j2 = "$group:spring-boot-starter-log4j2"
|
||||
}
|
||||
|
||||
object SpringDependencyManagement {
|
||||
const val version = "1.0.11.RELEASE"
|
||||
const val plugin = "io.spring.dependency-management"
|
||||
}
|
@ -25,7 +25,14 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static picocli.CommandLine.*;
|
||||
|
||||
@Command
|
||||
public class Client extends TimeoutContext<ClientState, Client> implements Runnable {
|
||||
@Option(names = "-v")
|
||||
@Getter
|
||||
private boolean verbose;
|
||||
|
||||
private LineReader reader;
|
||||
|
||||
@Getter
|
||||
@ -39,7 +46,7 @@ public class Client extends TimeoutContext<ClientState, Client> implements Runna
|
||||
private CommandSpec spec;
|
||||
|
||||
public static void main(String[] args) {
|
||||
new Client().run();
|
||||
new CommandLine(new Client()).execute(args);
|
||||
}
|
||||
|
||||
public Client() {
|
||||
@ -126,6 +133,9 @@ public class Client extends TimeoutContext<ClientState, Client> implements Runna
|
||||
}
|
||||
|
||||
public void onMessage(ServerMessage message) {
|
||||
if (verbose) {
|
||||
println(message.toString());
|
||||
}
|
||||
execute(s -> s.onMessage(this, message));
|
||||
}
|
||||
|
||||
@ -239,7 +249,7 @@ public class Client extends TimeoutContext<ClientState, Client> implements Runna
|
||||
Exception exception = exceptionRef.get();
|
||||
if (exception == null) {
|
||||
Object result;
|
||||
CommandLine.ParseResult parseResult = commandLine.getParseResult();
|
||||
ParseResult parseResult = commandLine.getParseResult();
|
||||
if (parseResult.subcommand() != null) {
|
||||
CommandLine sub = parseResult.subcommand().commandSpec().commandLine();
|
||||
result = sub.getExecutionResult();
|
||||
|
@ -1,15 +1,12 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingAcknowledgement;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Game;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Menu;
|
||||
import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.JuggleMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -21,60 +18,41 @@ import static picocli.CommandLine.*;
|
||||
@Command(name = "\b", subcommands = {HelpCommand.class, QuitCommand.class, ShowCommand.class})
|
||||
public class GameCommand {
|
||||
private final Client client;
|
||||
private final Game game;
|
||||
|
||||
public GameCommand(Client client, Game game) {
|
||||
public GameCommand(Client client) {
|
||||
this.client = client;
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
@Command(name = "play")
|
||||
public AwaitingAcknowledgement play(
|
||||
public void play(
|
||||
@Parameters(index = "0", paramLabel = "<card>", completionCandidates = HandCompletion.class) Card card
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PlayCardMessage(card)));
|
||||
return awaitAcknowledgement();
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "predict")
|
||||
public AwaitingAcknowledgement predict(
|
||||
public void predict(
|
||||
@Parameters(index = "0", paramLabel = "<prediction>") int prediction
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PredictMessage(prediction)));
|
||||
return awaitAcknowledgement();
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "trump")
|
||||
public AwaitingAcknowledgement trump(
|
||||
public void trump(
|
||||
@Parameters(index = "0", paramLabel = "<suit>") Card.Suit suit
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new PickTrumpMessage(suit)));
|
||||
return awaitAcknowledgement();
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "juggle")
|
||||
public AwaitingAcknowledgement juggle(
|
||||
public void juggle(
|
||||
@Parameters(index = "0", paramLabel = "<card>", completionCandidates = JuggleCompletion.class) Card card
|
||||
) {
|
||||
this.client.send(new InteractionMessage(new JuggleMessage(card)));
|
||||
return awaitAcknowledgement();
|
||||
}
|
||||
|
||||
private AwaitingAcknowledgement awaitAcknowledgement() {
|
||||
return new AwaitingAcknowledgement(
|
||||
() -> game,
|
||||
message -> {
|
||||
if (message instanceof NackMessage nack) {
|
||||
int code = nack.getCode();
|
||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
||||
client.println("Error: " + nack.getMessage());
|
||||
return game;
|
||||
}
|
||||
}
|
||||
client.println("Fatal: Unexpected message " + message + ". Returning to menu.");
|
||||
return new Menu();
|
||||
}
|
||||
);
|
||||
this.client.waitForReady();
|
||||
}
|
||||
|
||||
public static class HandCompletion implements Iterable<String> {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package eu.jonahbauer.wizard.client.cli.commands;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingAcknowledgement;
|
||||
import eu.jonahbauer.wizard.client.cli.state.AwaitingJoinLobby;
|
||||
import eu.jonahbauer.wizard.client.cli.state.Session;
|
||||
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
|
||||
@ -20,13 +19,12 @@ public class SessionCommand {
|
||||
}
|
||||
|
||||
@Command(name = "ready")
|
||||
public AwaitingAcknowledgement ready(
|
||||
public void ready(
|
||||
@Parameters(index = "0", paramLabel = "<ready>", defaultValue = "true") boolean ready
|
||||
) {
|
||||
session.setNextReady(ready);
|
||||
client.send(new ReadyMessage(ready));
|
||||
return new AwaitingAcknowledgement(
|
||||
() -> new Session(session.getSelf(), session.getSession(), ready, session.getPlayers())
|
||||
);
|
||||
client.waitForReady();
|
||||
}
|
||||
|
||||
@Command(name = "leave", description = "Leaves the current session and returns to the lobby")
|
||||
|
@ -1,43 +0,0 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.Response;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class AwaitingAcknowledgement extends Awaiting {
|
||||
private final Supplier<ClientState> success;
|
||||
private final Function<ServerMessage, ClientState> failure;
|
||||
|
||||
public AwaitingAcknowledgement(Supplier<ClientState> success, Function<ServerMessage, ClientState> failure) {
|
||||
this.success = success;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public AwaitingAcknowledgement(Supplier<ClientState> success) {
|
||||
this.success = success;
|
||||
this.failure = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onEnter(Client client) {
|
||||
client.println("Waiting for acknowledgment...");
|
||||
return super.onEnter(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
if (message instanceof AckMessage) {
|
||||
return Optional.of(success.get());
|
||||
} else if (failure != null) {
|
||||
return Optional.of(failure.apply(message));
|
||||
} else {
|
||||
return super.onMessage(client, message);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package eu.jonahbauer.wizard.client.cli.state;
|
||||
|
||||
import eu.jonahbauer.wizard.client.cli.Client;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.SessionJoinedMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@ -28,6 +26,9 @@ public final class AwaitingJoinSession extends Awaiting {
|
||||
default -> { return super.onMessage(client, message); }
|
||||
}
|
||||
return Optional.of(new AwaitingJoinLobby());
|
||||
} else if (message instanceof SessionModifiedMessage || message instanceof SessionRemovedMessage) {
|
||||
// drop
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return super.onMessage(client, message);
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ public abstract class BaseState implements ClientState {
|
||||
protected static Optional<ClientState> unexpectedMessage(Client client, ServerMessage message) {
|
||||
// return to menu on unexpected message
|
||||
client.println("Fatal: Unexpected message " + message + ". Returning to menu.");
|
||||
if (client.isVerbose()) {
|
||||
new Exception().printStackTrace();
|
||||
}
|
||||
return Optional.of(new Menu());
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import eu.jonahbauer.wizard.client.cli.commands.GameCommand;
|
||||
import eu.jonahbauer.wizard.client.cli.util.Pair;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.*;
|
||||
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
|
||||
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
|
||||
import eu.jonahbauer.wizard.common.model.Card;
|
||||
import lombok.Getter;
|
||||
@ -149,6 +151,18 @@ public final class Game extends BaseState {
|
||||
throw new AssertionError("Unknown observer message " + observerMessage.getClass().getSimpleName() + "");
|
||||
}
|
||||
return Optional.empty();
|
||||
} else if (message instanceof NackMessage nack) {
|
||||
int code = nack.getCode();
|
||||
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
|
||||
client.println("Error: " + nack.getMessage());
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
} else if (message instanceof AckMessage) {
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ public final class Menu extends BaseState {
|
||||
|
||||
@Override
|
||||
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
|
||||
throw new AssertionError("Received a ServerMessage while not connected.");
|
||||
// it is possible that there are messages still queued after
|
||||
// returning to the menu as a result of a previous message
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -5,6 +5,7 @@ import eu.jonahbauer.wizard.client.cli.commands.SessionCommand;
|
||||
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
|
||||
import eu.jonahbauer.wizard.common.messages.server.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -19,9 +20,12 @@ public final class Session extends BaseState {
|
||||
|
||||
private final UUID self;
|
||||
private final UUID session;
|
||||
private final boolean ready;
|
||||
private final Map<UUID, PlayerData> players = new HashMap<>();
|
||||
|
||||
private boolean ready;
|
||||
@Setter
|
||||
private Boolean nextReady;
|
||||
|
||||
public Session(SessionJoinedMessage joined) {
|
||||
this.self = joined.getPlayer();
|
||||
this.session = joined.getSession();
|
||||
@ -68,6 +72,16 @@ public final class Session extends BaseState {
|
||||
session,
|
||||
players.values().stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName))
|
||||
));
|
||||
} else if (nextReady != null && message instanceof NackMessage nack) {
|
||||
client.println("Error: " + nack.getMessage());
|
||||
nextReady = null;
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else if (nextReady != null && message instanceof AckMessage) {
|
||||
ready = nextReady;
|
||||
nextReady = null;
|
||||
client.ready();
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return unexpectedMessage(client, message);
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
plugins {
|
||||
id("org.springframework.boot").version("2.5.6")
|
||||
//id("io.spring.dependency-management").version("1.0.7-RELEASE")
|
||||
id(SpringBoot.plugin) version SpringBoot.version
|
||||
id(SpringDependencyManagement.plugin) version SpringDependencyManagement.version
|
||||
id("java")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
id("war")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":wizard-core"))
|
||||
implementation(project(":wizard-common"))
|
||||
implementation("org.springframework.boot:spring-boot-starter-websocket:2.5.6")
|
||||
implementation(SpringBoot.starterWebsocket) {
|
||||
exclude(group = SpringBoot.group, module = "spring-boot-starter-logging")
|
||||
}
|
||||
implementation(SpringBoot.starterLog4j2)
|
||||
providedRuntime(SpringBoot.starterTomcat)
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user