Update game-service to operate on player IDs instead of just names

This commit is contained in:
Andrew Guibert 2018-03-09 14:20:07 -06:00
parent ac3d2387b1
commit abbcefb612
15 changed files with 184 additions and 108 deletions

View File

@ -1,6 +1,6 @@
<server>
<featureManager>
<feature>microProfile-1.3</feature>
<feature>microProfile-1.2</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />

View File

@ -105,9 +105,7 @@ export class GameComponent implements OnInit {
}
onConnect(evt: MessageEvent) {
const name = sessionStorage.getItem('username');
const isSpectator = sessionStorage.getItem('isSpectator');
if (isSpectator === 'true') {
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);
@ -116,7 +114,7 @@ export class GameComponent implements OnInit {
gameId.addClass('d-inline-block');
this.gameSocket.sendText(JSON.stringify({'spectatorjoined': true}));
} else {
this.gameSocket.sendText(JSON.stringify({'playerjoined': name}));
this.gameSocket.sendText(JSON.stringify({'playerjoined': sessionStorage.getItem('userId')}));
}
}

View File

@ -28,11 +28,17 @@ export class LoginComponent implements OnInit {
let ngZone = this.ngZone;
let router = this.router;
let roundID: string = $('#roundid').val();
let username: string = $('#username').val();
roundID = roundID.toUpperCase().replace(/[^A-Z]/g, '');
// TODO: Validate form input in a more elegant way than alert()
if (roundID.length !== 4) {
alert(roundID + ' is not a valid round ID, because it must be 4 letters long');
return;
}
if (username.length < 1) {
alert('Username must not be empty');
return;
}
$.get(`http://${document.location.hostname}:8080/round/${roundID}`, function(data) {
if (data === undefined) {
@ -51,18 +57,27 @@ export class LoginComponent implements OnInit {
alert('Game round has already finished!');
return;
}
sessionStorage.setItem('username', $('#username').val());
sessionStorage.setItem('roundId', roundID);
if (gameBoard === true) {
ngZone.run(() => {
router.navigate(['/game']);
});
} else {
ngZone.run(() => {
router.navigate(['/play']);
});
}
// TODO: Register the player here until we get a proper 'login' flow
$.post(`http://${document.location.hostname}:8081/player/create?name=${username}`, function(response) {
console.log(`Created a new player with ID=${response}`);
sessionStorage.setItem('userId', response);
// TEMP: to prevent a race condition, putting this code inside of the player create callback to ensure that
// userId is set in the session storage before proceeding to the game board
sessionStorage.setItem('username', username);
sessionStorage.setItem('roundId', roundID);
if (gameBoard === true) {
ngZone.run(() => {
router.navigate(['/game']);
});
} else {
ngZone.run(() => {
router.navigate(['/play']);
});
}
});
});
}

View File

@ -12,6 +12,7 @@ liberty {
name = 'game-service'
dropins = [war]
bootstrapProperties = ['httpPort': httpPort, 'httpsPort': httpsPort]
jvmOptions = ['-Dorg.libertybikes.restclient.PlayerService/mp-rest/url=http://localhost:8081/']
}
}

View File

@ -22,7 +22,6 @@ import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.websocket.Session;
import org.libertybikes.game.core.ClientMessage.GameEvent;
import org.libertybikes.game.core.Player.STATUS;
import org.libertybikes.game.round.service.GameRoundService;
import org.libertybikes.game.round.service.GameRoundWebsocket;
@ -49,7 +48,7 @@ public class GameRound implements Runnable {
private final AtomicBoolean gameRunning = new AtomicBoolean(false);
private final AtomicBoolean paused = new AtomicBoolean(false);
private final Map<Session, Client> clients = new HashMap<>();
private final boolean[] takenPlayerSlots = new boolean[PlayerFactory.MAX_PLAYERS];
private final boolean[] takenPlayerSlots = new boolean[Player.MAX_PLAYERS];
private int ticksFromGameEnd = 0;
@ -77,26 +76,13 @@ public class GameRound implements Runnable {
return board;
}
public void handleMessage(ClientMessage msg, Session session) {
if (GameEvent.GAME_START == msg.event)
startGame();
if (msg.direction != null) {
Client c = clients.get(session);
if (c.isPlayer())
c.player.setDirection(msg.direction);
}
if (msg.playerJoinedId != null) {
addPlayer(session, msg.playerJoinedId);
}
if (Boolean.TRUE == msg.isSpectator) {
addSpectator(session);
}
public void updatePlayerDirection(Session playerSession, ClientMessage msg) {
Client c = clients.get(playerSession);
if (c.isPlayer())
c.player.setDirection(msg.direction);
}
public void addPlayer(Session s, String playerId) {
public void addPlayer(Session s, String playerId, String playerName) {
// Front end should be preventing a player joining a full game but
// defensive programming
if (gameState != State.OPEN) {
@ -104,7 +90,7 @@ public class GameRound implements Runnable {
return;
}
if (board.players.size() + 1 > PlayerFactory.MAX_PLAYERS - 1) {
if (board.players.size() + 1 > Player.MAX_PLAYERS - 1) {
gameState = State.FULL;
}
@ -120,7 +106,7 @@ public class GameRound implements Runnable {
}
// Initialize Player
Player p = PlayerFactory.initNextPlayer(this, playerId, playerNum);
Player p = new Player(playerId, playerName, playerNum);
board.addPlayer(p);
clients.put(s, new Client(s, p));
System.out.println("Player " + playerId + " has joined.");
@ -137,11 +123,11 @@ public class GameRound implements Runnable {
private void removePlayer(Player p) {
p.disconnect();
System.out.println(p.playerName + " disconnected.");
System.out.println(p.name + " disconnected.");
broadcastPlayerList();
// Open player slot for new joiners
if (State.FULL == gameState && board.players.size() - 1 < PlayerFactory.MAX_PLAYERS) {
if (State.FULL == gameState && board.players.size() - 1 < Player.MAX_PLAYERS) {
gameState = State.OPEN;
}
takenPlayerSlots[p.getPlayerNum()] = false;
@ -230,7 +216,7 @@ public class GameRound implements Runnable {
JsonArrayBuilder array = Json.createArrayBuilder();
for (Player p : players()) {
array.add(Json.createObjectBuilder()
.add("name", p.playerName)
.add("name", p.name)
.add("status", p.getStatus().toString())
.add("color", p.color));
}

View File

@ -3,6 +3,10 @@
*/
package org.libertybikes.game.core;
import javax.json.bind.annotation.JsonbPropertyOrder;
import javax.json.bind.annotation.JsonbTransient;
@JsonbPropertyOrder({ "id", "name", "color", "status", "x", "y", "isAlive" })
public class Player {
public static enum STATUS {
@ -13,29 +17,58 @@ public class Player {
Disconnected
}
private static enum PlayerStartingData {
START_1("#DF740C", 10, 10, DIRECTION.RIGHT),
START_2("#FF0000", 10, GameBoard.BOARD_SIZE - 10, DIRECTION.UP),
START_3("#6FC3DF", GameBoard.BOARD_SIZE - 10, 10, DIRECTION.DOWN),
START_4("#FFE64D", GameBoard.BOARD_SIZE - 10, GameBoard.BOARD_SIZE - 10, DIRECTION.LEFT);
public final String color;
public final int x;
public final int y;
public final DIRECTION dir;
PlayerStartingData(String color, int x, int y, DIRECTION dir) {
this.color = color;
this.x = x;
this.y = y;
this.dir = dir;
}
}
public static final int MAX_PLAYERS = PlayerStartingData.values().length;
private static final PlayerStartingData[] startingData = new PlayerStartingData[] { PlayerStartingData.START_1, PlayerStartingData.START_2,
PlayerStartingData.START_3, PlayerStartingData.START_4 };
// Properties exposed by JSON-B
public final String name;
public final String id;
public final String color;
public DIRECTION direction = DIRECTION.RIGHT;
private DIRECTION lastDirection = null;
private DIRECTION desiredNextDirection = null;
public int x;
public int y;
public int playerNum;
public String playerName;
public boolean isAlive = true;
private STATUS playerStatus = STATUS.Connected;
public Player(String color) {
this.color = color;
}
private final int playerNum;
private DIRECTION direction;
private DIRECTION lastDirection = null;
private DIRECTION desiredNextDirection = null;
public Player(String color, int xstart, int ystart, int playerNum) {
this.color = color;
x = xstart;
y = ystart;
public Player(String id, String name, int playerNum) {
this.id = id;
this.name = name;
this.playerNum = playerNum;
// Initialize starting data
PlayerStartingData data = startingData[playerNum];
color = data.color;
direction = data.dir;
x = data.x;
y = data.y;
}
public String toJson() {
// TODO: Use JSON-B to eliminate the need for this method
// {"color":"#FF0000","coords":{"x":251,"y":89}}
StringBuffer sb = new StringBuffer("{\"color\":\"");
sb.append(this.color);
@ -65,10 +98,6 @@ public class Player {
direction = newDirection;
}
public DIRECTION getDirection() {
return direction;
}
/**
* Move a player forward one space in whatever direction they are facing currently.
*
@ -128,6 +157,7 @@ public class Player {
return this.playerStatus;
}
@JsonbTransient
public int getPlayerNum() {
return this.playerNum;
}

View File

@ -1,38 +0,0 @@
/**
*
*/
package org.libertybikes.game.core;
public class PlayerFactory {
public static int MAX_PLAYERS = PlayerData.values().length;
private static enum PlayerData {
START_1("#DF740C", 10, 10, DIRECTION.RIGHT),
START_2("#FF0000", 10, GameBoard.BOARD_SIZE - 10, DIRECTION.UP),
START_3("#6FC3DF", GameBoard.BOARD_SIZE - 10, 10, DIRECTION.DOWN),
START_4("#FFE64D", GameBoard.BOARD_SIZE - 10, GameBoard.BOARD_SIZE - 10, DIRECTION.LEFT);
public final String color;
public final int x;
public final int y;
public final DIRECTION dir;
PlayerData(String color, int x, int y, DIRECTION dir) {
this.color = color;
this.x = x;
this.y = y;
this.dir = dir;
}
}
private static final PlayerData[] startingData = new PlayerData[] { PlayerData.START_1, PlayerData.START_2, PlayerData.START_3, PlayerData.START_4 };
public static Player initNextPlayer(GameRound g, String name, int playerNum) {
PlayerData data = startingData[playerNum];
Player p = new Player(data.color, data.x, data.y, playerNum);
p.direction = data.dir;
p.playerName = name;
return p;
}
}

View File

@ -18,9 +18,11 @@ import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.libertybikes.game.core.ClientMessage;
import org.libertybikes.game.core.ClientMessage.GameEvent;
import org.libertybikes.game.core.GameRound;
import org.libertybikes.restclient.PlayerService;
@Dependent
@ServerEndpoint("/round/ws/{roundId}")
@ -29,6 +31,10 @@ public class GameRoundWebsocket {
@Inject
GameRoundService gameSvc;
@Inject
@RestClient
PlayerService playerSvc;
private final Jsonb jsonb = JsonbBuilder.create();
@OnOpen
@ -59,7 +65,17 @@ public class GameRoundWebsocket {
if (msg.event != null && GameEvent.GAME_REQUEUE == msg.event) {
requeueClient(gameSvc, round, session);
} else {
round.handleMessage(msg, session);
if (GameEvent.GAME_START == msg.event)
round.startGame();
if (msg.direction != null)
round.updatePlayerDirection(session, msg);
if (msg.playerJoinedId != null) {
org.libertybikes.restclient.Player playerResponse = playerSvc.getPlayerById(msg.playerJoinedId);
round.addPlayer(session, msg.playerJoinedId, playerResponse.name);
}
if (Boolean.TRUE == msg.isSpectator) {
round.addSpectator(session);
}
}
} catch (Exception e) {
e.printStackTrace();

View File

@ -0,0 +1,9 @@
package org.libertybikes.restclient;
public class Player {
public String id;
public String name;
}

View File

@ -0,0 +1,32 @@
package org.libertybikes.restclient;
import javax.enterprise.context.Dependent;
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;
import javax.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Dependent
@RegisterRestClient
@Path("/player")
public interface PlayerService {
@GET
@Path("/{playerId}")
@Produces(MediaType.APPLICATION_JSON)
public Player getPlayerById(@PathParam("playerId") String id);
@POST
@Path("/{playerId}/win")
public Response addWin(@PathParam("playerId") String id);
@POST
@Path("/{playerId}/loss")
public Response addLoss(@PathParam("playerId") String id);
}

View File

@ -1,6 +1,7 @@
<server>
<featureManager>
<feature>microProfile-1.3</feature>
<feature>microProfile-1.2</feature>
<feature>mpRestClient-1.0</feature>
<feature>websocket-1.1</feature>
<feature>jaxrs-2.1</feature>
<feature>jsonb-1.0</feature>

View File

@ -58,15 +58,19 @@ public class JsonDataTest {
GameBoard board = new GameBoard();
assertEquals("{\"movingObstacles\":[],\"obstacles\":[],\"players\":[]}", jsonb.toJson(board));
String obstacleJson = "{\"height\":2,\"width\":1,\"x\":3,\"y\":4}";
board.addObstacle(new Obstacle(1, 2, 3, 4));
assertEquals("{\"movingObstacles\":[],\"obstacles\":[{\"height\":2,\"width\":1,\"x\":3,\"y\":4}],\"players\":[]}", jsonb.toJson(board));
assertEquals("{\"movingObstacles\":[],\"obstacles\":[" + obstacleJson + "],\"players\":[]}", jsonb.toJson(board));
board.addPlayer(new Player("#1234", 10, 11, 0));
assertEquals("{\"movingObstacles\":[],\"obstacles\":[{\"height\":2,\"width\":1,\"x\":3,\"y\":4}],\"players\":[{\"color\":\"#1234\",\"direction\":\"RIGHT\",\"isAlive\":true,\"playerNum\":0,\"status\":\"Connected\",\"x\":10,\"y\":11}]}",
// @JsonbPropertyOrder({ "id", "name", "color", "x", "y", "isAlive" })
String bobJson = "{\"id\":\"1234\",\"name\":\"Bob\",\"color\":\"#DF740C\",\"status\":\"Connected\",\"x\":10,\"y\":10,\"isAlive\":true}";
board.addPlayer(new Player("1234", "Bob", 0));
assertEquals("{\"movingObstacles\":[],\"obstacles\":[" + obstacleJson + "],\"players\":[" + bobJson + "]}",
jsonb.toJson(board));
board.addObstacle(new MovingObstacle(11,12,13,14,-1,2,50));
assertEquals("{\"movingObstacles\":[{\"height\":12,\"width\":11,\"x\":13,\"y\":14,\"currentDelay\":0,\"hasMoved\":false,\"moveDelay\":50,\"oldX\":0,\"oldY\":0,\"xDir\":-1,\"yDir\":2}],\"obstacles\":[{\"height\":2,\"width\":1,\"x\":3,\"y\":4}],\"players\":[{\"color\":\"#1234\",\"direction\":\"RIGHT\",\"isAlive\":true,\"playerNum\":0,\"status\":\"Connected\",\"x\":10,\"y\":11}]}",
board.addObstacle(new MovingObstacle(11, 12, 13, 14, -1, 2, 50));
assertEquals("{\"movingObstacles\":[{\"height\":12,\"width\":11,\"x\":13,\"y\":14,\"currentDelay\":0,\"hasMoved\":false,\"moveDelay\":50,\"oldX\":0,\"oldY\":0,\"xDir\":-1,\"yDir\":2}],\"obstacles\":["
+ obstacleJson + "],\"players\":[" + bobJson + "]}",
jsonb.toJson(board));
}
@ -78,6 +82,14 @@ public class JsonDataTest {
assertContains("nextRoundId\":\"", jsonb.toJson(round));
}
@Test
public void testBindPlayer() {
String playerSvcResponse = "{\"id\":\"112233\",\"name\":\"andy\",\"stats\":{\"totalGames\":0,\"numWins\":0}}";
org.libertybikes.restclient.Player p = jsonb.fromJson(playerSvcResponse, org.libertybikes.restclient.Player.class);
assertEquals("112233", p.id);
assertEquals("andy", p.name);
}
private void assertContains(String expected, String search) {
assertTrue("Did not find '" + expected + "' inside of the string: " + search, search.contains(expected));
}

View File

@ -45,7 +45,6 @@ public class PlayerService {
return db.getAll();
}
@GET
@POST
@Path("/create")
public String createPlayer(@QueryParam("name") String name) {
@ -61,7 +60,7 @@ public class PlayerService {
return db.get(id);
}
@GET
@POST
@Path("/{playerId}/win")
public Response addWin(@PathParam("playerId") String id) {
Player p = getPlayerById(id);
@ -74,7 +73,7 @@ public class PlayerService {
return Response.ok(wins).build();
}
@GET
@POST
@Path("/{playerId}/loss")
public Response addLoss(@PathParam("playerId") String id) {
Player p = getPlayerById(id);

View File

@ -1,6 +1,6 @@
<server>
<featureManager>
<feature>microProfile-1.3</feature>
<feature>microProfile-1.2</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-ext
xmlns="http://websphere.ibm.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-web-ext_1_1.xsd"
version="1.1">
<reload-interval value="3"/>
<context-root uri="/" />
<enable-directory-browsing value="false"/>
<enable-file-serving value="true"/>
<enable-reloading value="true"/>
<enable-serving-servlets-by-class-name value="false" />
</web-ext>