Merge branch 'Story62_Reading_Keystrokes' into story61_player_perspective

# Conflicts:
#	src/main/java/seng302/controllers/GameViewController.java
#	src/main/java/seng302/fxObjects/BoatGroup.java
This commit is contained in:
Calum
2017-07-20 12:20:12 +12:00
46 changed files with 1530 additions and 741 deletions
+17
View File
@@ -0,0 +1,17 @@
engines:
pmd:
enabled: true
channel: "beta"
fixme:
enabled: true
config:
strings:
- FIXME
- TODO
- BUG
- FIX
ratings:
paths:
- "**.java"
+4 -43
View File
@@ -4,23 +4,24 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import seng302.models.PolarTable;
import seng302.models.stream.StreamParser;
import seng302.models.stream.StreamReceiver;
import seng302.server.ServerThread;
public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile());
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root, 1530, 960));
primaryStage.setMaxWidth(1530);
primaryStage.setMaxHeight(960);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
// primaryStage.setMaximized(true);
primaryStage.show();
@@ -29,51 +30,11 @@ public class App extends Application {
StreamReceiver.noMoreBytes();
System.exit(0);
});
}
public static void main(String[] args) {
StreamReceiver sr = null;
new ServerThread("Racevision Test Server");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (args.length == 1 && args[0].equals("-standalone")) {
return;
}
if (args.length == 3 && args[0].equals("-server")) {
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
} else if (args.length == 2 && args[0].equals("-server")) {
switch (args[1]) {
case "internal":
sr = new StreamReceiver("localhost", 4949, "RaceStream");
break;
case "staffserver":
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
break;
case "official":
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
break;
}
}
//Change the StreamReceiver in this else block to change the default data source.
else{
sr = new StreamReceiver("localhost", 4949, "RaceStream");
}
sr.start();
StreamParser streamParser = new StreamParser("StreamParser");
streamParser.start();
launch(args);
}
}
@@ -0,0 +1,143 @@
package seng302.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import seng302.models.stream.StreamParser;
import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.BoatActionType;
import seng302.server.messages.Message;
/**
* Created by kre39 on 13/07/17.
*/
public class ClientToServerThread extends Thread {
private Socket socket;
private InputStream is;
private OutputStream os;
private final int PORT_NUMBER = 0;
private static final int LOG_LEVEL = 1;
private Boolean updateClient = true;
private ByteArrayOutputStream crcBuffer;
public ClientToServerThread(String ipAddress, Integer portNumber){
try {
socket = new Socket(ipAddress, portNumber);
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
static void serverLog(String message, int logLevel){
if(logLevel <= LOG_LEVEL){
System.out.println("[SERVER] " + message);
}
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while(true) {
try {
//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
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
updateClient = false;
}
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = Message.bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
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!?!?
// packetBufferDelegate.addToBuffer(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
System.err.println("Packet has been dropped");
}
}
} catch (Exception e) {
closeSocket();
return;
}
}
}
/**
* Send the post-start race course information
*/
public void sendBoatActionMessage(BoatActionMessage boatActionMessage) {
try {
os.write(boatActionMessage.getBuffer());
} catch (IOException e) {
e.printStackTrace();
}
}
public void closeSocket() {
try {
socket.close();
} catch (IOException e) {
System.out.println("IO error in server thread upon trying to close socket");
}
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = is.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
byte[] bytes = new byte[n];
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++){
readByte();
}
}
}
@@ -1,39 +1,91 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import seng302.models.stream.StreamParser;
import seng302.client.ClientToServerThread;
import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.BoatActionType;
public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
private ClientToServerThread clientToServerThread;
private void setContentPane(String jfxUrl) {
private Object setContentPane(String jfxUrl) {
try {
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl)));
Parent view = fxmlLoader.load();
contentPane.getChildren().addAll(view);
return fxmlLoader.getController();
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
} catch (IOException e) {
System.err.println(e);
}
return null;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml");
StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
startScreenController.setController(this);
StreamParser.boatLocations.clear();
}
/** Handle the key-pressed event from the text field. */
public void keyPressed(KeyEvent e) {
BoatActionMessage boatActionMessage;
switch (e.getCode()){
case SPACE: // align with vmg
boatActionMessage = new BoatActionMessage(BoatActionType.VMG);
clientToServerThread.sendBoatActionMessage(boatActionMessage);
break;
case PAGE_UP: // upwind
boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND);
clientToServerThread.sendBoatActionMessage(boatActionMessage);
break;
case PAGE_DOWN: // downwind
boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND);
clientToServerThread.sendBoatActionMessage(boatActionMessage);
break;
case ENTER: // tack/gybe
boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE);
clientToServerThread.sendBoatActionMessage(boatActionMessage);
break;
//TODO Allow a zoom in and zoom out methods
case Z: // zoom in
System.out.println("Zoom in");
break;
case X: // zoom out
System.out.println("Zoom out");
break;
}
}
public void keyReleased(KeyEvent e) {
switch (e.getCode()) {
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
case SHIFT: // sails in/sails out
BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN);
clientToServerThread.sendBoatActionMessage(boatActionMessage);
break;
}
}
public void setClientToServerThread(ClientToServerThread ctt) {
clientToServerThread = ctt;
}
}
@@ -14,6 +14,7 @@ import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
@@ -525,4 +526,5 @@ public class GameViewController {
List<MarkGroup> getMarkGroups() {
return markGroups;
}
}
@@ -0,0 +1,64 @@
package seng302.controllers;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import seng302.gameServer.GameServerThread;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import java.io.IOException;
/**
* A class describing the actions of the lobby screen
* Created by wmu16 on 10/07/17.
*/
public class LobbyController {
@FXML
private GridPane lobbyScreen;
@FXML
private Text lobbyIpText;
private GameServerThread gameServerThread;
private void setContentPane(String jfxUrl) {
try {
AnchorPane contentPane = (AnchorPane) lobbyScreen.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@FXML
public void leaveLobbyButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function!
setContentPane("/views/StartScreenView.fxml");
System.out.println("Leaving lobby!");
GameState.setCurrentStage(GameStages.CANCELLED);
gameServerThread.terminateGame();
}
@FXML
public void readyButtonPressed() {
GameState.setCurrentStage(GameStages.RACING);
}
protected void setGameServerThread(GameServerThread gameServerThread) {
this.gameServerThread = gameServerThread;
}
}
@@ -4,6 +4,7 @@ import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point2D;
@@ -16,6 +17,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
@@ -27,7 +29,7 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import javafx.util.StringConverter;
import seng302.utilities.GeometryUtils;
import seng302.utilities.GeoUtility;
import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate;
@@ -103,6 +105,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
initialiseBoatSelectionComboBox();
gameViewController.timer.start();
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
}
@@ -136,7 +139,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
stage.show();
@@ -446,7 +448,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
Line rightLayline = new Line();
Line leftLayline = new Line();
if (lineFuncResult == 1) {
@@ -639,4 +641,5 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId);
}
}
@@ -1,191 +1,92 @@
package seng302.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import seng302.client.ClientToServerThread;
import seng302.gameServer.GameState;
import seng302.gameServerWithThreading.MainServerThread;
import seng302.models.stream.StreamReceiver;
public class StartScreenController implements Initializable {
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* A Class describing the actions of the start screen controller
* Created by wmu16 on 10/07/17.
*/
public class StartScreenController {
@FXML
private GridPane gridPane;
private TextField ipTextField;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
private GridPane startScreen2;
private boolean switchedToRaceView = false;
private Controller controller;
private void setContentPane(String jfxUrl) {
/**
* Loads the fxml content into the parent pane
* @param jfxUrl
* @return the controller of the fxml
*/
private Object setContentPane(String jfxUrl) {
try {
// get the main controller anchor pane (MainView.fxml)
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
contentPane.getChildren().addAll((Pane) fxmlLoader.load());
return fxmlLoader.getController();
} catch (javafx.fxml.LoadException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1
* second.
* ATTEMPTS TO:
* Sets up a new game state with your IP address as designated as the host.
* Starts a thread to listen for incoming connections
* Switches to the lobby screen
*/
public void startStream() {
// reset boolean for switch to race view
switchedToRaceView = false;
@FXML
public void hostButtonPressed() {
try {
String ipAddress = InetAddress.getLocalHost().getHostAddress();
new GameState(ipAddress);
new MainServerThread().start();
// new GameServerThread("Fuck you");
// get the lobby controller so that we can pass the game server thread to it
setContentPane("/views/LobbyView.fxml");
if (StreamParser.isStreamStatus()) {
streamButton.setVisible(false);
realTime.setVisible(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN);
timeTillLive.setText("Connecting...");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (StreamParser.isRaceStarted()) {
if (!switchedToRaceView) {
switchToRaceView();
}
timer.cancel();
}
if (StreamParser.isRaceFinished()) {
realTime.setText(StreamParser.getCurrentTimeString());
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0) {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long
.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long
.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = "-" + timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
} else {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long
.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long
.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
}
});
}
}, 0, 1000);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
} catch (UnknownHostException e) {
System.err.println("COULD NOT FIND YOUR IP ADDRESS!");
e.printStackTrace();
}
}
public void switchToRaceView() {
StreamParser.boatLocations.clear();
switchedToRaceView = true;
setContentPane("/views/RaceView.fxml");
@FXML
public void connectButtonPressed() {
// TODO: 10/07/17 wmu16 - Finish function
String ipAddress = ipTextField.getText().trim().toLowerCase();
ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, 4950);
controller.setClientToServerThread(clientToServerThread);
clientToServerThread.start();
}
private void updateTeamList() {
ObservableList<Yacht> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
// check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
// add boats to the start screen list
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
} else { // else use StreamParser.getBoats()
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
}
teamList.refresh();
public void setController(Controller controller) {
this.controller = controller;
}
}
@@ -10,8 +10,8 @@ import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate;
import seng302.models.Yacht;
import seng302.utilities.GeometryUtils;
import seng302.controllers.GameViewController;
import seng302.utilities.GeoUtility;
import seng302.controllers.CanvasController;
import seng302.models.mark.GateMark;
import seng302.models.mark.Mark;
import seng302.models.mark.SingleMark;
@@ -246,11 +246,11 @@ public class BoatGroup extends Group {
Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
Point2D boatCurrentPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
Point2D windTestPoint = GeometryUtils.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
Integer boatLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeometryUtils.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
/*
@@ -0,0 +1,17 @@
package seng302.gameServer;
import seng302.models.Player;
public interface ClientConnectionDelegate {
/**
* A player has connected to the server
* @param player The player that has connected
*/
void clientConnected(Player player);
/**
* A player has disconnected from the server
* @param player The player that has disconnected
*/
void clientDisconnected(Player player);
}
@@ -1,47 +1,43 @@
package seng302.server;
package seng302.gameServer;
import seng302.models.Player;
import seng302.models.Yacht;
import seng302.server.messages.*;
import seng302.server.simulator.Boat;
import seng302.server.simulator.Simulator;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
public class ServerThread implements Runnable, Observer {
private StreamingServerSocket server;
private long startTime;
private boolean raceStarted = false;
private Map<Integer,Boolean> boatsFinished = new HashMap<>();
private List<Boat> boats;
private Simulator raceSimulator;
private boolean sendingRaceFinishedLocationMessages = true;
public class GameServerThread implements Runnable, Observer, ClientConnectionDelegate{
private static final Integer MAX_NUM_PLAYERS = 10;
public static final int PORT_NUMBER = 4950;
private final int HEARTBEAT_PERIOD = 5000;
private Boolean hosting = true;
private ServerSocketChannel server;
private long startTime;
private short seqNum;
private final int RACE_STATUS_PERIOD = 1000/2;
private final int RACE_START_STATUS_PERIOD = 1000;
private final int BOAT_LOCATION_PERIOD = 1000/5;
private final int PORT_NUMBER = 4949;
private final int TIME_TILL_RACE_START = 20*1000;
private static final int LOG_LEVEL = 1;
public ServerThread(String threadName){
public GameServerThread(String threadName){
Thread runner = new Thread(this, threadName);
runner.setDaemon(true);
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
raceSimulator.addObserver(this);
// run race simulator, so it can send boats' static location.
Thread raceSimulatorThread = new Thread(raceSimulator, "Race Simulator");
boats = raceSimulator.getBoats();
for (Boat b : boats){
boatsFinished.put(b.getSourceID(), false);
}
seqNum = 0;
runner.start();
raceSimulatorThread.start();
}
static void serverLog(String message, int logLevel){
@@ -69,7 +65,7 @@ public class ServerThread implements Runnable, Observer {
}
if (fileContents != null){
return new XMLMessage(fileContents, type, server.getSequenceNumber());
return new XMLMessage(fileContents, type, seqNum);
}
return null;
@@ -79,30 +75,33 @@ public class ServerThread implements Runnable, Observer {
* @return Get a race status message for the current race
*/
private Message getRaceStatusMessage(){
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
BoatStatus boatStatus;
RaceStatus raceStatus;
boolean thereAreBoatsNotFinished = false;
for (Boat b : boats){
if (!raceStarted){
for (Player player : GameState.getPlayers()){
Yacht y = player.getYacht();
if (GameState.getCurrentStage() == GameStages.PRE_RACE){
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
else if(boatsFinished.get(b.getSourceID())){
boatStatus = BoatStatus.FINISHED;
else if(false){ //@TODO if boat has finished
boatStatus = BoatStatus.FINISHED;
}
else{
boatStatus = BoatStatus.PRESTART;
thereAreBoatsNotFinished = true;
}
BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, b.getEstimatedTimeTillFinish(), b.getEstimatedTimeTillFinish());
BoatSubMessage m = new BoatSubMessage(y.getSourceID(), boatStatus, y.getLastMarkRounded().getId(), 0, 0, 1234l, 1234l);
boatSubMessages.add(m);
}
if (thereAreBoatsNotFinished){
if (raceStarted){
if (GameState.getCurrentStage() == GameStages.RACING){
raceStatus = RaceStatus.STARTED;
}
else{
@@ -125,35 +124,7 @@ public class ServerThread implements Runnable, Observer {
}
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.SOUTH,
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
}
/**
* Starts an instance of the race simulator
*/
private void startRaceSim(){
// set race started to true, so the simulator will start moving boats
raceSimulator.setRaceStarted(true);
}
/**
* Starts sending heartbeat messages to the client
*/
private void startSendingHeartbeats() {
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message heartbeat = new Heartbeat(server.getSequenceNumber());
try {
server.send(heartbeat);
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, HEARTBEAT_PERIOD);
100, GameState.getPlayers().size(), RaceType.MATCH_RACE, 1, boatSubMessages);
}
/**
@@ -164,15 +135,13 @@ public class ServerThread implements Runnable, Observer {
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1,
Message raceStartStatusMessage = new RaceStartStatusMessage(seqNum, startTime , 1,
RaceStartNotificationType.SET_RACE_START_TIME);
try {
if (startTime < System.currentTimeMillis() && !raceStarted){
startRaceSim();
raceStarted = true;
if (startTime < System.currentTimeMillis() && GameState.getCurrentStage() != GameStages.RACING){
}
else{
server.send(raceStartStatusMessage);
broadcast(raceStartStatusMessage);
}
} catch (IOException e) {
@@ -186,13 +155,14 @@ public class ServerThread implements Runnable, Observer {
* Start sending race start status messages until race starts
*/
private void startSendingRaceStatusMessages(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Message raceStatusMessage = getRaceStatusMessage();
try {
server.send(raceStatusMessage);
broadcast(raceStatusMessage);
} catch (IOException e) {
e.printStackTrace();
}
@@ -210,13 +180,13 @@ public class ServerThread implements Runnable, Observer {
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
if (raceData != null){
server.send(raceData);
broadcast(raceData);
}
if (boatData != null){
server.send(boatData);
broadcast(boatData);
}
if (regatta != null){
server.send(regatta);
broadcast(regatta);
}
} catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
@@ -234,58 +204,115 @@ public class ServerThread implements Runnable, Observer {
try {
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
if (raceData != null) {
server.send(raceData);
broadcast(raceData);
}
}catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
},25000);
},1000);
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
}
public void run() {
ServerListenThread serverListenThread;
HeartbeatThread heartbeatThread;
Boolean serverIsSendingMessages = false;
try{
server = new StreamingServerSocket(PORT_NUMBER);
server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress("localhost", PORT_NUMBER));
serverListenThread = new ServerListenThread(server, this);
heartbeatThread = new HeartbeatThread(this);
heartbeatThread.start();
serverListenThread.start();
}
catch (IOException e){
serverLog("Failed to bind socket: " + e.getMessage(), 0);
}
// Wait for client to connect
server.start();
while (hosting) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
if (GameState.getCurrentStage() == GameStages.RACING && !serverIsSendingMessages) {
serverLog("Race Started", 0);
startSendingHeartbeats();
sendXml();
startSendingRaceStartStatusMessages();
startSendingRaceStatusMessages();
sendPostStartCourseXml();
sendXml();
startSendingRaceStartStatusMessages();
//startSendingRaceStatusMessages();
sendPostStartCourseXml();
serverIsSendingMessages = true;
}
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
serverLog("Race Finished", 0);
}
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
}
}
// /**
// * Start sending static boat position updates when race has finished
// */
// private void startSendingRaceFinishedBoatPositions(){
// Timer t = new Timer();
// t.schedule(new TimerTask() {
// @Override
// public void run() {
// try {
// for (Boat b : raceSimulator.getBoats()){
// Message m = new BoatLocationMessage(b.getSourceID(), seqNum, b.getLat(),
// b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
// ((long) 0));
//
// server.send(m);
// }
//
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }, 0, BOAT_LOCATION_PERIOD);
// }
/**
* A client has tried to connect to the server
* @param player The player that connected
*/
@Override
public void clientConnected(Player player) {
if (GameState.getPlayers().size() < MAX_NUM_PLAYERS && GameState.getCurrentStage() == GameStages.LOBBYING) {
serverLog("Player Connected", 0);
GameState.addPlayer(player);
sendXml();
}
}
/**
* Start sending static boat position updates when race has finished
* A player has left the game, remove the player from the GameState
* @param player The player that left
*/
private void startSendingRaceFinishedBoatPositions(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
try {
for (Boat b : raceSimulator.getBoats()){
Message m = new BoatLocationMessage(b.getSourceID(), server.getSequenceNumber(), b.getLat(),
b.getLng(), b.getLastPassedCorner().getBearingToNextCorner(),
((long) 0));
@Override
public void clientDisconnected(Player player) {
serverLog("Player disconnected", 0);
GameState.removePlayer(player);
sendXml();
}
server.send(m);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}, 0, BOAT_LOCATION_PERIOD);
void broadcast(Message message) throws IOException{
for(Player player : GameState.getPlayers()) {
//heh
player.getSocketChannel().socket().getOutputStream().write(message.getBuffer());
}
seqNum++;
}
/**
@@ -296,7 +323,7 @@ public class ServerThread implements Runnable, Observer {
@Override
@SuppressWarnings("unchecked")
public void update(Observable o, Object arg) {
// Only send if server started
/* Only send if server started
// TODO: I don't understand why i need to check server is null or not ... confused - haoming 2/5/17
if(server == null || !server.isStarted()){
return;
@@ -314,19 +341,28 @@ public class ServerThread implements Runnable, Observer {
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
boat.getLng(), boat.getLastPassedCorner().getBearingToNextCorner(),
((long) boat.getSpeed()));
server.send(m);
broadcast(m);
} catch (IOException e) {
serverLog("Couldn't send a boat status message", 3);
return;
}
catch (NullPointerException e){
e.printStackTrace();
}
}*/
}
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
startSendingRaceFinishedBoatPositions();
}
// if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
// startSendingRaceFinishedBoatPositions();
// }
//}
public void terminateGame() {
try {
//TODO: for now, I just close the socket, but i think we should terminate the whole thread instead. -hyi25 13 July
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,24 @@
package seng302.gameServer;
/**
* An enum describing the states of the game
* Created by wmu16 on 11/07/17.
*/
public enum GameStages {
LOBBYING(0),
PRE_RACE(1),
RACING(2),
FINISHED(3),
CANCELLED(4);
private long code;
GameStages(long code) {
this.code = code;
}
public long getCode(){
return code;
}
}
@@ -0,0 +1,65 @@
package seng302.gameServer;
import seng302.models.Player;
import java.util.ArrayList;
/**
* A Static class to hold information about the current state of the game (model)
* Created by wmu16 on 10/07/17.
*/
public class GameState {
private static String hostIpAddress;
private static ArrayList<Player> players;
private static Boolean isRaceStarted;
private static GameStages currentStage;
public GameState(String hostIpAddress) {
GameState.hostIpAddress = hostIpAddress;
players = new ArrayList<>();
currentStage = GameStages.LOBBYING;
isRaceStarted = false;
}
public static String getHostIpAddress() {
return hostIpAddress;
}
public static ArrayList<Player> getPlayers() {
return players;
}
public static void addPlayer(Player player) {
players.add(player);
}
public static void removePlayer(Player player) {
players.remove(player);
}
public static Boolean getIsRaceStarted() {
return isRaceStarted;
}
public static GameStages getCurrentStage() {
return currentStage;
}
public static void setCurrentStage(GameStages currentStage) {
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
}
}
}
@@ -0,0 +1,81 @@
package seng302.gameServer;
import seng302.models.Player;
import seng302.server.messages.Heartbeat;
import seng302.server.messages.Message;
import java.io.IOException;
import java.util.*;
/**
* Send Heartbeat messages to connected player at a specified interval
* Will call .clientDisconnected on the delegate when a heartbeat message
* cannot be sent to a player
*/
public class HeartbeatThread extends Thread{
private final int HEARTBEAT_PERIOD = 5000;
private ClientConnectionDelegate delegate;
private Integer seqNum;
private Stack<Player> disconnectedPlayers;
HeartbeatThread(ClientConnectionDelegate delegate){
this.delegate = delegate;
seqNum = 0;
disconnectedPlayers = new Stack<>();
}
/**
* A player has lost connection to the server
* The player is added to a stack so that the delegate
* can be notified
*
* @param player The player that has disconnected
*/
private void playerLostConnection(Player player){
disconnectedPlayers.push(player);
}
/**
* Sends a heartbeat message to each connected player
* The delegate is notified if a player has disconnected
*/
private void sendHeartbeatToAllPlayers(){
Message heartbeat = new Heartbeat(seqNum);
for (Player player : GameState.getPlayers()){
if (!player.getSocketChannel().isConnected()){
playerLostConnection(player);
}
//
// try {
// player.getSocketChannel().socket().getOutputStream().write(heartbeat.getBuffer());
// } catch (IOException e) {
// playerLostConnection(player);
// }
}
updateDelegate();
seqNum++;
}
/**
* Notifies the delegate about
* each disconnected player
*/
private void updateDelegate() {
while (!disconnectedPlayers.empty()){
delegate.clientDisconnected(disconnectedPlayers.pop());
}
}
public void run(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
sendHeartbeatToAllPlayers();
}
}, 0, HEARTBEAT_PERIOD);
}
}
@@ -0,0 +1,42 @@
package seng302.gameServer;
import seng302.models.Player;
import java.io.IOException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* A class for a thread to listen to connections
* Created by wmu16 on 11/07/17.
*/
public class ServerListenThread extends Thread{
private ServerSocketChannel socketChannel;
private ClientConnectionDelegate delegate;
ServerListenThread(ServerSocketChannel socketChannel, ClientConnectionDelegate delegate){
this.socketChannel = socketChannel;
this.delegate = delegate;
}
/**
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
*/
private void acceptConnection() {
try {
SocketChannel thisClient = socketChannel.accept();
if (thisClient.socket() != null){
Player thisPlayer = new Player(thisClient);
delegate.clientConnected(thisPlayer);
}
} catch (IOException e) {
e.getMessage();
}
}
public void run(){
while (true){
acceptConnection();
}
}
}
@@ -0,0 +1,111 @@
package seng302.gameServerWithThreading;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.models.stream.PacketBufferDelegate;
import seng302.models.stream.StreamParser;
import seng302.models.stream.packets.StreamPacket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
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{
private static final int PORT = 4950;
private static final Integer MAX_NUM_PLAYERS = 1;
private ServerSocket serverSocket = null;
private Socket socket;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private PriorityBlockingQueue<StreamPacket> packetBuffer;
public MainServerThread() {
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
System.out.println("IO error in server thread handler upon trying to make new server socket");
}
packetBuffer = new PriorityBlockingQueue<>();
}
public void run() {
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
while (!isInterrupted()) {
try {
Thread.sleep(1000 / 60); //60 times per second we should calculate the game state
} catch (InterruptedException e) {
e.printStackTrace();
}
//LOBBYING
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getPlayers().size() < MAX_NUM_PLAYERS) {
try {
// TODO: 14/07/17 wmu16 - Get out of blocking call somehow after a time
socket = serverSocket.accept();
} catch (IOException e) {
System.out.println("IO error in server thread handler upon trying to accept connection");
}
ServerToClientThread thread = new ServerToClientThread(socket, this);
serverToClientThreads.add(thread);
thread.start();
}
//RACING
else if (GameState.getCurrentStage() == GameStages.RACING) {
}
//FINISHED
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
}
updateClients();
while (!packetBuffer.isEmpty()){
System.out.println("WHATUPPP");
try {
StreamPacket packet = packetBuffer.take();
StreamParser.parsePacket(packet);
} catch (InterruptedException e) {
continue;
}
}
}
System.out.println("WHOOPSIES");
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
try {
serverSocket.close();
return;
} catch (IOException e) {
System.out.println("IO error in server thread handler upon closing socket");
}
}
public void updateClients() {
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
serverToClientThread.updateClient();
}
}
@Override
public boolean addToBuffer(StreamPacket streamPacket) {
System.out.println("HEY HI");
return packetBuffer.add(streamPacket);
}
}
@@ -0,0 +1,166 @@
package seng302.gameServerWithThreading;
import seng302.gameServer.GameState;
import seng302.models.Player;
import seng302.models.stream.PacketBufferDelegate;
import seng302.models.stream.StreamParser;
import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.Message;
import java.io.*;
import java.net.Socket;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
/**
* 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 {
private static final Integer MAX_ID_ATTEMPTS = 10;
private InputStream is;
private OutputStream os;
private Socket socket;
private ByteArrayOutputStream crcBuffer;
private final PacketBufferDelegate packetBufferDelegate;
private Boolean userIdentified = false;
private Boolean connected = true;
private Boolean updateClient = true;
public ServerToClientThread(Socket socket, PacketBufferDelegate packetBufferDelegate) {
this.socket = socket;
try {
is = socket.getInputStream();
os = socket.getOutputStream();
} catch (IOException e) {
System.out.println("IO error in server thread upon grabbing streams");
}
this.packetBufferDelegate = packetBufferDelegate;
// threeWayHandshake();
GameState.addPlayer(new Player(socket.getChannel()));
}
public void run() {
int sync1;
int sync2;
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
while(true) {
try {
//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
// try {
// GameState.outputState(os);
// } catch (IOException e) {
// System.out.println("IO error in server thread upon writing to output stream");
// }
updateClient = false;
}
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = Message.bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = Message.bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
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!?!?
// packetBufferDelegate.addToBuffer(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
System.err.println("Packet has been dropped");
}
}
} catch (Exception e) {
closeSocket();
return;
}
}
}
public void updateClient() {
updateClient = true;
}
/**
* Tries to confirm the connection just accepted.
* Sends ID, expects that ID echoed for confirmation,
* 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
*/
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++;
// }
}
public void closeSocket() {
try {
socket.close();
} catch (IOException e) {
System.out.println("IO error in server thread upon trying to close socket");
}
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = is.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
}
if (currentByte == -1){
throw new Exception();
}
return currentByte;
}
private byte[] getBytes(int n) throws Exception{
byte[] bytes = new byte[n];
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++){
readByte();
}
}
}
+73
View File
@@ -0,0 +1,73 @@
package seng302.models;
import javafx.scene.paint.Color;
import java.io.IOException;
import java.nio.channels.SocketChannel;
/**
* A Class defining a player and their respective details in the game as held by the model
* Created by wmu16 on 10/07/17.
*/
public class Player {
private SocketChannel socketChannel;
private Yacht yacht;
private Integer lastMarkPassed;
public Player(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
public SocketChannel getSocketChannel() {
return socketChannel;
}
public Integer getLastMarkPassed() {
return lastMarkPassed;
}
public void setLastMarkPassed(Integer lastMarkPassed) {
this.lastMarkPassed = lastMarkPassed;
}
public Yacht getYacht() {
return yacht;
}
@Override
public String toString() {
String playerAddress = null;
if (socketChannel == null){
return "Disconnected Player";
}
try {
playerAddress = socketChannel.getRemoteAddress().toString();
} catch (IOException e) {
e.printStackTrace();
}
return playerAddress;
}
@Override
public boolean equals(Object obj) {
if (obj == null){
return false;
}
if (!(obj instanceof Player)){
return false;
}
return ((Player) obj).socketChannel.equals(socketChannel);
}
@Override
public int hashCode(){
return socketChannel.hashCode();
}
}
+4 -3
View File
@@ -24,9 +24,8 @@ public final class PolarTable {
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
* as a value
* @param file containing the polar csv information
*/
public static void parsePolarFile(String file) {
public static void parsePolarFile(InputStream polarFile) {
polarTable = new HashMap<>();
upwindOptimal = new HashMap<>();
downwindOptimal = new HashMap<>();
@@ -34,7 +33,7 @@ public final class PolarTable {
String line;
Boolean isHeaderLine = true;
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
while ((line = br.readLine()) != null) {
String[] thisLine = line.split(",");
@@ -69,6 +68,8 @@ public final class PolarTable {
} catch (IOException e) {
e.printStackTrace();
}
}
+39 -8
View File
@@ -24,6 +24,10 @@ public class Yacht {
private String shortName;
private String boatName;
private String country;
// Situational data
// Boat status
private Integer boatStatus;
private Integer legNumber;
@@ -31,6 +35,9 @@ public class Yacht {
private Integer penaltiesServed;
private Long estimateTimeAtFinish;
private String position;
private Double lat;
private Double lon;
private Float heading;
private double velocity;
private Long timeTillNext;
private Long markRoundTime;
@@ -191,17 +198,41 @@ public class Yacht {
this.lastMarkRounded = lastMarkRounded;
}
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
public Float getHeading() {
return heading;
}
public void setHeading(Float heading) {
this.heading = heading;
}
@Override
public String toString() {
return boatName;
}
public void setNextMark(Mark nextMark) {
this.nextMark = nextMark;
}
public Mark getNextMark(){
return nextMark;
}
}
@@ -0,0 +1,7 @@
package seng302.models.stream;
import seng302.models.stream.packets.StreamPacket;
public interface PacketBufferDelegate {
boolean addToBuffer(StreamPacket streamPacket);
}
@@ -11,7 +11,6 @@ import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.PriorityBlockingQueue;
@@ -32,7 +31,7 @@ import seng302.models.stream.packets.StreamPacket;
* that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17.
*/
public class StreamParser extends Thread {
public class StreamParser{
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
@@ -58,54 +57,16 @@ public class StreamParser extends Thread {
/**
* Used to initialise the thread name and stream parser object so a thread can be executed
*
* @param threadName name of the thread
*/
public StreamParser(String threadName) {
this.threadName = threadName;
public StreamParser() {
}
/**
* Used to within threading so when the stream parser thread runs, it will keep looking for a
* packet to process until it is unable to find anymore packets
*/
public void run() {
appRunning = true;
try {
streamStatus = true;
xmlObject = new XMLParser();
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
Thread.sleep(1);
}
while (appRunning) {
StreamPacket packet = StreamReceiver.packetBuffer.take();
parsePacket(packet);
Thread.sleep(1);
while (StreamReceiver.packetBuffer.peek() == null) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Used to start the stream parser thread when multithreading
*/
public void start() {
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
/**
* Looks at the type of the packet then sends it to the appropriate parser to extract the
* specific data associated with that packet type
*
* @param packet the packet to be looked at and processed
*/
private static void parsePacket(StreamPacket packet) {
public static void parsePacket(StreamPacket packet) {
try {
switch (packet.getType()) {
case HEARTBEAT:
@@ -145,7 +106,8 @@ public class StreamParser extends Thread {
case AVG_WIND:
extractAvgWind(packet);
break;
default:
case BOAT_ACTION:
extractBoatAction(packet);
break;
}
} catch (NullPointerException e) {
@@ -527,6 +489,27 @@ public class StreamParser extends Thread {
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
}
private static void extractBoatAction(StreamPacket packet) {
byte[] payload = packet.getPayload();
int messageVersionNo = payload[0];
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
if (actionType == 1) {
System.out.println("VMG");
} else if (actionType == 2) {
System.out.println("SAILS IN");
} else if (actionType == 3) {
System.out.println("SAILS OUT");
} else if (actionType == 4) {
System.out.println("TACK/GYBE");
} else if (actionType == 5) {
System.out.println("UPWIND");
} else if (actionType == 6) {
System.out.println("DOWNWIND");
}
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
@@ -1,11 +1,15 @@
package seng302.models.stream;
import seng302.models.stream.packets.StreamPacket;
import seng302.server.messages.BoatActionMessage;
import seng302.server.messages.BoatActionType;
import seng302.server.messages.Heartbeat;
import seng302.server.messages.Message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32;
@@ -13,9 +17,10 @@ import java.util.zip.Checksum;
public class StreamReceiver extends Thread {
private InputStream stream;
private InputStream inputStream;
private OutputStream outputStream;
private Socket host;
private ByteArrayOutputStream crcBuffer;
private ByteArrayOutputStream crcBuffer;
private Thread t;
private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
@@ -58,49 +63,43 @@ public class StreamReceiver extends Thread {
public void connect(){
try {
stream = host.getInputStream();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
int sync1;
int sync2;
moreBytes = true;
while(moreBytes) {
try {
crcBuffer = new ByteArrayOutputStream();
sync1 = readByte();
sync2 = readByte();
//checking if it is the start of the packet
if(sync1 == 0x47 && sync2 == 0x83) {
int type = readByte();
//No. of milliseconds since Jan 1st 1970
long timeStamp = bytesToLong(getBytes(6));
skipBytes(4);
long payloadLength = bytesToLong(getBytes(2));
byte[] payload = getBytes((int) payloadLength);
Checksum checksum = new CRC32();
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
long computedCrc = checksum.getValue();
long packetCrc = bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
} else {
System.err.println("Packet has been dropped");
}
}
} catch (Exception e) {
moreBytes = false;
}
}
// int sync1;
// int sync2;
// moreBytes = true;
// while(moreBytes) {
// try {
// crcBuffer = new ByteArrayOutputStream();
// sync1 = readByte();
// sync2 = readByte();
// //checking if it is the start of the packet
// if(sync1 == 0x47 && sync2 == 0x83) {
// int type = readByte();
// //No. of milliseconds since Jan 1st 1970
// long timeStamp = bytesToLong(getBytes(6));
// skipBytes(4);
// long payloadLength = bytesToLong(getBytes(2));
// byte[] payload = getBytes((int) payloadLength);
// Checksum checksum = new CRC32();
// checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
// long computedCrc = checksum.getValue();
// long packetCrc = bytesToLong(getBytes(4));
// if (computedCrc == packetCrc) {
// packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload));
// } else {
// System.err.println("Packet has been dropped");
// }
// }
// } catch (Exception e) {
// moreBytes = false;
// }
// }
}
private int readByte() throws Exception {
int currentByte = -1;
try {
currentByte = stream.read();
currentByte = inputStream.read();
crcBuffer.write(currentByte);
} catch (IOException e) {
e.printStackTrace();
@@ -16,6 +16,7 @@ public enum PacketType {
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
BOAT_ACTION,
OTHER;
public static PacketType assignPacketType(int packetType){
@@ -44,6 +45,8 @@ public enum PacketType {
return COURSE_WIND;
case 47:
return AVG_WIND;
case 100:
return BOAT_ACTION;
default:
}
return OTHER;
@@ -1,61 +0,0 @@
package seng302.server;
import seng302.server.messages.Message;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.Channels;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
class StreamingServerSocket {
private ServerSocketChannel socket;
private SocketChannel client;
private short seqNum;
private boolean isServerStarted;
StreamingServerSocket(int port) throws IOException{
socket = ServerSocketChannel.open();
socket.socket().bind(new InetSocketAddress("localhost", port));
//socket.setSoTimeout(10000);
seqNum = 0;
isServerStarted = false;
}
void start(){
try {
client = socket.accept();
} catch (IOException e) {
e.getMessage();
}
if (client.socket() == null){
start();
}
else{
isServerStarted = true;
}
}
void send(Message message) throws IOException{
if (client == null){
return;
}
message.send(client);
seqNum++;
}
public short getSequenceNumber(){
return seqNum;
}
public boolean isStarted(){
return isServerStarted;
}
}
@@ -0,0 +1,32 @@
package seng302.server.messages;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Created by kre39 on 12/07/17.
*/
public class BoatActionMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
private final int MESSAGE_SIZE = 1;
private BoatActionType actionType;
public BoatActionMessage(BoatActionType actionType) {
this.actionType = actionType;
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
allocateBuffer();
writeHeaderToBuffer();
// Write message fields
putInt((int) BoatActionType.getBoatPacketType(actionType), 1);
writeCRC();
rewind();
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -0,0 +1,42 @@
package seng302.server.messages;
/**
* Created by kre39 on 12/07/17.
*/
public enum BoatActionType {
VMG(1),
SAILS_IN(2),
SAILS_OUT(3),
TACK_GYBE(4),
UPWIND(5),
DOWNWIND(6);
private int type;
BoatActionType(int type){
this.type = type;
}
public int getType(){
return this.type;
}
public static Short getBoatPacketType(BoatActionType type){
switch (type){
case VMG:
return 1;
case SAILS_IN:
return 2;
case SAILS_OUT:
return 3;
case TACK_GYBE:
return 4;
case UPWIND:
return 5;
case DOWNWIND:
return 6;
}
return 0;
}
}
@@ -1,7 +1,7 @@
package seng302.server.messages;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.io.OutputStream;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
@@ -64,6 +64,36 @@ public class BoatLocationMessage extends Message {
this.rudderAngle = 0;
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
long headingToSend = (long)((heading/360.0) * 65535.0);
putByte((byte) messageVersionNumber);
putInt(time, 6);
putInt((int) sourceId, 4);
putUnsignedInt((int) sequenceNum, 4);
putByte((byte) deviceType.getCode());
putInt((int) latLonToBinaryPackedLong(latitude), 4);
putInt((int) latLonToBinaryPackedLong(longitude), 4);
putInt((int) altitude, 4);
putInt(headingToSend, 2);
putInt((int) pitch, 2);
putInt((int) roll, 2);
putInt((int) boatSpeed, 2);
putUnsignedInt((int) COG, 2);
putUnsignedInt((int) SOG, 2);
putUnsignedInt((int) apparentWindSpeed, 2);
putInt((int) apparentWindAngle, 2);
putUnsignedInt((int) trueWindSpeed, 2);
putUnsignedInt((int) trueWindDirection, 2);
putInt((int) trueWindAngle, 2);
putUnsignedInt((int) currentDrift, 2);
putUnsignedInt((int) currentSet, 2);
putInt((int) rudderAngle, 2);
writeCRC();
rewind();
}
/**
@@ -124,41 +154,4 @@ public class BoatLocationMessage extends Message {
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException{
allocateBuffer();
writeHeaderToBuffer();
long headingToSend = (long)((heading/360.0) * 65535.0);
putByte((byte) messageVersionNumber);
putInt(time, 6);
putInt((int) sourceId, 4);
putUnsignedInt((int) sequenceNum, 4);
putByte((byte) deviceType.getCode());
putInt((int) latLonToBinaryPackedLong(latitude), 4);
putInt((int) latLonToBinaryPackedLong(longitude), 4);
putInt((int) altitude, 4);
putInt(headingToSend, 2);
putInt((int) pitch, 2);
putInt((int) roll, 2);
putInt((int) boatSpeed, 2);
putUnsignedInt((int) COG, 2);
putUnsignedInt((int) SOG, 2);
putUnsignedInt((int) apparentWindSpeed, 2);
putInt((int) apparentWindAngle, 2);
putUnsignedInt((int) trueWindSpeed, 2);
putUnsignedInt((int) trueWindDirection, 2);
putInt((int) trueWindAngle, 2);
putUnsignedInt((int) currentDrift, 2);
putUnsignedInt((int) currentSet, 2);
putInt((int) rudderAngle, 2);
writeCRC();
rewind();
outputStream.write(getBuffer());
}
}
@@ -43,10 +43,21 @@ public class Header {
buff.position(buffPos);
}
/**
* Reset the buffer
*/
public void reset(){
buffPos = 0;
buff.clear();
buff.position(buffPos);
}
/**
* @return a ByteBuffer containing the message header
*/
public ByteBuffer getByteBuffer(){
reset();
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
@@ -1,32 +1,16 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
import java.io.OutputStream;
public class Heartbeat extends Message {
private final int MESSAGE_SIZE = 4;
private int seqNo;
/**
* Heartbeat from the AC35 Streaming data spec
* @param seqNo Increment every time a message is sent
*/
public Heartbeat(int seqNo){
this.seqNo = seqNo;
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
allocateBuffer();
@@ -36,7 +20,11 @@ public class Heartbeat extends Message {
writeCRC();
rewind();
outputStream.write(getBuffer());
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -1,10 +1,7 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.io.OutputStream;
public class MarkRoundingMessage extends Message{
private final long MESSAGE_VERSION_NUMBER = 1;
@@ -33,15 +30,6 @@ public class MarkRoundingMessage extends Message{
this.markId = markId;
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -56,7 +44,10 @@ public class MarkRoundingMessage extends Message{
writeCRC();
rewind();
}
outputStream.write(getBuffer());
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -1,9 +1,9 @@
package seng302.server.messages;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.zip.CRC32;
@@ -33,11 +33,6 @@ public abstract class Message {
*/
public abstract int getSize();
/**
* Send the message as through the outputStream
*/
public abstract void send(SocketChannel outputStream) throws IOException;
/**
* Allocate byte buffer to correct size
*/
@@ -45,6 +40,7 @@ public abstract class Message {
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
buffer.order(ByteOrder.LITTLE_ENDIAN);
bufferPosition = 0;
buffer.position(bufferPosition);
}
/**
@@ -161,10 +157,10 @@ public abstract class Message {
}
/**
* @return The current buffer
* @return The current buffer as a byte array
*/
public ByteBuffer getBuffer(){
return buffer;
public byte[] getBuffer(){
return buffer.array();
}
/**
@@ -193,6 +189,25 @@ public abstract class Message {
return data;
}
/**
* takes an array of up to 7 bytes in little endian format and
* returns a positive long constructed from the input bytes
*
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
public 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;
}
/**
* Reverse an array of bytes
* @param data The byte[] to reverse
@@ -204,4 +219,5 @@ public abstract class Message {
data[right] = (byte) (temp & 0xff);
}
}
}
@@ -16,7 +16,8 @@ public enum MessageType {
BOAT_LOCATION(37),
MARK_ROUNDING(38),
COURSE_WIND(44),
AVERAGE_WIND(47);
AVERAGE_WIND(47),
BOAT_ACTION(100);
private int code;
@@ -1,10 +1,7 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.io.OutputStream;
public class RaceStartStatusMessage extends Message {
private final int MESSAGE_SIZE = 20;
@@ -32,15 +29,6 @@ public class RaceStartStatusMessage extends Message {
this.raceId = raceId;
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -53,7 +41,11 @@ public class RaceStartStatusMessage extends Message {
writeCRC();
rewind();
outputStream.write(getBuffer());
}
@Override
public int getSize() {
return MESSAGE_SIZE;
}
}
@@ -1,7 +1,7 @@
package seng302.server.messages;
import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.CRC32;
@@ -47,22 +47,6 @@ public class RaceStatusMessage extends Message{
crc = new CRC32();
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
}
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
@Override
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -82,7 +66,14 @@ public class RaceStatusMessage extends Message{
writeCRC();
rewind();
outputStream.write(getBuffer());
}
/**
* @return the size of this message in bytes
*/
@Override
public int getSize() {
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
}
}
@@ -1,12 +1,7 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.zip.CRC32;
import java.io.OutputStream;
public class XMLMessage extends Message{
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
@@ -35,20 +30,6 @@ public class XMLMessage extends Message{
sequence = sequenceNum;
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
}
/**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
}
/**
* Send this message as a stream of bytes
* @param outputStream The output stream to send the message
*/
public void send(SocketChannel outputStream) throws IOException {
allocateBuffer();
writeHeaderToBuffer();
@@ -63,7 +44,12 @@ public class XMLMessage extends Message{
writeCRC();
rewind();
}
outputStream.write(getBuffer());
/**
* @return The length of this message
*/
public int getSize(){
return MESSAGE_SIZE + content.length();
}
}
@@ -1,5 +1,7 @@
package seng302.utilities;
import javafx.geometry.Point2D;
public class GeoUtility {
private static double EARTH_RADIUS = 6378.137;
@@ -77,4 +79,55 @@ public class GeoUtility {
return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng));
}
/**
* Performs the line function on two points of a line and a test point to test which side of the line that point is
* on. If the return value is
* return 1, then the point is on one side of the line,
* return -1 then the point is on the other side of the line
* return 0 then the point is exactly on the line.
* @param linePoint1 One point of the line
* @param linePoint2 Second point of the line
* @param testPoint The point to test with this line
* @return A return value indicating which side of the line the point is on
*/
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
Double x = testPoint.getX();
Double y = testPoint.getY();
Double x1 = linePoint1.getX();
Double y1 = linePoint1.getY();
Double x2 = linePoint2.getX();
Double y2 = linePoint2.getY();
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
if (result > 0) {
return 1;
}
else if (result < 0) {
return -1;
}
else {
return 0;
}
}
/**
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
* point
* @param originPoint The point with which to use as the base for our vector addition
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
* @param vectorLength The length out on this angle from the origin point to create the new point
* @return a Point2D
*/
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
return new Point2D(endPointX, endPointY);
}
}
@@ -1,63 +0,0 @@
package seng302.utilities;
import javafx.geometry.Point2D;
/**
* A Class for performing geometric calculations on the canvas
* Created by wmu16 on 24/05/17.
*/
public final class GeometryUtils {
/**
* Performs the line function on two points of a line and a test point to test which side of the line that point is
* on. If the return value is
* return 1, then the point is on one side of the line,
* return -1 then the point is on the other side of the line
* return 0 then the point is exactly on the line.
* @param linePoint1 One point of the line
* @param linePoint2 Second point of the line
* @param testPoint The point to test with this line
* @return A return value indicating which side of the line the point is on
*/
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
Double x = testPoint.getX();
Double y = testPoint.getY();
Double x1 = linePoint1.getX();
Double y1 = linePoint1.getY();
Double x2 = linePoint2.getX();
Double y2 = linePoint2.getY();
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
if (result > 0) {
return 1;
}
else if (result < 0) {
return -1;
}
else {
return 0;
}
}
/**
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
* point
* @param originPoint The point with which to use as the base for our vector addition
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
* @param vectorLength The length out on this angle from the origin point to create the new point
* @return a Point2D
*/
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
return new Point2D(endPointX, endPointY);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+2 -2
View File
@@ -168,7 +168,7 @@ Remove scroll bars
.ui-table *.scroll-bar:vertical *.increment-arrow,
.ui-table *.scroll-bar:vertical *.decrement-arrow {
-fx-background-color: null;
-fx-background-color: #0e6d6c;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
@@ -177,7 +177,7 @@ Remove scroll bars
.ui-table *.scroll-bar:vertical *.increment-button,
.ui-table *.scroll-bar:vertical *.decrement-button {
-fx-background-color: null;
-fx-background-color: #0e6d6c;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="lobbyScreen" nodeOrientation="LEFT_TO_RIGHT" prefHeight="533.0" prefWidth="802.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.LobbyController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="171.0" minHeight="0.0" prefHeight="31.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="399.0" minHeight="10.0" prefHeight="394.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="63.0" minHeight="10.0" prefHeight="26.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Text fx:id="lobbyIpText" fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Lobby: IP" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER">
<font>
<Font size="29.0" />
</font>
</Text>
<GridPane GridPane.rowIndex="2">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button focusTraversable="false" mnemonicParsing="false" onAction="#readyButtonPressed" prefWidth="101.0" text="Ready" GridPane.halignment="CENTER" />
<Button focusTraversable="false" mnemonicParsing="false" onAction="#leaveLobbyButtonPressed" text="Leave Lobby" GridPane.columnIndex="1" GridPane.halignment="CENTER" />
</children>
</GridPane>
<AnchorPane focusTraversable="true" prefHeight="200.0" prefWidth="200.0" GridPane.rowIndex="1" />
</children>
</GridPane>
+2 -1
View File
@@ -2,4 +2,5 @@
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller" />
<AnchorPane fx:id="contentPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onKeyPressed="#keyPressed" onKeyReleased="#keyReleased" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.Controller" />
+3 -3
View File
@@ -35,7 +35,7 @@
<Font name="System Bold" size="13.0" />
</font>
</Text>
<CheckBox fx:id="toggleFps" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
<CheckBox fx:id="toggleFps" focusTraversable="false" graphicTextGap="0.0" layoutX="21.0" layoutY="453.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="143.0" selected="true" styleClass="ui-checkbox" text="Show FPS" textFill="WHITE" />
<VBox fx:id="positionVbox" layoutX="12.0" layoutY="280.0" prefHeight="140.0" prefWidth="200.0" styleClass="text-white" />
<Pane layoutX="11.0" layoutY="30.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="51.0" prefWidth="193.0">
<children>
@@ -48,9 +48,9 @@
</Pane>
<Slider fx:id="annotationSlider" blockIncrement="1.0" layoutX="38.0" layoutY="527.0" majorTickUnit="1.0" max="2.0" minorTickCount="0" prefHeight="51.0" prefWidth="170.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" styleClass="ui-slider" />
<Label layoutX="10.0" layoutY="499.0" text="Annotations" textFill="WHITE" />
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Button fx:id="selectAnnotationBtn" focusTraversable="false" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
<ComboBox fx:id="boatSelectionComboBox" focusTraversable="false" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
<LineChart fx:id="raceSparkLine" layoutX="-1.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="246.0" title="Boat Positions">
<xAxis>
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
+30 -42
View File
@@ -1,60 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>
<GridPane fx:id="gridPane" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.StartScreenController">
<GridPane fx:id="startScreen2" nodeOrientation="LEFT_TO_RIGHT" prefWidth="800.0" style="-fx-background-color: #2C2c36;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.StartScreenController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints percentHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="52.0" minHeight="52.0" prefHeight="52.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" percentHeight="8.0" prefHeight="0.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="28.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="55.0" minHeight="55.0" percentHeight="9.0" prefHeight="55.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="0.0" minHeight="0.0" percentHeight="29.0" prefHeight="0.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="93.0" minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="283.0" minHeight="262.0" prefHeight="283.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="65.0" minHeight="36.0" prefHeight="46.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="108.0" minHeight="72.0" prefHeight="98.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="72.0" prefHeight="72.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label alignment="CENTER" text="Welcome to Race Vision" textFill="WHITE" GridPane.halignment="CENTER" GridPane.valignment="BOTTOM">
<Label alignment="CENTER" text="Welcome to Race Vision" textFill="WHITE" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<font>
<Font size="40.0" />
</font>
</Label>
<Label text="Your live AC35 livestream" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="1">
<font>
<Font size="20.0" />
</font>
</Label>
<Label text="Livestream Status:" textFill="WHITE" GridPane.halignment="CENTER" GridPane.rowIndex="2" GridPane.valignment="BOTTOM">
<font>
<Font size="28.0" />
</font>
</Label>
<Label fx:id="timeTillLive" text="0:00 minutes" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="4">
<font>
<Font size="27.0" />
</font>
</Label>
<Button fx:id="streamButton" mnemonicParsing="false" onAction="#startStream" styleClass="blue-ui-btn" text="Click to stream" GridPane.halignment="CENTER" GridPane.rowIndex="4" />
<Button fx:id="switchToRaceViewButton" disable="true" mnemonicParsing="false" onAction="#switchToRaceView" styleClass="blue-ui-btn" text="Watch Race" GridPane.halignment="CENTER" GridPane.rowIndex="7" GridPane.valignment="TOP" />
<TableView fx:id="teamList" maxWidth="661.0" prefHeight="324.0" prefWidth="629.0" styleClass="ui-table" GridPane.halignment="CENTER" GridPane.hgrow="NEVER" GridPane.rowIndex="5" GridPane.vgrow="NEVER">
<columns>
<TableColumn fx:id="posCol" editable="false" maxWidth="74.0" minWidth="74.0" prefWidth="74.0" resizable="false" sortable="false" text="Position" />
<TableColumn fx:id="boatNameCol" editable="false" maxWidth="171.0" minWidth="171.0" prefWidth="171.0" resizable="false" sortable="false" text="Boat Name" />
<TableColumn fx:id="shortNameCol" editable="false" maxWidth="155.18472290039062" minWidth="107.0" prefWidth="155.18472290039062" resizable="false" sortable="false" text="Short Name" />
<TableColumn fx:id="countryCol" editable="false" maxWidth="258.9999694824219" minWidth="147.0" prefWidth="258.9999694824219" resizable="false" sortable="false" text="Country" />
</columns>
<GridPane.margin>
<Insets top="10.0" />
</GridPane.margin>
</TableView>
<Label fx:id="realTime" text="Local time" textFill="WHITE" visible="false" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="BOTTOM" />
<Button mnemonicParsing="false" onAction="#hostButtonPressed" prefHeight="25.0" prefWidth="175.0" text="Host" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
<Button mnemonicParsing="false" onAction="#connectButtonPressed" prefHeight="25.0" prefWidth="147.0" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<TextField fx:id="ipTextField" maxWidth="-Infinity" prefHeight="25.0" prefWidth="200.0" text="localhost" GridPane.halignment="RIGHT" GridPane.rowIndex="4">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</TextField>
<Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="OR" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.rowIndex="3">
<font>
<Font size="21.0" />
</font>
</Text>
</children>
</GridPane>
+6 -6
View File
@@ -3,7 +3,7 @@ package seng302;
import javafx.geometry.Point2D;
import org.junit.Before;
import org.junit.Test;
import seng302.utilities.GeometryUtils;
import seng302.utilities.GeoUtility;
import static org.junit.Assert.*;
@@ -35,9 +35,9 @@ public class TestGeoUtils {
@Test
public void testLineFunction() {
Integer lineFunctionResult1 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint1);
Integer lineFunctionResult2 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint2);
Integer lineFunctionResult3 = GeometryUtils.lineFunction(linePoint1, linePoint2, arbitraryPoint3);
Integer lineFunctionResult1 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint1);
Integer lineFunctionResult2 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint2);
Integer lineFunctionResult3 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint3);
//Point1 and Point2 are on opposite sides
assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2));
@@ -51,13 +51,13 @@ public class TestGeoUtils {
public void testMakeArbitraryVectorPoint() {
//Make a point (1,0) from point (0,0)
Point2D newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 0d, 1d);
Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d);
Point2D expected = new Point2D(1,0);
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
assertEquals(expected.getY(), newPoint.getY(), 1E-6);
newPoint = GeometryUtils.makeArbitraryVectorPoint(linePoint1, 90d, 1d);
newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d);
expected = new Point2D(0, 1);
assertEquals(expected.getX(), newPoint.getX(), 1E-6);
@@ -61,16 +61,16 @@ public class StreamReceiverTest {
assert pq.size() == 0;
}
@Test
public void connectReadsAPacket() throws Exception {
Socket host=mock(Socket.class);
InputStream stream = new ByteArrayInputStream(workingPacket);
when(host.getInputStream()).thenReturn(stream);
StreamReceiver streamReceiver = new StreamReceiver(host, pq);
streamReceiver.connect();
assert pq.size() == 1;
}
// @Test
// public void connectReadsAPacket() throws Exception {
// Socket host=mock(Socket.class);
// InputStream stream = new ByteArrayInputStream(workingPacket);
// when(host.getInputStream()).thenReturn(stream);
// StreamReceiver streamReceiver = new StreamReceiver(host, pq);
//
// streamReceiver.connect();
// assert pq.size() == 1;
// }
@Test
public void connectDropsAMismatchedCrc() throws Exception {