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 PLAYER_JOINED = topicDestination("playerJoined");
public static final String PLAYER_LEFT = topicDestination("playerLeft");
public static final String PLAYER_REPORTS = topicDestination("playerReports");
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 USER_JOINED = topicDestination("userJoined");
private static String topicDestination(String suffix) {
return "/topic/" + suffix;
}

View File

@ -2,19 +2,17 @@ package com.charego.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.function.Consumer;
import javax.annotation.PostConstruct;
import com.charego.lingo.common.LobbyData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession.Subscription;
@ -142,9 +140,9 @@ public class MultiplayerPresenter implements FxmlController {
username = UUID.randomUUID().toString().substring(0, 8);
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;
for (Game game : games) {
for (Game game : lobbyData.getGames()) {
if (game.getPlayerTwo() == null) {
log.debug("Joining game...");
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 java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -11,6 +10,8 @@ import java.util.TreeMap;
import javax.annotation.PostConstruct;
import com.charego.lingo.api.Destinations;
import com.charego.lingo.common.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.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
public class LingoController {
@ -63,9 +56,9 @@ public class LingoController {
return new ChatMessage(player.getUsername(), message);
}
@RequestMapping("/games")
public Collection<Game> getGames() {
return gameById.values();
@RequestMapping("/data")
public LobbyData getLobbyData() {
return new LobbyData(gameById.values(), sessionManager.getPlayers());
}
@MessageMapping("/guess")
@ -179,14 +172,13 @@ public class LingoController {
final String username = player.getUsername();
usernames.remove(username);
final Game game = gameByPlayer.remove(player);
if (game == null) {
if (username != null) {
log.info("{} left", username);
send(Destinations.CHAT, new ChatMessage(null, username + " left"));
}
} else {
if (game != null) {
leaveGame(game, player);
}
if (username != null) {
log.info("{} left", username);
send(Destinations.PLAYER_LEFT, username);
}
}
@MessageMapping("/leaveGame")
@ -256,7 +248,7 @@ public class LingoController {
player.setUsername(username);
log.info("{} --> {}", sessionId, username);
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 {
log.warn("{} -/> {} : Username taken", sessionId, username);
final SetUsernameMessage response = new SetUsernameMessage(false, null, "Username taken");

View File

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

View File

@ -10,6 +10,7 @@ var sessionId = null;
var vm = new Vue({
el: '#vue-app',
data: {
players: [],
games: [],
messages: [],
username: null,
@ -197,6 +198,16 @@ var vm = new Vue({
}
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) {
if (e.key === 'Backspace') {
e.preventDefault();
@ -362,7 +373,7 @@ function main() {
}
if (usernameSubscription === null) {
usernameSubscription = client.subscribe(usernameTopic, usernameHandler);
client.subscribe('/topic/userJoined', onUserJoined);
client.subscribe('/topic/playerJoined', onPlayerJoined);
}
client.publish({destination: '/app/setUsername', body: usernameValue})
}
@ -398,7 +409,9 @@ function start() {
}
// 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++) {
var game = games[i];
vm.games.push({
@ -409,6 +422,12 @@ function start() {
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
@ -417,6 +436,7 @@ function start() {
client.subscribe('/topic/gameHosted', onGameHosted);
client.subscribe('/topic/gameJoined', onGameJoined);
client.subscribe('/topic/gameLeft', onGameLeft);
client.subscribe('/topic/playerLeft', onPlayerLeft);
client.subscribe('/user/topic/opponentJoined', onOpponentJoined);
client.subscribe('/user/topic/opponentReports', onOpponentReport);
client.subscribe('/user/topic/playerReports', onPlayerReport);
@ -511,9 +531,7 @@ function onGameJoined(message) {
var playerOne = game.playerOne.username;
var playerTwo = game.playerTwo.username;
var chatMessage = playerTwo + ' joined ' + playerOne + "'s game"
console.log(chatMessage);
addChatAnnouncement(chatMessage);
console.log(playerTwo + ' joined ' + playerOne + "'s game");
var vueGame = null;
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 username = report[0];
var numUsers = report[1];
@ -627,9 +645,18 @@ function onUserJoined(message) {
}
} else {
addChatAnnouncement(username + ' joined');
vm.players.push({
username: username
});
}
}
function onPlayerLeft(message) {
var username = message.body;
addChatAnnouncement(username + ' left');
vm.removePlayer(username);
}
function canShowNotification() {
if (document.hidden === 'undefined' || document.hidden === 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="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>
<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 v-bind:class="{ primary: inStartedGame }" class="game column">

View File

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