Display round permalinks after the round is over, if enabled.
This takes advantage of the metrics logging, the metrics processor, and the metrics viewer to provide a permalink to a particular round, instead of requiring players to take a screenshot of the round. This will not be enabled immediately as the viewer is not quite ready for deployment, but this is all that needs done on the game server to support this, so it can just be dynamically enabled when it's ready.
This commit is contained in:
parent
88ab1ac640
commit
99958c0dcf
|
@ -358,6 +358,7 @@ cah.$.LongPollResponse.SIGIL = "?";
|
|||
cah.$.LongPollResponse.EMOTE = "me";
|
||||
cah.$.LongPollResponse.CARDCAST_DECK_INFO = "cdi";
|
||||
cah.$.LongPollResponse.GAME_ID = "gid";
|
||||
cah.$.LongPollResponse.ROUND_PERMALINK = "rP";
|
||||
cah.$.LongPollResponse.NICKNAME = "n";
|
||||
cah.$.LongPollResponse.BLACK_CARD = "bc";
|
||||
cah.$.LongPollResponse.GAME_STATE = "gs";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Andy Janata
|
||||
* Copyright (c) 2012-2018, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
|
@ -988,8 +988,13 @@ cah.Game.prototype.roundComplete = function(data) {
|
|||
var scoreCard = this.scoreCards_[roundWinner];
|
||||
$(scoreCard.getElement()).addClass("selected");
|
||||
$(".confirm_card", this.element_).attr("disabled", "disabled");
|
||||
cah.log.status_with_game(this, roundWinner + " wins the round. The next round will begin in "
|
||||
+ (data[cah.$.LongPollResponse.INTERMISSION] / 1000) + " seconds.");
|
||||
var msg = roundWinner + " wins the round. The next round will begin in "
|
||||
+ (data[cah.$.LongPollResponse.INTERMISSION] / 1000) + " seconds.";
|
||||
if (cah.$.LongPollResponse.ROUND_PERMALINK in data) {
|
||||
msg = msg + " <a href='" + data[cah.$.LongPollResponse.ROUND_PERMALINK]
|
||||
+ "' rel='noopener' target='_blank'>Permalink</a>";
|
||||
}
|
||||
cah.log.status_with_game(this, msg, undefined, true);
|
||||
|
||||
// update the previous round display
|
||||
$(".game_last_round_winner", this.element_).text(roundWinner);
|
||||
|
|
|
@ -86,6 +86,16 @@ hibernate.cache.use_second_level_cache=false
|
|||
hibernate.cache.use_query_cache=false
|
||||
hibernate.cache.provider_class=org.hibernate.cache.NoCacheProvider
|
||||
|
||||
# If the server should send round IDs to clients after the round is over, and if the client should
|
||||
# display a permalink to them. You must be using the Kafka metrics implementation, and have
|
||||
# pyx-metrics-processor running somewhere to put the data into a database, and have
|
||||
# pyx-metrics-viewer running somewhere connected to that database. The URL to the viewer is provided
|
||||
# below. If you don't know what any of that is, you certainly don't have it running, so leave this
|
||||
# set to false.
|
||||
pyx.metrics.round.enabled=false
|
||||
# Format string to the URL to view a previous round. Must contain exactly one %s which will be
|
||||
# replaced with the round's ID.
|
||||
pyx.metrics.round.url_format=http://localhost:4080/static/round.html#%s
|
||||
# Metrics implementation.
|
||||
pyx.metrics.impl=net.socialgamer.cah.metrics.NoOpMetrics
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ pyx.chat.game.flood_count=${pyx.game.flood_count}
|
|||
pyx.chat.game.flood_time=${pyx.game.flood_time}
|
||||
pyx.build=${buildNumber}
|
||||
|
||||
pyx.metrics.round.enabled=${pyx.metrics.round.enabled}
|
||||
pyx.metrics.round.url_format=${pyx.metrics.round.url_format}
|
||||
# this is NOT allowed to be changed during a reload, as metrics depend on previous events
|
||||
pyx.metrics.impl=${pyx.metrics.impl}
|
||||
|
||||
|
|
|
@ -169,6 +169,22 @@ public class CahModule extends AbstractModule {
|
|||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ShowRoundPermalink
|
||||
Boolean provideShowRoundPermalink() {
|
||||
synchronized (properties) {
|
||||
return Boolean.valueOf(properties.getProperty("pyx.metrics.round.enabled", "false"));
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RoundPermalinkUrlFormat
|
||||
String provideRoundPermalinkUrlFormat() {
|
||||
synchronized (properties) {
|
||||
return properties.getProperty("pyx.metrics.round.url_format", "about:blank#%s");
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@InsecureIdAllowed
|
||||
Boolean provideInsecureIdAllowed() {
|
||||
|
@ -235,6 +251,16 @@ public class CahModule extends AbstractModule {
|
|||
public @interface MaxUsers {
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ShowRoundPermalink {
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RoundPermalinkUrlFormat {
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface BroadcastConnectsAndDisconnects {
|
||||
|
|
|
@ -561,6 +561,7 @@ public class Constants {
|
|||
* Reason why a player disconnected.
|
||||
*/
|
||||
REASON("qr"),
|
||||
ROUND_PERMALINK("rP"),
|
||||
ROUND_WINNER("rw"),
|
||||
/**
|
||||
* Sigil to display next to user's name.
|
||||
|
|
|
@ -48,6 +48,8 @@ import org.hibernate.Session;
|
|||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import net.socialgamer.cah.CahModule.RoundPermalinkUrlFormat;
|
||||
import net.socialgamer.cah.CahModule.ShowRoundPermalink;
|
||||
import net.socialgamer.cah.CahModule.UniqueId;
|
||||
import net.socialgamer.cah.Constants.BlackCardData;
|
||||
import net.socialgamer.cah.Constants.ErrorCode;
|
||||
|
@ -112,6 +114,8 @@ public class Game {
|
|||
private final GameOptions options = new GameOptions();
|
||||
private final Set<String> cardcastDeckIds = Collections.synchronizedSet(new HashSet<String>());
|
||||
private final Metrics metrics;
|
||||
private final Provider<Boolean> showRoundLinkProvider;
|
||||
private final Provider<String> roundPermalinkFormatProvider;
|
||||
private final long created = System.currentTimeMillis();
|
||||
|
||||
private int judgeIndex = 0;
|
||||
|
@ -204,7 +208,8 @@ public class Game {
|
|||
final Provider<Session> sessionProvider,
|
||||
final Provider<CardcastService> cardcastServiceProvider,
|
||||
@UniqueId final Provider<String> uniqueIdProvider,
|
||||
final Metrics metrics) {
|
||||
final Metrics metrics, @ShowRoundPermalink final Provider<Boolean> showRoundLinkProvider,
|
||||
@RoundPermalinkUrlFormat final Provider<String> roundPermalinkFormatProvider) {
|
||||
this.id = id;
|
||||
this.connectedUsers = connectedUsers;
|
||||
this.gameManager = gameManager;
|
||||
|
@ -213,6 +218,8 @@ public class Game {
|
|||
this.cardcastServiceProvider = cardcastServiceProvider;
|
||||
this.uniqueIdProvider = uniqueIdProvider;
|
||||
this.metrics = metrics;
|
||||
this.showRoundLinkProvider = showRoundLinkProvider;
|
||||
this.roundPermalinkFormatProvider = roundPermalinkFormatProvider;
|
||||
|
||||
state = GameState.LOBBY;
|
||||
}
|
||||
|
@ -1489,11 +1496,16 @@ public class Game {
|
|||
}
|
||||
final int clientCardId = playedCards.getCards(cardPlayer).get(0).getId();
|
||||
|
||||
final String roundId = uniqueIdProvider.get();
|
||||
final HashMap<ReturnableData, Object> data = getEventMap();
|
||||
data.put(LongPollResponse.EVENT, LongPollEvent.GAME_ROUND_COMPLETE.toString());
|
||||
data.put(LongPollResponse.ROUND_WINNER, cardPlayer.getUser().getNickname());
|
||||
data.put(LongPollResponse.WINNING_CARD, clientCardId);
|
||||
data.put(LongPollResponse.INTERMISSION, ROUND_INTERMISSION);
|
||||
if (showRoundLinkProvider.get()) {
|
||||
data.put(LongPollResponse.ROUND_PERMALINK,
|
||||
String.format(roundPermalinkFormatProvider.get(), roundId));
|
||||
}
|
||||
broadcastToPlayers(MessageType.GAME_EVENT, data);
|
||||
|
||||
notifyPlayerInfoChange(getJudge());
|
||||
|
@ -1523,7 +1535,7 @@ public class Game {
|
|||
final Map<String, List<WhiteCard>> cardsBySessionId = new HashMap<>();
|
||||
playedCards.cardsByUser().forEach(
|
||||
(key, value) -> cardsBySessionId.put(key.getSessionId(), value));
|
||||
metrics.roundComplete(currentUniqueId, uniqueIdProvider.get(), judge.getSessionId(),
|
||||
metrics.roundComplete(currentUniqueId, roundId, judge.getSessionId(),
|
||||
cardPlayer.getUser().getSessionId(), blackCard, cardsBySessionId);
|
||||
|
||||
return null;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (c) 2012-2017, Andy Janata
|
||||
* Copyright (c) 2012-2018, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
|
@ -39,15 +39,6 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import net.socialgamer.cah.CahModule.UniqueId;
|
||||
import net.socialgamer.cah.HibernateUtil;
|
||||
import net.socialgamer.cah.cardcast.CardcastModule.CardcastCardId;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.GameManager.MaxGames;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.metrics.Metrics;
|
||||
import net.socialgamer.cah.metrics.NoOpMetrics;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -56,8 +47,20 @@ import org.junit.Test;
|
|||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import net.socialgamer.cah.CahModule.RoundPermalinkUrlFormat;
|
||||
import net.socialgamer.cah.CahModule.ShowRoundPermalink;
|
||||
import net.socialgamer.cah.CahModule.UniqueId;
|
||||
import net.socialgamer.cah.HibernateUtil;
|
||||
import net.socialgamer.cah.cardcast.CardcastModule.CardcastCardId;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.GameManager.MaxGames;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.metrics.Metrics;
|
||||
import net.socialgamer.cah.metrics.NoOpMetrics;
|
||||
|
||||
|
||||
/**
|
||||
* Tests for {@code GameManager}.
|
||||
|
@ -73,6 +76,18 @@ public class GameManagerTest {
|
|||
private int gameId;
|
||||
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
|
||||
private Metrics metricsMock;
|
||||
private final Provider<Boolean> falseProvider = new Provider<Boolean>() {
|
||||
@Override
|
||||
public Boolean get() {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
};
|
||||
private final Provider<String> formatProvider = new Provider<String>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return "%s";
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
@ -100,6 +115,8 @@ public class GameManagerTest {
|
|||
});
|
||||
bind(ScheduledThreadPoolExecutor.class).toInstance(threadPool);
|
||||
bind(Metrics.class).to(NoOpMetrics.class);
|
||||
bind(Boolean.class).annotatedWith(ShowRoundPermalink.class).toProvider(falseProvider);
|
||||
bind(String.class).annotatedWith(RoundPermalinkUrlFormat.class).toProvider(formatProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -149,13 +166,16 @@ public class GameManagerTest {
|
|||
// fill it up with 3 games
|
||||
assertEquals(0, gameManager.get().intValue());
|
||||
gameManager.getGames().put(0,
|
||||
new Game(0, cuMock, gameManager, timer, null, null, null, metricsMock));
|
||||
new Game(0, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider));
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock));
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider));
|
||||
assertEquals(2, gameManager.get().intValue());
|
||||
gameManager.getGames().put(2,
|
||||
new Game(2, cuMock, gameManager, timer, null, null, null, metricsMock));
|
||||
new Game(2, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider));
|
||||
// make sure it says it can't make any more
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
|
@ -164,14 +184,16 @@ public class GameManagerTest {
|
|||
// make sure it re-uses that id
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock));
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
// remove game 1 out from under it, to make sure it'll fix itself
|
||||
gameManager.getGames().remove(1);
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock));
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
gameManager.destroyGame(2);
|
||||
|
|
|
@ -41,6 +41,8 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import net.socialgamer.cah.data.Game.TooManyPlayersException;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.metrics.Metrics;
|
||||
|
@ -58,13 +60,26 @@ public class GameTest {
|
|||
private GameManager gmMock;
|
||||
private Metrics metricsMock;
|
||||
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
|
||||
private final Provider<Boolean> falseProvider = new Provider<Boolean>() {
|
||||
@Override
|
||||
public Boolean get() {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
};
|
||||
private final Provider<String> formatProvider = new Provider<String>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return "%s";
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
cuMock = createMock(ConnectedUsers.class);
|
||||
gmMock = createMock(GameManager.class);
|
||||
metricsMock = createMock(Metrics.class);
|
||||
game = new Game(0, cuMock, gmMock, timer, null, null, null, metricsMock);
|
||||
game = new Game(0, cuMock, gmMock, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
Loading…
Reference in New Issue