Transmit player locations more efficiently

Instead of sending each individual player location update to each client on every game tick, instead send all player locations as an array in a single message.  This cuts down the amount of messages per game tick from 16messages to 4 (when 4 players are involved).

Also, fix a bug that would allow players to move backwards on themselves if they change direction more than once per game tick.
This commit is contained in:
Andrew Guibert 2018-02-09 10:53:44 -06:00
parent 93082f3d59
commit e5177c652f
5 changed files with 84 additions and 55 deletions

View File

@ -52,9 +52,7 @@ export class GameWebsocket {
location.reload();
}
if (json.playerlocs) {
console.log(`Drawing player locations...`);
for (let playerLoc of json.playerlocs) {
console.log(`Got playerloc ${playerLoc}`);
this.whiteboard.drawSquare(playerLoc);
}
}

View File

@ -3,6 +3,8 @@
*/
package org.libertybikes.game.core;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.annotation.JsonbProperty;
/**
@ -11,6 +13,8 @@ import javax.json.bind.annotation.JsonbProperty;
*/
public class ClientMessage {
private static final Jsonb jsonb = JsonbBuilder.create();
public static enum GameEvent {
GAME_START,
GAME_PAUSE,
@ -27,7 +31,7 @@ public class ClientMessage {
@Override
public String toString() {
return "{ direction=" + direction + ", playerjoined=" + playerJoinedId + ", event=" + event + " }";
return jsonb.toJson(this);
}
}

View File

@ -21,21 +21,20 @@ public class GameRound implements Runnable {
OPEN, FULL, RUNNING, FINISHED
}
public static final int GAME_TICK_SPEED = 50;
public static final int GAME_SIZE = 600;
public static final int GAME_SPEED = 50;
private static final Random r = new Random();
public final String id;
public final String nextRoundId;
public Set<Player> players = new HashSet<Player>();
public State state = State.OPEN;
boolean[][] board = new boolean[121][121];
AtomicBoolean gameRunning = new AtomicBoolean(false);
AtomicBoolean paused = new AtomicBoolean(false);
private static final Random r = new Random();
private boolean[][] board = new boolean[121][121];
private AtomicBoolean gameRunning = new AtomicBoolean(false);
private AtomicBoolean paused = new AtomicBoolean(false);
// Get a string of 6 random uppercase letters (A-Z)
private static String getRandomId() {
@ -57,45 +56,55 @@ public class GameRound implements Runnable {
public void addPlayer(Player p) {
players.add(p);
System.out.println("Player " + players.size() + " has joined.");
for (Player cur : players)
broadcastLocation(cur);
broadcastPlayerList(players);
broadcastPlayerLocations();
broadcastPlayerList();
}
public void removePlayer(Player p) {
p.disconnect();
System.out.println(p.playerName + " disconnected.");
broadcastPlayerList(players);
broadcastPlayerList();
}
@Override
public void run() {
for (int i = 0; i < GAME_SIZE / Player.PLAYER_SIZE + 1; i++) {
for (int i = 0; i < GAME_SIZE / Player.PLAYER_SIZE + 1; i++)
Arrays.fill(board[i], true);
}
gameRunning.set(true);
System.out.println("Starting round: " + id);
while (gameRunning.get()) {
while (!paused.get()) {
delay(GAME_SPEED);
for (Player p : players) {
if (p.isAlive) {
if (p.movePlayer()) {
broadcastLocation(p);
} else {
// Since someone died, check for winning player
checkForWinner(p);
broadcastPlayerList(players);
}
}
}
delay(GAME_TICK_SPEED);
gameTick();
}
delay(500); // don't thrash for pausing
delay(500); // don't thrash when game is paused
}
System.out.println("Finished round: " + id);
}
private void gameTick() {
// Move all living players forward 1
boolean playerStatusChange = false;
boolean playersMoved = false;
for (Player p : players) {
if (p.isAlive) {
if (p.movePlayer(board)) {
playersMoved = true;
} else {
// Since someone died, check for winning player
checkForWinner(p);
playerStatusChange = true;
}
}
}
if (playersMoved)
broadcastPlayerLocations();
if (playerStatusChange)
broadcastPlayerList();
}
private void delay(long ms) {
try {
Thread.sleep(ms);
@ -103,13 +112,16 @@ public class GameRound implements Runnable {
}
}
private void broadcastLocation(Player p) {
String json = p.toJson();
for (Player player : players)
player.sendTextToClient(json);
private void broadcastPlayerLocations() {
JsonArrayBuilder arr = Json.createArrayBuilder();
for (Player p : players)
arr.add(p.toJson());
String playerLocations = Json.createObjectBuilder().add("playerlocs", arr).build().toString();
for (Player client : players)
client.sendTextToClient(playerLocations);
}
private void broadcastPlayerList(Set<Player> players) {
private void broadcastPlayerList() {
JsonArrayBuilder array = Json.createArrayBuilder();
for (Player p : players) {
array.add(Json.createObjectBuilder()
@ -144,7 +156,7 @@ public class GameRound implements Runnable {
for (Player p : players)
if (STATUS.Connected == p.getStatus())
p.setStatus(STATUS.Alive);
broadcastPlayerList(players);
broadcastPlayerList();
if (!gameRunning.get()) {
try {
ExecutorService exec = InitialContext.doLookup("java:comp/DefaultManagedExecutorService");

View File

@ -28,24 +28,23 @@ public class Player {
}
public static final int PLAYER_SIZE = 5;
private final GameRound game;
private Session client;
public final String color;
public DIRECTION direction = DIRECTION.RIGHT;
private DIRECTION lastDirection = null;
public int x;
public int y;
public String playerName;
public boolean isAlive = true;
private STATUS playerStatus = STATUS.Connected;
public Player(GameRound g, Session client, String color) {
this.game = g;
public Player(Session client, String color) {
this.color = color;
this.client = client;
}
public Player(GameRound g, Session client, String color, int xstart, int ystart) {
this.game = g;
public Player(Session client, String color, int xstart, int ystart) {
this.color = color;
this.client = client;
x = xstart;
@ -53,8 +52,8 @@ public class Player {
}
public String toJson() {
// {"shape":"square","color":"#FF0000","coords":{"x":251,"y":89}}
StringBuffer sb = new StringBuffer("{\"shape\":\"square\",\"color\":\"");
// {"color":"#FF0000","coords":{"x":251,"y":89}}
StringBuffer sb = new StringBuffer("{\"color\":\"");
sb.append(this.color);
sb.append("\",\"coords\":{\"x\":");
sb.append(this.x);
@ -64,22 +63,35 @@ public class Player {
return sb.toString();
}
public void setDirection(DIRECTION dir) {
direction = dir;
public void setDirection(DIRECTION newDirection) {
// Make sure the player doesn't move backwards on themselves
if (lastDirection != null) {
if (newDirection == DIRECTION.UP && lastDirection == DIRECTION.DOWN)
return;
else if (newDirection == DIRECTION.DOWN && lastDirection == DIRECTION.UP)
return;
else if (newDirection == DIRECTION.LEFT && lastDirection == DIRECTION.RIGHT)
return;
else if (newDirection == DIRECTION.RIGHT && lastDirection == DIRECTION.LEFT)
return;
}
direction = newDirection;
}
public DIRECTION getDrirection() {
return direction;
}
private boolean checkPosition(int x, int y) {
int realXPosition = x / PLAYER_SIZE;
int realYPosition = y / PLAYER_SIZE;
return game.board[realXPosition][realYPosition];
}
/**
* Move a player forward one space in whatever direction they are facing currently.
*
* @return True if the player is still alive after moving forward one space. False otherwise.
*/
public boolean movePlayer(boolean[][] board) {
// Consume the space the player was in before the move
board[x / PLAYER_SIZE][y / PLAYER_SIZE] = false;
public boolean movePlayer() {
game.board[x / PLAYER_SIZE][y / PLAYER_SIZE] = false;
switch (direction) {
case UP:
if (y - PLAYER_SIZE >= 0)
@ -98,11 +110,14 @@ public class Player {
x -= PLAYER_SIZE;
break;
}
boolean checkResult = checkPosition(x, y);
if (!checkResult) {
// Check if the player is now dead after moving
boolean spaceAvailable = board[x / PLAYER_SIZE][y / PLAYER_SIZE];
if (!spaceAvailable) {
setStatus(STATUS.Dead);
}
return checkResult;
lastDirection = direction;
return isAlive;
}
public void sendTextToClient(String message) {

View File

@ -38,7 +38,7 @@ public class PlayerFactory {
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);
Player p = new Player(client, data.color, data.x, data.y);
p.direction = data.dir;
p.playerName = name;
return p;