Implement non-moving obstacles

This commit is contained in:
Andrew Guibert 2018-02-14 11:35:59 -06:00
parent 412289695a
commit 13e01f091d
11 changed files with 319 additions and 59 deletions

View File

@ -27,8 +27,11 @@ subprojects {
}
dependencies {
compileOnly group: 'org.eclipse.microprofile', name: 'microprofile', version: '1.2'
compileOnly group: 'javax', name: 'javaee-api', version: '8.0'
providedCompile group: 'org.eclipse.microprofile', name: 'microprofile', version: '1.2'
providedCompile group: 'javax', name: 'javaee-api', version: '8.0'
testCompile group: 'junit', name: 'junit', version: '4.+'
testCompile group: 'org.eclipse', name: 'yasson', version: '1.0'
testCompile group: 'org.glassfish', name: 'javax.json', version: '1.1.+'
}
liberty {

View File

@ -47,9 +47,14 @@ export class GameWebsocket {
sessionStorage.setItem('roundId', this.roundId);
location.reload();
}
if (json.playerlocs) {
for (let playerLoc of json.playerlocs) {
this.whiteboard.drawSquare(playerLoc);
if (json.obstacles) {
for (let obstacle of json.obstacles) {
this.whiteboard.drawObstacle(obstacle);
}
}
if (json.players) {
for (let player of json.players) {
this.whiteboard.drawPlayer(player);
}
}
}

View File

@ -2,7 +2,7 @@ import * as $ from 'jquery';
import { GameWebsocket } from './websocket';
export class Whiteboard {
static readonly PLAYER_SIZE = 5;
static readonly BOX_SIZE = 5;
canvas: any;
context: any;
gamesocket: GameWebsocket;
@ -27,11 +27,16 @@ export class Whiteboard {
};
}
drawSquare(data) {
const json = JSON.parse(data);
this.context.fillStyle = json.color;
this.context.fillRect(Whiteboard.PLAYER_SIZE * json.coords.x, Whiteboard.PLAYER_SIZE * json.coords.y,
Whiteboard.PLAYER_SIZE, Whiteboard.PLAYER_SIZE);
drawPlayer(player) {
this.context.fillStyle = player.color;
this.context.fillRect(Whiteboard.BOX_SIZE * player.x, Whiteboard.BOX_SIZE * player.y,
Whiteboard.BOX_SIZE, Whiteboard.BOX_SIZE);
}
drawObstacle(obstacle) {
this.context.fillStyle = '#808080'; // obstacles always grey
this.context.fillRect(Whiteboard.BOX_SIZE * obstacle.x, Whiteboard.BOX_SIZE * obstacle.y,
Whiteboard.BOX_SIZE * obstacle.height, Whiteboard.BOX_SIZE * obstacle.width);
}
updatePlayerList(json) {

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="src" path="src/test/java" output="build/classes/java/test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer">

View File

@ -0,0 +1,70 @@
/**
*
*/
package org.libertybikes.game.core;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class GameBoard {
public static final int BOARD_SIZE = 121;
public static final boolean SPOT_AVAILABLE = true, SPOT_TAKEN = false;
// @JsonbTransient // TODO use annotation here once OpenLiberty upgrades to yasson 1.0.1 (contains bug fix)
private final boolean[][] board = new boolean[BOARD_SIZE][BOARD_SIZE];
public final Set<Obstacle> obstacles = new HashSet<>();
public final Set<Player> players = new HashSet<>();
public GameBoard() {
for (int i = 0; i < BOARD_SIZE; i++)
Arrays.fill(board[i], SPOT_AVAILABLE);
}
public boolean addObstacle(Obstacle o) {
if (o.x + o.width > BOARD_SIZE || o.y + o.height > BOARD_SIZE)
throw new IllegalArgumentException("Obstacle does not fit on board: " + o);
// First make sure all spaces are available
for (int x = 0; x < o.width; x++)
for (int y = 0; y < o.height; y++)
if (!board[o.x + x][o.y + y]) {
System.out.println("Obstacle cannot be added to board because spot [" + o.x + x + "][" + o.y + y + "] is taken.");
return false;
}
// If all spaces are available, claim them
for (int x = 0; x < o.width; x++)
for (int y = 0; y < o.height; y++)
board[o.x + x][o.y + y] = SPOT_TAKEN;
return obstacles.add(o);
}
public boolean addPlayer(Player p) {
if (p.x > BOARD_SIZE || p.y > BOARD_SIZE)
throw new IllegalArgumentException("Player does not fit on board: " + p);
board[p.x][p.y] = SPOT_TAKEN;
return players.add(p);
}
// TODO: once OpenLiberty moves up to yasson 1.0.1 this method can be removed
public boolean[][] board() {
return board;
}
// For debugging
public void dumpBoard() {
for (int i = 0; i < BOARD_SIZE; i++) {
StringBuilder row = new StringBuilder();
for (int j = 0; j < BOARD_SIZE; j++)
row.append(board[i][j] == SPOT_TAKEN ? "X" : "_");
System.out.println(String.format("%03d %s", i, row.toString()));
}
}
}

View File

@ -3,7 +3,6 @@ package org.libertybikes.game.core;
import static org.libertybikes.game.round.service.GameRoundWebsocket.sendTextToClient;
import static org.libertybikes.game.round.service.GameRoundWebsocket.sendTextToClients;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@ -16,6 +15,9 @@ import javax.enterprise.concurrent.ManagedScheduledExecutorService;
import javax.enterprise.inject.spi.CDI;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.annotation.JsonbPropertyOrder;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.websocket.Session;
@ -25,31 +27,30 @@ import org.libertybikes.game.core.Player.STATUS;
import org.libertybikes.game.round.service.GameRoundService;
import org.libertybikes.game.round.service.GameRoundWebsocket;
@JsonbPropertyOrder({ "id", "gameState", "board", "nextRoundId" })
public class GameRound implements Runnable {
public static enum State {
OPEN, FULL, RUNNING, FINISHED
}
public static final Jsonb jsonb = JsonbBuilder.create();
public static final int GAME_TICK_SPEED = 50; // ms
public static final int BOARD_SIZE = 121;
private static final Random r = new Random();
private static final AtomicInteger runningGames = new AtomicInteger();
// Properties exposed in JSON representation of object
public final String id;
public final String nextRoundId;
private final Map<Session, Client> clients = new HashMap<>();
public State gameState = State.OPEN;
private final GameBoard board = new GameBoard();
private boolean[][] board = new boolean[BOARD_SIZE][BOARD_SIZE];
private AtomicBoolean gameRunning = new AtomicBoolean(false);
private AtomicBoolean paused = new AtomicBoolean(false);
private int ticksWithoutMoves = 0;
private int numOfPlayers = 0;
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 boolean[] takenPlayerSlots = new boolean[PlayerFactory.MAX_PLAYERS];
private int ticksWithoutMovement = 0;
// Get a string of 6 random uppercase letters (A-Z)
private static String getRandomId() {
@ -66,6 +67,11 @@ public class GameRound implements Runnable {
public GameRound(String id) {
this.id = id;
nextRoundId = getRandomId();
board.addObstacle(new Obstacle(5, 5, 60, 60));
}
public GameBoard getBoard() {
return board;
}
public void handleMessage(ClientMessage msg, Session session) {
@ -91,10 +97,11 @@ public class GameRound implements Runnable {
// Front end should be preventing a player joining a full game but
// defensive programming
if (gameState != State.OPEN) {
System.out.println("Cannot add player " + playerId + " to game " + id + " because game has already started.");
return;
}
if (++numOfPlayers > PlayerFactory.MAX_PLAYERS - 1) {
if (board.players.size() + 1 > PlayerFactory.MAX_PLAYERS - 1) {
gameState = State.FULL;
}
@ -111,17 +118,18 @@ public class GameRound implements Runnable {
// Initialize Player
Player p = PlayerFactory.initNextPlayer(this, playerId, playerNum);
board.addPlayer(p);
clients.put(s, new Client(s, p));
System.out.println("Player " + playerId + " has joined.");
broadcastPlayerList();
broadcastPlayerLocations();
broadcastGameBoard();
}
public void addSpectator(Session s) {
System.out.println("A spectator has joined.");
clients.put(s, new Client(s));
sendTextToClient(s, getPlayerList());
sendTextToClient(s, getPlayerLocations());
sendTextToClient(s, jsonb.toJson(board));
}
private void removePlayer(Player p) {
@ -130,8 +138,8 @@ public class GameRound implements Runnable {
broadcastPlayerList();
// Open player slot for new joiners
if (--numOfPlayers < PlayerFactory.MAX_PLAYERS) {
gameState = (gameState == State.FULL) ? State.OPEN : gameState;
if (State.FULL == gameState && board.players.size() - 1 < PlayerFactory.MAX_PLAYERS) {
gameState = State.OPEN;
}
takenPlayerSlots[p.getPlayerNum()] = false;
}
@ -143,7 +151,8 @@ public class GameRound implements Runnable {
return clients.size();
}
public Set<Player> getPlayers() {
// @JsonbTransient // TODO re-enable this anno once Liberty upgrades to yasson 1.0.1
public Set<Player> players() {
return clients.values()
.stream()
.filter(c -> c.isPlayer())
@ -153,8 +162,6 @@ public class GameRound implements Runnable {
@Override
public void run() {
for (int i = 0; i < BOARD_SIZE; i++)
Arrays.fill(board[i], true);
gameRunning.set(true);
System.out.println("Starting round: " + id);
int numGames = runningGames.incrementAndGet();
@ -163,7 +170,7 @@ public class GameRound implements Runnable {
while (gameRunning.get()) {
delay(GAME_TICK_SPEED);
gameTick();
if (ticksWithoutMoves > 5)
if (ticksWithoutMovement > 5)
gameRunning.set(false); // end the game if nobody can move anymore
}
runningGames.decrementAndGet();
@ -181,9 +188,9 @@ public class GameRound implements Runnable {
// Move all living players forward 1
boolean playerStatusChange = false;
boolean playersMoved = false;
for (Player p : getPlayers()) {
for (Player p : players()) {
if (p.isAlive) {
if (p.movePlayer(board)) {
if (p.movePlayer(board.board())) {
playersMoved = true;
} else {
// Since someone died, check for winning player
@ -194,10 +201,10 @@ public class GameRound implements Runnable {
}
if (playersMoved) {
ticksWithoutMoves = 0;
broadcastPlayerLocations();
ticksWithoutMovement = 0;
broadcastGameBoard();
} else {
ticksWithoutMoves++;
ticksWithoutMovement++;
}
if (playerStatusChange)
@ -211,26 +218,21 @@ public class GameRound implements Runnable {
}
}
private String getPlayerLocations() {
JsonArrayBuilder arr = Json.createArrayBuilder();
for (Player p : getPlayers())
arr.add(p.toJson());
return Json.createObjectBuilder().add("playerlocs", arr).build().toString();
}
private String getPlayerList() {
// TODO: Use JSON-B instead of JSON-P here
JsonArrayBuilder array = Json.createArrayBuilder();
for (Player p : getPlayers()) {
array.add(Json.createObjectBuilder()
.add("name", p.playerName)
.add("status", p.getStatus().toString())
.add("color", p.color));
for (Player p : players()) {
if (p.isAlive)
array.add(Json.createObjectBuilder()
.add("name", p.playerName)
.add("status", p.getStatus().toString())
.add("color", p.color));
}
return Json.createObjectBuilder().add("playerlist", array).build().toString();
}
private void broadcastPlayerLocations() {
sendTextToClients(clients.keySet(), getPlayerLocations());
private void broadcastGameBoard() {
sendTextToClients(clients.keySet(), jsonb.toJson(board));
}
private void broadcastPlayerList() {
@ -238,11 +240,11 @@ public class GameRound implements Runnable {
}
private void checkForWinner(Player dead) {
if (getPlayers().size() < 2) // 1 player game, no winner
if (players().size() < 2) // 1 player game, no winner
return;
int alivePlayers = 0;
Player alive = null;
for (Player cur : getPlayers()) {
for (Player cur : players()) {
if (cur.isAlive) {
alivePlayers++;
alive = cur;
@ -256,7 +258,7 @@ public class GameRound implements Runnable {
public void startGame() {
paused.set(false);
for (Player p : getPlayers())
for (Player p : players())
if (STATUS.Connected == p.getStatus())
p.setStatus(STATUS.Alive);
broadcastPlayerList();

View File

@ -0,0 +1,26 @@
/**
*
*/
package org.libertybikes.game.core;
import javax.json.bind.annotation.JsonbCreator;
public class Obstacle {
public final int height;
public final int width;
public int x;
public int y;
@JsonbCreator
public Obstacle(int w, int h, int x, int y) {
this.height = h;
this.width = w;
this.x = x;
this.y = y;
}
}

View File

@ -72,7 +72,7 @@ public class Player {
direction = newDirection;
}
public DIRECTION getDrirection() {
public DIRECTION getDirection() {
return direction;
}
@ -98,11 +98,11 @@ public class Player {
y--;
break;
case DOWN:
if (y + 1 < GameRound.BOARD_SIZE)
if (y + 1 < GameBoard.BOARD_SIZE)
y++;
break;
case RIGHT:
if (x + 1 < GameRound.BOARD_SIZE)
if (x + 1 < GameBoard.BOARD_SIZE)
x++;
break;
case LEFT:

View File

@ -11,9 +11,9 @@ public class PlayerFactory {
private static enum PlayerData {
START_1("#DF740C", 10, 10, DIRECTION.RIGHT),
START_2("#FF0000", 10, GameRound.BOARD_SIZE - 10, DIRECTION.UP),
START_3("#6FC3DF", GameRound.BOARD_SIZE - 10, 10, DIRECTION.DOWN),
START_4("#FFE64D", GameRound.BOARD_SIZE - 10, GameRound.BOARD_SIZE - 10, DIRECTION.LEFT);
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;

View File

@ -0,0 +1,67 @@
/**
*
*/
package org.libertybikes.game.core;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.libertybikes.game.core.GameBoard.BOARD_SIZE;
import org.junit.Before;
import org.junit.Test;
public class GameBoardTest {
GameBoard board = null;
@Before
public void initBoard() {
board = new GameBoard();
}
@Test
public void testAddObstacle() {
assertTrue(board.addObstacle(new Obstacle(5, 10, 0, 0)));
verifyTaken(0, 0);
verifyTaken(4, 9);
}
@Test
public void testAdd2Obstacles() {
assertTrue(board.addObstacle(new Obstacle(5, 10, 0, 0)));
assertTrue(board.addObstacle(new Obstacle(25, 25, BOARD_SIZE - 25, BOARD_SIZE - 25)));
verifyTaken(0, 0);
verifyTaken(4, 9);
verifyTaken(BOARD_SIZE - 25, BOARD_SIZE - 25);
verifyTaken(BOARD_SIZE - 1, BOARD_SIZE - 1);
}
@Test
public void testOverlappingObstacles() {
assertTrue(board.addObstacle(new Obstacle(5, 10, 0, 0)));
assertFalse(board.addObstacle(new Obstacle(5, 10, 4, 9)));
verifyTaken(0, 0);
verifyTaken(4, 9);
verifyAvailable(5, 9);
verifyAvailable(4, 10);
verifyAvailable(5, 10);
}
private void verifyTaken(int x, int y) {
if (board.board()[x][y] == GameBoard.SPOT_AVAILABLE) {
board.dumpBoard();
fail("Spot should be taken but it was available: [" + x + "][" + y + "]");
}
}
private void verifyAvailable(int x, int y) {
if (board.board()[x][y] == GameBoard.SPOT_TAKEN) {
board.dumpBoard();
fail("Spot should be availble but it was taken: [" + x + "][" + y + "]");
}
}
}

View File

@ -0,0 +1,81 @@
/**
*
*/
package org.libertybikes.game.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import org.junit.Test;
import org.libertybikes.game.core.ClientMessage.GameEvent;
public class JsonDataTest {
private static final Jsonb jsonb = JsonbBuilder.create();
@Test
public void testPlayerJoined() {
ClientMessage playerJoined = new ClientMessage();
playerJoined.playerJoinedId = "1234";
assertEquals("{\"playerjoined\":\"1234\"}", jsonb.toJson(playerJoined));
}
@Test
public void testGameEventRequeue() {
ClientMessage gameEvent = new ClientMessage();
gameEvent.event = GameEvent.GAME_REQUEUE;
assertEquals("{\"message\":\"GAME_REQUEUE\"}", jsonb.toJson(gameEvent));
}
@Test
public void testGameEventStart() {
ClientMessage gameEvent = new ClientMessage();
gameEvent.event = GameEvent.GAME_START;
assertEquals("{\"message\":\"GAME_START\"}", jsonb.toJson(gameEvent));
}
@Test
public void testSpectatorJoined() {
ClientMessage msg = new ClientMessage();
msg.isSpectator = Boolean.FALSE;
assertEquals("{\"spectatorjoined\":false}", jsonb.toJson(msg));
msg.isSpectator = Boolean.TRUE;
assertEquals("{\"spectatorjoined\":true}", jsonb.toJson(msg));
}
@Test
public void testObstacle() {
Obstacle o = new Obstacle(1, 2, 3, 4);
assertEquals("{\"height\":2,\"width\":1,\"x\":3,\"y\":4}", jsonb.toJson(o));
}
@Test
public void testGameBoard() {
GameBoard board = new GameBoard();
assertEquals("{\"obstacles\":[],\"players\":[]}", jsonb.toJson(board));
board.addObstacle(new Obstacle(1, 2, 3, 4));
assertEquals("{\"obstacles\":[{\"height\":2,\"width\":1,\"x\":3,\"y\":4}],\"players\":[]}", jsonb.toJson(board));
board.addPlayer(new Player("#1234", 10, 11, 0));
assertEquals("{\"obstacles\":[{\"height\":2,\"width\":1,\"x\":3,\"y\":4}],\"players\":[{\"color\":\"#1234\",\"direction\":\"RIGHT\",\"isAlive\":true,\"playerNum\":0,\"status\":\"Connected\",\"x\":10,\"y\":11}]}",
jsonb.toJson(board));
}
@Test
public void testGameRound() {
GameRound round = new GameRound("ABCDEF");
System.out.println(jsonb.toJson(round));
assertContains("{\"id\":\"ABCDEF\",\"gameState\":\"OPEN\",\"board\":{", jsonb.toJson(round));
assertContains("nextRoundId\":\"", jsonb.toJson(round));
}
private void assertContains(String expected, String search) {
assertTrue("Did not find '" + expected + "' inside of the string: " + search, search.contains(expected));
}
}