Merge branch 'Story62_Creating_Game_Loop' into Merging_GameLoop_with_Broadcast

# Conflicts:
#	src/main/java/seng302/controllers/StartScreenController.java
#	src/main/java/seng302/gameServer/ServerListenThread.java
#	src/main/java/seng302/gameServer/ServerToClientThread.java
#	src/main/java/seng302/models/Yacht.java
This commit is contained in:
Kusal Ekanayake
2017-07-23 17:34:24 +12:00
7 changed files with 246 additions and 55 deletions
@@ -15,12 +15,17 @@ import seng302.server.messages.Message;
/** /**
* Created by kre39 on 13/07/17. * Created by kre39 on 13/07/17.
*/ */
public class ClientToServerThread extends Thread { public class ClientToServerThread implements Runnable {
private static final int LOG_LEVEL = 1;
private Thread thread;
private Integer ourID;
private Socket socket; private Socket socket;
private InputStream is; private InputStream is;
private OutputStream os; private OutputStream os;
private final int PORT_NUMBER = 0;
private static final int LOG_LEVEL = 1;
private Boolean updateClient = true; private Boolean updateClient = true;
private ByteArrayOutputStream crcBuffer; private ByteArrayOutputStream crcBuffer;
@@ -34,11 +39,24 @@ public class ClientToServerThread extends Thread {
e.printStackTrace(); e.printStackTrace();
} }
Integer allocatedID = threeWayHandshake();
if (allocatedID != null) {
ourID = allocatedID;
clientLog("Successful handshake. Allocated ID: " + ourID, 1);
} else {
clientLog("Unsuccessful handhsake", 1);
closeSocket();
return;
} }
static void serverLog(String message, int logLevel){ thread = new Thread(this);
thread.start();
}
static void clientLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){ if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message); System.out.println("[CLIENT] " + message);
} }
} }
@@ -91,6 +109,33 @@ public class ClientToServerThread extends Thread {
} }
/**
* Listens for an allocated sourceID and returns it to the server if recieved
* @return the sourceID allocated to us by the server
*/
private Integer threeWayHandshake() {
Integer ourSourceID = null;
while (true) {
try {
ourSourceID = is.read();
} catch (IOException e) {
e.printStackTrace();
}
if (ourSourceID != null) {
try {
os.write(ourSourceID);
return ourSourceID;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
}
/** /**
* Send the post-start race course information * Send the post-start race course information
*/ */
@@ -98,6 +143,7 @@ public class ClientToServerThread extends Thread {
try { try {
os.write(boatActionMessage.getBuffer()); os.write(boatActionMessage.getBuffer());
} catch (IOException e) { } catch (IOException e) {
clientLog("COULD NOT WRITE TO SERVER", 0);
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -139,4 +185,8 @@ public class ClientToServerThread extends Thread {
readByte(); readByte();
} }
} }
public Thread getThread() {
return thread;
}
} }
@@ -40,6 +40,7 @@ public class StartScreenController {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
contentPane.getChildren().addAll((Pane) fxmlLoader.load()); contentPane.getChildren().addAll((Pane) fxmlLoader.load());
return fxmlLoader.getController(); return fxmlLoader.getController();
} catch (javafx.fxml.LoadException e) { } catch (javafx.fxml.LoadException e) {
e.printStackTrace(); e.printStackTrace();
@@ -61,10 +62,9 @@ public class StartScreenController {
try { try {
String ipAddress = InetAddress.getLocalHost().getHostAddress(); String ipAddress = InetAddress.getLocalHost().getHostAddress();
new GameState(ipAddress); new GameState(ipAddress);
new MainServerThread().start(); new MainServerThread()
ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
controller.setClientToServerThread(clientToServerThread); controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start();
// new GameServerThread("Fuck you"); // new GameServerThread("Fuck you");
// get the lobby controller so that we can pass the game server thread to it // get the lobby controller so that we can pass the game server thread to it
setContentPane("/views/LobbyView.fxml"); setContentPane("/views/LobbyView.fxml");
@@ -82,9 +82,9 @@ public class StartScreenController {
// TODO: 10/07/17 wmu16 - Finish function // TODO: 10/07/17 wmu16 - Finish function
String ipAddress = ipTextField.getText().trim().toLowerCase(); String ipAddress = ipTextField.getText().trim().toLowerCase();
try { try {
// TODO: 22/07/17 wmu 16 - make this port number some static constant somewhere perhaps a config file?
ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950); ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950);
controller.setClientToServerThread(clientToServerThread); controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start();
setContentPane("/views/LobbyView.fxml"); setContentPane("/views/LobbyView.fxml");
} catch (Exception e){ } catch (Exception e){
e.printStackTrace(); e.printStackTrace();
@@ -14,8 +14,8 @@ import seng302.server.messages.BoatActionType;
public class GameState { public class GameState {
private static Long previousUpdateTime; private static Long previousUpdateTime;
private static Double windDirection = 0d; public static Double windDirection;
private static Double windSpeed = 0d; private static Double windSpeed;
private static String hostIpAddress; private static String hostIpAddress;
private static List<Player> players; private static List<Player> players;
@@ -24,6 +24,12 @@ public class GameState {
private static GameStages currentStage; private static GameStages currentStage;
public GameState(String hostIpAddress) { public GameState(String hostIpAddress) {
windDirection = 170d;
windSpeed = 0d;
yachts = new HashMap<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress; GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>(); players = new ArrayList<>();
currentStage = GameStages.LOBBYING; currentStage = GameStages.LOBBYING;
@@ -49,8 +55,8 @@ public class GameState {
players.remove(player); players.remove(player);
} }
public static void addYacht(Integer sourceId, Yacht yatch) { public static void addYacht(Integer sourceId, Yacht yacht) {
yachts.put(sourceId, yatch); yachts.put(sourceId, yacht);
} }
public static Boolean getIsRaceStarted() { public static Boolean getIsRaceStarted() {
@@ -78,20 +84,39 @@ public class GameState {
} }
public static void updateBoat(Integer sourceId, BoatActionType actionType) { public static void updateBoat(Integer sourceId, BoatActionType actionType) {
Yacht playerYacht = yachts.get(sourceId);
System.out.println("-----------------------");
switch (actionType) { switch (actionType) {
case VMG: case VMG:
System.out.println("Snapping to VMG");
// TODO: 22/07/17 wmu16 - Add in the vmg calculation code here
break; break;
case SAILS_IN: case SAILS_IN:
playerYacht.toggleSailIn();
System.out.println("Toggling Sails");
break; break;
case SAILS_OUT: case SAILS_OUT:
playerYacht.toggleSailIn();
System.out.println("Toggling Sails");
break; break;
case TACK_GYBE: case TACK_GYBE:
playerYacht.tackGybe(windDirection);
System.out.println("Tack/Gybe");
break; break;
case UPWIND: case UPWIND:
playerYacht.turnUpwind();
System.out.println("Moving upwind");
break; break;
case DOWNWIND: case DOWNWIND:
playerYacht.turnDownwind();
System.out.println("Moving downwind");
break; break;
} }
System.out.println("-----------------------");
System.out.println("Heading: " + playerYacht.getHeading());
System.out.println("Sails are in: " + playerYacht.getSailIn());
System.out.println("-----------------------\n");
} }
public static void update() { public static void update() {
@@ -102,4 +127,14 @@ public class GameState {
yacht.update(timeInterval); 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;
}
} }
@@ -15,12 +15,14 @@ import java.util.concurrent.PriorityBlockingQueue;
* A class describing the overall server, which creates and collects server threads for each client * A class describing the overall server, which creates and collects server threads for each client
* Created by wmu16 on 13/07/17. * Created by wmu16 on 13/07/17.
*/ */
public class MainServerThread extends Thread implements PacketBufferDelegate, ClientConnectionDelegate{ public class MainServerThread implements Runnable, PacketBufferDelegate, ClientConnectionDelegate{
private static final int PORT = 4950; private static final int PORT = 4950;
private static final Integer MAX_NUM_PLAYERS = 3; private static final Integer MAX_NUM_PLAYERS = 3;
private static final int LOG_LEVEL = 1; private static final int LOG_LEVEL = 1;
private Thread thread;
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
private Socket socket; private Socket socket;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>(); private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
@@ -36,6 +38,9 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
} }
packetBuffer = new PriorityBlockingQueue<>(); packetBuffer = new PriorityBlockingQueue<>();
thread = new Thread(this);
thread.start();
} }
@@ -50,7 +55,7 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
serverListenThread.start(); serverListenThread.start();
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app. //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 { try {
Thread.sleep(1000 / 60); //60 times per second we should calculate the game state Thread.sleep(1000 / 60); //60 times per second we should calculate the game state
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -123,7 +128,7 @@ public class MainServerThread extends Thread implements PacketBufferDelegate, Cl
*/ */
@Override @Override
public void clientConnected(ServerToClientThread serverToClientThread) { public void clientConnected(ServerToClientThread serverToClientThread) {
serverLog("Player Connected From " + serverToClientThread.getName(), 0); serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
serverToClientThreads.add(serverToClientThread); serverToClientThreads.add(serverToClientThread);
} }
@@ -29,8 +29,6 @@ public class ServerListenThread extends Thread{
Socket thisClient = serverSocket.accept(); Socket thisClient = serverSocket.accept();
if (thisClient != null){ if (thisClient != null){
ServerToClientThread thisConnection = new ServerToClientThread(thisClient); ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
thisConnection.initialiseRace();
thisConnection.start();
delegate.clientConnected(thisConnection); delegate.clientConnected(thisConnection);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -29,9 +29,13 @@ import seng302.utilities.GeoPoint;
* All server threads created and owned by the server thread handler which can trigger client updates on its threads * 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. * Created by wmu16 on 13/07/17.
*/ */
public class ServerToClientThread extends Thread { public class ServerToClientThread implements Runnable {
private static final Integer LOG_LEVEL = 1;
private static final Integer MAX_ID_ATTEMPTS = 10; private static final Integer MAX_ID_ATTEMPTS = 10;
private Thread thread;
private InputStream is; private InputStream is;
private OutputStream os; private OutputStream os;
private Socket socket; private Socket socket;
@@ -56,14 +60,31 @@ public class ServerToClientThread extends Thread {
} catch (IOException e) { } catch (IOException e) {
System.out.println("IO error in server thread upon grabbing streams"); System.out.println("IO error in server thread upon grabbing streams");
} }
// threeWayHandshake(); //Attempt threeway handshake with connection
Random rand = new Random(); sourceId = GameState.getUniquePlayerID();
sourceId = rand.nextInt(1000); if (threeWayHandshake(sourceId)) {
serverLog("Successful handshake. Client allocated id: " + sourceId, 1);
GameState.addYacht(sourceId,
new Yacht("Kappa", "Kap", new GeoPoint(0.0, 0.0), 0.0));
GameState.addPlayer(new Player(socket)); //Is this neccesary???
} else {
serverLog("Unsuccessful handshake. Connection rejected", 1);
closeSocket();
return;
}
Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kap", "Kappa", "NZ"); Yacht yacht = new Yacht("Yacht", sourceId, sourceId.toString(), "Kap", "Kappa", "NZ");
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0); // Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
GameState.addYacht(sourceId, yacht); GameState.addYacht(sourceId, yacht);
GameState.addPlayer(new Player(socket, yacht)); GameState.addPlayer(new Player(socket, yacht));
seqNo = 0; seqNo = 0;
thread = new Thread(this);
thread.start();
}
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
}
} }
public void run() { public void run() {
@@ -93,11 +114,9 @@ public class ServerToClientThread extends Thread {
updateClient = false; updateClient = false;
} }
crcBuffer = new ByteArrayOutputStream(); crcBuffer = new ByteArrayOutputStream();
sync1 = readByte(); sync1 = readByte();
sync2 = readByte(); sync2 = readByte();
//checking if it is the start of the packet //checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) { if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte(); int type = readByte();
@@ -125,6 +144,7 @@ public class ServerToClientThread extends Thread {
} }
} }
} catch (Exception e) { } catch (Exception e) {
serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
e.printStackTrace(); e.printStackTrace();
closeSocket(); closeSocket();
return; return;
@@ -167,28 +187,32 @@ public class ServerToClientThread extends Thread {
* if so, sends a confirmation packet back to that connection * 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 * 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 * 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() { private Boolean threeWayHandshake(Integer id) {
// // TODO: 13/07/17 Finish using AC35 Integer confirmationID = null;
// Integer playerID = GameState.getUniquePlayerID(); Integer identificationAttempt = 0;
// Integer confirmationID = null; while (!userIdentified) {
// Integer identificationAttempt = 0 try {
// while (!userIdentified) { os.write(id); //Send out new ID looking for echo
// os.write(playerID); //Send out new ID looking for echo confirmationID = is.read();
// confirmationID = is.read(); } catch (IOException e) {
// if (playerID == idConfirmation) { //ID is echoed back. Connection is a client e.printStackTrace();
// 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++;
// }
} }
public void closeSocket() { 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;
}
private void closeSocket() {
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
@@ -252,4 +276,9 @@ public class ServerToClientThread extends Thread {
sendMessage(boatLocationMessage); sendMessage(boatLocationMessage);
} }
} }
public Thread getThread() {
return thread;
}
} }
+76 -2
View File
@@ -7,6 +7,7 @@ import java.text.SimpleDateFormat;
import java.util.Map; import java.util.Map;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import seng302.controllers.RaceViewController; import seng302.controllers.RaceViewController;
import seng302.gameServer.GameState;
import seng302.models.mark.Mark; import seng302.models.mark.Mark;
import seng302.utilities.GeoPoint; import seng302.utilities.GeoPoint;
@@ -18,6 +19,12 @@ import seng302.utilities.GeoPoint;
*/ */
public class Yacht { public class Yacht {
private final Double TURN_STEP = 3.0;
private Double lastHeading;
private Boolean sailIn;
// Used in boat group // Used in boat group
private Color colour; private Color colour;
@@ -49,6 +56,18 @@ public class Yacht {
private Mark nextMark; private Mark nextMark;
/**
* @param location latlon location of the boat stored in a geopoint
* @param heading heading of the boat in degrees from 0 to 365 with 0 being north
*/
public Yacht(GeoPoint location, Double heading) {
this.location = location;
this.heading = heading;
this.velocity = 0.0;
this.sailIn = false;
}
/** /**
* Used in EventTest and RaceTest. * Used in EventTest and RaceTest.
* *
@@ -60,6 +79,7 @@ public class Yacht {
this.location = location; this.location = location;
this.heading = heading; this.heading = heading;
this.velocity = 0.0; this.velocity = 0.0;
this.sailIn = false;
} }
/** /**
@@ -73,10 +93,11 @@ public class Yacht {
this.boatName = boatName; this.boatName = boatName;
this.velocity = boatVelocity; this.velocity = boatVelocity;
this.shortName = shortName; this.shortName = shortName;
this.sourceId = id; this.sourceID = id;
this.location = new GeoPoint(0.0, 0.0); this.sailIn = false;
} }
public Yacht(String boatType, Integer sourceId, String hullID, String shortName, public Yacht(String boatType, Integer sourceId, String hullID, String shortName,
String boatName, String country) { String boatName, String country) {
this.boatType = boatType; this.boatType = boatType;
@@ -86,6 +107,7 @@ public class Yacht {
this.boatName = boatName; this.boatName = boatName;
this.country = country; this.country = country;
this.position = "-"; this.position = "-";
this.sailIn = false;
this.location = new GeoPoint(0.0, 0.0); this.location = new GeoPoint(0.0, 0.0);
this.heading = 0.0; this.heading = 0.0;
this.velocity = 0.0; this.velocity = 0.0;
@@ -95,10 +117,12 @@ public class Yacht {
* @param timeInterval since last update in milliseconds * @param timeInterval since last update in milliseconds
*/ */
public void update(Long timeInterval) { public void update(Long timeInterval) {
if (sailIn) {
Double secondsElapsed = timeInterval / 1000000.0; Double secondsElapsed = timeInterval / 1000000.0;
Double metersCovered = velocity * secondsElapsed; Double metersCovered = velocity * secondsElapsed;
location = getGeoCoordinate(location, heading, metersCovered); location = getGeoCoordinate(location, heading, metersCovered);
} }
}
/** /**
* Adjusts the yachts velocity based on the wind direction and speed from the polar table. * Adjusts the yachts velocity based on the wind direction and speed from the polar table.
@@ -125,6 +149,52 @@ public class Yacht {
velocity = polarsFromClosestSpd.get(closest_key); velocity = polarsFromClosestSpd.get(closest_key);
} }
public Double getHeading() {
return heading;
}
public void adjustHeading(Double amount) {
lastHeading = heading;
heading = (heading + amount) % 360.0;
}
public void tackGybe(Double windDirection) {
adjustHeading(-2 * ((heading - windDirection) % 360));
}
public void toggleSailIn() {
sailIn = !sailIn;
}
public void turnUpwind() {
Double normalizedHeading = (heading - GameState.windDirection) % 360;
if (normalizedHeading == 0) {
if (lastHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
} else if (normalizedHeading == 180) {
if (lastHeading < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
} else if (normalizedHeading < 180) {
adjustHeading(-TURN_STEP);
} else {
adjustHeading(TURN_STEP);
}
}
public void turnDownwind() {
if ((heading - GameState.windDirection) % 360 < 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
public String getBoatType() { public String getBoatType() {
return boatType; return boatType;
@@ -256,6 +326,10 @@ public class Yacht {
return nextMark; return nextMark;
} }
public Boolean getSailIn() {
return sailIn;
}
@Override @Override
public String toString() { public String toString() {
return boatName; return boatName;