Workaround to get JavaFX multiplayer working
This commit is contained in:
parent
075b19cede
commit
394f75cd6e
@ -1,7 +1,9 @@
|
|||||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||||
|
|
||||||
# Development
|
# Development
|
||||||
web.socket.url: ws://localhost:8080/stomp
|
web.base.url: http://localhost:8080
|
||||||
|
web.socket.url: ws://localhost:8080/sockjs
|
||||||
|
|
||||||
# Production
|
# Production
|
||||||
#web.socket.url: ws://lingo.charego.com/stomp
|
#web.base.url: http://lingo.charego.com
|
||||||
|
#web.socket.url: ws://lingo.charego.com/sockjs
|
||||||
|
@ -3,14 +3,17 @@ package lingo.client.multiplayer;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.web.client.RootUriTemplateHandler;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||||
import org.springframework.messaging.converter.MessageConverter;
|
import org.springframework.messaging.converter.MessageConverter;
|
||||||
import org.springframework.messaging.converter.StringMessageConverter;
|
import org.springframework.messaging.converter.StringMessageConverter;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
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.WebSocketClient;
|
||||||
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||||
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
import org.springframework.web.socket.messaging.WebSocketStompClient;
|
||||||
@ -47,4 +50,11 @@ public class MultiplayerConfig {
|
|||||||
return new CompositeMessageConverter(converters);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package lingo.client.multiplayer;
|
|||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
@ -10,9 +12,12 @@ import javax.annotation.PostConstruct;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.StompFrameHandler;
|
||||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
@ -35,6 +40,7 @@ import lingo.client.view.Board;
|
|||||||
import lingo.client.view.OpponentBoard;
|
import lingo.client.view.OpponentBoard;
|
||||||
import lingo.client.view.PlayerBoard;
|
import lingo.client.view.PlayerBoard;
|
||||||
import lingo.common.Game;
|
import lingo.common.Game;
|
||||||
|
import lingo.common.GameLeftMessage;
|
||||||
import lingo.common.Report;
|
import lingo.common.Report;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@ -59,6 +65,9 @@ public class MultiplayerPresenter implements FxmlController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StompTemplate stompTemplate;
|
private StompTemplate stompTemplate;
|
||||||
|
|
||||||
@ -72,7 +81,11 @@ public class MultiplayerPresenter implements FxmlController {
|
|||||||
|
|
||||||
private OpponentBoard opponentBoard;
|
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) {
|
private void clearBoards(boolean clearScore) {
|
||||||
playerBoard.clearBoard();
|
playerBoard.clearBoard();
|
||||||
@ -123,7 +136,22 @@ public class MultiplayerPresenter implements FxmlController {
|
|||||||
ok.printStackTrace();
|
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) {
|
} else if (keyCode == KeyCode.ENTER) {
|
||||||
final String guess = playerBoard.handleEnter();
|
final String guess = playerBoard.handleEnter();
|
||||||
if (guess != null) {
|
if (guess != null) {
|
||||||
executorService.execute(() -> stompTemplate.getSession().send("/app/lingo/guess", guess));
|
executorService.execute(() -> stompTemplate.getSession().send("/app/guess", guess));
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
} else if (keyCode.isLetterKey()) {
|
} else if (keyCode.isLetterKey()) {
|
||||||
@ -154,10 +182,16 @@ public class MultiplayerPresenter implements FxmlController {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
private void postConstruct() {
|
private void postConstruct() {
|
||||||
executorService.execute(() -> {
|
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(),
|
stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(),
|
||||||
subscription -> subscriptionsLatch.countDown());
|
subscription -> subscriptionsLatch.countDown());
|
||||||
//stompTemplate.subscribe("/user" + Destinations.OPPONENT_LEFT, new OpponentLeftHandler(),
|
|
||||||
// subscription -> subscriptionsLatch.countDown());
|
|
||||||
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
|
stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(),
|
||||||
subscription -> subscriptionsLatch.countDown());
|
subscription -> subscriptionsLatch.countDown());
|
||||||
stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(),
|
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 {
|
private class OpponentJoinedHandler implements StompFrameHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type getPayloadType(StompHeaders headers) {
|
public Type getPayloadType(StompHeaders headers) {
|
||||||
return String.class;
|
return String[].class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleFrame(StompHeaders headers, Object payload) {
|
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(() -> {
|
Platform.runLater(() -> {
|
||||||
clearBoards(true);
|
clearBoards(true);
|
||||||
newWord(firstLetter);
|
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 {
|
private class OpponentReportHandler implements StompFrameHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -319,4 +419,8 @@ public class MultiplayerPresenter implements FxmlController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class GameList extends ParameterizedTypeReference<Collection<Game>> {
|
||||||
|
// intentionally left empty
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<VBox spacing="20" alignment="CENTER">
|
<VBox spacing="20" alignment="CENTER">
|
||||||
<children>
|
<children>
|
||||||
<Button text="Practice" onAction="#showSinglePlayer" prefWidth="350" styleClass="game-mode" />
|
<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>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
</center>
|
</center>
|
||||||
|
@ -18,7 +18,7 @@ public class Game {
|
|||||||
|
|
||||||
private static final AtomicInteger idCounter = new AtomicInteger(0);
|
private static final AtomicInteger idCounter = new AtomicInteger(0);
|
||||||
|
|
||||||
public final int id;
|
private int id;
|
||||||
|
|
||||||
private Player playerOne;
|
private Player playerOne;
|
||||||
|
|
||||||
@ -32,6 +32,10 @@ public class Game {
|
|||||||
|
|
||||||
private int wordIndex = 0;
|
private int wordIndex = 0;
|
||||||
|
|
||||||
|
public Game() {
|
||||||
|
// Empty constructor required for serialization
|
||||||
|
}
|
||||||
|
|
||||||
public Game(Player host) {
|
public Game(Player host) {
|
||||||
this.id = idCounter.incrementAndGet();
|
this.id = idCounter.incrementAndGet();
|
||||||
this.playerOne = host;
|
this.playerOne = host;
|
||||||
@ -87,6 +91,10 @@ public class Game {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
public Player getPlayerOne() {
|
public Player getPlayerOne() {
|
||||||
return playerOne;
|
return playerOne;
|
||||||
}
|
}
|
||||||
@ -110,6 +118,10 @@ public class Game {
|
|||||||
this.acceptableGuesses = value;
|
this.acceptableGuesses = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setId(int value) {
|
||||||
|
this.id = value;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPlayerOne(Player value) {
|
public void setPlayerOne(Player value) {
|
||||||
this.playerOne = value;
|
this.playerOne = value;
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
public class Player {
|
public class Player {
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private final String sessionId;
|
private String sessionId;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
public Player() {
|
||||||
|
// Empty constructor required for serialization
|
||||||
|
}
|
||||||
|
|
||||||
public Player(String sessionId) {
|
public Player(String sessionId) {
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
@ -121,9 +121,9 @@ public class LingoController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Game game = new Game(player);
|
final Game game = new Game(player);
|
||||||
gameById.put(game.id, game);
|
gameById.put(game.getId(), game);
|
||||||
gameByPlayer.put(player, game);
|
gameByPlayer.put(player, game);
|
||||||
log.info("{} hosted Game {}", player, game.id);
|
log.info("{} hosted Game {}", player, game.getId());
|
||||||
send(Destinations.GAME_HOSTED, game);
|
send(Destinations.GAME_HOSTED, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +198,8 @@ public class LingoController {
|
|||||||
if (playerOne == player) {
|
if (playerOne == player) {
|
||||||
if (playerTwo == null) {
|
if (playerTwo == null) {
|
||||||
// Close the game
|
// Close the game
|
||||||
log.info("{} closed Game {}", player, game.id);
|
log.info("{} closed Game {}", player, game.getId());
|
||||||
gameById.remove(game.id);
|
gameById.remove(game.getId());
|
||||||
send(Destinations.GAME_CLOSED, game);
|
send(Destinations.GAME_CLOSED, game);
|
||||||
} else {
|
} else {
|
||||||
// Leave the game
|
// Leave the game
|
||||||
|
@ -357,7 +357,6 @@ function start() {
|
|||||||
client.subscribe('/topic/gameJoined', onGameJoined);
|
client.subscribe('/topic/gameJoined', onGameJoined);
|
||||||
client.subscribe('/topic/gameLeft', onGameLeft);
|
client.subscribe('/topic/gameLeft', onGameLeft);
|
||||||
client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
|
client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
|
||||||
client.subscribe('/user/topic/opponentLeft', onOpponentLeft);
|
|
||||||
client.subscribe('/user/topic/opponentReports', onOpponentReport);
|
client.subscribe('/user/topic/opponentReports', onOpponentReport);
|
||||||
client.subscribe('/user/topic/playerReports', onPlayerReport);
|
client.subscribe('/user/topic/playerReports', onPlayerReport);
|
||||||
}
|
}
|
||||||
@ -495,7 +494,9 @@ function onGameLeft(message) {
|
|||||||
vm.gameId = null;
|
vm.gameId = null;
|
||||||
}
|
}
|
||||||
if (previousPlayers.indexOf(vm.username) != -1) {
|
if (previousPlayers.indexOf(vm.username) != -1) {
|
||||||
onOpponentLeft();
|
vm.opponentUsername = null;
|
||||||
|
vm.lastWord = null;
|
||||||
|
vm.repaint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,12 +509,6 @@ function onOpponentJoined(message) {
|
|||||||
vm.repaint();
|
vm.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpponentLeft(message) {
|
|
||||||
vm.opponentUsername = null;
|
|
||||||
vm.lastWord = null;
|
|
||||||
vm.repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOpponentReport(message) {
|
function onOpponentReport(message) {
|
||||||
var report = JSON.parse(message.body);
|
var report = JSON.parse(message.body);
|
||||||
if (report.correct === true) {
|
if (report.correct === true) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user