Divide game service into separate rounds

This commit is contained in:
Andrew Guibert 2018-01-28 21:05:52 -06:00
parent ef426eaca0
commit f90f0ff4ea
13 changed files with 314 additions and 152 deletions

View File

@ -27,11 +27,13 @@ subprojects {
}
dependencies {
compileOnly 'javax:javaee-api:7.0'
compileOnly group: 'org.eclipse.microprofile', name: 'microprofile', version: '1.2'
compileOnly group: 'javax', name: 'javaee-api', version: '8.0'
}
liberty {
install {
// use 1 liberty install for the whole repo
baseDir = rootProject.buildDir
runtimeUrl = "https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/downloads/wlp/beta/wlp-beta-2017.12.0.0.zip"
}

View File

@ -1,3 +1,7 @@
dependencies {
compileOnly group: 'javax.websocket', name: 'javax.websocket-api', version: '1.1'
}
ext {
httpPort = 8080
httpsPort = 8443

View File

@ -0,0 +1,24 @@
/**
*
*/
package org.libertybikes.game.core;
/**
* @author Andrew
*
*/
public class ClientMessage {
public static enum GameEvent {
GAME_START,
GAME_PAUSE,
GAME_REQUEUE
}
public Player.DIRECTION direction;
public String playerjoined;
public GameEvent event;
}

View File

@ -1,56 +1,61 @@
/**
*
*/
package org.libertybikes.game.core;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.libertybikes.game.core.Player.STATUS;
/**
* @author Andrew
*/
public class Game extends Thread
{
private static Game instance = new Game();
public class GameRound implements Runnable {
public static enum State {
OPEN,
FULL,
RUNNING,
FINISHED
}
public final String id;
public Set<Player> players = new HashSet<Player>();
public State state = State.OPEN;
public static final int GAME_SIZE = 600;
public static final int GAME_SPEED = 50;
public boolean[][] board = new boolean[121][121];
Set<Player> players = Collections.synchronizedSet(new HashSet<Player>());
boolean[][] board = new boolean[121][121];
AtomicBoolean gameRunning = new AtomicBoolean(false);
AtomicBoolean paused = new AtomicBoolean(false);
private Game() {}
public GameRound() {
id = UUID.randomUUID().toString();
}
public static Game getUnstartedGame() {
if (!instance.gameRunning.get() && instance.players.size() < PlayerFactory.MAX_PLAYERS)
return instance;
else {
instance = new Game();
return instance;
}
public GameRound(String id) {
this.id = id;
}
public void addPlayer(Player p) {
players.add(p);
System.out.println("Player " + players.size() + " has joined.");
for (Player cur : players)
broadcastLocation(this, cur);
broadcastPlayerList(this, players);
broadcastLocation(cur);
broadcastPlayerList(players);
}
public void removePlayer(Player p) {
p.disconnect();
System.out.println(p.playerName + " disconnected.");
broadcastPlayerList(this, players);
broadcastPlayerList(players);
}
@Override
@ -59,7 +64,7 @@ public class Game extends Thread
Arrays.fill(board[i], true);
}
gameRunning.set(true);
System.out.println("Game started");
System.out.println("Starting round: " + id);
while (gameRunning.get()) {
while (!paused.get()) {
@ -67,17 +72,18 @@ public class Game extends Thread
for (Player p : players) {
if (p.isAlive) {
if (p.movePlayer()) {
broadcastLocation(this, p);
broadcastLocation(p);
} else {
// Since someone died, check for winning player
checkForWinner(p);
broadcastPlayerList(this, players);
broadcastPlayerList(players);
}
}
}
}
delay(500); // don't thrash for pausing
}
System.out.println("Finished round: " + id);
}
private void delay(long ms) {
@ -87,13 +93,13 @@ public class Game extends Thread
}
}
private void broadcastLocation(Game g, Player p) {
private void broadcastLocation(Player p) {
String json = p.toJson();
for (Player player : g.players)
for (Player player : players)
player.sendTextToClient(json);
}
private void broadcastPlayerList(Game g, Set<Player> players) {
private void broadcastPlayerList(Set<Player> players) {
JsonArrayBuilder array = Json.createArrayBuilder();
for (Player p : players) {
array.add(Json.createObjectBuilder()
@ -104,7 +110,7 @@ public class Game extends Thread
JsonObject obj = Json.createObjectBuilder().add("playerlist", array).build();
System.out.println("Playerlist: " + obj.toString());
for (Player player : g.players)
for (Player player : players)
player.sendTextToClient(obj.toString());
}
@ -128,9 +134,16 @@ public class Game extends Thread
for (Player p : players)
if (STATUS.Connected == p.getStatus())
p.setStatus(STATUS.Alive);
broadcastPlayerList(this, players);
if (!gameRunning.get())
this.start();
broadcastPlayerList(players);
if (!gameRunning.get()) {
try {
ExecutorService exec = InitialContext.doLookup("java:comp/DefaultManagedExecutorService");
exec.submit(this);
} catch (NamingException e) {
System.out.println("Unable to start game due to: " + e);
e.printStackTrace();
}
}
}
public void pause() {
@ -140,4 +153,5 @@ public class Game extends Thread
public void stopGame() {
gameRunning.set(false);
}
}

View File

@ -28,8 +28,8 @@ public class Player {
}
public static final int PLAYER_SIZE = 5;
public final Game game;
public Session client;
private final GameRound game;
private Session client;
public final String color;
public DIRECTION direction = DIRECTION.RIGHT;
public int x;
@ -38,13 +38,13 @@ public class Player {
public boolean isAlive = true;
private STATUS playerStatus = STATUS.Connected;
public Player(Game g, Session client, String color) {
public Player(GameRound g, Session client, String color) {
this.game = g;
this.color = color;
this.client = client;
}
public Player(Game g, Session client, String color, int xstart, int ystart) {
public Player(GameRound g, Session client, String color, int xstart, int ystart) {
this.game = g;
this.color = color;
this.client = client;
@ -52,33 +52,6 @@ public class Player {
y = ystart;
}
public boolean movePlayer() {
game.board[x / PLAYER_SIZE][y / PLAYER_SIZE] = false;
switch (direction) {
case UP:
if (y - PLAYER_SIZE >= 0)
y -= PLAYER_SIZE;
break;
case DOWN:
if (y + PLAYER_SIZE < Game.GAME_SIZE)
y += PLAYER_SIZE;
break;
case RIGHT:
if (x + PLAYER_SIZE < Game.GAME_SIZE)
x += PLAYER_SIZE;
break;
case LEFT:
if (x - PLAYER_SIZE >= 0)
x -= PLAYER_SIZE;
break;
}
boolean checkResult = checkPosition(x, y);
if (!checkResult) {
setStatus(STATUS.Dead);
}
return checkResult;
}
public String toJson() {
// {"shape":"square","color":"#FF0000","coords":{"x":251,"y":89}}
StringBuffer sb = new StringBuffer("{\"shape\":\"square\",\"color\":\"");
@ -105,6 +78,33 @@ public class Player {
return game.board[realXPosition][realYPosition];
}
public boolean movePlayer() {
game.board[x / PLAYER_SIZE][y / PLAYER_SIZE] = false;
switch (direction) {
case UP:
if (y - PLAYER_SIZE >= 0)
y -= PLAYER_SIZE;
break;
case DOWN:
if (y + PLAYER_SIZE < GameRound.GAME_SIZE)
y += PLAYER_SIZE;
break;
case RIGHT:
if (x + PLAYER_SIZE < GameRound.GAME_SIZE)
x += PLAYER_SIZE;
break;
case LEFT:
if (x - PLAYER_SIZE >= 0)
x -= PLAYER_SIZE;
break;
}
boolean checkResult = checkPosition(x, y);
if (!checkResult) {
setStatus(STATUS.Dead);
}
return checkResult;
}
public void sendTextToClient(String message) {
if (client != null) {
try {

View File

@ -9,7 +9,7 @@ import org.libertybikes.game.core.Player.DIRECTION;
/**
* @author Andrew
*
*
*/
public class PlayerFactory {
@ -17,9 +17,9 @@ public class PlayerFactory {
private static enum PlayerData {
START_1("#DF740C", 50, 50, DIRECTION.RIGHT),
START_2("#FF0000", 50, Game.GAME_SIZE - 50, DIRECTION.UP),
START_3("#6FC3DF", Game.GAME_SIZE - 50, 50, DIRECTION.DOWN),
START_4("#FFE64D", Game.GAME_SIZE - 50, Game.GAME_SIZE - 50, DIRECTION.LEFT);
START_2("#FF0000", 50, GameRound.GAME_SIZE - 50, DIRECTION.UP),
START_3("#6FC3DF", GameRound.GAME_SIZE - 50, 50, DIRECTION.DOWN),
START_4("#FFE64D", GameRound.GAME_SIZE - 50, GameRound.GAME_SIZE - 50, DIRECTION.LEFT);
public final String color;
public final int x;
@ -36,7 +36,7 @@ public class PlayerFactory {
private static final PlayerData[] startingData = new PlayerData[] { PlayerData.START_1, PlayerData.START_2, PlayerData.START_3, PlayerData.START_4 };
public static Player initNextPlayer(Game g, Session client, String name) {
public static Player initNextPlayer(GameRound g, Session client, String name) {
PlayerData data = startingData[g.players.size()];
Player p = new Player(g, client, data.color, data.x, data.y);
p.direction = data.dir;

View File

@ -1,77 +0,0 @@
package org.libertybikes.game.core;
import java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.json.Json;
import javax.json.JsonObject;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/websocket")
public class WebsocketHandler {
private static final ConcurrentMap<Session, Player> clients = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {}
@OnClose
public void onClose(Session peer) {
Game game = clients.get(peer).game;
game.removePlayer(clients.get(peer));
clients.remove(peer);
}
@OnMessage
public void onMessage(String message, Session session) throws IOException, EncodeException {
System.out.println("got message: " + message);
JsonObject json = Json.createReader(new StringReader(message)).readObject();
if (json.containsKey("message")) {
if (json.getString("message").equals("GAME_START")) {
clients.get(session).game.startGame();
}
if (json.getString("message").equals("GAME_PAUSE")) {
clients.get(session).game.pause();
System.out.println("Stopped the game");
}
if (json.getString("message").equals("GAME_REQUEUE")) {
Player p = clients.get(session);
Game.getUnstartedGame();
JsonObject obj = Json.createObjectBuilder().add("requeue", "requeue").build();
p.sendTextToClient(obj.toString());
}
}
else if (json.containsKey("direction")) {
Player p = clients.get(session);
Player.DIRECTION curDir = p.getDrirection();
String noGo;
if (curDir == Player.DIRECTION.UP) {
noGo = "down";
} else if (curDir == Player.DIRECTION.RIGHT) {
noGo = "left";
} else if (curDir == Player.DIRECTION.DOWN) {
noGo = "up";
} else {
noGo = "right";
}
if (!noGo.equalsIgnoreCase(json.getString("direction"))) {
p.setDirection(json.getString("direction"));
}
}
else if (json.containsKey("playerjoined")) {
Game game = Game.getUnstartedGame();
Player p = PlayerFactory.initNextPlayer(game, session, json.getString("playerjoined"));
clients.put(session, p);
game.addPlayer(p);
}
}
}

View File

@ -0,0 +1,9 @@
package org.libertybikes.game.round.service;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/round")
public class GameRoundApp extends Application {
}

View File

@ -0,0 +1,63 @@
package org.libertybikes.game.round.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
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.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.libertybikes.game.core.GameRound;
@Path("/")
@ApplicationScoped
public class GameRoundService {
@Context
UriInfo uri;
Map<String, GameRound> allRounds = new HashMap<>();
@GET
@Produces(MediaType.APPLICATION_JSON)
public Collection<GameRound> listAllGames() {
return allRounds.values();
}
@POST
@Path("/create")
public String createRound() {
GameRound p = new GameRound();
allRounds.put(p.id, p);
System.out.println("Created round id=" + p.id);
return p.id;
}
@GET
@Path("/{roundId}")
@Produces(MediaType.APPLICATION_JSON)
public GameRound getRound(@PathParam("roundId") String roundId) {
GameRound r = allRounds.get(roundId);
if (r == null)
return null;
return r;
}
// @GET
// @Path("/{roundId}/join")
// @Produces(MediaType.APPLICATION_JSON)
// public GameRound joinRound(@PathParam("roundId") String roundId, @QueryParam("playerId") String playerId) {
// GameRound r = allRounds.get(roundId);
// if (r == null)
// return null;
// r.players.add(playerId);
// return r;
// }
}

View File

@ -0,0 +1,102 @@
/**
*
*/
package org.libertybikes.game.round.service;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.libertybikes.game.core.ClientMessage;
import org.libertybikes.game.core.GameRound;
import org.libertybikes.game.core.Player;
import org.libertybikes.game.core.PlayerFactory;
@Dependent
@ServerEndpoint("/round/ws/{roundId}") //
public class GameRoundWebsocket {
@Inject
GameRoundService gameSvc;
private final Jsonb jsonb = JsonbBuilder.create();
private static final Map<Session, Player> clients = new HashMap<>();
@OnOpen
public void onOpen(@PathParam("roundId") String roundId, Session session) {
System.out.println("@AGG opened a session for game: " + roundId + " using ws=" + this.toString());
}
@OnClose
public void onClose(@PathParam("roundId") String roundId, Session peer) {
System.out.println("@AGG closed a session for game: " + roundId);
GameRound game = gameSvc.getRound(roundId);
game.removePlayer(clients.get(peer));
clients.remove(peer);
}
@OnMessage
public void processMsg(@PathParam("roundId") final String roundId, String message, Session session) {
System.out.println("roundId=" + roundId + " msg=" + message);
JsonObject json = Json.createReader(new StringReader(message)).readObject();
ClientMessage msg = jsonb.fromJson(message, ClientMessage.class);
System.out.println("@AGG got jsonb msg: " + msg);
final GameRound round = gameSvc.getRound(roundId);
if (json.containsKey("message")) {
if (json.getString("message").equals("GAME_START")) {
round.startGame();
}
if (json.getString("message").equals("GAME_PAUSE")) {
round.pause();
System.out.println("Stopped the game");
}
if (json.getString("message").equals("GAME_REQUEUE")) {
Player p = clients.get(session);
// TODO Game.getUnstartedGame();
JsonObject obj = Json.createObjectBuilder().add("requeue", "requeue").build();
p.sendTextToClient(obj.toString());
System.out.println("@AGG TODO: requeue not implemented");
throw new RuntimeException("Not yet implemented");
}
} else if (json.containsKey("direction")) {
System.out.println("@AGG got direction: " + json.getString("direction"));
// TODO movement not working
Player p = clients.get(session);
Player.DIRECTION curDir = p.getDrirection();
String noGo;
if (curDir == Player.DIRECTION.UP) {
noGo = "down";
} else if (curDir == Player.DIRECTION.RIGHT) {
noGo = "left";
} else if (curDir == Player.DIRECTION.DOWN) {
noGo = "up";
} else {
noGo = "right";
}
if (!noGo.equalsIgnoreCase(json.getString("direction"))) {
p.setDirection(json.getString("direction"));
}
} else if (json.containsKey("playerjoined")) {
Player p = PlayerFactory.initNextPlayer(round, session, json.getString("playerjoined"));
clients.put(session, p);
round.addPlayer(p);
}
}
}

View File

@ -1,9 +1,19 @@
<server>
<featureManager>
<feature>webProfile-7.0</feature>
<feature>microProfile-1.2</feature>
<feature>websocket-1.1</feature>
<feature>apiDiscovery-1.0</feature>
<feature>servlet-4.0</feature>
<feature>jaxrs-2.1</feature>
<feature>jsonb-1.0</feature>
<feature>jsp-2.3</feature>
<feature>concurrent-1.0</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint" httpPort="${httpPort}" httpsPort="${httpsPort}" />
<applicationManager autoExpand="true"/>
<keyStore id="defaultKeyStore" password="Liberty"/>
<quickStartSecurity userName="admin" userPassword="admin"/>
</server>

View File

@ -13,13 +13,22 @@
<div class="col-md-offset-1">
Enter Username:
<input type="text" id="username" name="username"></input>
<input type="submit" class="btn btn-success" value="Login" onclick="storeName()">
Enter Round ID:
<input type="text" id="roundId" name="roundId"></input>
<input type="submit" class="btn btn-success" value="Login" onclick="joinRound()">
<input type="submit" class="btn btn-success" value="Create Round" onclick="createRound()">
</div>
</body>
<script type="text/javascript">
function storeName(){
function createRound(){
$.post("round/create", function(data) {
alert("Response is: " + data);
})
}
function joinRound(){
localStorage.setItem("username", $("#username").val());
location.href = "game.jsp";
localStorage.setItem("roundId", $("#roundId").val());
location.href = "game.jsp";
}
</script>
</html>

View File

@ -1,4 +1,6 @@
var wsUri = "ws://" + document.location.hostname + ":" + document.location.port + "/websocket";
//var wsUri = "ws://" + document.location.hostname + ":" + document.location.port + "/websocket";
var roundId = localStorage.getItem("roundId");
var wsUri = "ws://" + document.location.hostname + ":" + document.location.port + "/round/ws/" + roundId;
var websocket = new WebSocket(wsUri);
websocket.binaryType = "arraybuffer";
var output = document.getElementById("output");