mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Merge branch 'Story29' into merge_branch_front
# Conflicts: # src/main/java/seng302/App.java # src/main/java/seng302/controllers/CanvasController.java # src/main/java/seng302/controllers/Controller.java # src/main/java/seng302/controllers/RaceViewController.java # src/main/java/seng302/models/Boat.java # src/main/java/seng302/models/Colors.java # src/main/java/seng302/models/Event.java # src/main/java/seng302/models/Race.java # src/main/java/seng302/models/mark/GateMark.java # src/main/java/seng302/models/mark/Mark.java # src/main/java/seng302/models/mark/MarkType.java # src/main/java/seng302/models/mark/SingleMark.java # src/main/java/seng302/models/parsers/CourseParser.java # src/main/java/seng302/models/parsers/TeamsParser.java # src/main/resources/views/MainView.fxml # src/main/resources/views/RaceView.fxml # src/test/java/seng302/BoatTest.java # src/test/java/seng302/ColorsTest.java # src/test/java/seng302/EventTest.java # src/test/java/seng302/models/mark/MarkTest.java # src/test/java/seng302/models/parsers/CourseParserTest.java
This commit is contained in:
@@ -16,4 +16,4 @@
|
|||||||
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
||||||
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
||||||
|
|
||||||
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz> <michael@michaelrausch.net>
|
||||||
@@ -20,6 +20,11 @@
|
|||||||
<version>4.12</version>
|
<version>4.12</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>1.3.2</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.googlecode.json-simple</groupId>
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
<artifactId>json-simple</artifactId>
|
<artifactId>json-simple</artifactId>
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import seng302.models.Boat;
|
|
||||||
import seng302.models.Race;
|
|
||||||
import seng302.models.parsers.ConfigParser;
|
|
||||||
import seng302.models.parsers.CourseParser;
|
|
||||||
import seng302.models.parsers.TeamsParser;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by zyt10 on 17/03/17.
|
|
||||||
* run before CanvasController to initialize race events
|
|
||||||
* the CanvasController then uses the event data to make the animations
|
|
||||||
*/
|
|
||||||
public class RaceController {
|
|
||||||
Race race = null;
|
|
||||||
|
|
||||||
public void initializeRace() {
|
|
||||||
String raceConfigFile = "/config/config.xml";
|
|
||||||
String teamsConfigFile = "/config/teams.xml";
|
|
||||||
|
|
||||||
try {
|
|
||||||
race = createRace(raceConfigFile, teamsConfigFile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("There was an error creating the race.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (race != null) {
|
|
||||||
race.startRace();
|
|
||||||
} else {
|
|
||||||
System.out.println("There was an error creating the race. Exiting.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
|
|
||||||
Race race = new Race();
|
|
||||||
|
|
||||||
// Read team names from file
|
|
||||||
TeamsParser tp = new TeamsParser(teamsConfigFile);
|
|
||||||
|
|
||||||
// Read course from file
|
|
||||||
ConfigParser config = new ConfigParser(configFile);
|
|
||||||
|
|
||||||
ArrayList<String> boatNames = new ArrayList<>();
|
|
||||||
ArrayList<Boat> teams = tp.getBoats();
|
|
||||||
|
|
||||||
//get race size
|
|
||||||
int numberOfBoats = teams.size();
|
|
||||||
|
|
||||||
//get time scale
|
|
||||||
double timeScale = config.getTimeScale();
|
|
||||||
race.setTimeScale(timeScale);
|
|
||||||
|
|
||||||
for (Boat boat : teams) {
|
|
||||||
boatNames.add(boat.getTeamName());
|
|
||||||
race.addBoat(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle team names
|
|
||||||
long seed = System.nanoTime();
|
|
||||||
Collections.shuffle(boatNames, new Random(seed));
|
|
||||||
|
|
||||||
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
CourseParser course = new CourseParser("/config/course.xml");
|
|
||||||
race.addCourse(course.getCourse());
|
|
||||||
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Race getRace() {
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import seng302.models.Race;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ptg19 on 20/03/17.
|
|
||||||
*/
|
|
||||||
public class RaceResultController implements Initializable{
|
|
||||||
@FXML private AnchorPane window;
|
|
||||||
@FXML private VBox resultsVBox;
|
|
||||||
private Race race;
|
|
||||||
|
|
||||||
RaceResultController(Race race){
|
|
||||||
this.race = race;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
int boatPosition = this.race.getFinishedBoats().length;
|
|
||||||
|
|
||||||
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
|
|
||||||
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getTeamName()));
|
|
||||||
boatPosition--;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package seng302.models;
|
|
||||||
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the leg of a race.
|
|
||||||
*/
|
|
||||||
public class Leg {
|
|
||||||
private int heading;
|
|
||||||
private int distance;
|
|
||||||
private boolean isFinishingLeg;
|
|
||||||
private SingleMark startingSingleMark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new leg
|
|
||||||
*
|
|
||||||
* @param heading, the magnetic heading of this leg
|
|
||||||
* @param distance, the total distance of this leg in meters
|
|
||||||
* @param singleMark, the singleMark this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, SingleMark singleMark) {
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingSingleMark = singleMark;
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new leg
|
|
||||||
*
|
|
||||||
* @param heading, the magnetic heading of this leg
|
|
||||||
* @param distance, the total distance of this leg in meters
|
|
||||||
* @param markerName, the name of the marker this leg starts on
|
|
||||||
*/
|
|
||||||
public Leg(int heading, int distance, String markerName) {
|
|
||||||
this.heading = heading;
|
|
||||||
this.distance = distance;
|
|
||||||
this.startingSingleMark = new SingleMark(markerName);
|
|
||||||
this.isFinishingLeg = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the heading of this leg
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public int getHeading() {
|
|
||||||
return this.heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the heading for this leg
|
|
||||||
* @param heading
|
|
||||||
*/
|
|
||||||
public void setHeading(int heading) {
|
|
||||||
this.heading = heading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total distance of this leg in meters
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public int getDistance() {
|
|
||||||
return this.distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the distance of this leg in meters
|
|
||||||
* @param distance
|
|
||||||
*/
|
|
||||||
public void setDistance(int distance) {
|
|
||||||
this.distance = distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the marker this leg started on
|
|
||||||
* @return SingleMark
|
|
||||||
*/
|
|
||||||
public SingleMark getMarker() {
|
|
||||||
return this.startingSingleMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the singleMark this leg starts on
|
|
||||||
* @param singleMark
|
|
||||||
*/
|
|
||||||
public void setMarker(SingleMark singleMark) {
|
|
||||||
this.startingSingleMark = singleMark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the marker this leg started on
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String getMarkerLabel() {
|
|
||||||
return this.startingSingleMark.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify whether or not the race finishes on this leg
|
|
||||||
*
|
|
||||||
* @param isFinishingLeg whether or not the race finishes on this leg
|
|
||||||
*/
|
|
||||||
public void setFinishingLeg(boolean isFinishingLeg) {
|
|
||||||
this.isFinishingLeg = isFinishingLeg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the race finishes after this leg
|
|
||||||
* @return true if this the race finishes after this leg
|
|
||||||
*/
|
|
||||||
public boolean getIsFinishingLeg() {
|
|
||||||
return this.isFinishingLeg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package seng302.models;
|
|
||||||
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.beans.property.DoubleProperty;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by zyt10 on 17/03/17.
|
|
||||||
* this class is literally just to associate a timeline with a DoubleProperty x and y
|
|
||||||
*/
|
|
||||||
public class TimelineInfo {
|
|
||||||
private Timeline timeline;
|
|
||||||
private DoubleProperty x;
|
|
||||||
private DoubleProperty y;
|
|
||||||
|
|
||||||
public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) {
|
|
||||||
this.timeline = timeline;
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Timeline getTimeline() {
|
|
||||||
return timeline;
|
|
||||||
}
|
|
||||||
public DoubleProperty getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
public DoubleProperty getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package seng302.models.parsers;
|
|
||||||
|
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
|
|
||||||
import java.util.DoubleSummaryStatistics;
|
|
||||||
|
|
||||||
public class ConfigParser extends FileParser {
|
|
||||||
|
|
||||||
private Document doc;
|
|
||||||
|
|
||||||
public ConfigParser(String path) {
|
|
||||||
super(path);
|
|
||||||
this.doc = this.parseFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets wind direction from config file.
|
|
||||||
*
|
|
||||||
* @return a double type degree, or 0 if no value or invalid value is found
|
|
||||||
*/
|
|
||||||
public double getWindDirection() {
|
|
||||||
return getDoubleByTagName("wind-direction", 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a non negative time scale for the race
|
|
||||||
*
|
|
||||||
* @return a double type scale, or 0 if no scale or invalid scale is found
|
|
||||||
*/
|
|
||||||
public double getTimeScale() {
|
|
||||||
return getDoubleByTagName("time-scale", 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a double type number by given tag name found in xml file
|
|
||||||
*
|
|
||||||
* @param tagName a string of tag name
|
|
||||||
* @param defaultVal value returned if no value or invalid value is found
|
|
||||||
* @return value found
|
|
||||||
*/
|
|
||||||
public double getDoubleByTagName(String tagName, double defaultVal) {
|
|
||||||
double val = defaultVal;
|
|
||||||
try {
|
|
||||||
Node node = this.doc.getElementsByTagName(tagName).item(0);
|
|
||||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
|
||||||
Element element = (Element) node;
|
|
||||||
val = Double.valueOf(element.getTextContent());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a string by given tag name found in xml file
|
|
||||||
*
|
|
||||||
* @param tagName a string of tag name
|
|
||||||
* @param defaultVal a string returned if no value or invalid value is found
|
|
||||||
* @return string found
|
|
||||||
*/
|
|
||||||
public String getStringByTagName(String tagName, String defaultVal) {
|
|
||||||
String string = defaultVal;
|
|
||||||
try {
|
|
||||||
Node node = this.doc.getElementsByTagName(tagName).item(0);
|
|
||||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
|
||||||
Element element = (Element) node;
|
|
||||||
string = element.getTextContent();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
} finally {
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.Simulator;
|
||||||
|
import sun.misc.IOUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerThread implements Runnable, Observer {
|
||||||
|
private Thread runner;
|
||||||
|
private StreamingServerSocket server;
|
||||||
|
private long startTime;
|
||||||
|
boolean raceStarted = false;
|
||||||
|
Map<Integer,Boolean> boatsFinished = new HashMap<>();
|
||||||
|
private List<Boat> boats;
|
||||||
|
private Simulator raceSimulator;
|
||||||
|
|
||||||
|
private final int HEARTBEAT_PERIOD = 5000;
|
||||||
|
private final int RACE_STATUS_PERIOD = 1000;
|
||||||
|
private final int RACE_START_STATUS_PERIOD = 1000;
|
||||||
|
private final int BOAT_LOCATION_PERIOD = 1000/5;
|
||||||
|
private final int PORT_NUMBER = 8085;
|
||||||
|
private final int TIME_TILL_RACE_START = 20*1000;
|
||||||
|
private static final int LOG_LEVEL = 1;
|
||||||
|
|
||||||
|
public ServerThread(String threadName){
|
||||||
|
runner = new Thread(this, threadName);
|
||||||
|
serverLog("Spawning Server", 0);
|
||||||
|
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
|
||||||
|
boats = raceSimulator.getBoats();
|
||||||
|
|
||||||
|
for (Boat b : boats){
|
||||||
|
boatsFinished.put(b.getSourceID(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void serverLog(String message, int logLevel){
|
||||||
|
if(logLevel <= LOG_LEVEL){
|
||||||
|
System.out.println("[SERVER] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns an XML Message from the file specified
|
||||||
|
* @param fileName The source XML file
|
||||||
|
* @param type The XML Message type
|
||||||
|
* @return The XML Message
|
||||||
|
*/
|
||||||
|
public Message getXmlMessage(String fileName, XMLMessageSubType type){
|
||||||
|
String fileContents = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream thisStream = this.getClass().getResourceAsStream(fileName);
|
||||||
|
fileContents = new String(org.apache.commons.io.IOUtils.toByteArray(thisStream));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContents != null){
|
||||||
|
return new XMLMessage(fileContents, type, server.getSequenceNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Get a race status message for the current race
|
||||||
|
*/
|
||||||
|
public Message getRaceStatusMessage(){
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<BoatSubMessage>();
|
||||||
|
BoatStatus boatStatus;
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
boolean thereAreBoatsNotFinished = false;
|
||||||
|
|
||||||
|
for (Boat b : boats){
|
||||||
|
if (!raceStarted){
|
||||||
|
boatStatus = BoatStatus.PRESTART;
|
||||||
|
thereAreBoatsNotFinished = true;
|
||||||
|
}
|
||||||
|
else if(boatsFinished.get(b.getSourceID())){
|
||||||
|
boatStatus = BoatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
boatStatus = BoatStatus.PRESTART;
|
||||||
|
thereAreBoatsNotFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatSubMessage m = new BoatSubMessage(b.getSourceID(), boatStatus, b.getLastPassedCorner().getSeqID(), 0, 0, 0, 0);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thereAreBoatsNotFinished){
|
||||||
|
if (raceStarted){
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
long timeDifference = startTime - currentTime;
|
||||||
|
|
||||||
|
if (timeDifference > 60*3){
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
}
|
||||||
|
else if (timeDifference > 60){
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
raceStatus = RaceStatus.TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, startTime, WindDirection.EAST,
|
||||||
|
100, boats.size(), RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an instance of the race simulator
|
||||||
|
*/
|
||||||
|
private void startRaceSim(){
|
||||||
|
serverLog("Starting Race Simulator", 0);
|
||||||
|
raceSimulator.addObserver(this);
|
||||||
|
new Thread(raceSimulator).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts sending heartbeat messages to the client
|
||||||
|
*/
|
||||||
|
private void startSendingHeartbeats(){
|
||||||
|
serverLog("Sending Heartbeats", 0);
|
||||||
|
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) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, HEARTBEAT_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending race start status messages until race starts
|
||||||
|
*/
|
||||||
|
private void startSendingRaceStartStatusMessages(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Message raceStartStatusMessage = new RaceStartStatusMessage(server.getSequenceNumber(), startTime , 1,
|
||||||
|
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
try {
|
||||||
|
if (startTime < System.currentTimeMillis() && !raceStarted){
|
||||||
|
startRaceSim();
|
||||||
|
raceStarted = true;
|
||||||
|
serverLog("Race Started", 0);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
server.send(raceStartStatusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, RACE_START_STATUS_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start sending race start status messages until race starts
|
||||||
|
*/
|
||||||
|
private void startSendingRaceStatusMessages(){
|
||||||
|
serverLog("Sending race status messages", 0);
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Message raceStatusMessage = getRaceStatusMessage();
|
||||||
|
try {
|
||||||
|
server.send(raceStatusMessage);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.print("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, RACE_STATUS_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the race, boat, and regatta XML files to the client
|
||||||
|
*/
|
||||||
|
private void sendXml(){
|
||||||
|
try{
|
||||||
|
Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE);
|
||||||
|
Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT);
|
||||||
|
Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA);
|
||||||
|
|
||||||
|
if (raceData != null){
|
||||||
|
server.send(raceData);
|
||||||
|
serverLog("Sending race data", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boatData != null){
|
||||||
|
server.send(boatData);
|
||||||
|
serverLog("Sending boat data", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regatta != null){
|
||||||
|
server.send(regatta);
|
||||||
|
serverLog("Sending regatta data", 0);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try{
|
||||||
|
server = new StreamingServerSocket(PORT_NUMBER);
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
serverLog("Failed to bind socket: " + e.getMessage(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client to connect
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
startTime = System.currentTimeMillis() + TIME_TILL_RACE_START;
|
||||||
|
|
||||||
|
startSendingHeartbeats();
|
||||||
|
sendXml();
|
||||||
|
startSendingRaceStartStatusMessages();
|
||||||
|
startSendingRaceStatusMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a boat location message when they are updated by the simulator
|
||||||
|
* @param o .
|
||||||
|
* @param arg .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
// Only send if server started
|
||||||
|
if(!server.isStarted()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Boat b : ((Simulator) o).getBoats()){
|
||||||
|
try {
|
||||||
|
Message m = new BoatLocationMessage(b.getSourceID(), 1, b.getLat(),
|
||||||
|
b.getLng(), b.getHeadingCorner().getBearingToNextCorner(),
|
||||||
|
((long) b.getSpeed()));
|
||||||
|
server.send(m);
|
||||||
|
} catch (IOException e) {
|
||||||
|
serverLog("Couldn't send a boat status message", 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (NullPointerException e){
|
||||||
|
serverLog("Boat " + b.getSourceID() + " finished the race", 1);
|
||||||
|
boatsFinished.put(b.getSourceID(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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(){
|
||||||
|
ServerThread.serverLog("Listening For Connections",0);
|
||||||
|
try {
|
||||||
|
client = socket.accept();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.getMessage();
|
||||||
|
}
|
||||||
|
if (client.socket() == null){
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
isServerStarted = true;
|
||||||
|
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,166 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class BoatLocationMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 56;
|
||||||
|
|
||||||
|
private long messageVersionNumber;
|
||||||
|
private long time;
|
||||||
|
private long sourceId;
|
||||||
|
private long sequenceNum;
|
||||||
|
private DeviceType deviceType;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
private long altitude;
|
||||||
|
private Double heading;
|
||||||
|
private long pitch;
|
||||||
|
private long roll;
|
||||||
|
private long boatSpeed;
|
||||||
|
private long COG;
|
||||||
|
private long SOG;
|
||||||
|
private long apparentWindSpeed;
|
||||||
|
private long apparentWindAngle;
|
||||||
|
private long trueWindSpeed;
|
||||||
|
private long trueWindDirection;
|
||||||
|
private long trueWindAngle;
|
||||||
|
private long currentDrift;
|
||||||
|
private long currentSet;
|
||||||
|
private long rudderAngle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the location, altitude and sensor data from the boat.
|
||||||
|
* @param sourceId ID of the boat
|
||||||
|
* @param sequenceNum Sequence number of the message
|
||||||
|
* @param latitude The boats latitude
|
||||||
|
* @param longitude The boats longitude
|
||||||
|
* @param heading The boats heading
|
||||||
|
* @param boatSpeed The boats speed
|
||||||
|
*/
|
||||||
|
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||||
|
messageVersionNumber = 1;
|
||||||
|
time = System.currentTimeMillis() / 1000L;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.sequenceNum = sequenceNum;
|
||||||
|
this.deviceType = DeviceType.RACING_YACHT;
|
||||||
|
this.latitude = latitude;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.altitude = 0;
|
||||||
|
this.heading = heading;
|
||||||
|
this.pitch = 0;
|
||||||
|
this.roll = 0;
|
||||||
|
this.boatSpeed = boatSpeed;
|
||||||
|
this.COG = 0;
|
||||||
|
this.SOG = 0;
|
||||||
|
this.apparentWindSpeed = 0;
|
||||||
|
this.apparentWindAngle = 0;
|
||||||
|
this.trueWindSpeed = 0;
|
||||||
|
this.trueWindDirection = 0;
|
||||||
|
this.trueWindAngle = 0;
|
||||||
|
this.currentDrift = 0;
|
||||||
|
this.currentSet = 0;
|
||||||
|
this.rudderAngle = 0;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary latitude or longitude to floating point number
|
||||||
|
* @param binaryPackedLatLon Binary packed lat OR lon
|
||||||
|
* @return Floating point lat/lon
|
||||||
|
*/
|
||||||
|
public static double binaryPackedToLatLon(long binaryPackedLatLon){
|
||||||
|
return (double)binaryPackedLatLon * 180.0 / 2147483648.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary packed heading to floating point number
|
||||||
|
* @param binaryPackedHeading Binary packed heading
|
||||||
|
* @return heading as a decimal
|
||||||
|
*/
|
||||||
|
public static double binaryPackedHeadingToDouble(long binaryPackedHeading){
|
||||||
|
return (double)binaryPackedHeading * 360.0 / 65536.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary packed wind angle to floating point number
|
||||||
|
* @param binaryPackedWindAngle Binary packed wind angle
|
||||||
|
* @return wind angle as a decimal
|
||||||
|
*/
|
||||||
|
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){
|
||||||
|
return (double)binaryPackedWindAngle*180.0/32768.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a latitude or longitude to a binary packed long
|
||||||
|
* @param latLon A floating point latitude/longitude
|
||||||
|
* @return A binary packed lat/lon
|
||||||
|
*/
|
||||||
|
public static long latLonToBinaryPackedLong(double latLon){
|
||||||
|
return (long)((536870912 * latLon) / 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a heading to a binary packed long
|
||||||
|
* @param heading A floating point heading
|
||||||
|
* @return A binary packed heading
|
||||||
|
*/
|
||||||
|
public static long headingToBinaryPackedLong(double heading){
|
||||||
|
return (long)((8192*heading)/45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a wind angle to a binary packed long
|
||||||
|
* @param windAngle Floating point wind angle
|
||||||
|
* @return A binary packed wind angle
|
||||||
|
*/
|
||||||
|
public static long windAngleToBinaryPackedLong(double windAngle){
|
||||||
|
return (long)((8192*windAngle)/45);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
putUnsignedInt((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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of a boat
|
||||||
|
*/
|
||||||
|
public enum BoatStatus {
|
||||||
|
UNDEFINED(0),
|
||||||
|
PRESTART(1),
|
||||||
|
RACING(2),
|
||||||
|
FINISHED(3),
|
||||||
|
DNS(4),
|
||||||
|
DNF(5),
|
||||||
|
DSQ(6),
|
||||||
|
CS(7);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
BoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of each boat, sent within a race status message
|
||||||
|
*/
|
||||||
|
public class BoatSubMessage{
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long sourceId;
|
||||||
|
private BoatStatus boatStatus;
|
||||||
|
private long legNumber;
|
||||||
|
private long numberPenaltiesAwarded;
|
||||||
|
private long numberPenaltiesServed;
|
||||||
|
private long estimatedTimeAtNextMark;
|
||||||
|
private long estimatedTimeAtFinish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boat Sub message from section 4.2 of the AC35 streaming data interface spec
|
||||||
|
* @param sourceId The source ID of the boat
|
||||||
|
* @param boatStatus The boats status
|
||||||
|
* @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc)
|
||||||
|
* @param numberPenaltiesAwarded The number of penalties awarded to the boat
|
||||||
|
* @param numberPenaltiesServed The number of penalties served to the boat
|
||||||
|
* @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race
|
||||||
|
* @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark
|
||||||
|
*/
|
||||||
|
public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed,
|
||||||
|
long estimatedTimeAtFinish, long estimatedTimeAtNextMark){
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = boatStatus;
|
||||||
|
this.legNumber = legNumber;
|
||||||
|
this.numberPenaltiesAwarded = numberPenaltiesAwarded;
|
||||||
|
this.numberPenaltiesServed = numberPenaltiesServed;
|
||||||
|
this.estimatedTimeAtFinish = estimatedTimeAtFinish;
|
||||||
|
this.estimatedTimeAtNextMark = estimatedTimeAtNextMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The size of this message in bytes
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing this boat status message
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
ByteBuffer buff = ByteBuffer.allocate(getSize());
|
||||||
|
int buffPos = 0;
|
||||||
|
|
||||||
|
// Source ID, 4 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(4).putInt((int) sourceId).array());
|
||||||
|
buffPos += 4;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Boat Status, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Leg number, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Number of penalties awarded, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Number of penalties served, 1 byte
|
||||||
|
buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array());
|
||||||
|
buffPos += 1;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Estimated time at next mark, 6 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtNextMark).array());
|
||||||
|
buffPos += 6;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
// Estimated time at finish, 6 bytes
|
||||||
|
buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtFinish).array());
|
||||||
|
buffPos += 6;
|
||||||
|
buff.position(buffPos);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
public enum DeviceType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING_YACHT(1);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
DeviceType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class Header {
|
||||||
|
// From API spec
|
||||||
|
private final int syncByte1 = 0x47;
|
||||||
|
private final int syncByte2 = 0x83;
|
||||||
|
|
||||||
|
private MessageType messageType;
|
||||||
|
private int timeStamp;
|
||||||
|
private int sourceId;
|
||||||
|
private short messageLength;
|
||||||
|
private static final int MESSAGE_LEN = 15;
|
||||||
|
private ByteBuffer buff;
|
||||||
|
private int buffPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message Header from section 3.2 of the AC35 Streaming
|
||||||
|
* Data spec
|
||||||
|
* @param messageType The type of the message following this header
|
||||||
|
* @param sourceId The message source (as defined in the spec)
|
||||||
|
* @param messageLength The length of the message following this header
|
||||||
|
*/
|
||||||
|
public Header(MessageType messageType, int sourceId, Short messageLength){
|
||||||
|
this.messageType = messageType;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.messageLength = messageLength;
|
||||||
|
timeStamp = (int) (System.currentTimeMillis() / 1000L);
|
||||||
|
buff = ByteBuffer.allocate(MESSAGE_LEN);
|
||||||
|
buffPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putInBuffer(byte[] bytes, long val){
|
||||||
|
byte[] tmp = bytes.clone();
|
||||||
|
Message.reverse(tmp);
|
||||||
|
|
||||||
|
buff.put(tmp);
|
||||||
|
buffPos += tmp.length;
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a ByteBuffer containing the message header
|
||||||
|
*/
|
||||||
|
public ByteBuffer getByteBuffer(){
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
||||||
|
|
||||||
|
putInBuffer(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array(), messageType.getCode());
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(timeStamp, 6), timeStamp);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(sourceId, 4), sourceId);
|
||||||
|
|
||||||
|
putInBuffer(Message.intToByteArray(messageLength, 2), messageLength);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of this message
|
||||||
|
* @return the size of the message
|
||||||
|
*/
|
||||||
|
public static Integer getSize(){
|
||||||
|
return MESSAGE_LEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putUnsignedInt(seqNo, 4);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class MarkRoundingMessage extends Message{
|
||||||
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
|
private final int MESSAGE_SIZE = 21;
|
||||||
|
|
||||||
|
private long time;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceId;
|
||||||
|
private long sourceId;
|
||||||
|
private RoundingBoatStatus boatStatus;
|
||||||
|
private RoundingSide roundingSide;
|
||||||
|
private long markId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message is sent when a boat passes a mark, start line, or finish line
|
||||||
|
* The purpose of this is to record the time when yachts cross marks
|
||||||
|
*/
|
||||||
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
|
RoundingSide roundingSide, int markId){
|
||||||
|
this.time = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.sourceId = sourceId;
|
||||||
|
this.boatStatus = roundingBoatStatus;
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
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();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
|
putInt((int) time, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putInt((int) sourceId, 4);
|
||||||
|
putByte((byte) boatStatus.getCode());
|
||||||
|
putByte((byte) roundingSide.getCode());
|
||||||
|
putByte((byte) markId);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of marks boats can round
|
||||||
|
*/
|
||||||
|
public enum MarkType {
|
||||||
|
UNKNOWN(0),
|
||||||
|
ROUNDING_MARK(1),
|
||||||
|
GATE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
MarkType(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public abstract class Message {
|
||||||
|
private final int CRC_SIZE = 4;
|
||||||
|
private Header header;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
private int bufferPosition;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param header Set the header for this message
|
||||||
|
*/
|
||||||
|
void setHeader(Header header){
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the header specified for this message
|
||||||
|
*/
|
||||||
|
Header getHeader(){
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of the 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
|
||||||
|
*/
|
||||||
|
void allocateBuffer(){
|
||||||
|
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bufferPosition = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the set header to the byte buffer
|
||||||
|
*/
|
||||||
|
void writeHeaderToBuffer(){
|
||||||
|
buffer.put(getHeader().getByteBuffer().array());
|
||||||
|
bufferPosition += Header.getSize();
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the buffer position by n bytes
|
||||||
|
* @param size Number of bytes to move the buffer by
|
||||||
|
*/
|
||||||
|
private void moveBufferPositionBy(int size){
|
||||||
|
bufferPosition += size;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an unsigned byte in the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put an signed byte in the buffer
|
||||||
|
*/
|
||||||
|
void putByte(byte b){
|
||||||
|
buffer.put(ByteBuffer.allocate(1).put(b).array());
|
||||||
|
moveBufferPositionBy(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Place an unsigned integer of the specified length in the buffer
|
||||||
|
* @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers)
|
||||||
|
* @param size The size of the int to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putUnsignedInt(long val, int size){
|
||||||
|
if (size <= 1){
|
||||||
|
putUnsignedByte((byte) val);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (size < 4){
|
||||||
|
// Use short
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size); //ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array();
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
// Use int
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a signed int of a specified length in the buffer
|
||||||
|
* @param val The integer value to add
|
||||||
|
* @param size The size of the integer to be added to the buffer
|
||||||
|
*/
|
||||||
|
void putInt(long val, int size){
|
||||||
|
if (size < 4){
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
byte[] tmp = Message.intToByteArray(val, size);
|
||||||
|
reverse(tmp);
|
||||||
|
buffer.put(tmp);
|
||||||
|
}
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write an array of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
*/
|
||||||
|
void putBytes(byte[] bytes){
|
||||||
|
buffer.put(bytes);
|
||||||
|
moveBufferPositionBy(bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a ByteBuffer of bytes to the buffer
|
||||||
|
* @param bytes to write
|
||||||
|
* @param size number of bytes
|
||||||
|
*/
|
||||||
|
void putBytes(ByteBuffer bytes, int size){
|
||||||
|
buffer.put(bytes);
|
||||||
|
moveBufferPositionBy(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the CRC of the buffer and append it to the end of the buffer
|
||||||
|
*/
|
||||||
|
void writeCRC(){
|
||||||
|
crc = new CRC32();
|
||||||
|
|
||||||
|
buffer.position(0);
|
||||||
|
|
||||||
|
byte[] data = Arrays.copyOfRange(buffer.array(), 0, buffer.array().length-CRC_SIZE);
|
||||||
|
crc.update(data);
|
||||||
|
buffer.position(bufferPosition);
|
||||||
|
|
||||||
|
putInt((int) crc.getValue(), CRC_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current buffer
|
||||||
|
*/
|
||||||
|
public ByteBuffer getBuffer(){
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewind the buffer to the beginning
|
||||||
|
*/
|
||||||
|
void rewind(){
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an integer to an array of bytes
|
||||||
|
* @param val The value to add
|
||||||
|
* @param len The width of the integer in the buffer
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] intToByteArray(long val, int len){
|
||||||
|
long vor = val;
|
||||||
|
int index = 0;
|
||||||
|
byte[] data = new byte[len];
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++){
|
||||||
|
data[len - index - 1] = (byte) (val & 0xFF);
|
||||||
|
val >>>= 8;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse an array of bytes
|
||||||
|
* @param data The byte[] to reverse
|
||||||
|
*/
|
||||||
|
public static void reverse(byte[] data) {
|
||||||
|
for (int left = 0, right = data.length - 1; left < right; left++, right--) {
|
||||||
|
byte temp = (byte) (data[left] & 0xff);
|
||||||
|
data[left] = (byte) (data[right] & 0xff);
|
||||||
|
data[right] = (byte) (temp & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of messages
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum MessageType {
|
||||||
|
HEARTBEAT(1),
|
||||||
|
RACE_STATUS(12),
|
||||||
|
DISPLAY_TEXT_MESSAGE(20),
|
||||||
|
XML_MESSAGE(26),
|
||||||
|
RACE_START_STATUS(27),
|
||||||
|
YACHT_EVENT_CODE(29),
|
||||||
|
YACHT_ACTION_CODE(31),
|
||||||
|
CHATTER_TEXT(36),
|
||||||
|
BOAT_LOCATION(37),
|
||||||
|
MARK_ROUNDING(38),
|
||||||
|
COURSE_WIND(44),
|
||||||
|
AVERAGE_WIND(47);
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
MessageType(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message code (From the API Spec)
|
||||||
|
* @return the message code
|
||||||
|
*/
|
||||||
|
int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The types of race start status messages
|
||||||
|
*/
|
||||||
|
public enum RaceStartNotificationType {
|
||||||
|
SET_RACE_START_TIME(1),
|
||||||
|
RACE_POSTPONED(2),
|
||||||
|
RACE_ABANDONED(3),
|
||||||
|
RACE_TERMINATED(4);
|
||||||
|
|
||||||
|
private final long type;
|
||||||
|
|
||||||
|
RaceStartNotificationType(long type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getType(){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class RaceStartStatusMessage extends Message {
|
||||||
|
private final int MESSAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private long version;
|
||||||
|
private long timeStamp;
|
||||||
|
private long ackNumber;
|
||||||
|
private long raceStartTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStartNotificationType notificationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent to clients with the expected start time of the race
|
||||||
|
* @param ackNumber Sequence number of message.
|
||||||
|
* @param raceStartTime Expected race start time
|
||||||
|
* @param raceId Race ID#
|
||||||
|
* @param notificationType Type of this notification
|
||||||
|
*/
|
||||||
|
public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){
|
||||||
|
this.version = 1;
|
||||||
|
this.timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
this.ackNumber = ackNumber;
|
||||||
|
this.raceStartTime = raceStartTime;
|
||||||
|
this.notificationType = notificationType;
|
||||||
|
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();
|
||||||
|
|
||||||
|
putUnsignedByte((byte) version);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putInt((int) ackNumber, 2);
|
||||||
|
putInt((int) raceStartTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putUnsignedByte((byte) notificationType.getType());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of the race
|
||||||
|
*/
|
||||||
|
public enum RaceStatus {
|
||||||
|
NOTACTIVE(0),
|
||||||
|
WARNING(1), // Between 3:00 and 1:00 before start
|
||||||
|
PREPARATORY(2), // Less than 1:00 before start
|
||||||
|
STARTED(3),
|
||||||
|
ABANDONED(6),
|
||||||
|
POSTPONED(7),
|
||||||
|
TERMINATED(8),
|
||||||
|
RACE_START_TIME_NOT_SET(9),
|
||||||
|
PRESTART(10); // More than 3:00 before start
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
RaceStatus(int code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
public class RaceStatusMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
||||||
|
private final int MESSAGE_VERSION = 2; //Always set to 1
|
||||||
|
private final int MESSAGE_BASE_SIZE = 24;
|
||||||
|
|
||||||
|
private long currentTime;
|
||||||
|
private long raceId;
|
||||||
|
private RaceStatus raceStatus;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private WindDirection raceWindDirection;
|
||||||
|
private long windSpeed;
|
||||||
|
private long numBoatsInRace;
|
||||||
|
private RaceType raceType;
|
||||||
|
private List<BoatSubMessage> boats;
|
||||||
|
private CRC32 crc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message containing the current status of the race
|
||||||
|
* @param raceId The ID of the current race
|
||||||
|
* @param raceStatus The status of the race
|
||||||
|
* @param expectedStartTime The expected start time
|
||||||
|
* @param raceWindDirection The wind direction (north, east, south)
|
||||||
|
* @param windSpeed The wind speed in mm/sec
|
||||||
|
* @param numBoatsInRace The number of boats in the race
|
||||||
|
* @param raceType The race type (Match/fleet)
|
||||||
|
* @param sourceId The source of this message
|
||||||
|
* @param boats A list of boat status sub messages
|
||||||
|
*/
|
||||||
|
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
|
||||||
|
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
||||||
|
currentTime = System.currentTimeMillis();
|
||||||
|
this.raceId = raceId;
|
||||||
|
this.raceStatus = raceStatus;
|
||||||
|
this.expectedStartTime = expectedStartTime;
|
||||||
|
this.raceWindDirection = raceWindDirection;
|
||||||
|
this.windSpeed = windSpeed;
|
||||||
|
this.numBoatsInRace = numBoatsInRace;
|
||||||
|
this.raceType = raceType;
|
||||||
|
this.boats = boats;
|
||||||
|
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();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt(currentTime, 6);
|
||||||
|
putInt((int) raceId, 4);
|
||||||
|
putByte((byte) raceStatus.getCode());
|
||||||
|
putInt(expectedStartTime, 6);
|
||||||
|
putInt((int) raceWindDirection.getCode(), 2);
|
||||||
|
putInt((int) windSpeed, 2);
|
||||||
|
putByte((byte) numBoatsInRace);
|
||||||
|
putByte((byte) raceType.getCode());
|
||||||
|
|
||||||
|
for (BoatSubMessage boatSubMessage : boats){
|
||||||
|
putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of races
|
||||||
|
* sent by the server
|
||||||
|
*/
|
||||||
|
public enum RaceType {
|
||||||
|
MATCH_RACE(1),
|
||||||
|
FLEET_RACE(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RaceType(long code){
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of a boat rounding a mark
|
||||||
|
*/
|
||||||
|
public enum RoundingBoatStatus {
|
||||||
|
UNKNOWN(0),
|
||||||
|
RACING(1),
|
||||||
|
DSQ(2),
|
||||||
|
WITHDRAWN(3);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingBoatStatus(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The side the boat rounded the mark
|
||||||
|
*/
|
||||||
|
public enum RoundingSide {
|
||||||
|
UNKNOWN(0),
|
||||||
|
PORT(1),
|
||||||
|
STARBOARD(2);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
RoundingSide(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the supported wind directions
|
||||||
|
*/
|
||||||
|
public enum WindDirection {
|
||||||
|
NORTH(0x0000L),
|
||||||
|
EAST(0x4000L),
|
||||||
|
SOUTH(0x8000L);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
WindDirection(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class XMLMessage extends Message{
|
||||||
|
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||||
|
private final int MESSAGE_VERSION = 1; //Always set to 1
|
||||||
|
private final int MESSAGE_SIZE = 14;
|
||||||
|
|
||||||
|
// Message fields
|
||||||
|
private long timeStamp;
|
||||||
|
private long ack = 0x00; //Unused
|
||||||
|
private XMLMessageSubType xmlMessageSubType;
|
||||||
|
private long length;
|
||||||
|
private long sequence;
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML Message from the AC35 Streaming data spec
|
||||||
|
* @param content The XML content
|
||||||
|
* @param type The XML Message Sub Type
|
||||||
|
*/
|
||||||
|
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||||
|
this.content = content;
|
||||||
|
this.xmlMessageSubType = type;
|
||||||
|
timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
|
ack = 0;
|
||||||
|
length = this.content.length();
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Write message fields
|
||||||
|
putUnsignedByte((byte) MESSAGE_VERSION);
|
||||||
|
putInt((int) ack, 2);
|
||||||
|
putInt((int) timeStamp, 6);
|
||||||
|
putByte((byte)xmlMessageSubType.getType());
|
||||||
|
putInt((int) sequence, 2);
|
||||||
|
putInt((int) length, 2);
|
||||||
|
putBytes(content.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
|
||||||
|
outputStream.write(getBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing the types of XML messages
|
||||||
|
*/
|
||||||
|
public enum XMLMessageSubType {
|
||||||
|
REGATTA(5),
|
||||||
|
RACE(6),
|
||||||
|
BOAT(7);
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
XMLMessageSubType(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType(){
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
public class Boat {
|
||||||
|
|
||||||
|
private int sourceID;
|
||||||
|
private double lat;
|
||||||
|
private double lng;
|
||||||
|
private double speed; // in mm/sec
|
||||||
|
private String boatName, shortName, shorterName;
|
||||||
|
|
||||||
|
private Corner lastPassedCorner, headingCorner;
|
||||||
|
|
||||||
|
public Boat(int sourceID, String boatName) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.boatName = boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves boat to the heading direction for a given time duration
|
||||||
|
* @param heading moving direction in degree.
|
||||||
|
* @param duration moving duration in millisecond.
|
||||||
|
*/
|
||||||
|
public void move(double heading, double duration) {
|
||||||
|
Double distance = speed * duration / 1000000; // convert mm to meter
|
||||||
|
Position originPos = new Position(lat, lng);
|
||||||
|
Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance);
|
||||||
|
this.lat = newPos.getLat();
|
||||||
|
this.lng = newPos.getLng();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getSpeed() {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpeed(double speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBoatName() {
|
||||||
|
return boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatName(String boatName) {
|
||||||
|
this.boatName = boatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShortName(String shortName) {
|
||||||
|
this.shortName = shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShorterName() {
|
||||||
|
return shorterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShorterName(String shorterName) {
|
||||||
|
this.shorterName = shorterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getLastPassedCorner() {
|
||||||
|
return lastPassedCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastPassedCorner(Corner lastPassedCorner) {
|
||||||
|
this.lastPassedCorner = lastPassedCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getHeadingCorner() {
|
||||||
|
return headingCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeadingCorner(Corner headingCorner) {
|
||||||
|
this.headingCorner = headingCorner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
public class GeoUtility {
|
||||||
|
|
||||||
|
private static double EARTH_RADIUS = 6378.137;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the euclidean distance between two markers on the canvas using xy coordinates
|
||||||
|
*
|
||||||
|
* @param p1 first geographical position
|
||||||
|
* @param p2 second geographical position
|
||||||
|
* @return the distance in meter between two points in meters
|
||||||
|
*/
|
||||||
|
public static Double getDistance(Position p1, Position p2) {
|
||||||
|
|
||||||
|
double dLat = Math.toRadians(p2.getLat() - p1.getLat());
|
||||||
|
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
|
||||||
|
|
||||||
|
double a = Math.pow(Math.sin(dLat / 2), 2.0)
|
||||||
|
+ Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat()))
|
||||||
|
* Math.pow(Math.sin(dLon / 2), 2.0);
|
||||||
|
|
||||||
|
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
double d = EARTH_RADIUS * c;
|
||||||
|
|
||||||
|
return d * 1000; // distance from km to meter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the angle between to angular co-ordinates on a sphere.
|
||||||
|
*
|
||||||
|
* @param p1 the first geographical position, start point
|
||||||
|
* @param p2 the second geographical position, end point
|
||||||
|
* @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.).
|
||||||
|
* vertical up is 0 deg. horizontal right is 90 deg.
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* The final bearing will differ from the initial bearing by varying degrees
|
||||||
|
* according to distance and latitude (if you were to go from say 35°N,45°E
|
||||||
|
* (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60°
|
||||||
|
* and end up on a heading of 120°
|
||||||
|
*/
|
||||||
|
public static Double getBearing(Position p1, Position p2) {
|
||||||
|
|
||||||
|
double dLon = Math.toRadians(p2.getLng() - p1.getLng());
|
||||||
|
|
||||||
|
double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat()));
|
||||||
|
double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat()))
|
||||||
|
- Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon);
|
||||||
|
|
||||||
|
double bearing = Math.toDegrees(Math.atan2(y, x));
|
||||||
|
|
||||||
|
return (bearing + 360.0) % 360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an existing point in lat/lng, distance in (in meter) and bearing
|
||||||
|
* (in degrees), calculates the new lat/lng.
|
||||||
|
*
|
||||||
|
* @param origin the original position within lat / lng
|
||||||
|
* @param bearing the bearing in degree, from original position to the new position
|
||||||
|
* @param distance the distance in meter, from original position to the new position
|
||||||
|
* @return the new position
|
||||||
|
*/
|
||||||
|
public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) {
|
||||||
|
double b = Math.toRadians(bearing); // bearing to radians
|
||||||
|
double d = distance / 1000.0; // distance to km
|
||||||
|
|
||||||
|
double originLat = Math.toRadians(origin.getLat());
|
||||||
|
double originLng = Math.toRadians(origin.getLng());
|
||||||
|
|
||||||
|
double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS)
|
||||||
|
+ Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b));
|
||||||
|
double endLng = originLng
|
||||||
|
+ Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat),
|
||||||
|
Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat));
|
||||||
|
|
||||||
|
return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Mark;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
import seng302.server.simulator.parsers.RaceParser;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Observable;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class Simulator extends Observable implements Runnable {
|
||||||
|
|
||||||
|
private List<Corner> course;
|
||||||
|
private List<Boat> boats;
|
||||||
|
private long lapse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a simulator instance with given time lapse.
|
||||||
|
* @param lapse time duration in millisecond.
|
||||||
|
*/
|
||||||
|
public Simulator(long lapse) {
|
||||||
|
RaceParser rp = new RaceParser("/server_config/race.xml");
|
||||||
|
course = rp.getCourse();
|
||||||
|
boats = rp.getBoats();
|
||||||
|
this.lapse = lapse;
|
||||||
|
|
||||||
|
setLegs();
|
||||||
|
|
||||||
|
// set start line's coordinate to boats
|
||||||
|
Double startLat = course.get(0).getCompoundMark().getMark1().getLat();
|
||||||
|
Double startLng = course.get(0).getCompoundMark().getMark1().getLng();
|
||||||
|
for (Boat boat : boats) {
|
||||||
|
boat.setLat(startLat);
|
||||||
|
boat.setLng(startLng);
|
||||||
|
boat.setLastPassedCorner(course.get(0));
|
||||||
|
boat.setHeadingCorner(course.get(1));
|
||||||
|
boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
int numOfFinishedBoats = 0;
|
||||||
|
|
||||||
|
while (numOfFinishedBoats < boats.size()) {
|
||||||
|
for (Boat boat : boats) {
|
||||||
|
numOfFinishedBoats += moveBoat(boat, lapse);
|
||||||
|
}
|
||||||
|
//System.out.println(boats.get(0));
|
||||||
|
|
||||||
|
setChanged();
|
||||||
|
notifyObservers(boats);
|
||||||
|
|
||||||
|
// sleep for 1 second.
|
||||||
|
try {
|
||||||
|
Thread.sleep(lapse);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a boat with given time duration.
|
||||||
|
*
|
||||||
|
* @param boat the boat to be moved
|
||||||
|
* @param duration the moving duration in milliseconds
|
||||||
|
* @return 1 if the boat has reached the final line, otherwise return 0
|
||||||
|
*/
|
||||||
|
private int moveBoat(Boat boat, double duration) {
|
||||||
|
if (boat.getHeadingCorner() != null) {
|
||||||
|
|
||||||
|
boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration);
|
||||||
|
|
||||||
|
Position boatPos = new Position(boat.getLat(), boat.getLng());
|
||||||
|
Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1();
|
||||||
|
|
||||||
|
double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos);
|
||||||
|
// if a boat passes its heading mark
|
||||||
|
while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) {
|
||||||
|
double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner();
|
||||||
|
boat.setLastPassedCorner(boat.getHeadingCorner());
|
||||||
|
boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner());
|
||||||
|
|
||||||
|
// heading corner == null means boat has reached the final mark
|
||||||
|
if (boat.getHeadingCorner() == null) return 1;
|
||||||
|
|
||||||
|
// move compensate distance for the mark just passed
|
||||||
|
Position pos = GeoUtility.getGeoCoordinate(
|
||||||
|
boat.getLastPassedCorner().getCompoundMark().getMark1(),
|
||||||
|
boat.getLastPassedCorner().getBearingToNextCorner(),
|
||||||
|
compensateDistance);
|
||||||
|
boat.setLat(pos.getLat());
|
||||||
|
boat.setLng(pos.getLng());
|
||||||
|
distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()),
|
||||||
|
boat.getLastPassedCorner().getCompoundMark().getMark1());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link all the corners in the course list so that every corner knows its next
|
||||||
|
* corner, as well as the distance and bearing to its next corner. However,
|
||||||
|
* the last corner's heading is null, which means it is the final line.
|
||||||
|
*/
|
||||||
|
private void setLegs() {
|
||||||
|
// get the bearing from one mark to the next heading mark
|
||||||
|
for (int i = 0; i < course.size() - 1; i++) {
|
||||||
|
|
||||||
|
Mark mark1 = course.get(i).getCompoundMark().getMark1();
|
||||||
|
Mark mark2 = course.get(i + 1).getCompoundMark().getMark1();
|
||||||
|
course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2));
|
||||||
|
|
||||||
|
course.get(i).setNextCorner(course.get(i + 1));
|
||||||
|
|
||||||
|
course.get(i).setBearingToNextCorner(
|
||||||
|
GeoUtility.getBearing(course.get(i).getCompoundMark().getMark1(),
|
||||||
|
course.get(i + 1).getCompoundMark().getMark1()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Boat> getBoats(){
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class CompoundMark {
|
||||||
|
|
||||||
|
private int markID;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Mark mark1;
|
||||||
|
private Mark mark2;
|
||||||
|
|
||||||
|
public CompoundMark(int markID, String name) {
|
||||||
|
this.markID = markID;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMark(int seqId, Mark mark) {
|
||||||
|
if (seqId == 1) {
|
||||||
|
setMark1(mark);
|
||||||
|
} else if (seqId == 2) {
|
||||||
|
setMark2(mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out compoundMark's info and its marks, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
if (mark2 == null)
|
||||||
|
return String.format("CompoundMark: %d (%s), [%s]",
|
||||||
|
markID, name, mark1.toString());
|
||||||
|
return String.format("CompoundMark: %d (%s), [%s; %s]",
|
||||||
|
markID, name, mark1.toString(), mark2.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMarkID() {
|
||||||
|
return markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkID(int markID) {
|
||||||
|
this.markID = markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getMark1() {
|
||||||
|
return mark1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMark1(Mark mark1) {
|
||||||
|
this.mark1 = mark1;
|
||||||
|
mark1.setSeqID(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mark getMark2() {
|
||||||
|
return mark2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMark2(Mark mark2) {
|
||||||
|
this.mark2 = mark2;
|
||||||
|
mark2.setSeqID(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class Corner {
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private CompoundMark compoundMark;
|
||||||
|
//private int CompoundMarkID;
|
||||||
|
private RoundingType roundingType;
|
||||||
|
private int zoneSize; // size of the zone around a mark in boat-lengths.
|
||||||
|
|
||||||
|
// TODO: this shouldn't be used in the future!!!!
|
||||||
|
private double bearingToNextCorner, distanceToNextCorner;
|
||||||
|
private Corner nextCorner;
|
||||||
|
|
||||||
|
public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
this.compoundMark = compoundMark;
|
||||||
|
this.roundingType = roundingType;
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out corner's info and its compound mark, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Corner: %d - %s - %d, %s\n",
|
||||||
|
seqID, roundingType.getType(), zoneSize, compoundMark.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeqID(int seqID) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompoundMark getCompoundMark() {
|
||||||
|
return compoundMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompoundMark(CompoundMark compoundMark) {
|
||||||
|
this.compoundMark = compoundMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundingType getRoundingType() {
|
||||||
|
return roundingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingType(RoundingType roundingType) {
|
||||||
|
this.roundingType = roundingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoneSize() {
|
||||||
|
return zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoneSize(int zoneSize) {
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: next six setters & getters shouldn't be used in the future.
|
||||||
|
public double getBearingToNextCorner() {
|
||||||
|
return bearingToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBearingToNextCorner(double bearingToNextCorner) {
|
||||||
|
this.bearingToNextCorner = bearingToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDistanceToNextCorner() {
|
||||||
|
return distanceToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistanceToNextCorner(double distanceToNextCorner) {
|
||||||
|
this.distanceToNextCorner = distanceToNextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Corner getNextCorner() {
|
||||||
|
return nextCorner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextCorner(Corner nextCorner) {
|
||||||
|
this.nextCorner = nextCorner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public class Mark extends Position {
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private String name;
|
||||||
|
private int sourceID;
|
||||||
|
|
||||||
|
public Mark(String name, double lat, double lng, int sourceID) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.name = name;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out mark's info and its geo location, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeqID(int seqID) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public class Position {
|
||||||
|
|
||||||
|
double lat, lng;
|
||||||
|
|
||||||
|
public Position(double lat, double lng) {
|
||||||
|
this.lat = lat;
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Position at lat:%f lng:%f.", lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package seng302.server.simulator.mark;
|
||||||
|
|
||||||
|
public enum RoundingType {
|
||||||
|
|
||||||
|
// the mark should be rounded to port (boat's left)
|
||||||
|
PORT("Port"),
|
||||||
|
|
||||||
|
// the mark should be rounded to starboard (boat's right)
|
||||||
|
STARBOARD("Stbd"),
|
||||||
|
|
||||||
|
// the boat within the compound mark with the SeqID of 1 should be rounded
|
||||||
|
// to starboard and the boat within the compound mark with the SeqID of 2
|
||||||
|
// should be rounded to port.
|
||||||
|
SP("SP"),
|
||||||
|
|
||||||
|
// the opposite of SP
|
||||||
|
PS("PS");
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
RoundingType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoundingType typeOf(String type) {
|
||||||
|
switch (type) {
|
||||||
|
case "Port":
|
||||||
|
return PORT;
|
||||||
|
case "Stbd":
|
||||||
|
return STARBOARD;
|
||||||
|
case "SP":
|
||||||
|
return SP;
|
||||||
|
case "PS":
|
||||||
|
return PS;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class BoatsParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
|
||||||
|
public BoatsParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.server.simulator.mark.CompoundMark;
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
import seng302.server.simulator.mark.Mark;
|
||||||
|
import seng302.server.simulator.mark.RoundingType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class CourseParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private Map<Integer, CompoundMark> compoundMarksMap;
|
||||||
|
|
||||||
|
public CourseParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should handle error / invalid file gracefully
|
||||||
|
protected List<Corner> getCourse() {
|
||||||
|
compoundMarksMap = getCompoundMarks(doc.getDocumentElement());
|
||||||
|
List<Corner> corners = new ArrayList<>();
|
||||||
|
NodeList cMarksSequence = doc.getElementsByTagName("Corner");
|
||||||
|
|
||||||
|
for (int i = 0; i < cMarksSequence.getLength(); i++) {
|
||||||
|
corners.add(getCorner(cMarksSequence.item(i)));
|
||||||
|
}
|
||||||
|
return corners;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Corner getCorner(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
|
||||||
|
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
|
||||||
|
Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID"));
|
||||||
|
CompoundMark cMark = compoundMarksMap.get(cMarkId);
|
||||||
|
RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding"));
|
||||||
|
Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize"));
|
||||||
|
|
||||||
|
return new Corner(seqId, cMark, roundingType, zoneSize);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, CompoundMark> getCompoundMarks(Node node) {
|
||||||
|
Map<Integer, CompoundMark> compoundMarksMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
NodeList cMarks = element.getElementsByTagName("CompoundMark");
|
||||||
|
|
||||||
|
// loop through all compound marks who are the children of course node
|
||||||
|
for (int i = 0; i < cMarks.getLength(); i++) {
|
||||||
|
CompoundMark cMark = getCompoundMark(cMarks.item(i));
|
||||||
|
if (cMark != null)
|
||||||
|
compoundMarksMap.put(cMark.getMarkID(), cMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundMarksMap;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private CompoundMark getCompoundMark(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID"));
|
||||||
|
|
||||||
|
String name = e.getAttribute("Name");
|
||||||
|
CompoundMark cMark = new CompoundMark(markID, name);
|
||||||
|
|
||||||
|
NodeList marks = e.getElementsByTagName("Mark");
|
||||||
|
for (int i = 0; i < marks.getLength(); i++) {
|
||||||
|
Mark mark = getMark(marks.item(i));
|
||||||
|
if (mark != null)
|
||||||
|
cMark.addMark(mark.getSeqID(), mark);
|
||||||
|
}
|
||||||
|
return cMark;
|
||||||
|
}
|
||||||
|
System.out.println("Failed to create compound mark.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Mark getMark(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
Integer seqId = Integer.valueOf(e.getAttribute("SeqID"));
|
||||||
|
String name = e.getAttribute("Name");
|
||||||
|
Double lat = Double.valueOf(e.getAttribute("TargetLat"));
|
||||||
|
Double lng = Double.valueOf(e.getAttribute("TargetLng"));
|
||||||
|
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
||||||
|
|
||||||
|
Mark mark = new Mark(name, lat, lng, sourceId);
|
||||||
|
mark.setSeqID(seqId);
|
||||||
|
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
System.out.println("Failed to create mark.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-3
@@ -1,12 +1,10 @@
|
|||||||
package seng302.models.parsers;
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package seng302.server.simulator.parsers;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import seng302.server.simulator.Boat;
|
||||||
|
import seng302.server.simulator.mark.Corner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the race xml file to get course details
|
||||||
|
* Created by Haoming Yin (hyi25) on 16/3/2017
|
||||||
|
*/
|
||||||
|
public class RaceParser extends FileParser {
|
||||||
|
|
||||||
|
private Document doc;
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
public RaceParser(String path) {
|
||||||
|
super(path);
|
||||||
|
this.path = path;
|
||||||
|
this.doc = this.parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and returns a list of corner which is the race course.
|
||||||
|
* @return a list of ordered corner to represent the course.
|
||||||
|
*/
|
||||||
|
public List<Corner> getCourse() {
|
||||||
|
CourseParser cp = new CourseParser(path);
|
||||||
|
return cp.getCourse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses race.xml file and return a list of boats which will compete in the
|
||||||
|
* race.
|
||||||
|
* @return a list of boats that are going to compete in the race.
|
||||||
|
*/
|
||||||
|
public List<Boat> getBoats() {
|
||||||
|
NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht");
|
||||||
|
List<Boat> boats = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < yachts.getLength(); i++) {
|
||||||
|
boats.add(getBoat(yachts.item(i)));
|
||||||
|
}
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a single boat from the given node
|
||||||
|
* @param node a node within a boat tag
|
||||||
|
* @return a boat instance parsed from the given node
|
||||||
|
*/
|
||||||
|
private Boat getBoat(Node node) {
|
||||||
|
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
Element e = (Element) node;
|
||||||
|
|
||||||
|
Integer sourceId = Integer.valueOf(e.getAttribute("SourceID"));
|
||||||
|
return new Boat(sourceId, "Test Boat");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
<course>
|
<markers>
|
||||||
<marks>
|
<marks>
|
||||||
<gate>
|
<gate>
|
||||||
<name type="start-line">Start</name>
|
<name type="start-line">Start</name>
|
||||||
@@ -77,4 +77,4 @@
|
|||||||
<five>Leeward Gate</five>
|
<five>Leeward Gate</five>
|
||||||
<six>Finish</six>
|
<six>Finish</six>
|
||||||
</order>
|
</order>
|
||||||
</course>
|
</markers>
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<BoatConfig>
|
||||||
|
<Modified>2015-08-28T17:32:59+0100</Modified>
|
||||||
|
<Version>12</Version>
|
||||||
|
<Snapshot>219</Snapshot>
|
||||||
|
<Settings>
|
||||||
|
<RaceBoatType Type="AC45"/>
|
||||||
|
<BoatDimension BoatLength="14.019" HullLength="13.449"/>
|
||||||
|
<ZoneSize MarkZoneSize="40.347" CourseZoneSize="53.796"/>
|
||||||
|
<ZoneLimits Limit1="200" Limit2="100" Limit3="53.796" Limit4="0" Limit5="-100"/>
|
||||||
|
</Settings>
|
||||||
|
<BoatShapes>
|
||||||
|
<BoatShape ShapeID="0">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="3" Y="25" X="0"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="14">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1"/>
|
||||||
|
<Vtx Seq="2" Y="0.75" X="-1"/>
|
||||||
|
<Vtx Seq="3" Y="0.75" X="-0.25"/>
|
||||||
|
<Vtx Seq="4" Y="3.5" X="-0.25"/>
|
||||||
|
<Vtx Seq="5" Y="4.5" X="-1"/>
|
||||||
|
<Vtx Seq="6" Y="6.5" X="-1"/>
|
||||||
|
<Vtx Seq="7" Y="7" X="-0.5"/>
|
||||||
|
<Vtx Seq="8" Y="7" X="0.5"/>
|
||||||
|
<Vtx Seq="9" Y="6.5" X="1"/>
|
||||||
|
<Vtx Seq="10" Y="4.5" X="1"/>
|
||||||
|
<Vtx Seq="11" Y="3.5" X="0.25"/>
|
||||||
|
<Vtx Seq="12" Y="0.75" X="0.25"/>
|
||||||
|
<Vtx Seq="13" Y="0.75" X="1"/>
|
||||||
|
<Vtx Seq="14" Y="0" X="1"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="15">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-3.46"/>
|
||||||
|
<Vtx Seq="2" Y="13.449" X="-3.46"/>
|
||||||
|
<Vtx Seq="3" Y="14.019" X="0"/>
|
||||||
|
<Vtx Seq="4" Y="13.449" X="3.46"/>
|
||||||
|
<Vtx Seq="5" Y="0" X="3.46"/>
|
||||||
|
</Vertices>
|
||||||
|
<Catamaran>
|
||||||
|
<Vtx Seq="1" Y="1.769" X="-2.752"/>
|
||||||
|
<Vtx Seq="2" Y="0" X="-2.813"/>
|
||||||
|
<Vtx Seq="3" Y="0" X="-3.34"/>
|
||||||
|
<Vtx Seq="4" Y="5.351" X="-3.46"/>
|
||||||
|
<Vtx Seq="5" Y="10.544" X="-3.387"/>
|
||||||
|
<Vtx Seq="6" Y="13.449" X="-3.075"/>
|
||||||
|
<Vtx Seq="7" Y="10.851" X="-2.793"/>
|
||||||
|
<Vtx Seq="8" Y="6.669" X="-2.699"/>
|
||||||
|
<Vtx Seq="9" Y="6.669" X="2.699"/>
|
||||||
|
<Vtx Seq="10" Y="10.851" X="2.793"/>
|
||||||
|
<Vtx Seq="11" Y="13.449" X="3.075"/>
|
||||||
|
<Vtx Seq="12" Y="10.544" X="3.387"/>
|
||||||
|
<Vtx Seq="13" Y="5.351" X="3.46"/>
|
||||||
|
<Vtx Seq="14" Y="0" X="3.34"/>
|
||||||
|
<Vtx Seq="15" Y="0" X="2.813"/>
|
||||||
|
<Vtx Seq="16" Y="1.769" X="2.752"/>
|
||||||
|
</Catamaran>
|
||||||
|
<Bowsprit>
|
||||||
|
<Vtx Seq="1" Y="6.669" X="-0.2"/>
|
||||||
|
<Vtx Seq="2" Y="11.377" X="-0.2"/>
|
||||||
|
<Vtx Seq="3" Y="14.019" X="0"/>
|
||||||
|
<Vtx Seq="4" Y="11.377" X="0.2"/>
|
||||||
|
<Vtx Seq="5" Y="6.669" X="0.2"/>
|
||||||
|
</Bowsprit>
|
||||||
|
<Trampoline>
|
||||||
|
<Vtx Seq="1" Y="2" X="-2.699"/>
|
||||||
|
<Vtx Seq="2" Y="6.438" X="-2.699"/>
|
||||||
|
<Vtx Seq="3" Y="6.438" X="2.699"/>
|
||||||
|
<Vtx Seq="4" Y="2" X="2.699"/>
|
||||||
|
</Trampoline>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="18">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.04"/>
|
||||||
|
<Vtx Seq="2" Y="0.11" X="-1.18"/>
|
||||||
|
<Vtx Seq="3" Y="0.42" X="-1.28"/>
|
||||||
|
<Vtx Seq="4" Y="3.74" X="-1.29"/>
|
||||||
|
<Vtx Seq="5" Y="5.36" X="-1.21"/>
|
||||||
|
<Vtx Seq="6" Y="6.29" X="-1.08"/>
|
||||||
|
<Vtx Seq="7" Y="7.15" X="-0.84"/>
|
||||||
|
<Vtx Seq="8" Y="7.63" X="-0.62"/>
|
||||||
|
<Vtx Seq="9" Y="7.94" X="-0.34"/>
|
||||||
|
<Vtx Seq="10" Y="8.06" X="0"/>
|
||||||
|
<Vtx Seq="11" Y="7.94" X="0.34"/>
|
||||||
|
<Vtx Seq="12" Y="7.63" X="0.62"/>
|
||||||
|
<Vtx Seq="13" Y="7.15" X="0.84"/>
|
||||||
|
<Vtx Seq="14" Y="6.29" X="1.08"/>
|
||||||
|
<Vtx Seq="15" Y="5.36" X="1.21"/>
|
||||||
|
<Vtx Seq="16" Y="3.74" X="1.29"/>
|
||||||
|
<Vtx Seq="17" Y="0.42" X="1.28"/>
|
||||||
|
<Vtx Seq="18" Y="0.11" X="1.18"/>
|
||||||
|
<Vtx Seq="19" Y="0" X="1.04"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="24">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-2.5"/>
|
||||||
|
<Vtx Seq="2" Y="7" X="-2.5"/>
|
||||||
|
<Vtx Seq="3" Y="12.6" X="-2.2"/>
|
||||||
|
<Vtx Seq="4" Y="12.6" X="2.2"/>
|
||||||
|
<Vtx Seq="5" Y="7" X="2.5"/>
|
||||||
|
<Vtx Seq="6" Y="0" X="2.5"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="34">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.16"/>
|
||||||
|
<Vtx Seq="2" Y="5.51" X="-1.16"/>
|
||||||
|
<Vtx Seq="3" Y="5.846" X="-0.84"/>
|
||||||
|
<Vtx Seq="4" Y="5.846" X="0.84"/>
|
||||||
|
<Vtx Seq="5" Y="5.51" X="1.16"/>
|
||||||
|
<Vtx Seq="6" Y="0" X="1.16"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
<BoatShape ShapeID="35">
|
||||||
|
<Vertices>
|
||||||
|
<Vtx Seq="1" Y="0" X="-1.461"/>
|
||||||
|
<Vtx Seq="2" Y="6" X="-1.461"/>
|
||||||
|
<Vtx Seq="3" Y="7" X="-1.44"/>
|
||||||
|
<Vtx Seq="4" Y="8" X="-1.38"/>
|
||||||
|
<Vtx Seq="5" Y="9" X="-1.17"/>
|
||||||
|
<Vtx Seq="6" Y="10" X="-0.76"/>
|
||||||
|
<Vtx Seq="7" Y="10.6" X="-0.34"/>
|
||||||
|
<Vtx Seq="8" Y="10.61" X="0"/>
|
||||||
|
<Vtx Seq="9" Y="10.6" X="0.34"/>
|
||||||
|
<Vtx Seq="10" Y="10" X="0.76"/>
|
||||||
|
<Vtx Seq="11" Y="9" X="1.17"/>
|
||||||
|
<Vtx Seq="12" Y="8" X="1.38"/>
|
||||||
|
<Vtx Seq="13" Y="7" X="1.44"/>
|
||||||
|
<Vtx Seq="14" Y="6" X="1.461"/>
|
||||||
|
<Vtx Seq="15" Y="0" X="1.461"/>
|
||||||
|
</Vertices>
|
||||||
|
</BoatShape>
|
||||||
|
</BoatShapes>
|
||||||
|
<Boats>
|
||||||
|
<Boat Type="Yacht" SourceID="101" ShapeID="15" StoweName="USA" ShortName="ORACLE" ShorterName="USA" BoatName="ORACLE TEAM USA" HullNum="AC4515" Skipper="SPITHILL" Helmsman="SPITHILL" Country="USA" PeliID="101" RadioIP="172.20.2.101">
|
||||||
|
<GPSposition Z="1.78" Y="-0.331" X="-0.006"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="102" ShapeID="15" StoweName="SWE" ShortName="ARTEMIS" ShorterName="SWE" BoatName="ARTEMIS RACING" HullNum="AC4517" Skipper="OUTTERIDGE" Helmsman="OUTTERIDGE" Country="SWE" PeliID="102" RadioIP="172.20.2.102">
|
||||||
|
<GPSposition Z="1.727" Y="-0.359" X="-0.0121"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="103" ShapeID="15" StoweName="NZL" ShortName="ETNZ" ShorterName="NZL" BoatName="EMIRATES TEAM NZ" HullNum="AC4503" Skipper="ASHBY" Helmsman="BURLING" Country="NZL" PeliID="103" RadioIP="172.20.2.103">
|
||||||
|
<GPSposition Z="1.881" Y="-0.291" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="104" ShapeID="15" StoweName="JPN" ShortName="JAPAN" ShorterName="JPN" BoatName="SOFTBANK TEAM JAPAN" HullNum="AC4504" Skipper="BARKER" Helmsman="BARKER" Country="JPN" PeliID="104" RadioIP="172.20.2.104">
|
||||||
|
<GPSposition Z="1.805" Y="-0.322" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="105" ShapeID="15" StoweName="FRA" ShortName="FRANCE" ShorterName="FRA" BoatName="GROUPAMA TEAM FRANCE" HullNum="AC4505" Skipper="CAMMAS" Helmsman="CAMMAS" Country="FRA" PeliID="105" RadioIP="172.20.2.105">
|
||||||
|
<GPSposition Z="1.863" Y="-0.3" X="-0.003"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
<Boat Type="Yacht" SourceID="106" ShapeID="15" StoweName="GBR" ShortName="GBR" ShorterName="GBR" BoatName="LAND ROVER BAR" HullNum="AC4516" Skipper="ANSLIE" Helmsman="ANSLIE" Country="GBR" PeliID="106" RadioIP="172.20.2.106">
|
||||||
|
<GPSposition Z="1.734" Y="-0.352" X="0"/>
|
||||||
|
<MastTop Z="21.496" Y="3.7" X="0"/>
|
||||||
|
<FlagPosition Z="0" Y="6.2" X="0"/>
|
||||||
|
</Boat>
|
||||||
|
</Boats>
|
||||||
|
</BoatConfig>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Race>
|
||||||
|
<CreationTimeDate>2015-08-29T13:12:40+02:00</CreationTimeDate>
|
||||||
|
<RaceStartTime Start="2015-08-29T13:10:00+02:00" Postpone="False" />
|
||||||
|
<RaceID>15082901</RaceID>
|
||||||
|
<RaceType>Fleet</RaceType>
|
||||||
|
<Participants>
|
||||||
|
<Yacht SourceID="101" />
|
||||||
|
<Yacht SourceID="102" />
|
||||||
|
<Yacht SourceID="103" />
|
||||||
|
<Yacht SourceID="104" />
|
||||||
|
<Yacht SourceID="105" />
|
||||||
|
<Yacht SourceID="106" />
|
||||||
|
</Participants>
|
||||||
|
<Course>
|
||||||
|
<CompoundMark CompoundMarkID="1" Name="Mark0">
|
||||||
|
<Mark SeqID="1" Name="Start Line 1" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="122" />
|
||||||
|
<Mark SeqID="2" Name="Start Line 2" TargetLat="57.6703330" TargetLng="11.8278330" SourceID="123" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="2" Name="Mark1">
|
||||||
|
<Mark SeqID="1" Name="Mark1" TargetLat="57.6675700" TargetLng="11.8359880" SourceID="131" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="3" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="4" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="5" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="6" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="7" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="8" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="9" Name="Mark2">
|
||||||
|
<Mark SeqID="1" Name="Lee Gate 1" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="124" />
|
||||||
|
<Mark SeqID="2" Name="Lee Gate 2" TargetLat="57.6708220" TargetLng="11.8433900" SourceID="125" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="10" Name="Mark3">
|
||||||
|
<Mark SeqID="1" Name="Wind Gate 1" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="126" />
|
||||||
|
<Mark SeqID="2" Name="Wind Gate 2" TargetLat="57.6650170" TargetLng="11.8279170" SourceID="127" />
|
||||||
|
</CompoundMark>
|
||||||
|
<CompoundMark CompoundMarkID="11" Name="Mark4">
|
||||||
|
<Mark SeqID="1" Name="Finish Line 1" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="128" />
|
||||||
|
<Mark SeqID="2" Name="Finish Line 2" TargetLat="57.6715240" TargetLng="11.8444950" SourceID="129" />
|
||||||
|
</CompoundMark>
|
||||||
|
</Course>
|
||||||
|
<CompoundMarkSequence>
|
||||||
|
<Corner SeqID="1" CompoundMarkID="1" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="2" CompoundMarkID="2" Rounding="Port" ZoneSize="3" />
|
||||||
|
<Corner SeqID="3" CompoundMarkID="3" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="4" CompoundMarkID="4" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="5" CompoundMarkID="5" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="6" CompoundMarkID="6" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="7" CompoundMarkID="7" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="8" CompoundMarkID="8" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="9" CompoundMarkID="9" Rounding="SP" ZoneSize="3" />
|
||||||
|
<Corner SeqID="10" CompoundMarkID="10" Rounding="PS" ZoneSize="3" />
|
||||||
|
<Corner SeqID="11" CompoundMarkID="11" Rounding="PS" ZoneSize="3" />
|
||||||
|
</CompoundMarkSequence>
|
||||||
|
<CourseLimit>
|
||||||
|
<Limit SeqID="1" Lat="57.6739450" Lon="11.8417100" />
|
||||||
|
<Limit SeqID="2" Lat="57.6709520" Lon="11.8485010" />
|
||||||
|
<Limit SeqID="3" Lat="57.6690260" Lon="11.8472790" />
|
||||||
|
<Limit SeqID="4" Lat="57.6693140" Lon="11.8457610" />
|
||||||
|
<Limit SeqID="5" Lat="57.6665370" Lon="11.8432910" />
|
||||||
|
<Limit SeqID="6" Lat="57.6641400" Lon="11.8385840" />
|
||||||
|
<Limit SeqID="7" Lat="57.6629430" Lon="11.8332030" />
|
||||||
|
<Limit SeqID="8" Lat="57.6629480" Lon="11.8249660" />
|
||||||
|
<Limit SeqID="9" Lat="57.6686890" Lon="11.8250920" />
|
||||||
|
<Limit SeqID="10" Lat="57.6708220" Lon="11.8321340" />
|
||||||
|
</CourseLimit>
|
||||||
|
</Race>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RegattaConfig>
|
||||||
|
<RegattaID>24</RegattaID>
|
||||||
|
<RegattaName>Gothenburg World Series 2015</RegattaName>
|
||||||
|
<CourseName>Gothenburg</CourseName>
|
||||||
|
<CentralLatitude>57.6679590</CentralLatitude>
|
||||||
|
<CentralLongitude>11.8503233</CentralLongitude>
|
||||||
|
<CentralAltitude>6.95</CentralAltitude>
|
||||||
|
<UtcOffset>2</UtcOffset>
|
||||||
|
<MagneticVariation>3.2</MagneticVariation>
|
||||||
|
<ShorelineName>gothenburg_shoreline</ShorelineName>
|
||||||
|
</RegattaConfig>
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import java.lang.*?>
|
|
||||||
<?import javafx.scene.layout.*?>
|
|
||||||
|
|
||||||
|
|
||||||
<AnchorPane fx:id="canvasPane" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
|
||||||
<?import javafx.scene.text.*?>
|
|
||||||
<?import javafx.scene.control.*?>
|
|
||||||
<?import java.lang.*?>
|
|
||||||
<?import javafx.scene.layout.*?>
|
|
||||||
|
|
||||||
<AnchorPane fx:id="raceResults" maxHeight="1080.0" maxWidth="1920.0" minHeight="1080.0" minWidth="1920.0" prefHeight="1080.0" prefWidth="1920.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
<children>
|
|
||||||
<GridPane layoutX="444.0" layoutY="256.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
|
||||||
<columnConstraints>
|
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="620.1734008789062" minWidth="10.0" prefWidth="493.2829895019531" />
|
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="862.0581665039062" minWidth="10.0" prefWidth="786.7170104980469" />
|
|
||||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
|
||||||
</columnConstraints>
|
|
||||||
<rowConstraints>
|
|
||||||
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="112.0" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="348.0" minHeight="10.0" prefHeight="99.0" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="348.0" minHeight="7.0" prefHeight="88.0" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="348.0" minHeight="0.0" prefHeight="278.3376770019531" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="653.0" minHeight="0.0" prefHeight="98.66232299804688" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="812.0" minHeight="10.0" prefHeight="418.4577941894531" vgrow="SOMETIMES" />
|
|
||||||
</rowConstraints>
|
|
||||||
<children>
|
|
||||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Race Results:" wrappingWidth="616.5260620117188" GridPane.rowIndex="2">
|
|
||||||
<font>
|
|
||||||
<Font size="45.0" />
|
|
||||||
</font>
|
|
||||||
<GridPane.margin>
|
|
||||||
<Insets left="50.0" />
|
|
||||||
</GridPane.margin>
|
|
||||||
</Text>
|
|
||||||
<VBox fx:id="resultsVBox" prefHeight="44.0" prefWidth="571.0" GridPane.rowIndex="3">
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets />
|
|
||||||
</opaqueInsets>
|
|
||||||
<GridPane.margin>
|
|
||||||
<Insets left="50.0" />
|
|
||||||
</GridPane.margin>
|
|
||||||
<padding>
|
|
||||||
<Insets top="60.0" />
|
|
||||||
</padding></VBox>
|
|
||||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Finish!" wrappingWidth="623.9530334472656" GridPane.rowIndex="1">
|
|
||||||
<font>
|
|
||||||
<Font size="75.0" />
|
|
||||||
</font>
|
|
||||||
<GridPane.margin>
|
|
||||||
<Insets left="50.0" />
|
|
||||||
</GridPane.margin>
|
|
||||||
</Text>
|
|
||||||
</children>
|
|
||||||
</GridPane>
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Leg;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for the Leg class.
|
|
||||||
*/
|
|
||||||
public class LegTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test creation of the leg by specifying a string
|
|
||||||
* for the marker label
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testLegCreationUsingMarkerLabel() {
|
|
||||||
Leg leg = new Leg(010, 100, "SingleMark");
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
|
||||||
assertEquals(leg.getDistance(), 100);
|
|
||||||
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test creation of the leg by providing a
|
|
||||||
* SingleMark object
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testLegCreation() {
|
|
||||||
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
|
|
||||||
|
|
||||||
assertEquals(leg.getHeading(), 010);
|
|
||||||
assertEquals(leg.getDistance(), 100);
|
|
||||||
assertEquals(leg.getMarkerLabel(), "SingleMark");
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test changing whether or not a
|
|
||||||
* leg is the finishing leg
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSetFinishLeg() {
|
|
||||||
Leg leg = new Leg(010, 100, "SingleMark");
|
|
||||||
|
|
||||||
leg.setFinishingLeg(true);
|
|
||||||
assertEquals(leg.getIsFinishingLeg(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Boat;
|
|
||||||
import seng302.models.Race;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for the Race class.
|
|
||||||
*/
|
|
||||||
public class RaceTest {
|
|
||||||
/**
|
|
||||||
* Test that all boats were added to the race
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testAddingBoatsToRace() {
|
|
||||||
Boat boat1 = new Boat("Team 1");
|
|
||||||
Boat boat2 = new Boat("Team 2");
|
|
||||||
|
|
||||||
Race race = new Race();
|
|
||||||
race.addBoat(boat1);
|
|
||||||
race.addBoat(boat2);
|
|
||||||
|
|
||||||
assertEquals(Array.getLength(race.getBoats()), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetShuffledBoats(){
|
|
||||||
Boat boat1 = new Boat("Team 1");
|
|
||||||
Boat boat2 = new Boat("Team 2");
|
|
||||||
|
|
||||||
Race race = new Race();
|
|
||||||
race.addBoat(boat1);
|
|
||||||
race.addBoat(boat2);
|
|
||||||
|
|
||||||
assertEquals(Array.getLength(race.getShuffledBoats()), 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.controllers.RaceViewController;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
|
|
||||||
public class TestRaceTimer {
|
|
||||||
@Test
|
|
||||||
public void testPositiveTimeString(){
|
|
||||||
RaceViewController controller = new RaceViewController();
|
|
||||||
String result = controller.convertTimeToMinutesSeconds(61);
|
|
||||||
|
|
||||||
assertTrue(result.equals("01:01"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNegativeTimeString(){
|
|
||||||
RaceViewController controller = new RaceViewController();
|
|
||||||
String result = controller.convertTimeToMinutesSeconds(-61);
|
|
||||||
|
|
||||||
assertTrue(result.equals("-01:01"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package seng302.models.parsers;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Haoming on 23/03/17.
|
|
||||||
*/
|
|
||||||
public class ConfigParserTest {
|
|
||||||
|
|
||||||
private ConfigParser cp;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initializeParser() throws Exception {
|
|
||||||
cp = new ConfigParser("/config/config.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getWindDirection() throws Exception {
|
|
||||||
assertEquals(135, cp.getWindDirection(), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getTimeScale() throws Exception {
|
|
||||||
assertEquals(10.0, cp.getTimeScale(), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getDoubleByTagName() throws Exception {
|
|
||||||
assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10);
|
|
||||||
assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getStringByTagName() throws Exception {
|
|
||||||
assertEquals("AC35", cp.getStringByTagName("race-name", "11"));
|
|
||||||
assertEquals("oops", cp.getStringByTagName("noTag", "oops"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package seng302.models.parsers;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import seng302.models.Boat;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Haoming on 18/03/17.
|
|
||||||
*/
|
|
||||||
public class TeamsParserTest {
|
|
||||||
|
|
||||||
private TeamsParser tp;
|
|
||||||
@Before
|
|
||||||
public void readFile() {
|
|
||||||
tp = new TeamsParser("/config/teams.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getBoats() throws Exception {
|
|
||||||
ArrayList<Boat> boats = tp.getBoats();
|
|
||||||
|
|
||||||
assertEquals(6, boats.size(), 1e-10);
|
|
||||||
|
|
||||||
assertEquals("Oracle Team USA", boats.get(0).getTeamName());
|
|
||||||
//assertEquals(30.9, boats.get(0).getVelocity(), 1e-10);
|
|
||||||
|
|
||||||
assertEquals("Groupama Team France", boats.get(5).getTeamName());
|
|
||||||
//assertEquals(45.6, boats.get(5).getVelocity(), 1e-10);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.BoatLocationMessage;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test conversions used by the boat location messages
|
||||||
|
*/
|
||||||
|
public class TestConversions {
|
||||||
|
@Test
|
||||||
|
public void testLatLonConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.latLonToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedToLatLon(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWindAngleConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.windAngleToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedWindAngleToDouble(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeadingConversion(){
|
||||||
|
long binaryPacked = BoatLocationMessage.headingToBinaryPackedLong(3232.323);
|
||||||
|
double original = BoatLocationMessage.binaryPackedHeadingToDouble(binaryPacked);
|
||||||
|
|
||||||
|
assertEquals(3232.323, original, 0.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests message header
|
||||||
|
*/
|
||||||
|
public class TestHeader {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHeaderSizeEqualsActualSize(){
|
||||||
|
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
|
||||||
|
assertTrue(h.getSize() == h.getByteBuffer().array().length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headerSizeIsSameAsSpec(){
|
||||||
|
Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1);
|
||||||
|
assertTrue(h.getSize() == 15); // Spec specifies 15 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package seng302.server;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.messages.*;
|
||||||
|
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
public class TestMessage {
|
||||||
|
private static int XML_MESSAGE_LEN = 14;
|
||||||
|
private static int CRC_LEN = 4;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test output expected is the same as the spec
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testXmlMessageSize(){
|
||||||
|
Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1);
|
||||||
|
assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMessageBytesReverse(){
|
||||||
|
byte[] bytes = {1,2,3,4,5};
|
||||||
|
Message.reverse(bytes);
|
||||||
|
|
||||||
|
int testValue = 1;
|
||||||
|
for (int i = 5; i > 0; i--){
|
||||||
|
assertEquals((byte) testValue, bytes[i-1]);
|
||||||
|
testValue++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIntToByteArray(){
|
||||||
|
long originalValue = 0x5050;
|
||||||
|
long testValue = 0;
|
||||||
|
|
||||||
|
byte[] bytes = Message.intToByteArray(originalValue, 6);
|
||||||
|
Message.reverse(bytes);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.length; i++){
|
||||||
|
testValue += ((long) bytes[i] & 0xffL) << (8 * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(originalValue, testValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package seng302.server.simulator;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To test methods in GeoUtility.
|
||||||
|
* Created by Haoming on 28/04/17.
|
||||||
|
*/
|
||||||
|
public class GeoUtilityTest {
|
||||||
|
|
||||||
|
private Position p1 = new Position(57.670333, 11.827833);
|
||||||
|
private Position p2 = new Position(57.671524, 11.844495);
|
||||||
|
private Position p3 = new Position(57.670822, 11.843392);
|
||||||
|
private Position p4 = new Position(25.694829, 98.392049);
|
||||||
|
|
||||||
|
private double toleranceRate = 0.01;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getDistance() throws Exception {
|
||||||
|
double expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p1, p2);
|
||||||
|
expected = 1000;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p1, p3);
|
||||||
|
expected = 927;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getDistance(p2, p4);
|
||||||
|
expected = 7430180;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getBearing() throws Exception {
|
||||||
|
double expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p1, p2);
|
||||||
|
expected = 82;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p1, p3);
|
||||||
|
expected = 86;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getBearing(p2, p4);
|
||||||
|
expected = 78;
|
||||||
|
assertEquals(expected, actual, expected * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getGeoCoordinate() throws Exception {
|
||||||
|
Position expected, actual;
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0);
|
||||||
|
expected = p2;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0);
|
||||||
|
expected = p3;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
|
||||||
|
actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0);
|
||||||
|
expected = p4;
|
||||||
|
assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
|
||||||
|
assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user