mirror of
https://github.com/OpenLiberty/liberty-bikes.git
synced 2025-01-30 10:40:13 +08:00
commit
d5fec1fa14
@ -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');
|
||||
|
@ -12,17 +12,17 @@
|
||||
<div class="button-bar">
|
||||
<button type="button" (click)="quickJoin()">Quick Join</button>
|
||||
</div>
|
||||
<div></div>
|
||||
<hr/>
|
||||
<div class="login-form">
|
||||
<div class="form-item">
|
||||
<label>Code</label>
|
||||
<input type="text" id="roundid" name="roundid" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
<input type="text" id="partyid" name="roundid" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button type="button" (click)="joinRound()">Join Round</button>
|
||||
<button type="button" (click)="createRound()">Create Round</button>
|
||||
<button id="hostButton" type="button" (click)="hostRound()" data-loading-text="Connecting..." autocomplete="off">Host Round</button>
|
||||
<button type="button" (click)="joinRound()">Join Game</button>
|
||||
<hr/>
|
||||
<button id="hostButton" type="button" (click)="hostRound()" data-loading-text="Connecting..." autocomplete="off">Host Games</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -81,7 +81,7 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#roundid {
|
||||
#partyid {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
@ -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']);
|
||||
});
|
||||
|
@ -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`,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<Session, Client> clients = new HashMap<>();
|
||||
private final Deque<Player> playerRanks = new ArrayDeque<>();
|
||||
private final Set<LifecycleCallback> 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<Void> {}
|
||||
|
||||
private class HeartbeatTrigger implements Trigger {
|
||||
|
||||
private static final int HEARTBEAT_INTERVAL_SEC = 100;
|
||||
|
@ -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 {
|
||||
|
||||
}
|
||||
|
@ -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<GameRound> 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())
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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<String, Party> allParties = new ConcurrentHashMap<>();
|
||||
|
||||
@GET
|
||||
public Collection<Party> 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;
|
||||
}
|
||||
|
||||
}
|
@ -31,6 +31,8 @@ public class PlayerService {
|
||||
String id = createPlayer("SamplePlayer-" + i, null);
|
||||
for (int j = 0; j < 3; j++)
|
||||
recordGame(id, r.nextInt(4) + 1);
|
||||
for (int j = 0; j < 10; j++)
|
||||
recordGame(id, 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user