From 11b5933d70362e51eac2793f3898a310799e890b Mon Sep 17 00:00:00 2001 From: "marcins78@gmail.com" Date: Tue, 1 Jan 2013 04:01:53 +0000 Subject: [PATCH] Communication with HallServer is now less bandwith intensive, as only updates are sent on the line. --- gemp-lotr/gemp-lotr-server/pom.xml | 5 + .../gempukku/lotro/db/CachedPlayerDAO.java | 14 +- .../lotro/game/LotroGameMediator.java | 7 +- .../gempukku/lotro/hall/AwaitingTable.java | 4 +- .../lotro/hall/HallChannelVisitor.java | 19 + .../lotro/hall/HallCommunicationChannel.java | 119 ++++++ .../gempukku/lotro/hall/HallInfoVisitor.java | 16 +- .../com/gempukku/lotro/hall/HallServer.java | 57 ++- .../gempukku/lotro/server/HallResource.java | 115 ++++-- .../gemp-lotr-web/src/main/webapp/hall.html | 6 + .../main/webapp/js/gemp-013/communication.js | 13 + .../src/main/webapp/js/gemp-013/hallUi.js | 353 ++++++++++++++++-- 12 files changed, 629 insertions(+), 99 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallChannelVisitor.java create mode 100644 gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallCommunicationChannel.java diff --git a/gemp-lotr/gemp-lotr-server/pom.xml b/gemp-lotr/gemp-lotr-server/pom.xml index ce3fec557..80b774f66 100644 --- a/gemp-lotr/gemp-lotr-server/pom.xml +++ b/gemp-lotr/gemp-lotr-server/pom.xml @@ -40,6 +40,11 @@ commons-collections 3.0 + + commons-lang + commons-lang + 2.6 + com.sun.jersey jersey-server diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java index d670a20b9..1962d24b8 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java @@ -25,7 +25,7 @@ public class CachedPlayerDAO implements PlayerDAO, Cached { @Override public int getItemCount() { - return _playerById.size()+_playerByName.size(); + return _playerById.size() + _playerByName.size(); } @Override @@ -33,8 +33,10 @@ public class CachedPlayerDAO implements PlayerDAO, Cached { Player player = (Player) _playerById.get(id); if (player == null) { player = _delegate.getPlayer(id); - _playerById.put(id, player); - _playerByName.put(player.getName(), player); + if (player != null) { + _playerById.put(id, player); + _playerByName.put(player.getName(), player); + } } return player; } @@ -44,8 +46,10 @@ public class CachedPlayerDAO implements PlayerDAO, Cached { Player player = (Player) _playerByName.get(playerName); if (player == null) { player = _delegate.getPlayer(playerName); - _playerById.put(player.getId(), player); - _playerByName.put(player.getName(), player); + if (player != null) { + _playerById.put(player.getId(), player); + _playerByName.put(player.getName(), player); + } } return player; } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java index 23d5eac83..8e09e6d0a 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java @@ -33,7 +33,6 @@ public class LotroGameMediator { private boolean _allowSpectators; private boolean _cancellable; // private final int _maxSecondsForGamePerPlayer = 60 * 40; // 40 minutes - private final int _channelInactivityTimeoutPeriod = 1000 * 60 * 5; // 5 minutes private final int _playerDecisionTimeoutPeriod = 1000 * 60 * 10; // 10 minutes private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(true); @@ -97,8 +96,8 @@ public class LotroGameMediator { return _lotroGame.getWinnerPlayerId(); } - public Set getPlayersPlaying() { - return Collections.unmodifiableSet(_playersPlaying); + public List getPlayersPlaying() { + return new LinkedList(_playersPlaying); } public String getGameStatus() { @@ -236,7 +235,7 @@ public class LotroGameMediator { // Channel is stale (user no longer connected to game, to save memory, we remove the channel // User can always reconnect and establish a new channel GatheringParticipantCommunicationChannel channel = playerChannels.getValue(); - if (currentTime > channel.getLastConsumed().getTime() + _channelInactivityTimeoutPeriod) { + if (currentTime > channel.getLastConsumed().getTime() + _playerDecisionTimeoutPeriod) { _lotroGame.removeGameStateListener(channel); _communicationChannels.remove(playerId); } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/AwaitingTable.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/AwaitingTable.java index 7c582d92c..51762f6f5 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/AwaitingTable.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/AwaitingTable.java @@ -38,8 +38,8 @@ public class AwaitingTable { return _players.containsKey(playerId); } - public Set getPlayerNames() { - return Collections.unmodifiableSet(_players.keySet()); + public List getPlayerNames() { + return new LinkedList(_players.keySet()); } public Set getPlayers() { diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallChannelVisitor.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallChannelVisitor.java new file mode 100644 index 000000000..22b78c42e --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallChannelVisitor.java @@ -0,0 +1,19 @@ +package com.gempukku.lotro.hall; + +import java.util.Map; + +public interface HallChannelVisitor { + public void channelNumber(int channelNumber); + + public void serverTime(String serverTime); + public void runningPlayerGame(String gameId); + public void playerBusy(boolean busy); + + public void addTournamentQueue(String queueId, Map props); + public void updateTournamentQueue(String queueId, Map props); + public void removeTournamentQueue(String queueId); + + public void addTable(String tableId, Map props); + public void updateTable(String tableId, Map props); + public void removeTable(String tableId); +} diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallCommunicationChannel.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallCommunicationChannel.java new file mode 100644 index 000000000..5a73307e9 --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallCommunicationChannel.java @@ -0,0 +1,119 @@ +package com.gempukku.lotro.hall; + +import com.gempukku.lotro.game.Player; +import org.apache.commons.lang.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HallCommunicationChannel { + private int _channelNumber; + private long _lastConsumed; + private Map> _tournamentQueuePropsOnClient = new HashMap>(); + private Map> _tablePropsOnClient = new HashMap>(); + + public HallCommunicationChannel(int channelNumber) { + _channelNumber = channelNumber; + } + + public int getChannelNumber() { + return _channelNumber; + } + + public long getLastConsumed() { + return _lastConsumed; + } + + public synchronized void processCommunicationChannel(HallServer hallServer, Player player, final HallChannelVisitor hallChannelVisitor) { + hallChannelVisitor.channelNumber(_channelNumber); + + final Map> tournamentsOnServer = new HashMap>(); + final Map> tablesOnServer = new HashMap>(); + + hallServer.processHall(player, + new HallInfoVisitor() { + @Override + public void serverTime(String time) { + hallChannelVisitor.serverTime(time); + } + + @Override + public void playerBusy(boolean busy) { + hallChannelVisitor.playerBusy(busy); + } + + @Override + public void visitTable(String tableId, String gameId, boolean watchable, TableStatus status, String statusDescription, String formatName, String tournamentName, List playerIds, String winner) { + Map props = new HashMap(); + props.put("gameId", gameId); + props.put("watchable", String.valueOf(watchable)); + props.put("status", String.valueOf(status)); + props.put("statusDescription", statusDescription); + props.put("format", formatName); + props.put("tournament", tournamentName); + props.put("players", StringUtils.join(playerIds, ",")); + if (winner != null) + props.put("winner", winner); + + tablesOnServer.put(tableId, props); + } + + @Override + public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName, String tournamentQueueName, int playerCount, boolean playerSignedUp) { + Map props = new HashMap(); + props.put("cost", String.valueOf(cost)); + props.put("collection", collectionName); + props.put("format", formatName); + props.put("queue", tournamentQueueName); + props.put("playerCount", String.valueOf(playerCount)); + props.put("signedUp", String.valueOf(playerSignedUp)); + + tournamentsOnServer.put(tournamentQueueKey, props); + } + + @Override + public void runningPlayerGame(String gameId) { + hallChannelVisitor.runningPlayerGame(gameId); + } + }); + + for (Map.Entry> tournamentQueueOnClient : _tournamentQueuePropsOnClient.entrySet()) { + String tournamentQueueId = tournamentQueueOnClient.getKey(); + Map tournamentProps = tournamentQueueOnClient.getValue(); + Map tournamentLatestProps = tournamentsOnServer.get(tournamentQueueId); + if (tournamentLatestProps != null) { + if (!tournamentProps.equals(tournamentLatestProps)) + hallChannelVisitor.updateTournamentQueue(tournamentQueueId, tournamentLatestProps); + } else { + hallChannelVisitor.removeTournamentQueue(tournamentQueueId); + } + } + + for (Map.Entry> tournamentQueueOnServer : tournamentsOnServer.entrySet()) + if (!_tournamentQueuePropsOnClient.containsKey(tournamentQueueOnServer.getKey())) + hallChannelVisitor.addTournamentQueue(tournamentQueueOnServer.getKey(), tournamentQueueOnServer.getValue()); + + _tournamentQueuePropsOnClient = tournamentsOnServer; + + for (Map.Entry> tableOnClient : _tablePropsOnClient.entrySet()) { + String tableId = tableOnClient.getKey(); + Map tableProps = tableOnClient.getValue(); + Map tableLatestProps = tablesOnServer.get(tableId); + if (tableLatestProps != null) { + if (!tableProps.equals(tableLatestProps)) + hallChannelVisitor.updateTable(tableId, tableLatestProps); + } else { + hallChannelVisitor.removeTable(tableId); + } + } + + for (Map.Entry> tableOnServer : tablesOnServer.entrySet()) + if (!_tablePropsOnClient.containsKey(tableOnServer.getKey())) + hallChannelVisitor.addTable(tableOnServer.getKey(), tableOnServer.getValue()); + + _tablePropsOnClient = tablesOnServer; + + _lastConsumed = System.currentTimeMillis(); + } +} diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallInfoVisitor.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallInfoVisitor.java index 0a3ce6934..c28d86d40 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallInfoVisitor.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallInfoVisitor.java @@ -1,17 +1,19 @@ package com.gempukku.lotro.hall; -import java.util.Set; +import java.util.List; public interface HallInfoVisitor { + public enum TableStatus { + WAITING, PLAYING, FINISHED + } + public void serverTime(String time); - public void playerIsWaiting(boolean waiting); + public void playerBusy(boolean busy); - public void visitTable(String tableId, String gameId, boolean watchable, String tableStatus, String formatName, String tournamentName, Set playerIds, String winner); - - public void runningPlayerDraft(String draftId); - - public void runningPlayerGame(String gameId); + public void visitTable(String tableId, String gameId, boolean watchable, TableStatus status, String statusDescription, String formatName, String tournamentName, List playerIds, String winner); public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName, String tournamentQueueName, int playerCount, boolean playerSignedUp); + + public void runningPlayerGame(String gameId); } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallServer.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallServer.java index f602ec3f6..86ed29cf4 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallServer.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/hall/HallServer.java @@ -16,6 +16,8 @@ import com.gempukku.lotro.logic.timing.GameResultListener; import com.gempukku.lotro.logic.vo.LotroDeck; import com.gempukku.lotro.tournament.*; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -44,7 +46,8 @@ public class HallServer extends AbstractServer { private Map _awaitingTables = new LinkedHashMap(); private Map _runningTables = new LinkedHashMap(); - private Map _lastVisitedPlayers = new HashMap(); + private Map _playerChannelCommunication = new HashMap(); + private int _nextChannelNumber = 0; private Map _runningTournaments = new LinkedHashMap(); private Map _tournamentQueues = new HashMap(); @@ -259,23 +262,51 @@ public class HallServer extends AbstractServer { } } - public void processHall(Player player, HallInfoVisitor visitor) { + public void signupUserForHall(Player player, HallChannelVisitor hallChannelVisitor) { + _hallDataAccessLock.readLock().lock(); + try { + HallCommunicationChannel channel = new HallCommunicationChannel(_nextChannelNumber++); + channel.processCommunicationChannel(this, player, hallChannelVisitor); + _playerChannelCommunication.put(player, channel); + } finally { + _hallDataAccessLock.readLock().unlock(); + } + } + + public void processHall(Player player, int channelNumber, HallChannelVisitor hallChannelVisitor) { + _hallDataAccessLock.readLock().lock(); + try { + HallCommunicationChannel communicationChannel = _playerChannelCommunication.get(player); + if (communicationChannel != null) { + if (communicationChannel.getChannelNumber() == channelNumber) { + communicationChannel.processCommunicationChannel(this, player, hallChannelVisitor); + } else { + throw new WebApplicationException(Response.Status.CONFLICT); + } + } else { + throw new WebApplicationException(Response.Status.GONE); + } + } finally { + _hallDataAccessLock.readLock().unlock(); + } + } + + protected void processHall(Player player, HallInfoVisitor visitor) { _hallDataAccessLock.readLock().lock(); try { - _lastVisitedPlayers.put(player, System.currentTimeMillis()); - visitor.playerIsWaiting(isPlayerBusy(player.getName())); visitor.serverTime(DateUtils.getStringDateWithHour()); + visitor.playerBusy(isPlayerBusy(player.getName())); // First waiting for (Map.Entry tableInformation : _awaitingTables.entrySet()) { final AwaitingTable table = tableInformation.getValue(); - Set players; + List players; if (table.getLeague() != null) - players = Collections.emptySet(); + players = Collections.emptyList(); else players = table.getPlayerNames(); - visitor.visitTable(tableInformation.getKey(), null, false, "Waiting", table.getLotroFormat().getName(), getTournamentName(table), players, null); + visitor.visitTable(tableInformation.getKey(), null, false, HallInfoVisitor.TableStatus.WAITING, "Waiting", table.getLotroFormat().getName(), getTournamentName(table), players, null); } // Then non-finished @@ -286,7 +317,7 @@ public class HallServer extends AbstractServer { LotroGameMediator lotroGameMediator = _lotroServer.getGameById(runningTable.getGameId()); if (lotroGameMediator != null) { if (!lotroGameMediator.isFinished()) - visitor.visitTable(runningGame.getKey(), runningTable.getGameId(), player.getType().contains("a") || lotroGameMediator.isAllowSpectators(), lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner()); + visitor.visitTable(runningGame.getKey(), runningTable.getGameId(), player.getType().contains("a") || lotroGameMediator.isAllowSpectators(), HallInfoVisitor.TableStatus.PLAYING, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner()); else finishedTables.put(runningGame.getKey(), runningTable); } @@ -297,7 +328,7 @@ public class HallServer extends AbstractServer { final RunningTable runningTable = nonPlayingGame.getValue(); LotroGameMediator lotroGameMediator = _lotroServer.getGameById(runningTable.getGameId()); if (lotroGameMediator != null) - visitor.visitTable(nonPlayingGame.getKey(), runningTable.getGameId(), false, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner()); + visitor.visitTable(nonPlayingGame.getKey(), runningTable.getGameId(), false, HallInfoVisitor.TableStatus.FINISHED, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner()); } for (Map.Entry tournamentQueueEntry : _tournamentQueues.entrySet()) { @@ -486,11 +517,11 @@ public class HallServer extends AbstractServer { } long currentTime = System.currentTimeMillis(); - Map visitCopy = new LinkedHashMap(_lastVisitedPlayers); - for (Map.Entry lastVisitedPlayer : visitCopy.entrySet()) { - if (currentTime > lastVisitedPlayer.getValue() + _playerInactivityPeriod) { + Map visitCopy = new LinkedHashMap(_playerChannelCommunication); + for (Map.Entry lastVisitedPlayer : visitCopy.entrySet()) { + if (currentTime > lastVisitedPlayer.getValue().getLastConsumed() + _playerInactivityPeriod) { Player player = lastVisitedPlayer.getKey(); - _lastVisitedPlayers.remove(player); + _playerChannelCommunication.remove(player); leaveAwaitingTables(player); leaveQueues(player); } diff --git a/gemp-lotr/gemp-lotr-web-server/src/main/java/com/gempukku/lotro/server/HallResource.java b/gemp-lotr/gemp-lotr-web-server/src/main/java/com/gempukku/lotro/server/HallResource.java index f368eff8e..7290798c3 100644 --- a/gemp-lotr/gemp-lotr-web-server/src/main/java/com/gempukku/lotro/server/HallResource.java +++ b/gemp-lotr/gemp-lotr-web-server/src/main/java/com/gempukku/lotro/server/HallResource.java @@ -5,8 +5,8 @@ import com.gempukku.lotro.game.LotroCardBlueprintLibrary; import com.gempukku.lotro.game.LotroFormat; import com.gempukku.lotro.game.Player; import com.gempukku.lotro.game.formats.LotroFormatLibrary; +import com.gempukku.lotro.hall.HallChannelVisitor; import com.gempukku.lotro.hall.HallException; -import com.gempukku.lotro.hall.HallInfoVisitor; import com.gempukku.lotro.hall.HallServer; import com.gempukku.lotro.league.LeagueSerieData; import com.gempukku.lotro.league.LeagueService; @@ -122,6 +122,36 @@ public class HallResource extends AbstractResource { return result.toString(); } + @POST + @Produces(MediaType.APPLICATION_XML) + @Path("/update") + public Document updateHall( + @FormParam("participantId") String participantId, + @FormParam("channelNumber") int channelNumber, + @Context HttpServletRequest request, + @Context HttpServletResponse response) throws ParserConfigurationException, Exception { + Player resourceOwner = getResourceOwnerSafely(request, participantId); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + + Document doc = documentBuilder.newDocument(); + + Element hall = doc.createElement("hall"); + hall.setAttribute("currency", String.valueOf(_collectionManager.getPlayerCollection(resourceOwner, "permanent").getCurrency())); + String motd = _hallServer.getMOTD(); + if (motd != null) + hall.setAttribute("motd", motd); + + _hallServer.processHall(resourceOwner, channelNumber, new SerializeHallInfoVisitor(doc, hall)); + + doc.appendChild(hall); + + processDeliveryServiceNotification(request, response); + + return doc; + } + @GET @Produces(MediaType.APPLICATION_XML) public Document getHall( @@ -141,7 +171,7 @@ public class HallResource extends AbstractResource { if (motd != null) hall.setAttribute("motd", motd); - _hallServer.processHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall)); + _hallServer.signupUserForHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall)); for (Map.Entry format : _formatLibrary.getHallFormats().entrySet()) { Element formatElem = doc.createElement("format"); formatElem.setAttribute("type", format.getKey()); @@ -248,7 +278,7 @@ public class HallResource extends AbstractResource { return doc; } - private class SerializeHallInfoVisitor implements HallInfoVisitor { + private class SerializeHallInfoVisitor implements HallChannelVisitor { private Document _doc; private Element _hall; @@ -258,47 +288,69 @@ public class HallResource extends AbstractResource { } @Override - public void serverTime(String time) { - _hall.setAttribute("serverTime", time); + public void channelNumber(int channelNumber) { + _hall.setAttribute("channelNumber", String.valueOf(channelNumber)); } @Override - public void playerIsWaiting(boolean waiting) { - _hall.setAttribute("waiting", String.valueOf(waiting)); + public void serverTime(String serverTime) { + _hall.setAttribute("serverTime", serverTime); } @Override - public void visitTable(String tableId, String gameId, boolean watchable, String tableStatus, String formatName, String tournamentName, Set playerIds, String winner) { + public void addTournamentQueue(String queueId, Map props) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "add"); + queue.setAttribute("id", queueId); + for (Map.Entry attribute : props.entrySet()) + queue.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(queue); + } + + @Override + public void updateTournamentQueue(String queueId, Map props) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "update"); + queue.setAttribute("id", queueId); + for (Map.Entry attribute : props.entrySet()) + queue.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(queue); + } + + @Override + public void removeTournamentQueue(String queueId) { + Element queue = _doc.createElement("queue"); + queue.setAttribute("action", "remove"); + queue.setAttribute("id", queueId); + _hall.appendChild(queue); + } + + @Override + public void addTable(String tableId, Map props) { Element table = _doc.createElement("table"); + table.setAttribute("action", "add"); table.setAttribute("id", tableId); - if (gameId != null) - table.setAttribute("gameId", gameId); - table.setAttribute("status", tableStatus); - table.setAttribute("watchable", String.valueOf(watchable)); - table.setAttribute("format", formatName); - table.setAttribute("tournament", tournamentName); - table.setAttribute("players", mergeStrings(playerIds)); - if (winner != null) - table.setAttribute("winner", winner); + for (Map.Entry attribute : props.entrySet()) + table.setAttribute(attribute.getKey(), attribute.getValue()); _hall.appendChild(table); } @Override - public void runningPlayerDraft(String draftId) { - //To change body of implemented methods use File | Settings | File Templates. + public void updateTable(String tableId, Map props) { + Element table = _doc.createElement("table"); + table.setAttribute("action", "update"); + table.setAttribute("id", tableId); + for (Map.Entry attribute : props.entrySet()) + table.setAttribute(attribute.getKey(), attribute.getValue()); + _hall.appendChild(table); } @Override - public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName, - String tournamentQueueName, int playerCount, boolean playerSignedUp) { - Element tournamentQueue = _doc.createElement("tournamentQueue"); - tournamentQueue.setAttribute("id", tournamentQueueKey); - tournamentQueue.setAttribute("cost", String.valueOf(cost)); - tournamentQueue.setAttribute("tournament", tournamentQueueName); - tournamentQueue.setAttribute("players", String.valueOf(playerCount)); - tournamentQueue.setAttribute("format", formatName); - tournamentQueue.setAttribute("joined", String.valueOf(playerSignedUp)); - _hall.appendChild(tournamentQueue); + public void removeTable(String tableId) { + Element table = _doc.createElement("table"); + table.setAttribute("action", "remove"); + table.setAttribute("id", tableId); + _hall.appendChild(table); } @Override @@ -307,6 +359,11 @@ public class HallResource extends AbstractResource { runningGame.setAttribute("id", gameId); _hall.appendChild(runningGame); } + + @Override + public void playerBusy(boolean busy) { + _hall.setAttribute("busy", String.valueOf(busy)); + } } private String mergeStrings(Set strings) { diff --git a/gemp-lotr/gemp-lotr-web/src/main/webapp/hall.html b/gemp-lotr/gemp-lotr-web/src/main/webapp/hall.html index 12adeb784..e2bd36b32 100644 --- a/gemp-lotr/gemp-lotr-web/src/main/webapp/hall.html +++ b/gemp-lotr/gemp-lotr-web/src/main/webapp/hall.html @@ -14,6 +14,12 @@ background-color: #000000; } + .eventHeader { + font-size: 120%; + font-weight: bolder; + background-color: #666666; + } + .leagueName, #stats .period, .tournamentName, .playerStatHeader { font-size: 200%; font-weight: bolder; diff --git a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/communication.js b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/communication.js index 0381ec26a..b5e888a20 100644 --- a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/communication.js +++ b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/communication.js @@ -499,6 +499,19 @@ var GempLotrCommunication = Class.extend({ dataType:"xml" }); }, + updateHall:function (callback, channelNumber, errorMap) { + $.ajax({ + type:"POST", + url:this.url + "/hall/update", + cache:false, + data:{ + channelNumber:channelNumber, + participantId:getUrlParam("participantId") }, + success:this.deliveryCheck(callback), + error:this.errorCheck(errorMap), + dataType:"xml" + }); + }, joinQueue:function (queueId, deckName, callback, errorMap) { $.ajax({ type:"POST", diff --git a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/hallUi.js b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/hallUi.js index 932934d8f..04cbf4a13 100644 --- a/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/hallUi.js +++ b/gemp-lotr/gemp-lotr-web/src/main/webapp/js/gemp-013/hallUi.js @@ -11,9 +11,8 @@ var GempLotrHallUI = Class.extend({ tablesDiv:null, buttonsDiv:null, - showEmptyTableQueues:null, - pocketDiv:null, + hallChannelId: null, init:function (div, url, chat) { this.div = div; @@ -40,6 +39,12 @@ var GempLotrHallUI = Class.extend({ this.tablesDiv = $("
"); this.tablesDiv.css({overflow:"auto", left:"0px", top:"0px", width:width + "px", height:(height - 30) + "px"}); + + this.addQueuesTable(); + this.addWaitingTablesTable(); + this.addPlayingTablesTable(); + this.addFinishedTablesTable(); + this.div.append(this.tablesDiv); this.buttonsDiv = $("
"); @@ -90,8 +95,6 @@ var GempLotrHallUI = Class.extend({ this.decksSelect = $(""); this.decksSelect.hide(); - var showQueuesCheck = $(""); - this.buttonsDiv.append(this.supportedFormatsSelect); this.buttonsDiv.append(this.decksSelect); this.buttonsDiv.append(this.createTableButton); @@ -106,25 +109,163 @@ var GempLotrHallUI = Class.extend({ this.buttonsDiv.append(this.leaveTableButton); - this.buttonsDiv.append(" | "); - this.buttonsDiv.append(showQueuesCheck); - this.div.append(this.buttonsDiv); - this.updateHall(); + this.getHall(); this.updateDecks(); }, + addQueuesTable: function() { + var header = $("
"); + + var content = $("
"); + + var toggleContent = $("
Toggle tournament queues
").button({ + icons: { + primary: "ui-icon-circlesmall-minus" + }, + text: false + }); + toggleContent.css({width: "13px", height: "15px"}); + toggleContent.click( + function() { + if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus") + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"}); + else + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"}); + content.toggle("blind", {}, 200); + }); + header.append(toggleContent); + header.append(" Tournament queues"); + + var table = $("
"); + table.append("FormatCollectionQueue namePlayersCostActions"); + content.append(table); + + this.tablesDiv.append(header); + this.tablesDiv.append(content); + }, + + addWaitingTablesTable: function() { + var header = $("
"); + + var content = $("
"); + + var toggleContent = $("
Toggle waiting tables
").button({ + icons: { + primary: "ui-icon-circlesmall-minus" + }, + text: false + }); + toggleContent.css({width: "13px", height: "15px"}); + toggleContent.click( + function() { + if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus") + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"}); + else + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"}); + content.toggle("blind", {}, 200); + }); + header.append(toggleContent); + header.append(" Waiting tables"); + + var table = $("
"); + table.append("FormatTournamentStatusPlayersActions"); + content.append(table); + + this.tablesDiv.append(header); + this.tablesDiv.append(content); + }, + + addPlayingTablesTable: function() { + var header = $("
"); + + var content = $("
"); + + var toggleContent = $("
Toggle playing tables
").button({ + icons: { + primary: "ui-icon-circlesmall-minus" + }, + text: false + }); + toggleContent.css({width: "13px", height: "15px"}); + toggleContent.click( + function() { + if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus") + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"}); + else + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"}); + content.toggle("blind", {}, 200); + }); + header.append(toggleContent); + header.append(" Playing tables"); + + var table = $("
"); + table.append("FormatTournamentStatusPlayersActions"); + content.append(table); + + this.tablesDiv.append(header); + this.tablesDiv.append(content); + }, + + addFinishedTablesTable: function() { + var header = $("
"); + + var content = $("
"); + + var toggleContent = $("
Toggle finished tables
").button({ + icons: { + primary: "ui-icon-circlesmall-minus" + }, + text: false + }); + toggleContent.css({width: "13px", height: "15px"}); + toggleContent.click( + function() { + if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus") + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"}); + else + toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"}); + content.toggle("blind", {}, 200); + }); + header.append(toggleContent); + header.append(" Finished tables"); + + var table = $("
"); + table.append("FormatTournamentStatusPlayersWinner"); + content.append(table); + + this.tablesDiv.append(header); + this.tablesDiv.append(content); + }, + hallResized:function (width, height) { this.tablesDiv.css({overflow:"auto", left:"0px", top:"0px", width:width + "px", height:(height - 30) + "px"}); this.buttonsDiv.css({left:"0px", top:(height - 30) + "px", width:width + "px", height:29 + "px", align:"right", backgroundColor:"#000000", "border-top-width":"1px", "border-top-color":"#ffffff", "border-top-style":"solid"}); }, + getHall: function() { + var that = this; + + this.comm.getHall(function(xml) { + that.processHall(xml); + }); + }, + updateHall:function () { var that = this; - this.comm.getHall(function (xml) { - that.processHall(xml); + this.comm.updateHall( + function (xml) { + that.processHall(xml); + }, this.hallChannelId, + { + "409":function() { + that.chat.appendMessage("You have accessed Game Hall in another browser, press F5 (refresh) to regain access in this window"); + }, + "410":function() { + that.chat.appendMessage("You have been inactive for too long, press F5 (refresh) to enter Game Hall again"); + } }); }, @@ -163,9 +304,11 @@ var GempLotrHallUI = Class.extend({ }, processHall:function (xml) { + var that = this; + var root = xml.documentElement; if (root.tagName == "hall") { - this.tablesDiv.html(""); + this.hallChannelId = root.getAttribute("channelNumber"); var currency = root.getAttribute("currency"); this.pocketDiv.html(formatPrice(currency)); @@ -174,41 +317,171 @@ var GempLotrHallUI = Class.extend({ if (motd != null) $("#motd").html("MOTD: " + motd); - var waiting = root.getAttribute("waiting") == "true"; + var busy = root.getAttribute("busy") == "true"; - var tablesTable = $("
"); - tablesTable.append("FormatTournamentStatusPlayersActions"); + var queues = root.getElementsByTagName("queue"); + for (var i = 0; i < queues.length; i++) { + var queue = queues[i]; + var id = queue.getAttribute("id"); + var action = queue.getAttribute("action"); + if (action == "add" || action == "update") { + var actionsField = $(""); - var tournamentQueues = root.getElementsByTagName("tournamentQueue"); - for (var i = 0; i < tournamentQueues.length; i++) { - var tournamentQueue = tournamentQueues[i]; - var id = tournamentQueue.getAttribute("id"); - var tournamentName = tournamentQueue.getAttribute("tournament"); - var players = parseInt(tournamentQueue.getAttribute("players")); - var formatName = tournamentQueue.getAttribute("format"); - var joined = tournamentQueue.getAttribute("joined"); - if (players > 0 || $("#showEmptyTableQueuesCheck").prop("checked")) - this.appendTournamentQueue(tablesTable, id, formatName, tournamentName, players, joined); + var joined = queue.getAttribute("signedUp"); + if (joined != "true") { + var but = $(""); + $(but).button().click( + function (event) { + var deck = that.decksSelect.val(); + if (deck != null) + that.comm.joinQueue(id, deck, function (xml) { + that.processResponse(xml); + }); + }); + actionsField.append(but); + } else { + var but = $(""); + $(but).button().click( + function (event) { + var deck = that.decksSelect.val(); + if (deck != null) + that.comm.leaveQueue(id, deck, function (xml) { + that.processResponse(xml); + }); + }); + actionsField.append(but); + } + + var row = $("" + queue.getAttribute("format") + "" + + "" + queue.getAttribute("collection") + "" + + "" + queue.getAttribute("queue") + "" + + "" + queue.getAttribute("playerCount") + "" + + "" + formatPrice(queue.getAttribute("cost")) + "" + + ""); + + row.append(actionsField); + + if (action == "add") { + $("table.queues", this.tablesDiv) + .append(row); + } else if (action == "update") { + $(".queue" + id, this.tablesDiv).replaceWith(row); + } + } else if (action == "remove") { + $(".queue" + id, this.tablesDiv).remove(); + } } var tables = root.getElementsByTagName("table"); for (var i = 0; i < tables.length; i++) { var table = tables[i]; var id = table.getAttribute("id"); - var gameId = table.getAttribute("gameId"); - var status = table.getAttribute("status"); - var watchable = table.getAttribute("watchable"); - var playersAttr = table.getAttribute("players"); - var formatName = table.getAttribute("format"); - var tournamentName = table.getAttribute("tournament"); - var players = new Array(); - if (playersAttr.length > 0) - players = playersAttr.split(","); - var winner = table.getAttribute("winner"); + var action = table.getAttribute("action"); + if (action == "add" || action == "update") { + var status = table.getAttribute("status"); - var tableDiv = this.appendTable(tablesTable, id, gameId, watchable, status, formatName, tournamentName, players, waiting, winner); + var gameId = table.getAttribute("gameId"); + var statusDescription = table.getAttribute("statusDescription"); + var watchable = table.getAttribute("watchable"); + var playersAttr = table.getAttribute("players"); + var formatName = table.getAttribute("format"); + var tournamentName = table.getAttribute("tournament"); + var players = new Array(); + if (playersAttr.length > 0) + players = playersAttr.split(","); + var winner = table.getAttribute("winner"); + + var row = $(""); + + row.append("" + formatName + ""); + row.append("" + tournamentName + ""); + row.append("" + statusDescription + ""); + + var playersStr = ""; + for (var i = 0; i < players.length; i++) { + if (i > 0) + playersStr += ", "; + playersStr += players[i]; + } + row.append("" + playersStr + ""); + + var lastField = $(""); + if (status == "WAITING" && !busy) { + var that = this; + + var but = $(""); + $(but).button().click( + function (event) { + var deck = that.decksSelect.val(); + if (deck != null) + that.comm.joinTable(id, deck, function (xml) { + that.processResponse(xml); + }); + }); + lastField.append(but); + } + + if (status == "PLAYING" && watchable == "true") { + var but = $(""); + $(but).button().click( + function (event) { + var participantId = getUrlParam("participantId"); + var participantIdAppend = ""; + if (participantId != null) + participantIdAppend = "&participantId=" + participantId; + location.href = "/gemp-lotr/game.html?gameId=" + gameId + participantIdAppend; + }); + lastField.append(but); + } + + if (status == "FINISHED" && winner != null) { + lastField.append(winner); + } + + row.append(lastField); + + if (action == "add") { + if (status == "WAITING") { + $("table.waitingTables", this.tablesDiv) + .append(row); + } else if (status == "PLAYING") { + $("table.playingTables", this.tablesDiv) + .append(row); + } else if (status == "FINISHED") { + $("table.finishedTables", this.tablesDiv) + .append(row); + } + } else if (action == "update") { + if (status == "WAITING") { + if ($(".table"+id, $("table.waitingTables")).length > 0) { + $(".table" + id, this.tablesDiv).replaceWith(row); + } else { + $(".table" + id, this.tablesDiv).remove(); + $("table.waitingTables", this.tablesDiv) + .append(row); + } + } else if (status == "PLAYING") { + if ($(".table"+id, $("table.playingTables")).length > 0) { + $(".table" + id, this.tablesDiv).replaceWith(row); + } else { + $(".table" + id, this.tablesDiv).remove(); + $("table.playingTables", this.tablesDiv) + .append(row); + } + } else if (status == "FINISHED") { + if ($(".table"+id, $("table.finishedTables")).length > 0) { + $(".table" + id, this.tablesDiv).replaceWith(row); + } else { + $(".table" + id, this.tablesDiv).remove(); + $("table.finishedTables", this.tablesDiv) + .append(row); + } + } + } + } else if (action == "remove") { + $(".table" + id, this.tablesDiv).remove(); + } } - this.tablesDiv.append(tablesTable); var skipReload = false; var games = root.getElementsByTagName("game"); @@ -233,7 +506,7 @@ var GempLotrHallUI = Class.extend({ this.supportedFormatsInitialized = true; } - if (waiting) { + if (busy) { this.supportedFormatsSelect.hide(); this.decksSelect.hide(); this.createTableButton.hide(); @@ -257,7 +530,8 @@ var GempLotrHallUI = Class.extend({ }, 1000); } } - }, + } + , appendTournamentQueue:function (container, id, formatName, tournamentName, players, joined) { var row = $(""); @@ -298,7 +572,8 @@ var GempLotrHallUI = Class.extend({ container.append(row); - }, + } + , appendTable:function (container, id, gameId, watchable, status, formatName, tournamentName, players, waiting, winner) { var row = $("");