diff --git a/client/src/main/config/application.yaml b/client/src/main/config/application.yaml index 69b6488..b6cf980 100644 --- a/client/src/main/config/application.yaml +++ b/client/src/main/config/application.yaml @@ -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 diff --git a/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java b/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java index c2fec0b..136f0b0 100644 --- a/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java +++ b/client/src/main/java/lingo/client/multiplayer/MultiplayerConfig.java @@ -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; + } + } diff --git a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java index 552d059..9250796 100644 --- a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java +++ b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java @@ -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 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> { + // intentionally left empty + } + } diff --git a/client/src/main/resources/fxml/Lingo.fxml b/client/src/main/resources/fxml/Lingo.fxml index fb6e8f3..dcd3469 100644 --- a/client/src/main/resources/fxml/Lingo.fxml +++ b/client/src/main/resources/fxml/Lingo.fxml @@ -18,7 +18,7 @@