Create practice controller, add skip button
This commit is contained in:
parent
fc849d2b0d
commit
84c47862d5
@ -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");
|
||||
|
@ -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<AbstractSubProtocolEvent> {
|
||||
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<AbstractSubProtocolE
|
||||
|
||||
private final Map<Player, Game> gameByPlayer = new HashMap<>();
|
||||
|
||||
private final Map<Player, Game> practiceByPlayer = new HashMap<>();
|
||||
|
||||
private final Map<String, Player> playerBySession = new HashMap<>();
|
||||
|
||||
private final Set<String> 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<AbstractSubProtocolE
|
||||
|
||||
@MessageMapping("/guess")
|
||||
public void guess(String guess, @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);
|
||||
@ -114,7 +110,7 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
|
||||
@MessageMapping("/hostGame")
|
||||
public synchronized void hostGame(@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);
|
||||
@ -133,7 +129,7 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
|
||||
@MessageMapping("/joinGame")
|
||||
public synchronized void joinGame(Integer gameId, @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);
|
||||
@ -171,8 +167,7 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void leave(String sessionId) {
|
||||
final Player player = playerBySession.remove(sessionId);
|
||||
private synchronized void leave(Player player) {
|
||||
final String username = player.getUsername();
|
||||
usernames.remove(username);
|
||||
final Game game = gameByPlayer.remove(player);
|
||||
@ -188,7 +183,7 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
|
||||
@MessageMapping("/leaveGame")
|
||||
public synchronized void leaveGame(@Header(SESSION_ID_HEADER) String sessionId) {
|
||||
final Player player = playerBySession.get(sessionId);
|
||||
final Player player = sessionManager.getPlayer(sessionId);
|
||||
final Game game = gameByPlayer.remove(player);
|
||||
if (game == null) {
|
||||
log.warn("{} is not in a game", player);
|
||||
@ -218,67 +213,14 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
log.info("Session connected: {}", sessionId);
|
||||
playerBySession.put(sessionId, new Player(sessionId));
|
||||
}
|
||||
|
||||
private void onSessionDisconnect(SessionDisconnectEvent event) {
|
||||
final String sessionId = event.getSessionId();
|
||||
log.info("Session disconnected: {}", sessionId);
|
||||
leave(sessionId);
|
||||
}
|
||||
|
||||
@SubscribeMapping("/topic/sessionId")
|
||||
public String onSessionId(@Header(SESSION_ID_HEADER) String sessionId) {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@MessageMapping("/practiceGame")
|
||||
public void practiceGame(@Header(SESSION_ID_HEADER) String sessionId) {
|
||||
final Player player = playerBySession.get(sessionId);
|
||||
log.info("{} wants a practice session", 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 = playerBySession.get(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);
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
sessionManager.addListener(new PlayerLeftListener());
|
||||
}
|
||||
|
||||
private void send(String destination, Object payload) {
|
||||
@ -300,12 +242,12 @@ public class LingoController implements ApplicationListener<AbstractSubProtocolE
|
||||
|
||||
@MessageMapping("/setUsername")
|
||||
public synchronized void setUsername(String username, @Header(SESSION_ID_HEADER) String sessionId) {
|
||||
final Player player = playerBySession.get(sessionId);
|
||||
final Player player = sessionManager.getPlayer(sessionId);
|
||||
if (usernames.add(username)) {
|
||||
player.setUsername(username);
|
||||
log.info("{} --> {}", 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<AbstractSubProtocolE
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerLeftListener implements SessionManager.Listener {
|
||||
|
||||
@Override
|
||||
public void playerJoined(Player player) {
|
||||
// Ignore joining players for now
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerLeft(Player player) {
|
||||
leave(player);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
100
server/src/main/java/lingo/server/PracticeController.java
Normal file
100
server/src/main/java/lingo/server/PracticeController.java
Normal file
@ -0,0 +1,100 @@
|
||||
package lingo.server;
|
||||
|
||||
import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.SESSION_ID_HEADER;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
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.web.bind.annotation.RestController;
|
||||
|
||||
import lingo.client.api.Destinations;
|
||||
import lingo.common.Game;
|
||||
import lingo.common.Player;
|
||||
import lingo.common.Report;
|
||||
|
||||
@RestController
|
||||
public class PracticeController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PracticeController.class);
|
||||
|
||||
@Autowired
|
||||
private SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@Autowired
|
||||
private SessionManager sessionManager;
|
||||
|
||||
@Autowired
|
||||
private WordRepository wordRepo;
|
||||
|
||||
private final Map<Player, Game> 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);
|
||||
}
|
||||
|
||||
}
|
85
server/src/main/java/lingo/server/SessionManager.java
Normal file
85
server/src/main/java/lingo/server/SessionManager.java
Normal file
@ -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<AbstractSubProtocolEvent> {
|
||||
|
||||
public interface Listener {
|
||||
void playerJoined(Player player);
|
||||
void playerLeft(Player player);
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
private final Map<String, Player> playerBySession = new HashMap<>();
|
||||
|
||||
private final Set<Listener> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,3 +2,23 @@ canvas {
|
||||
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;
|
||||
}
|
||||
|
@ -7,6 +7,9 @@
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="300" height="400"></canvas>
|
||||
<div id="skipDiv" class="hidden">
|
||||
<button id="skipButton" type="button">Skip Word</button>
|
||||
</div>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.1/sockjs.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user