From cde596132c7a60fad4b785affa4d3b5bd2460494 Mon Sep 17 00:00:00 2001 From: Charles Gould Date: Tue, 17 Jan 2017 22:17:54 -0500 Subject: [PATCH] Redesign with game rooms --- .../java/lingo/client/api/Destinations.java | 35 ++ .../java/lingo/client/api/StompTopics.java | 27 -- .../multiplayer/MultiplayerPresenter.java | 10 +- common/src/main/java/lingo/common/Game.java | 45 +- .../java/lingo/common/GameLeftMessage.java | 32 ++ common/src/main/java/lingo/common/Player.java | 25 ++ .../java/lingo/common/SetUsernameMessage.java | 43 ++ .../java/lingo/server/LingoController.java | 292 +++++++----- .../java/lingo/server/WebSocketConfig.java | 7 +- server/src/main/resources/static/client.html | 59 ++- server/src/main/resources/static/client.js | 419 ++++++++++++------ server/src/main/resources/static/index.html | 2 +- server/src/main/resources/static/layout.css | 22 +- server/src/main/resources/static/practice.js | 8 +- server/src/main/resources/static/style.css | 112 +++-- 15 files changed, 802 insertions(+), 336 deletions(-) create mode 100644 client-api/src/main/java/lingo/client/api/Destinations.java delete mode 100644 client-api/src/main/java/lingo/client/api/StompTopics.java create mode 100644 common/src/main/java/lingo/common/GameLeftMessage.java create mode 100644 common/src/main/java/lingo/common/Player.java create mode 100644 common/src/main/java/lingo/common/SetUsernameMessage.java diff --git a/client-api/src/main/java/lingo/client/api/Destinations.java b/client-api/src/main/java/lingo/client/api/Destinations.java new file mode 100644 index 0000000..09383e7 --- /dev/null +++ b/client-api/src/main/java/lingo/client/api/Destinations.java @@ -0,0 +1,35 @@ +package lingo.client.api; + +public class Destinations { + + public static final String CHAT = topicDestination("chat"); + + public static final String GAME_CLOSED = topicDestination("gameClosed"); + + public static final String GAME_HOSTED = topicDestination("gameHosted"); + + public static final String GAME_JOINED = topicDestination("gameJoined"); + + public static final String GAME_LEFT = topicDestination("gameLeft"); + + public static final String GAME_STARTED = topicDestination("gameStarted"); + + public static final String OPPONENT_JOINED = topicDestination("opponentJoined"); + + public static final String OPPONENT_REPORTS = topicDestination("opponentReports"); + + public static final String PLAYER_REPORTS = topicDestination("playerReports"); + + public static final String PRACTICE_GAME = topicDestination("practiceGame"); + + public static final String PRACTICE_REPORTS = topicDestination("practiceReports"); + + public static final String SESSION_USERNAME = topicDestination("sessionUsername"); + + public static final String USER_JOINED = topicDestination("userJoined"); + + private static String topicDestination(String suffix) { + return "/topic/" + suffix; + } + +} diff --git a/client-api/src/main/java/lingo/client/api/StompTopics.java b/client-api/src/main/java/lingo/client/api/StompTopics.java deleted file mode 100644 index 9618611..0000000 --- a/client-api/src/main/java/lingo/client/api/StompTopics.java +++ /dev/null @@ -1,27 +0,0 @@ -package lingo.client.api; - -public class StompTopics { - - public static final String CHAT = createTopicName("chat"); - - public static final String GAME_STARTED = createTopicName("gameStarted"); - - public static final String OPPONENT_JOINED = createTopicName("opponentJoined"); - - public static final String OPPONENT_LEFT = createTopicName("opponentLeft"); - - public static final String OPPONENT_REPORTS = createTopicName("opponentReports"); - - public static final String PLAYER_REPORTS = createTopicName("playerReports"); - - public static final String PRACTICE_GAME = createTopicName("practiceGame"); - - public static final String PRACTICE_REPORTS = createTopicName("practiceReports"); - - public static final String USER_JOINED = createTopicName("userJoined"); - - private static String createTopicName(String suffix) { - return "/topic/lingo/" + suffix; - } - -} diff --git a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java index 8b3ba64..c5c4749 100644 --- a/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java +++ b/client/src/main/java/lingo/client/multiplayer/MultiplayerPresenter.java @@ -29,7 +29,7 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; import javafx.scene.web.WebView; -import lingo.client.api.StompTopics; +import lingo.client.api.Destinations; import lingo.client.util.FxmlController; import lingo.client.view.Board; import lingo.client.view.OpponentBoard; @@ -154,13 +154,13 @@ public class MultiplayerPresenter implements FxmlController { @PostConstruct private void postConstruct() { executorService.execute(() -> { - stompTemplate.subscribe("/user" + StompTopics.OPPONENT_JOINED, new OpponentJoinedHandler(), + stompTemplate.subscribe("/user" + Destinations.OPPONENT_JOINED, new OpponentJoinedHandler(), subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + StompTopics.OPPONENT_LEFT, new OpponentLeftHandler(), + stompTemplate.subscribe("/user" + Destinations.OPPONENT_LEFT, new OpponentLeftHandler(), subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + StompTopics.OPPONENT_REPORTS, new OpponentReportHandler(), + stompTemplate.subscribe("/user" + Destinations.OPPONENT_REPORTS, new OpponentReportHandler(), subscription -> subscriptionsLatch.countDown()); - stompTemplate.subscribe("/user" + StompTopics.PLAYER_REPORTS, new PlayerReportHandler(), + stompTemplate.subscribe("/user" + Destinations.PLAYER_REPORTS, new PlayerReportHandler(), subscription -> subscriptionsLatch.countDown()); }); } diff --git a/common/src/main/java/lingo/common/Game.java b/common/src/main/java/lingo/common/Game.java index 8ba4070..dfe697c 100644 --- a/common/src/main/java/lingo/common/Game.java +++ b/common/src/main/java/lingo/common/Game.java @@ -3,6 +3,7 @@ package lingo.common; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; public class Game { @@ -12,23 +13,25 @@ public class Game { public static final int WORD_LENGTH = 5; public static final int[] INVALID_GUESS = new int[] { 9, 9, 9, 9, 9 }; - public final String playerOne; + private static final AtomicInteger idCounter = new AtomicInteger(0); - public final String playerTwo; + public final int id; - private final Set acceptableGuesses; + private Player host; - private final List possibleWords; + private Player challenger; + + private Set acceptableGuesses; + + private List possibleWords; private String word; private int wordIndex = 0; - public Game(String playerOne, String playerTwo, List possibleWords, Set acceptableGuesses) { - this.playerOne = playerOne; - this.playerTwo = playerTwo; - this.possibleWords = possibleWords; - this.acceptableGuesses = acceptableGuesses; + public Game(Player host) { + this.id = idCounter.incrementAndGet(); + this.host = host; } private static int indexOf(char[] array, char searchTerm) { @@ -77,6 +80,14 @@ public class Game { return result; } + public Player getChallenger() { + return challenger; + } + + public Player getHost() { + return host; + } + public String newGame() { Collections.shuffle(possibleWords); wordIndex = 0; @@ -88,4 +99,20 @@ public class Game { return word; } + public void setAcceptableGuesses(Set value) { + this.acceptableGuesses = value; + } + + public void setChallenger(Player value) { + this.challenger = value; + } + + public void setHost(Player value) { + this.host = value; + } + + public void setPossibleWords(List value) { + this.possibleWords = value; + } + } diff --git a/common/src/main/java/lingo/common/GameLeftMessage.java b/common/src/main/java/lingo/common/GameLeftMessage.java new file mode 100644 index 0000000..989e1d0 --- /dev/null +++ b/common/src/main/java/lingo/common/GameLeftMessage.java @@ -0,0 +1,32 @@ +package lingo.common; + +public class GameLeftMessage { + + private Game game; + + private Player gameLeaver; + + public GameLeftMessage() {} + + public GameLeftMessage(Game game, Player gameLeaver) { + this.game = game; + this.gameLeaver = gameLeaver; + } + + public Game getGame() { + return game; + } + + public Player getGameLeaver() { + return gameLeaver; + } + + public void setGame(Game game) { + this.game = game; + } + + public void setGameLeaver(Player gameLeaver) { + this.gameLeaver = gameLeaver; + } + +} diff --git a/common/src/main/java/lingo/common/Player.java b/common/src/main/java/lingo/common/Player.java new file mode 100644 index 0000000..ebd7a11 --- /dev/null +++ b/common/src/main/java/lingo/common/Player.java @@ -0,0 +1,25 @@ +package lingo.common; + +public class Player { + + private final String sessionId; + + private String username; + + public Player(String sessionId) { + this.sessionId = sessionId; + } + + public String getSessionId() { + return sessionId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + +} diff --git a/common/src/main/java/lingo/common/SetUsernameMessage.java b/common/src/main/java/lingo/common/SetUsernameMessage.java new file mode 100644 index 0000000..fab52f4 --- /dev/null +++ b/common/src/main/java/lingo/common/SetUsernameMessage.java @@ -0,0 +1,43 @@ +package lingo.common; + +public class SetUsernameMessage { + + private String errorMessage; + + private boolean success; + + private String username; + + public SetUsernameMessage() {} + + public SetUsernameMessage(boolean success, String username, String errorMessage) { + this.errorMessage = errorMessage; + this.success = success; + this.username = username; + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean isSuccess() { + return success; + } + + public String getUsername() { + return username; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setUsername(String username) { + this.username = username; + } + +} diff --git a/server/src/main/java/lingo/server/LingoController.java b/server/src/main/java/lingo/server/LingoController.java index 7a7310c..bf7cc27 100644 --- a/server/src/main/java/lingo/server/LingoController.java +++ b/server/src/main/java/lingo/server/LingoController.java @@ -2,12 +2,12 @@ package lingo.server; import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.SESSION_ID_HEADER; -import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; import java.util.Map; - -import javax.annotation.PostConstruct; +import java.util.Set; +import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,19 +19,23 @@ import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; -import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.socket.messaging.AbstractSubProtocolEvent; import org.springframework.web.socket.messaging.SessionConnectedEvent; import org.springframework.web.socket.messaging.SessionDisconnectEvent; -import lingo.client.api.StompTopics; +import lingo.client.api.Destinations; import lingo.common.ChatMessage; import lingo.common.Game; +import lingo.common.GameLeftMessage; +import lingo.common.Player; import lingo.common.Report; +import lingo.common.SetUsernameMessage; -@Controller -@MessageMapping("/lingo") +@RestController public class LingoController implements ApplicationListener { private static final Logger log = LoggerFactory.getLogger(LingoController.class); @@ -42,25 +46,41 @@ public class LingoController implements ApplicationListener waitingList = new ArrayList<>(); + private final Map gameById = new TreeMap<>(); - private final Map gameBySession = new HashMap<>(); + private final Map gameByPlayer = new HashMap<>(); - private final Map practiceBySession = new HashMap<>(); + private final Map practiceByPlayer = new HashMap<>(); - private final Map usernameBySession = new HashMap<>(); + private final Map playerBySession = new HashMap<>(); + + private final Set usernames = new HashSet<>(); @MessageMapping("/chat") public ChatMessage chat(String message, @Header(SESSION_ID_HEADER) String sessionId) { - final String username = usernameBySession.get(sessionId); - return new ChatMessage(username, message); + final Player player = playerBySession.get(sessionId); + if (player == null) { + log.warn("No player for session {}", sessionId); + throw new IllegalStateException("No player for session " + sessionId); + } + return new ChatMessage(player.getUsername(), message); + } + + @RequestMapping("/games") + public Collection getGames() { + return gameById.values(); } @MessageMapping("/guess") public void guess(String guess, @Header(SESSION_ID_HEADER) String sessionId) { + final Player player = playerBySession.get(sessionId); + if (player == null) { + log.warn("No player for session {}", sessionId); + return; + } guess = guess.toUpperCase(); - log.info("Player {} guessed: {}", sessionId, guess); - final Game game = gameBySession.get(sessionId); + log.info("{} guessed {}", sessionId, guess); + final Game game = gameByPlayer.get(player); final int[] result = game.evaluate(guess); // Generate reports @@ -80,49 +100,123 @@ public class LingoController implements ApplicationListener {}", sessionId, username); + sendToPlayer(player, Destinations.SESSION_USERNAME, new SetUsernameMessage(true, username, null)); + send(Destinations.USER_JOINED, new Object[] { username, playerBySession.size() }); + } else { + log.warn("{} -/> {} : Username taken", sessionId, username); + final SetUsernameMessage response = new SetUsernameMessage(false, null, "Username taken"); + sendToPlayer(player, Destinations.SESSION_USERNAME, response); } } diff --git a/server/src/main/java/lingo/server/WebSocketConfig.java b/server/src/main/java/lingo/server/WebSocketConfig.java index d710e8a..c956c73 100644 --- a/server/src/main/java/lingo/server/WebSocketConfig.java +++ b/server/src/main/java/lingo/server/WebSocketConfig.java @@ -24,9 +24,10 @@ public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { } @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - config.enableSimpleBroker("/topic"); - config.setApplicationDestinationPrefixes("/app"); + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/topic"); + registry.setApplicationDestinationPrefixes("/app", "/user"); + registry.setUserDestinationPrefix("/user"); } @Override diff --git a/server/src/main/resources/static/client.html b/server/src/main/resources/static/client.html index e731ab0..202683a 100644 --- a/server/src/main/resources/static/client.html +++ b/server/src/main/resources/static/client.html @@ -2,28 +2,57 @@ -Lingo +Lingo | Client + -
-

What is your name?

-
-
- -
-

- -
-
-