Merge branch 'develop' into story61_player_perspective

# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/client/ClientPacketParser.java
#	src/main/java/seng302/controllers/Controller.java
#	src/main/java/seng302/controllers/RaceViewController.java
#	src/main/java/seng302/fxObjects/BoatAnnotations.java
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/model/Boat.java
#	src/main/java/seng302/models/stream/XMLParser.java
#	src/main/java/seng302/visualiser/ClientToServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java
#	src/main/java/seng302/visualiser/controllers/StartScreenController.java
#	src/main/java/seng302/visualiser/fxObjects/BoatObject.java
#	src/main/resources/views/LobbyView.fxml
#	src/main/resources/views/MainView.fxml
This commit is contained in:
Calum
2017-07-25 21:05:15 +12:00
48 changed files with 2952 additions and 268 deletions
+94 -11
View File
@@ -1,8 +1,12 @@
package seng302.gameServer;
import java.util.*;
import seng302.models.Player;
import seng302.model.Player;
import java.util.ArrayList;
import seng302.models.Yacht;
import seng302.server.messages.BoatActionType;
/**
* A Static class to hold information about the current state of the game (model)
@@ -10,23 +14,38 @@ import java.util.ArrayList;
*/
public class GameState {
private static Long previousUpdateTime;
public static Double windDirection;
private static Double windSpeed;
private static String hostIpAddress;
private static ArrayList<Player> players;
private static List<Player> players;
private static Map<Integer, Yacht> yachts;
private static Boolean isRaceStarted;
private static GameStages currentStage;
public GameState(String hostIpAddress) {
windDirection = 170d;
windSpeed = 10000d;
yachts = new HashMap<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>();
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
yachts = new HashMap<>();
//set this when game stage changes to prerace
previousUpdateTime = System.currentTimeMillis();
yachts = new HashMap<>();
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static ArrayList<Player> getPlayers() {
public static List<Player> getPlayers() {
return players;
}
@@ -38,6 +57,14 @@ public class GameState {
players.remove(player);
}
public static void addYacht(Integer sourceId, Yacht yacht) {
yachts.put(sourceId, yacht);
}
public static void removeYacht(Integer yachtId) {
yachts.remove(yachtId);
}
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
@@ -50,16 +77,72 @@ public class GameState {
GameState.currentStage = currentStage;
}
/**
* This iterates through all players and updates each players info to its new state based on its current data
*/
private void update(){
for(Player player : players) {
// TODO: 10/07/17 wmu16 - Update all player info
public static Double getWindDirection() {
return windDirection;
}
public static Double getWindSpeed() {
return windSpeed;
}
public static Map<Integer, Yacht> getYachts() {
return yachts;
}
public static void updateBoat(Integer sourceId, BoatActionType actionType) {
Yacht playerYacht = yachts.get(sourceId);
// System.out.println("-----------------------");
switch (actionType) {
case VMG:
// System.out.println("Snapping to VMG");
// TODO: 22/07/17 wmu16 - Add in the vmg calculation code here
break;
case SAILS_IN:
playerYacht.toggleSailIn();
// System.out.println("Toggling Sails");
break;
case SAILS_OUT:
playerYacht.toggleSailIn();
// System.out.println("Toggling Sails");
break;
case TACK_GYBE:
playerYacht.tackGybe(windDirection);
// System.out.println("Tack/Gybe");
break;
case UPWIND:
playerYacht.turnUpwind();
// System.out.println("Moving upwind");
break;
case DOWNWIND:
playerYacht.turnDownwind();
// System.out.println("Moving downwind");
break;
}
// System.out.println("-----------------------");
// System.out.println("Heading: " + playerYacht.getHeading());
// System.out.println("Sails are in: " + playerYacht.getSailIn());
// System.out.println("Lat: " + playerYacht.getLocation().getLat());
// System.out.println("Lng: " + playerYacht.getLocation().getLng());
// System.out.println("-----------------------\n");
}
public static void update() {
Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
previousUpdateTime = System.currentTimeMillis();
for (Yacht yacht : yachts.values()) {
yacht.update(timeInterval);
}
}
/**
* Generates a new ID based off the size of current players + 1
* @return a playerID to be allocated to a new connetion
*/
public static Integer getUniquePlayerID() {
// TODO: 22/07/17 wmu16 - This may not be robust enough and may have to be improved on.
return yachts.size() + 1;
}
}
@@ -13,7 +13,7 @@ import java.util.*;
* cannot be sent to a player
*/
public class HeartbeatThread extends Thread{
private final int HEARTBEAT_PERIOD = 5000;
private final int HEARTBEAT_PERIOD = 200;
private ClientConnectionDelegate delegate;
private Integer seqNum;
private Stack<Player> disconnectedPlayers;
@@ -43,7 +43,7 @@ public class HeartbeatThread extends Thread{
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
if (!player.getSocket().isConnected()){
if (!player.getSocket().isConnected()) {
playerLostConnection(player);
}
@@ -1,9 +1,11 @@
package seng302.gameServer;
import seng302.model.Player;
import seng302.model.stream.PacketBufferDelegate;
import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.packets.StreamPacket;
import java.time.LocalDateTime;
import java.util.Observable;
import seng302.client.ClientPacketParser;
import seng302.models.Player;
import seng302.models.stream.PacketBufferDelegate;
import seng302.models.stream.packets.StreamPacket;
import java.io.IOException;
import java.net.ServerSocket;
@@ -15,12 +17,15 @@ import java.util.concurrent.PriorityBlockingQueue;
* A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17.
*/
public class MainServerThread extends Thread implements PacketBufferDelegate, ClientConnectionDelegate{
public class MainServerThread extends Observable implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{
private static final int PORT = 4950;
private static final int PORT = 4942;
private static final Integer MAX_NUM_PLAYERS = 3;
private static final Integer UPDATES_PER_SECOND = 2;
private static final int LOG_LEVEL = 1;
private Thread thread;
private ServerSocket serverSocket = null;
private Socket socket;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
@@ -32,10 +37,13 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
System.out.println("IO error in server thread handler upon trying to make new server socket");
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
}
packetBuffer = new PriorityBlockingQueue<>();
thread = new Thread(this);
thread.start();
}
@@ -49,43 +57,40 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
heartbeatThread.start();
serverListenThread.start();
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!isInterrupted()) {
while (!thread.isInterrupted()) {
try {
Thread.sleep(1000 / 60); //60 times per second we should calculate the game state
Thread.sleep(1000 / UPDATES_PER_SECOND);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
GameState.update();
}
//RACING
if (GameState.getCurrentStage() == GameStages.RACING) {
GameState.update();
updateClients();
}
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
}
updateClients();
while (!packetBuffer.isEmpty()){
System.out.println("WHATUPPP");
try {
StreamPacket packet = packetBuffer.take();
StreamParser.parsePacket(packet);
ClientPacketParser.parsePacket(packet);
} catch (InterruptedException e) {
continue;
}
}
}
System.out.println("WHOOPSIES");
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
try {
serverSocket.close();
@@ -95,7 +100,6 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
}
}
public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.updateClient();
@@ -105,32 +109,26 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
}
@Override
public boolean addToBuffer(StreamPacket streamPacket) {
System.out.println("HEY HI");
return packetBuffer.add(streamPacket);
}
private void initializeRace(){
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.updateClient();
}
}
/**
* A client has tried to connect to the server
* @param serverToClientThread The player that connected
*/
@Override
public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getName(), 0);
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread);
this.addObserver(serverToClientThread);
setChanged();
notifyObservers();
}
/**
@@ -139,9 +137,26 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
*/
@Override
public void clientDisconnected(Player player) {
serverLog("Player disconnected", 0);
try {
player.getSocket().close();
} catch (Exception e) {
serverLog("Cannot disconnect the socket for the disconnected player.", 0);
}
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
GameState.removeYacht(player.getYacht().getSourceId());
GameState.removePlayer(player);
// sendXml();
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
if (serverToClientThread.getSocket() == player.getSocket()) {
this.deleteObserver(serverToClientThread);
}
}
setChanged();
notifyObservers();
}
public void startGame() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.sendRaceStatusMessage();
}
}
}
@@ -25,7 +25,6 @@ public class ServerListenThread extends Thread{
Socket thisClient = serverSocket.accept();
if (thisClient != null){
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
thisConnection.start();
delegate.clientConnected(thisConnection);
}
} catch (IOException e) {
@@ -0,0 +1,37 @@
package seng302.gameServer;
import java.util.Arrays;
import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionType;
public class ServerPacketParser {
public static BoatActionType extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
return BoatActionType.getType((int) actionTypeValue);
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
*
* @return a positive long if there is less than 7 bytes -1 otherwise
*/
private static long bytesToLong(byte[] bytes) {
long partialLong = 0;
int index = 0;
for (byte b : bytes) {
if (index > 6) {
return -1;
}
partialLong = partialLong | (b & 0xFFL) << (index * 8);
index++;
}
return partialLong;
}
}
@@ -1,34 +1,74 @@
package seng302.gameServer;
import seng302.model.Player;
import seng302.model.stream.parsers.StreamParser;
import seng302.model.stream.packets.StreamPacket;
import seng302.server.messages.ChatterMessage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import org.apache.commons.io.IOUtils;
import seng302.models.Player;
import seng302.models.Yacht;
import seng302.models.stream.packets.PacketType;
import seng302.models.stream.packets.StreamPacket;
import seng302.models.xml.Race;
import seng302.models.xml.Regatta;
import seng302.models.xml.XMLGenerator;
import seng302.server.messages.BoatActionType;
import seng302.server.messages.BoatLocationMessage;
import seng302.server.messages.BoatStatus;
import seng302.server.messages.BoatSubMessage;
import seng302.server.messages.Message;
import java.io.*;
import java.net.Socket;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import seng302.server.messages.RaceStatus;
import seng302.server.messages.RaceStatusMessage;
import seng302.server.messages.RaceType;
import seng302.server.messages.XMLMessage;
import seng302.server.messages.XMLMessageSubType;
import seng302.server.messages.XMLMessage;
import seng302.server.messages.XMLMessageSubType;
import seng302.utilities.GeoPoint;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on its own thread.
* All server threads created and owned by the server thread handler which can trigger client updates on its threads
* Created by wmu16 on 13/07/17.
* A class describing a single connection to a Client for the purposes of sending and receiving on
* its own thread. All server threads created and owned by the server thread handler which can
* trigger client updates on its threads Created by wmu16 on 13/07/17.
*/
public class ServerToClientThread extends Thread {
public class ServerToClientThread implements Runnable, Observer {
private static final Integer LOG_LEVEL = 1;
private static final Integer MAX_ID_ATTEMPTS = 10;
private Thread thread;
private InputStream is;
private OutputStream os;
private Socket socket;
private ByteArrayOutputStream crcBuffer;
private ByteArrayOutputStream crcBuffer;
private Boolean userIdentified = false;
private Boolean connected = true;
private Boolean updateClient = true;
// private Boolean initialisedRace = true;
private Integer seqNo;
private Integer sourceId;
private XMLGenerator xml;
public ServerToClientThread(Socket socket) {
this.socket = socket;
@@ -38,39 +78,69 @@ public class ServerToClientThread extends Thread {
} catch (IOException e) {
System.out.println("IO error in server thread upon grabbing streams");
}
// threeWayHandshake();
GameState.addPlayer(new Player(socket));
//Attempt threeway handshake with connection
sourceId = GameState.getUniquePlayerID();
if (threeWayHandshake(sourceId)) {
serverLog("Successful handshake. Client allocated id: " + sourceId, 1);
Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kapa", "Kappa", "NZ");
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
GameState.addYacht(sourceId, yacht);
GameState.addPlayer(new Player(socket, yacht));
} else {
serverLog("Unsuccessful handshake. Connection rejected", 1);
closeSocket();
return;
}
seqNo = 0;
thread = new Thread(this);
thread.start();
}
static void serverLog(String message, int logLevel) {
if (logLevel <= LOG_LEVEL) {
System.out.println(
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
}
}
@Override
public void update(Observable o, Object arg) {
sendSetupMessages();
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while(true) {
//System.out.print(".");
while (socket.isConnected()) {
try {
// if (initialisedRace) {
// sendSetupMessages();
// initialisedRace = false;
// }
//Perform a write if it is time to as delegated by the MainServerThread
if (updateClient) {
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
sendMessage(chatterMessage);
// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
// sendMessage(chatterMessage);
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
// sendBoatLocationPackets();
updateClient = false;
}
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) {
if (sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
@@ -83,14 +153,22 @@ public class ServerToClientThread extends Thread {
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
//System.out.println("RECEIVED A PACKET");
StreamParser.parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload));
// TODO: 17/07/17 wmu16 - Fix this or maybe we dont need to go through the main server at all!?!?
switch (PacketType.assignPacketType(type)) {
case BOAT_ACTION:
BoatActionType actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
GameState.updateBoat(sourceId, actionType);
break;
}
} else {
System.err.println("Packet has been dropped");
serverLog("Packet has been dropped", 1);
}
}
} catch (Exception e) {
e.printStackTrace();
// TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
// e.printStackTrace();
closeSocket();
return;
}
@@ -98,7 +176,34 @@ public class ServerToClientThread extends Thread {
}
private void sendSetupMessages() {
xml = new XMLGenerator();
Race race = new Race();
for (Yacht yacht : GameState.getYachts().values()) {
race.addBoat(yacht);
}
//@TODO calculate lat/lng values
xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233));
xml.setRace(race);
XMLMessage xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xml.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
xml.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
xml.getRaceAsXml().length());
sendMessage(xmlMessage);
// System.out.println("Sent xml messages for " + thread.getName());
}
public void updateClient() {
sendBoatLocationPackets();
updateClient = true;
}
@@ -109,28 +214,33 @@ public class ServerToClientThread extends Thread {
* if so, sends a confirmation packet back to that connection
* Creates a player instance with that ID and this thread and adds it to the GameState
* If not, close the socket and end the threads execution
*
* @param id the id to try and assign to the connection
* @return A boolean indicating if it was a successful handshake
*/
private void threeWayHandshake() {
// // TODO: 13/07/17 Finish using AC35
// Integer playerID = GameState.getUniquePlayerID();
// Integer confirmationID = null;
// Integer identificationAttempt = 0
// while (!userIdentified) {
// os.write(playerID); //Send out new ID looking for echo
// confirmationID = is.read();
// if (playerID == idConfirmation) { //ID is echoed back. Connection is a client
// os.write( some determined confirmation message ); //Confirm to client
// GameState.addPlayer(new Player(playerID, this)); //Create a player in game state for client
// userIdentified = true;
// } else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
// closeSocket();
// return;
// }
// identificationAttempt++;
// }
private Boolean threeWayHandshake(Integer id) {
Integer confirmationID = null;
Integer identificationAttempt = 0;
while (!userIdentified) {
try {
os.write(id); //Send out new ID looking for echo
confirmationID = is.read();
} catch (IOException e) {
e.printStackTrace();
}
if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
return true;
} else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
return false;
}
identificationAttempt++;
}
return true;
}
public void closeSocket() {
private void closeSocket() {
try {
socket.close();
} catch (IOException e) {
@@ -148,31 +258,101 @@ public class ServerToClientThread extends Thread {
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
private byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++){
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
private void skipBytes(long n) throws Exception{
for (int i=0; i < n; i++){
private void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
public void sendMessage(Message message){
public void sendMessage(Message message) {
try {
os.write(message.getBuffer());
} catch (SocketException e) {
//serverLog("Player " + sourceId + " side socket disconnected", 0);
return;
} catch (IOException e) {
e.printStackTrace();
}
}
private int getSeqNo() {
seqNo++;
return seqNo;
}
private void sendBoatLocationPackets() {
ArrayList<Yacht> yachts = new ArrayList<>(GameState.getYachts().values());
for (Yacht yacht : yachts) {
// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng());
BoatLocationMessage boatLocationMessage =
new BoatLocationMessage(
yacht.getSourceId(),
getSeqNo(),
yacht.getLocation().getLat(),
yacht.getLocation().getLng(),
yacht.getHeading(),
(long) yacht.getVelocity());
sendMessage(boatLocationMessage);
}
}
public Thread getThread() {
return thread;
}
public void sendRaceStatusMessage() {
// variables taken from GameServerThread
int TIME_TILL_RACE_START = 20 * 1000;
long startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus;
RaceStatus raceStatus;
for (Player player : GameState.getPlayers()) {
Yacht y = player.getYacht();
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
boatStatus = BoatStatus.PRESTART;
} else if (GameState.getCurrentStage() == GameStages.RACING) {
boatStatus = BoatStatus.RACING;
} else {
boatStatus = BoatStatus.UNDEFINED;
}
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l,
1234l);
boatSubMessages.add(m);
}
if (GameState.getCurrentStage() == GameStages.RACING) {
raceStatus = RaceStatus.STARTED;
} else {
raceStatus = RaceStatus.WARNING;
}
sendMessage(new RaceStatusMessage(1, raceStatus, startTime, GameState.getWindDirection(),
GameState.getWindSpeed().longValue(), GameState.getPlayers().size(),
RaceType.MATCH_RACE, 1, boatSubMessages));
}
public Socket getSocket() {
return socket;
}
}