diff --git a/frontend/prebuild/src/app/game/game.component.ts b/frontend/prebuild/src/app/game/game.component.ts
index 001a57e..f17eebe 100644
--- a/frontend/prebuild/src/app/game/game.component.ts
+++ b/frontend/prebuild/src/app/game/game.component.ts
@@ -64,7 +64,7 @@ export class GameComponent implements OnInit {
if (sessionStorage.getItem('isSpectator') === 'true') {
console.log('is a spectator... showing game id');
// Set the Round ID and make visible
- $('#game-code').html(this.roundId);
+ $('#game-code').html(sessionStorage.getItem('partyId'));
const gameId = $('#game-code-display');
gameId.removeClass('d-none');
gameId.addClass('d-inline-block');
diff --git a/frontend/prebuild/src/app/login/login.component.html b/frontend/prebuild/src/app/login/login.component.html
index f5bf640..a3178aa 100644
--- a/frontend/prebuild/src/app/login/login.component.html
+++ b/frontend/prebuild/src/app/login/login.component.html
@@ -12,16 +12,16 @@
-
+
-
+
diff --git a/frontend/prebuild/src/app/login/login.component.scss b/frontend/prebuild/src/app/login/login.component.scss
index 26f77bb..9773c05 100644
--- a/frontend/prebuild/src/app/login/login.component.scss
+++ b/frontend/prebuild/src/app/login/login.component.scss
@@ -81,7 +81,7 @@
outline: none;
}
-#roundid {
+#partyid {
text-transform: uppercase;
}
diff --git a/frontend/prebuild/src/app/login/login.component.ts b/frontend/prebuild/src/app/login/login.component.ts
index e58d32f..02f6776 100644
--- a/frontend/prebuild/src/app/login/login.component.ts
+++ b/frontend/prebuild/src/app/login/login.component.ts
@@ -27,27 +27,19 @@ export class LoginComponent implements OnInit {
this.meta.addTag({name: 'viewport', content: `width=${viewWidth}px, height=${viewHeight}px, initial-scale=1.0`}, true);
}
- async createRound() {
- try {
- let data = await this.http.post(`${environment.API_URL_GAME_ROUND}/create`, "", { responseType: 'text'}).toPromise();
- $('#roundid').val(`${data}`)
- } catch (error) {
- console.log(error);
- }
-
- }
-
async quickJoin() {
// First get an unstarted round ID
- let roundID = await this.http.post(`${environment.API_URL_GAME_ROUND}/available`, "", { responseType: 'text' }).toPromise();
+ let roundID = await this.http.get(`${environment.API_URL_GAME_ROUND}/available`, { responseType: 'text' }).toPromise();
// Then join the round
this.joinRoundById(roundID);
}
async joinRound() {
- let roundID: string = $('#roundid').val();
- this.joinRoundById(roundID);
+ let partyID: string = $('#partyid').val();
+ let roundID: any = await this.http.get(`${environment.API_URL_PARTY}/${partyID}/round`, { responseType: 'text' }).toPromise();
+ console.log(`Got roundID=${roundID} for partyID=${partyID}`);
+ this.joinRoundById(roundID);
}
async joinRoundById(roundID: string) {
@@ -128,10 +120,10 @@ async joinRoundById(roundID: string) {
let router = this.router;
try {
- let data = await this.http.post(`${environment.API_URL_GAME_ROUND}/create`, "", { responseType: 'text' }).toPromise();
- console.log(`Created round with id=${data}`);
+ let party: any = await this.http.post(`${environment.API_URL_PARTY}/create`, "", { responseType: 'json' }).toPromise();
sessionStorage.setItem('isSpectator', 'true');
- sessionStorage.setItem('roundId', data);
+ sessionStorage.setItem('partyId', party.id);
+ sessionStorage.setItem('roundId', party.currentRound.id);
ngZone.run(() => {
router.navigate(['/game']);
});
diff --git a/frontend/prebuild/src/environments/environment.ts b/frontend/prebuild/src/environments/environment.ts
index 0cd3bf4..d0a45e7 100644
--- a/frontend/prebuild/src/environments/environment.ts
+++ b/frontend/prebuild/src/environments/environment.ts
@@ -6,6 +6,7 @@
export const environment = {
production: false,
API_URL_AUTH: `${document.location.hostname}:8082`,
+ API_URL_PARTY: `http://${document.location.hostname}:8080/party`,
API_URL_GAME_ROUND: `http://${document.location.hostname}:8080/round`,
API_URL_GAME_WS: `ws://${document.location.hostname}:8080/round/ws`,
API_URL_PLAYERS: `http://${document.location.hostname}:8081/player`,
diff --git a/game-service/src/main/java/org/libertybikes/game/core/GameBoard.java b/game-service/src/main/java/org/libertybikes/game/core/GameBoard.java
index d419c3e..bef6b1d 100644
--- a/game-service/src/main/java/org/libertybikes/game/core/GameBoard.java
+++ b/game-service/src/main/java/org/libertybikes/game/core/GameBoard.java
@@ -182,9 +182,6 @@ public class GameBoard {
}
}
- /**
- *
- */
public void addAI() {
// Find first open player slot to fill, which determines position
short playerNum = -1;
@@ -192,7 +189,6 @@ public class GameBoard {
if (!takenPlayerSlots[i]) {
playerNum = i;
takenPlayerSlots[i] = true;
- System.out.println("Player slot " + i + " taken");
break;
}
}
diff --git a/game-service/src/main/java/org/libertybikes/game/core/GameRound.java b/game-service/src/main/java/org/libertybikes/game/core/GameRound.java
index 87422f0..f43e2cd 100644
--- a/game-service/src/main/java/org/libertybikes/game/core/GameRound.java
+++ b/game-service/src/main/java/org/libertybikes/game/core/GameRound.java
@@ -8,12 +8,14 @@ import java.util.ArrayDeque;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.enterprise.concurrent.LastExecution;
@@ -63,6 +65,7 @@ public class GameRound implements Runnable {
private final AtomicBoolean heartbeatStarted = new AtomicBoolean();
private final Map clients = new HashMap<>();
private final Deque playerRanks = new ArrayDeque<>();
+ private final Set lifecycleCallbacks = new HashSet<>();
private int ticksFromGameEnd = 0;
@@ -142,6 +145,10 @@ public class GameRound implements Runnable {
beginHeartbeat();
}
+ public void addCallback(LifecycleCallback callback) {
+ lifecycleCallbacks.add(callback);
+ }
+
private void beginHeartbeat() {
// Send a heartbeat to connected clients every 100 seconds in an attempt to keep them connected.
// It appears that when running in IBM Cloud, sockets time out after 120 seconds
@@ -351,6 +358,8 @@ public class GameRound implements Runnable {
sendTextToClients(clients.keySet(), jsonb.toJson(new OutboundMessage.StartingCountdown(STARTING_COUNTDOWN)));
delay(TimeUnit.SECONDS.toMillis(STARTING_COUNTDOWN));
+ lifecycleCallbacks.forEach(c -> c.get());
+
paused.set(false);
for (Player p : getPlayers())
if (STATUS.Connected == p.getStatus())
@@ -372,6 +381,8 @@ public class GameRound implements Runnable {
System.out.println("[GameRound-" + id + "] " + msg);
}
+ public interface LifecycleCallback extends Supplier {}
+
private class HeartbeatTrigger implements Trigger {
private static final int HEARTBEAT_INTERVAL_SEC = 100;
diff --git a/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundApp.java b/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundApp.java
index 9cf35fe..55723e1 100644
--- a/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundApp.java
+++ b/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundApp.java
@@ -3,7 +3,7 @@ package org.libertybikes.game.round.service;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
-@ApplicationPath("/round")
+@ApplicationPath("/")
public class GameRoundApp extends Application {
}
diff --git a/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundService.java b/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundService.java
index 549631c..bf2f64f 100644
--- a/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundService.java
+++ b/game-service/src/main/java/org/libertybikes/game/round/service/GameRoundService.java
@@ -14,12 +14,13 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.libertybikes.game.core.GameRound;
import org.libertybikes.game.core.GameRound.State;
-@Path("/")
+@Path("/round")
@ApplicationScoped
public class GameRoundService {
@@ -47,6 +48,19 @@ public class GameRoundService {
}
@POST
+ @Path("/create")
+ public GameRound createRoundById(@QueryParam("gameId") String gameId) {
+ GameRound newRound = new GameRound(gameId);
+ GameRound existingRound = allRounds.putIfAbsent(gameId, newRound);
+ GameRound round = existingRound == null ? newRound : existingRound;
+ System.out.println("Created round id=" + round.id);
+ if (allRounds.size() > 5)
+ System.out.println("WARNING: Found " + allRounds.size() + " active games in GameRoundService. " +
+ "They are probably not being cleaned up properly: " + allRounds.keySet());
+ return round;
+ }
+
+ @GET
@Path("/available")
public String getAvailableRound() {
Optional availableRound = allRounds.values()
@@ -71,10 +85,7 @@ public class GameRoundService {
if (!oldRound.isStarted())
return null;
- GameRound newRound = new GameRound(oldRound.nextRoundId);
- GameRound existingRound = allRounds.putIfAbsent(oldRound.nextRoundId, newRound);
- GameRound nextRound = existingRound == null ? newRound : existingRound;
- System.out.println("Created round id=" + nextRound.id);
+ GameRound nextRound = createRoundById(oldRound.nextRoundId);
// If player tries to requeue and next game is already in progress, requeue ahead to the next game
if (isPlayer && nextRound.isStarted())
diff --git a/game-service/src/main/java/org/libertybikes/game/round/service/Party.java b/game-service/src/main/java/org/libertybikes/game/round/service/Party.java
new file mode 100644
index 0000000..1530592
--- /dev/null
+++ b/game-service/src/main/java/org/libertybikes/game/round/service/Party.java
@@ -0,0 +1,65 @@
+package org.libertybikes.game.round.service;
+
+import java.util.Random;
+
+import javax.enterprise.context.Dependent;
+import javax.inject.Inject;
+import javax.json.bind.annotation.JsonbTransient;
+
+import org.libertybikes.game.core.GameRound;
+
+@Dependent
+public class Party {
+
+ private static final Random r = new Random();
+
+ @Inject
+ @JsonbTransient
+ GameRoundService roundService;
+
+ public final String id;
+
+ private volatile GameRound currentRound;
+
+ @Inject
+ public Party() {
+ this(getRandomPartyID());
+ }
+
+ public Party(String id) {
+ this.id = id;
+ }
+
+ public GameRound getCurrentRound() {
+ if (currentRound == null) {
+ currentRound = roundService.getRound(roundService.createRound());
+ installCallback(currentRound);
+ }
+ log("Current round id=" + currentRound.id);
+ return this.currentRound;
+ }
+
+ // Get a string of 4 random uppercase letters (A-Z)
+ private static String getRandomPartyID() {
+ char[] chars = new char[4];
+ for (int i = 0; i < 4; i++)
+ chars[i] = (char) (r.nextInt(26) + 65);
+ return new String(chars);
+ }
+
+ private void log(String msg) {
+ System.out.println("[Party-" + id + "] " + msg);
+ }
+
+ // Installs a callback on the GameRound that updates this party's current round
+ private void installCallback(GameRound round) {
+ log("Install callback for round id=" + round.id);
+ round.addCallback(() -> {
+ log("Updating current round from " + round.id + " -> " + round.nextRoundId);
+ currentRound = roundService.createRoundById(round.nextRoundId);
+ this.installCallback(currentRound);
+ return null;
+ });
+ }
+
+}
diff --git a/game-service/src/main/java/org/libertybikes/game/round/service/PartyService.java b/game-service/src/main/java/org/libertybikes/game/round/service/PartyService.java
new file mode 100644
index 0000000..8908412
--- /dev/null
+++ b/game-service/src/main/java/org/libertybikes/game/round/service/PartyService.java
@@ -0,0 +1,57 @@
+/**
+ *
+ */
+package org.libertybikes.game.round.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.spi.CDI;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("/party")
+@ApplicationScoped
+@Produces(MediaType.APPLICATION_JSON)
+public class PartyService {
+
+ // Map of PartyID to current RoundID
+ private final Map allParties = new ConcurrentHashMap<>();
+
+ @GET
+ public Collection listParties() {
+ return allParties.values();
+ }
+
+ @POST
+ @Path("/create")
+ public Party createParty() {
+ Party p = CDI.current().select(Party.class).get();
+ allParties.put(p.id, p);
+ return p;
+ }
+
+ @GET
+ @Path("/{partyId}")
+ public Party getParty(@PathParam("partyId") String partyId) {
+ if (partyId == null)
+ return null;
+ return allParties.get(partyId.toUpperCase());
+ }
+
+ @GET
+ @Path("/{partyId}/round")
+ public String getCurrentRound(@PathParam("partyId") String partyId) {
+ if (partyId == null)
+ return null;
+ Party p = getParty(partyId.toUpperCase());
+ return p == null ? null : p.getCurrentRound().id;
+ }
+
+}