Add player list to game lobby

This commit is contained in:
Charles Gould 2020-05-18 00:21:23 -05:00
parent 38a36aaa13
commit 3d2fb445ed
8 changed files with 110 additions and 36 deletions

View File

@ -16,6 +16,10 @@ public class Destinations {
public static final String OPPONENT_REPORTS = topicDestination("opponentReports"); public static final String OPPONENT_REPORTS = topicDestination("opponentReports");
public static final String PLAYER_JOINED = topicDestination("playerJoined");
public static final String PLAYER_LEFT = topicDestination("playerLeft");
public static final String PLAYER_REPORTS = topicDestination("playerReports"); public static final String PLAYER_REPORTS = topicDestination("playerReports");
public static final String PRACTICE_GAME = topicDestination("practiceGame"); public static final String PRACTICE_GAME = topicDestination("practiceGame");
@ -26,8 +30,6 @@ public class Destinations {
public static final String SESSION_USERNAME = topicDestination("sessionUsername"); public static final String SESSION_USERNAME = topicDestination("sessionUsername");
public static final String USER_JOINED = topicDestination("userJoined");
private static String topicDestination(String suffix) { private static String topicDestination(String suffix) {
return "/topic/" + suffix; return "/topic/" + suffix;
} }

View File

@ -2,19 +2,17 @@ package com.charego.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.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.charego.lingo.common.LobbyData;
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.core.task.TaskExecutor; import org.springframework.core.task.TaskExecutor;
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.messaging.simp.stomp.StompSession.Subscription; import org.springframework.messaging.simp.stomp.StompSession.Subscription;
@ -142,9 +140,9 @@ public class MultiplayerPresenter implements FxmlController {
username = UUID.randomUUID().toString().substring(0, 8); username = UUID.randomUUID().toString().substring(0, 8);
stompTemplate.getSession().send("/app/setUsername", username); stompTemplate.getSession().send("/app/setUsername", username);
Collection<Game> games = restTemplate.exchange("/games", HttpMethod.GET, null, new GameList()).getBody(); LobbyData lobbyData = restTemplate.getForObject("/data", LobbyData.class);
boolean joinedGame = false; boolean joinedGame = false;
for (Game game : games) { for (Game game : lobbyData.getGames()) {
if (game.getPlayerTwo() == null) { if (game.getPlayerTwo() == null) {
log.debug("Joining game..."); log.debug("Joining game...");
stompTemplate.getSession().send("/app/joinGame", game.getId()); stompTemplate.getSession().send("/app/joinGame", game.getId());
@ -440,8 +438,4 @@ public class MultiplayerPresenter implements FxmlController {
} }
} }
private class GameList extends ParameterizedTypeReference<Collection<Game>> {
// intentionally left empty
}
} }

View File

@ -0,0 +1,35 @@
package com.charego.lingo.common;
import java.util.Collection;
public class LobbyData {
private Collection<Game> games;
private Collection<Player> players;
public LobbyData() {
// Empty constructor required for serialization
}
public LobbyData(Collection<Game> games, Collection<Player> players) {
this.games = games;
this.players = players;
}
public Collection<Game> getGames() {
return games;
}
public Collection<Player> getPlayers() {
return players;
}
public void setGames(Collection<Game> games) {
this.games = games;
}
public void setPlayers(Collection<Player> players) {
this.players = players;
}
}

View File

@ -2,7 +2,6 @@ package com.charego.lingo.server;
import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.SESSION_ID_HEADER; import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.SESSION_ID_HEADER;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -11,6 +10,8 @@ import java.util.TreeMap;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import com.charego.lingo.api.Destinations;
import com.charego.lingo.common.*;
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;
@ -24,14 +25,6 @@ import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.charego.lingo.api.Destinations;
import com.charego.lingo.common.ChatMessage;
import com.charego.lingo.common.Game;
import com.charego.lingo.common.GameLeftMessage;
import com.charego.lingo.common.Player;
import com.charego.lingo.common.Report;
import com.charego.lingo.common.SetUsernameMessage;
@RestController @RestController
public class LingoController { public class LingoController {
@ -63,9 +56,9 @@ public class LingoController {
return new ChatMessage(player.getUsername(), message); return new ChatMessage(player.getUsername(), message);
} }
@RequestMapping("/games") @RequestMapping("/data")
public Collection<Game> getGames() { public LobbyData getLobbyData() {
return gameById.values(); return new LobbyData(gameById.values(), sessionManager.getPlayers());
} }
@MessageMapping("/guess") @MessageMapping("/guess")
@ -179,13 +172,12 @@ public class LingoController {
final String username = player.getUsername(); final String username = player.getUsername();
usernames.remove(username); usernames.remove(username);
final Game game = gameByPlayer.remove(player); final Game game = gameByPlayer.remove(player);
if (game == null) { if (game != null) {
leaveGame(game, player);
}
if (username != null) { if (username != null) {
log.info("{} left", username); log.info("{} left", username);
send(Destinations.CHAT, new ChatMessage(null, username + " left")); send(Destinations.PLAYER_LEFT, username);
}
} else {
leaveGame(game, player);
} }
} }
@ -256,7 +248,7 @@ public class LingoController {
player.setUsername(username); player.setUsername(username);
log.info("{} --> {}", sessionId, username); log.info("{} --> {}", sessionId, username);
sendToPlayer(player, Destinations.SESSION_USERNAME, new SetUsernameMessage(true, username, null)); sendToPlayer(player, Destinations.SESSION_USERNAME, new SetUsernameMessage(true, username, null));
send(Destinations.USER_JOINED, new Object[] { username, sessionManager.getPlayerCount() }); send(Destinations.PLAYER_JOINED, new Object[] { username, sessionManager.getPlayerCount() });
} else { } else {
log.warn("{} -/> {} : Username taken", sessionId, username); log.warn("{} -/> {} : Username taken", sessionId, username);
final SetUsernameMessage response = new SetUsernameMessage(false, null, "Username taken"); final SetUsernameMessage response = new SetUsernameMessage(false, null, "Username taken");

View File

@ -1,5 +1,6 @@
package com.charego.lingo.server; package com.charego.lingo.server;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -44,6 +45,12 @@ public class SessionManager implements ApplicationListener<AbstractSubProtocolEv
return playerBySession.size(); return playerBySession.size();
} }
public Collection<Player> getPlayers() {
Collection<Player> allPlayers = new HashSet<>(playerBySession.values());
allPlayers.removeIf(p -> p.getUsername() == null);
return allPlayers;
}
@Override @Override
public void onApplicationEvent(AbstractSubProtocolEvent event) { public void onApplicationEvent(AbstractSubProtocolEvent event) {
if (event instanceof SessionConnectedEvent) { if (event instanceof SessionConnectedEvent) {

View File

@ -10,6 +10,7 @@ var sessionId = null;
var vm = new Vue({ var vm = new Vue({
el: '#vue-app', el: '#vue-app',
data: { data: {
players: [],
games: [], games: [],
messages: [], messages: [],
username: null, username: null,
@ -197,6 +198,16 @@ var vm = new Vue({
} }
this.games.splice(indexToRemove, 1); this.games.splice(indexToRemove, 1);
}, },
removePlayer: function(name) {
var indexToRemove = null;
for (var i = 0; i < this.players.length; i++) {
if (this.players[i].username === name) {
indexToRemove = i;
break;
}
}
this.players.splice(indexToRemove, 1);
},
onCanvasKeydown: function(e) { onCanvasKeydown: function(e) {
if (e.key === 'Backspace') { if (e.key === 'Backspace') {
e.preventDefault(); e.preventDefault();
@ -362,7 +373,7 @@ function main() {
} }
if (usernameSubscription === null) { if (usernameSubscription === null) {
usernameSubscription = client.subscribe(usernameTopic, usernameHandler); usernameSubscription = client.subscribe(usernameTopic, usernameHandler);
client.subscribe('/topic/userJoined', onUserJoined); client.subscribe('/topic/playerJoined', onPlayerJoined);
} }
client.publish({destination: '/app/setUsername', body: usernameValue}) client.publish({destination: '/app/setUsername', body: usernameValue})
} }
@ -398,7 +409,9 @@ function start() {
} }
// Load initial data // Load initial data
doHttpGet('/games', function(games) { doHttpGet('/data', function(data) {
var players = data.players;
var games = data.games;
for (var i = 0; i < games.length; i++) { for (var i = 0; i < games.length; i++) {
var game = games[i]; var game = games[i];
vm.games.push({ vm.games.push({
@ -409,6 +422,12 @@ function start() {
started: game.playerTwo !== null started: game.playerTwo !== null
}); });
} }
for (var i = 0; i < players.length; i++) {
var player = players[i];
vm.players.push({
username: player.username
});
}
}); });
// Subscribe to updates // Subscribe to updates
@ -417,6 +436,7 @@ function start() {
client.subscribe('/topic/gameHosted', onGameHosted); client.subscribe('/topic/gameHosted', onGameHosted);
client.subscribe('/topic/gameJoined', onGameJoined); client.subscribe('/topic/gameJoined', onGameJoined);
client.subscribe('/topic/gameLeft', onGameLeft); client.subscribe('/topic/gameLeft', onGameLeft);
client.subscribe('/topic/playerLeft', onPlayerLeft);
client.subscribe('/user/topic/opponentJoined', onOpponentJoined); client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
client.subscribe('/user/topic/opponentReports', onOpponentReport); client.subscribe('/user/topic/opponentReports', onOpponentReport);
client.subscribe('/user/topic/playerReports', onPlayerReport); client.subscribe('/user/topic/playerReports', onPlayerReport);
@ -511,9 +531,7 @@ function onGameJoined(message) {
var playerOne = game.playerOne.username; var playerOne = game.playerOne.username;
var playerTwo = game.playerTwo.username; var playerTwo = game.playerTwo.username;
var chatMessage = playerTwo + ' joined ' + playerOne + "'s game" console.log(playerTwo + ' joined ' + playerOne + "'s game");
console.log(chatMessage);
addChatAnnouncement(chatMessage);
var vueGame = null; var vueGame = null;
for (var i = 0; i < vm.games.length; i++) { for (var i = 0; i < vm.games.length; i++) {
@ -614,7 +632,7 @@ function onPlayerReport(message) {
} }
} }
function onUserJoined(message) { function onPlayerJoined(message) {
var report = JSON.parse(message.body); var report = JSON.parse(message.body);
var username = report[0]; var username = report[0];
var numUsers = report[1]; var numUsers = report[1];
@ -627,9 +645,18 @@ function onUserJoined(message) {
} }
} else { } else {
addChatAnnouncement(username + ' joined'); addChatAnnouncement(username + ' joined');
vm.players.push({
username: username
});
} }
} }
function onPlayerLeft(message) {
var username = message.body;
addChatAnnouncement(username + ' left');
vm.removePlayer(username);
}
function canShowNotification() { function canShowNotification() {
if (document.hidden === 'undefined' || document.hidden === false) { if (document.hidden === 'undefined' || document.hidden === false) {
return false; return false;

View File

@ -40,6 +40,18 @@
<button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button> <button v-show="inGame" @click="leaveGame" type="button" class="leave button">Leave game</button>
<button v-show="!inGame" @click="hostGame5" type="button" class="create button">Create 5-letter game</button> <button v-show="!inGame" @click="hostGame5" type="button" class="create button">Create 5-letter game</button>
<button v-show="!inGame" @click="hostGame6" type="button" class="create button">Create 6-letter game</button> <button v-show="!inGame" @click="hostGame6" type="button" class="create button">Create 6-letter game</button>
<div class="panel">
<div class="panel-heading">
<h3 class="panel-title">Players</h3>
</div>
<div class="list-group">
<div v-if="players.length === 0" class="list-group-item">There are no players</div>
<template v-for="player in players">
<div class="list-group-item user" v-if="player.username === username">{{ player.username }}</div>
<div class="list-group-item" v-else>{{ player.username }}</div>
</template>
</div>
</div>
</div> </div>
</div> </div>
<div v-bind:class="{ primary: inStartedGame }" class="game column"> <div v-bind:class="{ primary: inStartedGame }" class="game column">

View File

@ -125,6 +125,11 @@ button:hover:disabled {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.list-group-item.user {
background-color: steelblue;
color: white;
}
button.list-group-item { button.list-group-item {
width: 100%; width: 100%;
text-align: left; text-align: left;