From 84c47862d5782e54503d44ab1942ea2b1a47f268 Mon Sep 17 00:00:00 2001 From: Charles Gould Date: Sat, 28 Jan 2017 19:07:16 -0500 Subject: [PATCH] Create practice controller, add skip button --- .../java/lingo/client/api/Destinations.java | 2 + .../java/lingo/server/LingoController.java | 106 +++++------------- .../java/lingo/server/PracticeController.java | 100 +++++++++++++++++ .../java/lingo/server/SessionManager.java | 85 ++++++++++++++ server/src/main/resources/static/practice.css | 24 +++- .../src/main/resources/static/practice.html | 3 + server/src/main/resources/static/practice.js | 18 +++ 7 files changed, 261 insertions(+), 77 deletions(-) create mode 100644 server/src/main/java/lingo/server/PracticeController.java create mode 100644 server/src/main/java/lingo/server/SessionManager.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 index 09383e7..e96ff96 100644 --- a/client-api/src/main/java/lingo/client/api/Destinations.java +++ b/client-api/src/main/java/lingo/client/api/Destinations.java @@ -24,6 +24,8 @@ public class Destinations { public static final String PRACTICE_REPORTS = topicDestination("practiceReports"); + public static final String PRACTICE_WORD_SKIPPED = topicDestination("practiceWordSkipped"); + public static final String SESSION_USERNAME = topicDestination("sessionUsername"); public static final String USER_JOINED = topicDestination("userJoined"); diff --git a/server/src/main/java/lingo/server/LingoController.java b/server/src/main/java/lingo/server/LingoController.java index 9550aa1..ae4f85a 100644 --- a/server/src/main/java/lingo/server/LingoController.java +++ b/server/src/main/java/lingo/server/LingoController.java @@ -9,10 +9,11 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import javax.annotation.PostConstruct; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.handler.annotation.Header; import org.springframework.messaging.handler.annotation.MessageMapping; @@ -20,12 +21,8 @@ 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.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.Destinations; import lingo.common.ChatMessage; @@ -36,13 +33,16 @@ import lingo.common.Report; import lingo.common.SetUsernameMessage; @RestController -public class LingoController implements ApplicationListener { +public class LingoController { private static final Logger log = LoggerFactory.getLogger(LingoController.class); @Autowired private SimpMessagingTemplate messagingTemplate; + @Autowired + private SessionManager sessionManager; + @Autowired private WordRepository wordRepo; @@ -50,15 +50,11 @@ public class LingoController implements ApplicationListener gameByPlayer = new HashMap<>(); - private final Map practiceByPlayer = 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 Player player = playerBySession.get(sessionId); + final Player player = sessionManager.getPlayer(sessionId); final String username = player.getUsername(); if (username == null) { log.warn("No username for session {}", sessionId); @@ -74,7 +70,7 @@ 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() }); + send(Destinations.USER_JOINED, new Object[] { username, sessionManager.getPlayerCount() }); } else { log.warn("{} -/> {} : Username taken", sessionId, username); final SetUsernameMessage response = new SetUsernameMessage(false, null, "Username taken"); @@ -313,4 +255,18 @@ public class LingoController implements ApplicationListener practiceByPlayer = new HashMap<>(); + + @MessageMapping("/practiceGame") + public void practiceGame(@Header(SESSION_ID_HEADER) String sessionId) { + final Player player = sessionManager.getPlayer(sessionId); + log.info("{} is practicing", sessionId); + final Game game = new Game(player); + game.setAcceptableGuesses(wordRepo.getGuesses()); + game.setPossibleWords(wordRepo.getWords()); + practiceByPlayer.put(player, game); + final String firstWord = game.newGame(); + final String firstLetter = String.valueOf(firstWord.charAt(0)); + log.info("First word: {}", firstWord); + sendToPlayer(player, Destinations.PRACTICE_GAME, firstLetter); + } + + @MessageMapping("/practiceGuess") + public void practiceGuess(String guess, @Header(SESSION_ID_HEADER) String sessionId) { + final Player player = sessionManager.getPlayer(sessionId); + guess = guess.toUpperCase(); + log.info("{} guessed {}", player, guess); + final Game game = practiceByPlayer.get(player); + final int[] result = game.evaluate(guess); + + // Generate report + final Report report = new Report(); + report.setGuess(guess); + if (Game.isCorrect(result)) { + final String newWord = game.newWord(); + final String firstLetter = String.valueOf(newWord.charAt(0)); + log.info("New word: {}", newWord); + report.setCorrect(true); + report.setFirstLetter(firstLetter); + } else { + report.setResult(result); + } + sendToPlayer(player, Destinations.PRACTICE_REPORTS, report); + } + + @MessageMapping("/practiceSkip") + public void practiceSkip(@Header(SESSION_ID_HEADER) String sessionId) { + final Player player = sessionManager.getPlayer(sessionId); + final Game game = practiceByPlayer.get(player); + final String newWord = game.newWord(); + final String firstLetter = String.valueOf(newWord.charAt(0)); + log.info("New word: {}", newWord); + sendToPlayer(player, Destinations.PRACTICE_WORD_SKIPPED, firstLetter); + } + + private void sendToPlayer(Player player, String destination, Object payload) { + sendToSession(player.getSessionId(), destination, payload); + } + + private void sendToSession(String sessionId, String destination, Object payload) { + // TODO: cache the headers? + final SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); + headerAccessor.setSessionId(sessionId); + headerAccessor.setLeaveMutable(true); + final MessageHeaders headers = headerAccessor.getMessageHeaders(); + messagingTemplate.convertAndSendToUser(sessionId, destination, payload, headers); + } + +} diff --git a/server/src/main/java/lingo/server/SessionManager.java b/server/src/main/java/lingo/server/SessionManager.java new file mode 100644 index 0000000..7e6b41e --- /dev/null +++ b/server/src/main/java/lingo/server/SessionManager.java @@ -0,0 +1,85 @@ +package lingo.server; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.AbstractSubProtocolEvent; +import org.springframework.web.socket.messaging.SessionConnectedEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +import lingo.common.Player; + +@Component +public class SessionManager implements ApplicationListener { + + public interface Listener { + void playerJoined(Player player); + void playerLeft(Player player); + } + + private static final Logger log = LoggerFactory.getLogger(SessionManager.class); + + private final Map playerBySession = new HashMap<>(); + + private final Set listeners = new HashSet<>(); + + public void addListener(Listener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + public Player getPlayer(String sessionId) { + return playerBySession.get(sessionId); + } + + public int getPlayerCount() { + return playerBySession.size(); + } + + @Override + public void onApplicationEvent(AbstractSubProtocolEvent event) { + if (event instanceof SessionConnectedEvent) { + onSessionConnected((SessionConnectedEvent) event); + } else if (event instanceof SessionDisconnectEvent) { + onSessionDisconnect((SessionDisconnectEvent) event); + } + } + + private void onSessionConnected(SessionConnectedEvent event) { + final String sessionId = StompHeaderAccessor.wrap(event.getMessage()).getSessionId(); + final Player player = new Player(sessionId); + log.info("Player connected: {}", player); + playerBySession.put(sessionId, player); + synchronized (listeners) { + for (Listener listener : listeners) { + listener.playerJoined(player); + } + } + } + + private void onSessionDisconnect(SessionDisconnectEvent event) { + final String sessionId = event.getSessionId(); + final Player player = playerBySession.remove(sessionId); + log.info("Player disconnected: {}", player); + synchronized (listeners) { + for (Listener listener : listeners) { + listener.playerLeft(player); + } + } + } + + public void removeListener(Listener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + +} diff --git a/server/src/main/resources/static/practice.css b/server/src/main/resources/static/practice.css index 3558939..5dbffa3 100644 --- a/server/src/main/resources/static/practice.css +++ b/server/src/main/resources/static/practice.css @@ -1,4 +1,24 @@ canvas { - display: block; - margin: 0 auto; + display: block; + margin: 0 auto; +} + +#skipDiv { + margin-top: 20px; + text-align: center +} + +#skipButton { + width: 200px; + font-size: 28px; + font-variant: small-caps; + background-color: black; + color: white; + cursor: pointer; + padding: 10px; + border: none; +} + +.hidden { + display: none; } diff --git a/server/src/main/resources/static/practice.html b/server/src/main/resources/static/practice.html index 1a95e5a..c2c27b2 100644 --- a/server/src/main/resources/static/practice.html +++ b/server/src/main/resources/static/practice.html @@ -7,6 +7,9 @@ + diff --git a/server/src/main/resources/static/practice.js b/server/src/main/resources/static/practice.js index 3d95498..540d3c6 100644 --- a/server/src/main/resources/static/practice.js +++ b/server/src/main/resources/static/practice.js @@ -27,6 +27,7 @@ function start() { addKeydownListener(); addKeypressListener(); + addSkipButtonListener(); reset(); repaint(); @@ -36,6 +37,7 @@ function start() { client.connect({}, function(frame) { subscribeToPracticeGame(); subscribeToPracticeReports(); + subscribeToPracticeWordSkipped(); client.send('/app/practiceGame'); }); } @@ -77,6 +79,13 @@ function addKeypressListener() { }); } +// skip button +function addSkipButtonListener() { + document.getElementById('skipButton').addEventListener('click', function(e) { + client.send("/app/practiceSkip"); + }); +} + function drawMyBoard() { var x = 25, y = MARGIN_TOP; drawScore(x, y, myScore); @@ -227,6 +236,7 @@ function subscribeToPracticeGame() { var firstLetter = message.body; reset(firstLetter, true); repaint(); + document.getElementById('skipDiv').classList.remove('hidden'); }); } @@ -263,4 +273,12 @@ function subscribeToPracticeReports() { }); } +function subscribeToPracticeWordSkipped() { + client.subscribe('/user/topic/practiceWordSkipped', function(message) { + var firstLetter = message.body; + reset(firstLetter, false); + repaint(); + }); +} + main();