Workaround to get JavaFX multiplayer working

This commit is contained in:
Charles Gould 2017-10-22 13:37:09 -04:00
parent 075b19cede
commit 394f75cd6e
8 changed files with 169 additions and 42 deletions

View File

@ -1,7 +1,9 @@
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# Development
web.socket.url: ws://localhost:8080/stomp
web.base.url: http://localhost:8080
web.socket.url: ws://localhost:8080/sockjs
# Production
#web.socket.url: ws://lingo.charego.com/stomp
#web.base.url: http://lingo.charego.com
#web.socket.url: ws://lingo.charego.com/sockjs

View File

@ -3,14 +3,17 @@ package lingo.client.multiplayer;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
@ -47,4 +50,11 @@ public class MultiplayerConfig {
return new CompositeMessageConverter(converters);
}
@Bean
public RestTemplate restTemplate(Environment env) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new RootUriTemplateHandler(env.getProperty("web.base.url")));
return restTemplate;
}
}

View File

@ -2,6 +2,8 @@ package lingo.client.multiplayer;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@ -10,9 +12,12 @@ import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javafx.application.Platform;
import javafx.event.ActionEvent;
@ -35,6 +40,7 @@ import lingo.client.view.Board;
import lingo.client.view.OpponentBoard;
import lingo.client.view.PlayerBoard;
import lingo.common.Game;
import lingo.common.GameLeftMessage;
import lingo.common.Report;
@Component
@ -59,6 +65,9 @@ public class MultiplayerPresenter implements FxmlController {
@Autowired
private ExecutorService executorService;
@Autowired
private RestTemplate restTemplate;
@Autowired
private StompTemplate stompTemplate;
@ -72,7 +81,11 @@ public class MultiplayerPresenter implements FxmlController {
private OpponentBoard opponentBoard;
private final CountDownLatch subscriptionsLatch = new CountDownLatch(4);
private final CountDownLatch subscriptionsLatch = new CountDownLatch(7);
private String username;
private String opponentUsername;
private void clearBoards(boolean clearScore) {
playerBoard.clearBoard();
@ -123,7 +136,22 @@ public class MultiplayerPresenter implements FxmlController {
ok.printStackTrace();
}
}
stompTemplate.getSession().send("/app/lingo/join", null);
username = UUID.randomUUID().toString().substring(0, 8);
stompTemplate.getSession().send("/app/setUsername", username);
Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody();
boolean joinedGame = false;
for (Game game : games) {
if (game.getPlayerTwo() == null) {
stompTemplate.getSession().send("/app/joinGame", game.getId());
joinedGame = true;
break;
}
}
if (!joinedGame) {
stompTemplate.getSession().send("/app/hostGame", null);
}
});
}
@ -137,7 +165,7 @@ public class MultiplayerPresenter implements FxmlController {
} else if (keyCode == KeyCode.ENTER) {
final String guess = playerBoard.handleEnter();
if (guess != null) {
executorService.execute(() -> stompTemplate.getSession().send("/app/lingo/guess", guess));
executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess));
repaint();
}
} else if (keyCode.isLetterKey()) {
@ -154,10 +182,16 @@ public class MultiplayerPresenter implements FxmlController {
@PostConstruct
private void postConstruct() {
executorService.execute(() -> {
stompTemplate.subscribe(Destinations.GAME_CLOSED, new GameClosedHandler(),
subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_HOSTED, new GameHostedHandler(),
subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_JOINED, new GameJoinedHandler(),
subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe(Destinations.GAME_LEFT, new GameLeftHandler(),
subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(),
subscription -> subscriptionsLatch.countDown());
//stompTemplate.subscribe("/user" + Destinations.OPPONENT_LEFT, new OpponentLeftHandler(),
// subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
subscription -> subscriptionsLatch.countDown());
stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(),
@ -185,16 +219,100 @@ public class MultiplayerPresenter implements FxmlController {
}
}
private class GameClosedHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return Game.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload);
}
private void handleMessage(Game game) {
log.debug("{} closed Game {}", game.getPlayerOne(), game.getId());
}
}
private class GameHostedHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return Game.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload);
}
private void handleMessage(Game game) {
log.debug("{} hosted Game {}", game.getPlayerOne(), game.getId());
}
}
private class GameJoinedHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return Game.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((Game) payload);
}
private void handleMessage(Game game) {
log.debug("{} joined {}'s game", game.getPlayerTwo(), game.getPlayerOne());
}
}
private class GameLeftHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return GameLeftMessage.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
handleMessage((GameLeftMessage) payload);
}
private void handleMessage(GameLeftMessage message) {
final Game game = message.getGame();
final String gameLeaver = message.getGameLeaver().getUsername();
log.debug("{} left {}'s game", gameLeaver, game.getPlayerOne());
if (gameLeaver.equals(username) || gameLeaver.equals(opponentUsername)) {
Platform.runLater(() -> {
clearBoards(true);
showWaitingAnimation(true);
opponentUsername = null;
lastWord = null;
repaint();
});
}
}
}
private class OpponentJoinedHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
return String[].class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
final String firstLetter = payload.toString();
handleMessage((String[]) payload);
}
private void handleMessage(String[] message) {
final String firstLetter = message[0];
opponentUsername = message[1];
Platform.runLater(() -> {
clearBoards(true);
newWord(firstLetter);
@ -204,24 +322,6 @@ public class MultiplayerPresenter implements FxmlController {
}
}
private class OpponentLeftHandler implements StompFrameHandler {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
Platform.runLater(() -> {
clearBoards(true);
showWaitingAnimation(true);
lastWord = null;
repaint();
});
}
}
private class OpponentReportHandler implements StompFrameHandler {
@Override
@ -319,4 +419,8 @@ public class MultiplayerPresenter implements FxmlController {
}
}
private class GameList extends ParameterizedTypeReference<Collection<Game>> {
// intentionally left empty
}
}

View File

@ -18,7 +18,7 @@
<VBox spacing="20" alignment="CENTER">
<children>
<Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" />
<Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" disable="true" />
<Button text="Multiplayer" onAction="#showMultiplayer" prefWidth="350" styleClass="game-mode" />
</children>
</VBox>
</center>

View File

@ -18,7 +18,7 @@ public class Game {
private static final AtomicInteger idCounter = new AtomicInteger(0);
public final int id;
private int id;
private Player playerOne;
@ -32,6 +32,10 @@ public class Game {
private int wordIndex = 0;
public Game() {
// Empty constructor required for serialization
}
public Game(Player host) {
this.id = idCounter.incrementAndGet();
this.playerOne = host;
@ -87,6 +91,10 @@ public class Game {
return result;
}
public int getId() {
return id;
}
public Player getPlayerOne() {
return playerOne;
}
@ -110,6 +118,10 @@ public class Game {
this.acceptableGuesses = value;
}
public void setId(int value) {
this.id = value;
}
public void setPlayerOne(Player value) {
this.playerOne = value;
}

View File

@ -5,10 +5,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
public class Player {
@JsonIgnore
private final String sessionId;
private String sessionId;
private String username;
public Player() {
// Empty constructor required for serialization
}
public Player(String sessionId) {
this.sessionId = sessionId;
}

View File

@ -121,9 +121,9 @@ public class LingoController {
return;
}
final Game game = new Game(player);
gameById.put(game.id, game);
gameById.put(game.getId(), game);
gameByPlayer.put(player, game);
log.info("{} hosted Game {}", player, game.id);
log.info("{} hosted Game {}", player, game.getId());
send(Destinations.GAME_HOSTED, game);
}
@ -198,8 +198,8 @@ public class LingoController {
if (playerOne == player) {
if (playerTwo == null) {
// Close the game
log.info("{} closed Game {}", player, game.id);
gameById.remove(game.id);
log.info("{} closed Game {}", player, game.getId());
gameById.remove(game.getId());
send(Destinations.GAME_CLOSED, game);
} else {
// Leave the game

View File

@ -357,7 +357,6 @@ function start() {
client.subscribe('/topic/gameJoined', onGameJoined);
client.subscribe('/topic/gameLeft', onGameLeft);
client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
client.subscribe('/user/topic/opponentLeft', onOpponentLeft);
client.subscribe('/user/topic/opponentReports', onOpponentReport);
client.subscribe('/user/topic/playerReports', onPlayerReport);
}
@ -495,7 +494,9 @@ function onGameLeft(message) {
vm.gameId = null;
}
if (previousPlayers.indexOf(vm.username) != -1) {
onOpponentLeft();
vm.opponentUsername = null;
vm.lastWord = null;
vm.repaint();
}
}
@ -508,12 +509,6 @@ function onOpponentJoined(message) {
vm.repaint();
}
function onOpponentLeft(message) {
vm.opponentUsername = null;
vm.lastWord = null;
vm.repaint();
}
function onOpponentReport(message) {
var report = JSON.parse(message.body);
if (report.correct === true) {