diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf33..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/mock/pom.xml b/mock/pom.xml index 3d29db73..3c7b899b 100644 --- a/mock/pom.xml +++ b/mock/pom.xml @@ -13,69 +13,16 @@ 1.0-SNAPSHOT - - junit - junit - 4.12 - test - - - - org.geotools - gt-referencing - 9.0 - - - - - org.mockito - mockito-all - 1.9.5 - - - - com.github.bfsmith - geotimezone - 1.0.3 - - - - org.testng - testng - 6.11 - test - seng302 - network + racevisionGame 1.0-SNAPSHOT - - - maven2-repository.dev.java.net - Java.net repository - http://download.java.net/maven/2 - - - osgeo - Open Source Geospatial Foundation Repository - http://download.osgeo.org/webdav/geotools/ - - - - true - - opengeo - OpenGeo Maven Repository - http://repo.opengeo.org - - - 1.8 1.8 @@ -97,7 +44,7 @@ - seng302.App + mock.app.App ${maven.compiler.source} ${maven.compiler.target} @@ -161,4 +108,4 @@ - \ No newline at end of file + diff --git a/mock/src/main/java/seng302/App.java b/mock/src/main/java/seng302/App.java deleted file mode 100644 index 2b0f31a3..00000000 --- a/mock/src/main/java/seng302/App.java +++ /dev/null @@ -1,75 +0,0 @@ -package seng302; - - -import javafx.application.Application; -import javafx.stage.Stage; -import org.w3c.dom.Document; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import seng302.DataInput.PolarParser; -import seng302.DataInput.XMLReader; -import seng302.Model.Event; -import seng302.Model.Polars; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -public class App extends Application { - - /** - * Entry point for running the programme - * - * @param args for starting the programme - */ - public static void main(String[] args) { - launch(args); - } - - @Override - public void start(Stage primaryStage) { - try { - Polars boatPolars = PolarParser.parse("polars/acc_polars.csv"); - - String regattaXML = readFile("mockXML/regattaTest.xml", StandardCharsets.UTF_8); - String raceXML = readFile("mockXML/raceTest.xml", StandardCharsets.UTF_8); - String boatXML = readFile("mockXML/boatTest.xml", StandardCharsets.UTF_8); - - Event raceEvent = new Event(raceXML, regattaXML, boatXML, boatPolars); - raceEvent.start(); - - } catch (Exception e) { - //Catch all exceptions, print, and exit. - e.printStackTrace(); - System.exit(1); - } - } - - /** - * Reads the Initial Race XML files that are necessary to run the mock. - * @param path path of the XML - * @param encoding encoding of the xml - * @return - * @throws IOException No file etc - * @throws ParserConfigurationException Issue with the XML formatting - * @throws SAXException Issue with XML formatting - * @throws TransformerException Issue with the XML format - */ - private String readFile(String path, Charset encoding) throws IOException, ParserConfigurationException, SAXException, TransformerException { - - InputSource fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(path)); - - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - Document doc = dBuilder.parse(fXmlFile); - doc.getDocumentElement().normalize(); - - return XMLReader.getContents(doc); - - } - -} diff --git a/mock/src/main/java/seng302/DataInput/BoatDataSource.java b/mock/src/main/java/seng302/DataInput/BoatDataSource.java deleted file mode 100644 index 5b9eb411..00000000 --- a/mock/src/main/java/seng302/DataInput/BoatDataSource.java +++ /dev/null @@ -1,14 +0,0 @@ -package seng302.DataInput; - -import seng302.Model.Boat; -import seng302.Model.Mark; - -import java.util.Map; - -/** - * Boats Data - */ -public interface BoatDataSource { - Map getBoats(); - Map getMarkerBoats(); -} diff --git a/mock/src/main/java/seng302/DataInput/RaceDataSource.java b/mock/src/main/java/seng302/DataInput/RaceDataSource.java deleted file mode 100644 index 4de386f9..00000000 --- a/mock/src/main/java/seng302/DataInput/RaceDataSource.java +++ /dev/null @@ -1,32 +0,0 @@ -package seng302.DataInput; - -import seng302.Model.Boat; -import seng302.Model.CompoundMark; -import seng302.Model.GPSCoordinate; -import seng302.Model.Leg; - -import java.time.ZonedDateTime; -import java.util.List; - -/** - * Data Class for a Race - */ -public interface RaceDataSource { - List getBoats(); - - List getLegs(); - - List getBoundary(); - - List getCompoundMarks(); - - int getRaceId(); - - String getRaceType(); - - ZonedDateTime getZonedDateTime(); - - GPSCoordinate getMapTopLeft(); - - GPSCoordinate getMapBottomRight(); -} diff --git a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java b/mock/src/main/java/seng302/DataInput/RaceXMLReader.java deleted file mode 100644 index 7e7a0546..00000000 --- a/mock/src/main/java/seng302/DataInput/RaceXMLReader.java +++ /dev/null @@ -1,288 +0,0 @@ -package seng302.DataInput; - -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import seng302.Exceptions.StreamedCourseXMLException; -import seng302.Model.*; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.text.ParseException; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - -/** - * XML Reader that reads in the race data required for this race - */ -public class RaceXMLReader extends XMLReader implements RaceDataSource { - private static final double COORDINATEPADDING = 0.000; - private GPSCoordinate mapTopLeft, mapBottomRight; - private final List boundary = new ArrayList<>(); - private final Map compoundMarkMap = new HashMap<>(); - private final Map participants = new HashMap<>(); - private final List legs = new ArrayList<>(); - private final List compoundMarks = new ArrayList<>(); - private ZonedDateTime creationTimeDate; - private ZonedDateTime raceStartTime; - private int raceID; - private String raceType; - private boolean postpone; - - private Map boats; - private Map marks; - - /** - * Constructor for Streamed Race XML - * @param filePath path of the file - * @param boatData data for boats in race - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - * @throws ParseException error - * @throws StreamedCourseXMLException error - */ - public RaceXMLReader(String filePath, BoatDataSource boatData) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException { - this(filePath, boatData, true); - } - - - - /** - * Constructor for Streamed Race XML - * @param filePath file path to read - * @param boatData data of the boats in race - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - * @throws ParseException error - * @throws StreamedCourseXMLException error - */ - public RaceXMLReader(String filePath, BoatDataSource boatData, boolean read) throws IOException, SAXException, ParserConfigurationException, ParseException, StreamedCourseXMLException { - super(filePath); - this.boats = boatData.getBoats(); - this.marks = boatData.getMarkerBoats(); - if (read) { - read(); - } - } - - /** - * reads - * @throws StreamedCourseXMLException error - */ - private void read() throws StreamedCourseXMLException { - readRace(); - readParticipants(); - readCourse(); - } - - /** - * reads a race - */ - private void readRace() { - DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - Element settings = (Element) doc.getElementsByTagName("Race").item(0); - NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes(); - - if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); - - - raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID")); - raceType = getTextValueOfNode(settings, "RaceType"); - - creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat); - - if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat); - else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat); - - postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent()); - } - - /** - * Reads in the participants for htis race - */ - private void readParticipants() { - Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0); - nParticipants.getChildNodes().getLength(); - for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) { - int sourceID; - Node yacht = nParticipants.getChildNodes().item(i); - if (yacht.getNodeName().equals("Yacht")) { - if (exists(yacht, "SourceID")) { - sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent()); - participants.put(sourceID, boats.get(sourceID)); - } - } - } - } - - /** - * reads a course - * @throws StreamedCourseXMLException error - */ - private void readCourse() throws StreamedCourseXMLException { - readCompoundMarks(); - readCompoundMarkSequence(); - readCourseLimit(); - } - - /** - * Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers. - * @see CompoundMark - */ - private void readCompoundMarks() throws StreamedCourseXMLException { - Element nCourse = (Element) doc.getElementsByTagName("Course").item(0); - for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) { - Node compoundMark = nCourse.getChildNodes().item(i); - if(compoundMark.getNodeName().equals("CompoundMark")) { - int compoundMarkID = getCompoundMarkID((Element) compoundMark); - compoundMarkMap.put(compoundMarkID, (Element)compoundMark); - compoundMarks.add(getCompoundMark(compoundMarkID)); - } - } - } - - /** - * Generates a CompoundMark from the CompoundMark element with given ID. - * @param compoundMarkID index of required CompoundMark element - * @return generated CompoundMark - * @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks - * @see CompoundMark - */ - private CompoundMark getCompoundMark(int compoundMarkID) throws StreamedCourseXMLException { - Element compoundMark = compoundMarkMap.get(compoundMarkID); - NodeList nMarks = compoundMark.getElementsByTagName("Mark"); - CompoundMark marker; - - switch(nMarks.getLength()) { - case 1: marker = new CompoundMark(getMark((Element)nMarks.item(0))); - break; - case 2: marker = new CompoundMark(getMark((Element)nMarks.item(0)), getMark((Element)nMarks.item(1))); break; - default: throw new StreamedCourseXMLException(); - } - - return marker; - } - - /** - * Gets a mark from an Element - * @param mark Element the mark is suppose to be part of - * @return a Mark that existed in the element - */ - private Mark getMark(Element mark) { - int sourceID = Integer.parseInt(mark.getAttribute("SourceID")); - return marks.get(sourceID); - } - - /** - * Reads "compoundMarkID" attribute of CompoundMark or Corner element - * @param element with "compoundMarkID" attribute - * @return value of "compoundMarkID" attribute - */ - private int getCompoundMarkID(Element element) { - return Integer.parseInt(element.getAttribute("CompoundMarkID")); - } - - /** - * Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID - * @param compoundMarkID unique ID for CompoundMark element - * @return value of "name" attribute - */ - private String getCompoundMarkName(int compoundMarkID) { - return compoundMarkMap.get(compoundMarkID).getAttribute("Name"); - } - - /** - * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. - * @throws StreamedCourseXMLException if compoundMarks cannot be resolved from CompoundMark - */ - private void readCompoundMarkSequence() throws StreamedCourseXMLException { - Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0); - NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner"); - Element markXML = (Element)nCorners.item(0); - CompoundMark lastCompoundMark = getCompoundMark(getCompoundMarkID(markXML)); - String legName = getCompoundMarkName(getCompoundMarkID(markXML)); - for(int i = 1; i < nCorners.getLength(); i++) { - markXML = (Element)nCorners.item(i); - CompoundMark currentCompoundMark = getCompoundMark(getCompoundMarkID(markXML)); - legs.add(new Leg(legName, lastCompoundMark, currentCompoundMark, i-1)); - lastCompoundMark = currentCompoundMark; - legName = getCompoundMarkName(getCompoundMarkID(markXML)); - } - - } - - /** - * Reads the boundary limits of the course - */ - private void readCourseLimit() { - Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0); - for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) { - Node limit = nCourseLimit.getChildNodes().item(i); - if (limit.getNodeName().equals("Limit")) { - double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent()); - double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent()); - boundary.add(new GPSCoordinate(lat, lon)); - } - } - - double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING; - double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING; - double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING; - double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING; - - mapTopLeft = new GPSCoordinate(minLatitude, minLongitude); - mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude); - } - - public List getBoundary() { - return boundary; - } - - public GPSCoordinate getMapTopLeft() { - return mapTopLeft; - } - - public GPSCoordinate getMapBottomRight() { - return mapBottomRight; - } - - public List getLegs() { - return legs; - } - - public List getCompoundMarks() { return compoundMarks; } - - public Double getPadding() { - return COORDINATEPADDING; - } - - public ZonedDateTime getCreationTimeDate() { - return creationTimeDate; - } - - public ZonedDateTime getZonedDateTime() { - return raceStartTime; - } - - public int getRaceId() { - return raceID; - } - - public String getRaceType() { - return raceType; - } - - public boolean isPostpone() { - return postpone; - } - - public List getBoats() { - return new ArrayList<>(participants.values()); - } -} diff --git a/mock/src/main/java/seng302/DataInput/XMLReader.java b/mock/src/main/java/seng302/DataInput/XMLReader.java deleted file mode 100644 index d539539a..00000000 --- a/mock/src/main/java/seng302/DataInput/XMLReader.java +++ /dev/null @@ -1,112 +0,0 @@ -package seng302.DataInput; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; - -/** - * Base Reader for XML Files - */ -public abstract class XMLReader { - - protected Document doc; - - /** - * Read in XML file - * @param filePath filepath for XML file - * @throws ParserConfigurationException If a document builder cannot be created. - * @throws IOException If any IO errors occur while parsing the XML file. - * @throws SAXException If any parse error occurs while parsing. - */ - public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException { - - InputSource fXmlFile; - if (filePath.contains("<")) { - fXmlFile = new InputSource(); - fXmlFile.setCharacterStream(new StringReader(filePath)); - - } else { - fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath)); - } - - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(fXmlFile); - doc.getDocumentElement().normalize(); - } - - /** - * Alternate constructor - * @param xmlFile File to be read - * @param isWholeFile boolean value whether entire file is being passed - */ - public XMLReader(String xmlFile, Boolean isWholeFile) { - - } - - /** - * Return Document data of the read-in XML - * @return XML document - */ - public Document getDocument() { - return doc; - } - - /** - * Get content of a tag in an element - * @param n Element to read tags from - * @param tagName Name of the tag - * @return Content of the tag - */ - public String getTextValueOfNode(Element n, String tagName) { - return n.getElementsByTagName(tagName).item(0).getTextContent(); - } - - /** - * Get attributes for an element - * @param n Element to read attributes from - * @param attr Attributes of element - * @return Attributes of element - */ - public String getAttribute(Element n, String attr) { - return n.getAttribute(attr); - } - - protected boolean exists(Node node, String attribute) { - return node.getAttributes().getNamedItem(attribute) != null; - } - - /** - * Get the contents of the XML FILe. - * @param document holds all xml information - * @return String representation of document - * @throws TransformerException when document is malformed, and cannot be turned into a string - */ - public static String getContents(Document document) throws TransformerException { - DOMSource source = new DOMSource(document); - - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - Transformer transformer = transformerFactory.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - - StringWriter stringWriter = new StringWriter(); - StreamResult result = new StreamResult(stringWriter); - transformer.transform(source, result); - - return stringWriter.toString(); - } - -} diff --git a/mock/src/main/java/seng302/Exceptions/InvalidBoatDataException.java b/mock/src/main/java/seng302/Exceptions/InvalidBoatDataException.java deleted file mode 100644 index 693103b7..00000000 --- a/mock/src/main/java/seng302/Exceptions/InvalidBoatDataException.java +++ /dev/null @@ -1,14 +0,0 @@ -package seng302.Exceptions; - -/** - * An exception thrown when we cannot generate Boats.xml and send an XML message. - */ -public class InvalidBoatDataException extends RuntimeException { - - public InvalidBoatDataException() { - } - - public InvalidBoatDataException(String message) { - super(message); - } -} diff --git a/mock/src/main/java/seng302/Exceptions/InvalidRaceDataException.java b/mock/src/main/java/seng302/Exceptions/InvalidRaceDataException.java deleted file mode 100644 index 2b99a549..00000000 --- a/mock/src/main/java/seng302/Exceptions/InvalidRaceDataException.java +++ /dev/null @@ -1,13 +0,0 @@ -package seng302.Exceptions; - -/** - * Exception thrown when we cannot generate Race.xml data, and send an XML message. - */ -public class InvalidRaceDataException extends RuntimeException { - public InvalidRaceDataException() { - } - - public InvalidRaceDataException(String message) { - super(message); - } -} diff --git a/mock/src/main/java/seng302/Exceptions/StreamedCourseXMLException.java b/mock/src/main/java/seng302/Exceptions/StreamedCourseXMLException.java deleted file mode 100644 index eb21e0fa..00000000 --- a/mock/src/main/java/seng302/Exceptions/StreamedCourseXMLException.java +++ /dev/null @@ -1,7 +0,0 @@ -package seng302.Exceptions; - -/** - * Created by cbt24 on 25/04/17. - */ -public class StreamedCourseXMLException extends Throwable { -} diff --git a/mock/src/main/java/seng302/MockOutput.java b/mock/src/main/java/seng302/MockOutput.java deleted file mode 100644 index 5ec96d2a..00000000 --- a/mock/src/main/java/seng302/MockOutput.java +++ /dev/null @@ -1,251 +0,0 @@ -package seng302; - - -import seng302.Networking.BinaryMessageEncoder; -import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; -import seng302.Networking.MessageEncoders.XMLMessageEncoder; -import seng302.Networking.Messages.BoatLocation; -import seng302.Networking.Messages.Enums.MessageType; -import seng302.Networking.Messages.RaceStatus; -import seng302.Networking.Messages.XMLMessage; - -import java.io.*; -import java.net.*; - -import java.util.concurrent.ArrayBlockingQueue; - -/** - * TCP server to send race information to connected clients. - */ -public class MockOutput implements Runnable -{ - ///Timestamp of the last sent heartbeat message. - private long lastHeartbeatTime; - - ///Period for the heartbeat - that is, how often we send it. - private double heartbeatPeriod = 5.0; - - ///Port to expose server on. - private int serverPort = 4942; - ///Socket used to listen for clients on. - private ServerSocket serverSocket; - ///Socket used to communicate with a client. - private Socket mockSocket; - ///Output stream which wraps around mockSocket outstream. - private DataOutputStream outToVisualiser; - - ///A queue that contains items that are waiting to be sent. - private ArrayBlockingQueue messagesToSendQueue = new ArrayBlockingQueue<>(99999999); - - ///Sequence numbers used in messages. - private short messageNumber = 1; - private short xmlSequenceNumber = 1; - private int heartbeatSequenceNum = 1; - private int boatLocationSequenceNumber = 1; - private int raceStatusSequenceNumber = 1; - - ///Strings containing XML data as strings. - private String raceXml; - private String regattaXml; - private String boatsXml; - - private boolean stop = false; //whether or not hte thread keeps running - - /** - * Ctor. - * @throws IOException if server socket cannot be opened. - */ - public MockOutput() throws IOException { - lastHeartbeatTime = System.currentTimeMillis(); - serverSocket = new ServerSocket(serverPort); - - } - /** - * Calculates the time since last heartbeat message, in seconds. - * @return Time since last heartbeat message, in seconds. - */ - private double timeSinceHeartbeat() { - long now = System.currentTimeMillis(); - return (now - lastHeartbeatTime) / 1000.0; - } - - //returns the heartbeat message - - /** - * Increment the heartbeat value - * @return message for heartbeat data - */ - private byte[] heartbeat(){ - byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeatSequenceNum); - heartbeatSequenceNum++; - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.HEARTBEAT, System.currentTimeMillis(), messageNumber, (short)heartbeatMessage.length, heartbeatMessage); - messageNumber++; - return binaryMessageEncoder.getFullMessage(); - } - - /** - * Used to give the mockOutput an xml string to be made into a message and sent - * @param xmlString the xml string to send - * @param messageType the kind of xml string, values given in AC35 spec (5 regatta, 6 race, 7 boat) - */ - public synchronized void parseXMLString(String xmlString, int messageType){ - XMLMessageEncoder encoder = new XMLMessageEncoder(messageNumber, System.currentTimeMillis(), messageType, xmlSequenceNumber,(short) xmlString.length(), xmlString); - //iterates the sequence numbers - xmlSequenceNumber++; - byte[] encodedXML = encoder.encode(); - - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.XMLMESSAGE, System.currentTimeMillis(), messageNumber, (short)encodedXML.length, encodedXML); - //iterates the message number - messageNumber++; - - addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); - } - - /** - * Used to give the mocOutput information about boat location to be made into a message and sent - * @param sourceID id of the boat - * @param lat latitude of boat - * @param lon longitude of boat - * @param heading heading of boat - * @param speed speed of boat - * @param time historical time of race - */ - public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){ - - BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time); - //iterates the sequence number - boatLocationSequenceNumber++; - - //encodeds the messages - byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); - - //encodeds the full message with header - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.BOATLOCATION, System.currentTimeMillis(), messageNumber, (short)encodedBoatLoc.length, - encodedBoatLoc); - - //iterates the message number - messageNumber++; - - addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); - } - - /** - * Parse the race status data and add it to the buffer to be sent - * @param raceStatus race status to parses - */ - public synchronized void parseRaceStatus(RaceStatus raceStatus){ - - //iterates the sequence number - raceStatusSequenceNumber++; - - //encodeds the messages - byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); - - //encodeds the full message with header - BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder(MessageType.RACESTATUS, System.currentTimeMillis(), messageNumber, (short)encodedRaceStatus.length, - encodedRaceStatus); - - //iterates the message number - messageNumber++; - - addMessageToBufferToSend(binaryMessageEncoder.getFullMessage()); - - } - - /** - * Add a message to the buffer to be sent - * @param messagesToSendBuffer message to add to the buffer - */ - private synchronized void addMessageToBufferToSend(byte[] messagesToSendBuffer) { - this.messagesToSendQueue.add(messagesToSendBuffer); - } - - /** - * Sending loop of the Server - */ - public void run() { - - try { - while (!stop){ - System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE - mockSocket = serverSocket.accept(); - - outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); - - if (boatsXml == null || regattaXml == null || raceXml == null){ - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - continue; - } - - parseXMLString(raceXml, XMLMessage.XMLTypeRace); - parseXMLString(regattaXml, XMLMessage.XMLTypeRegatta); - parseXMLString(boatsXml, XMLMessage.XMLTypeBoat); - - - while(true) { - try { - //Sends a heartbeat every so often. - if (timeSinceHeartbeat() >= heartbeatPeriod) { - outToVisualiser.write(heartbeat()); - lastHeartbeatTime = System.currentTimeMillis(); - } - - //Checks the buffer to see if there is anything to send. - while (messagesToSendQueue.size() > 0) { - //Grabs message from head of queue. - byte[] binaryMessage = messagesToSendQueue.remove(); - - //sends the message to the visualiser - outToVisualiser.write(binaryMessage); - - } - }catch(SocketException e){ - break; - } - - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void stop(){ - stop = true; - } - - /** - * Sets the Race XML to send - * @param raceXml XML to send to the CLient - */ - public void setRaceXml(String raceXml) { - this.raceXml = raceXml; - } - - /** - * Sets the Regatta XMl to send - * @param regattaXml XML to send to CLient - */ - public void setRegattaXml(String regattaXml) { - this.regattaXml = regattaXml; - } - - /** - * Sets the Boats XML to send - * @param boatsXml XMl to send to the CLient - */ - public void setBoatsXml(String boatsXml) { - this.boatsXml = boatsXml; - } - - public static void main(String argv[]) throws Exception - { - MockOutput client = new MockOutput(); - client.run(); - } - -} diff --git a/mock/src/main/java/seng302/Model/Event.java b/mock/src/main/java/seng302/Model/Event.java deleted file mode 100644 index 27f8b1a9..00000000 --- a/mock/src/main/java/seng302/Model/Event.java +++ /dev/null @@ -1,82 +0,0 @@ -package seng302.Model; - -import org.xml.sax.SAXException; -import seng302.DataInput.*; -import seng302.Exceptions.StreamedCourseXMLException; -import seng302.MockOutput; -import seng302.Networking.Messages.Enums.MessageType; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.*; -import java.text.ParseException; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; - - -/** - * A Race Event, this holds all of the race's information as well as handling the connection to its clients. - */ -public class Event { - - String raceXML; - String regattaXML; - String boatXML; - Polars boatPolars; - MockOutput mockOutput; - - public Event(String raceXML, String regattaXML, String boatXML, Polars boatPolars) { - - this.raceXML = getRaceXMLAtCurrentTime(raceXML); - this.boatXML = boatXML; - this.regattaXML = regattaXML; - this.boatPolars = boatPolars; - try { - mockOutput = new MockOutput(); - new Thread(mockOutput).start(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Sends the initial race data and then begins race simulation - */ - public void start() { - try { - sendXMLs(); - Race newRace = new Race(new RaceXMLReader(this.raceXML, new BoatXMLReader(boatXML, this.boatPolars)), mockOutput); - new Thread((newRace)).start(); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - } - } - - /** - * Sends out each xml string, via the mock output - */ - private void sendXMLs() { - - mockOutput.setRegattaXml(regattaXML); - mockOutput.parseXMLString(regattaXML, MessageType.XMLMESSAGE.getValue()); - - mockOutput.setRaceXml(raceXML); - mockOutput.parseXMLString(raceXML, MessageType.XMLMESSAGE.getValue()); - - mockOutput.setBoatsXml(boatXML); - mockOutput.parseXMLString(boatXML, MessageType.XMLMESSAGE.getValue()); - } - - /** - * Sets the xml description of the race to show the race was created now, and starts in 3 minutes - * @param raceXML - * @return String containing edited xml - */ - private String getRaceXMLAtCurrentTime(String raceXML) { - DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); - ZonedDateTime creationTime = ZonedDateTime.now(); - return raceXML.replace("CREATION_TIME", dateFormat.format(creationTime)) - .replace("START_TIME", dateFormat.format(creationTime.plusMinutes(3))); - } - -} diff --git a/mock/src/main/java/seng302/Model/Marker.java b/mock/src/main/java/seng302/Model/Marker.java deleted file mode 100644 index e69de29b..00000000 diff --git a/mock/src/test/java/seng302/DataInput/PolarParserTest.java b/mock/src/test/java/seng302/DataInput/PolarParserTest.java deleted file mode 100644 index b10adece..00000000 --- a/mock/src/test/java/seng302/DataInput/PolarParserTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package seng302.DataInput; - - -import org.testng.annotations.Test; -import seng302.Exceptions.InvalidPolarFileException; -import seng302.Model.Polars; - -import static org.testng.Assert.*; - -/** - * Created by f123 on 10-May-17. - */ -public class PolarParserTest { - - @Test - /* - Tests if we can parse a polar data file (stored in a string), and create a polar table. - */ - public void testParse() throws Exception { - - try { - //Parse data file. - Polars polars = PolarParser.parse("polars/acc_polars.csv"); - - //If the parse function didn't through, it worked. - assertTrue(true); - } - catch (InvalidPolarFileException e) { - assertTrue(false); - } - - } - - - -} diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java deleted file mode 100644 index 10c898c9..00000000 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package seng302.Model; - - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * Created by esa46 on 22/03/17. - */ -public class BoatTest { - - - private GPSCoordinate ORIGIN_COORDS; - private Boat TEST_BOAT; - - @Before - public void setUp() { - ORIGIN_COORDS = new GPSCoordinate(0, 0); - TEST_BOAT = new Boat(1, "Test", "tt", new Polars()); - TEST_BOAT.setCurrentPosition(ORIGIN_COORDS); - } - - @Test - public void calculateDueNorthAzimuthReturns0() { - - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8); - } - - @Test - public void calculateDueSouthAzimuthReturns180() { - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8); - } - - - @Test - public void calculateDueEastAzimuthReturns90() { - - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8); - } - - - @Test - public void calculateDueWestAzimuthReturnsNegative90() { - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -90, 1e-8); - - } - - @Test - public void calculateDueNorthHeadingReturns0() { - - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(50, 0))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 0, 1e-8); - } - - - @Test - public void calculateDueEastHeadingReturns90() { - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, 50))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 90, 1e-8); - } - - @Test - public void calculateDueSouthHeadingReturns180() { - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(-50, 0))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 180, 1e-8); - } - - @Test - public void calculateDueWestHeadingReturns270() { - CompoundMark startMarker = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORDS)); - CompoundMark endMarker = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0, -50))); - Leg start = new Leg("Start", startMarker, endMarker, 0); - TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateBearingToNextMarker().degrees(), 270, 1e-8); - } -} diff --git a/mock/src/test/java/seng302/Model/RaceTest.java b/mock/src/test/java/seng302/Model/RaceTest.java deleted file mode 100644 index 3151c2ea..00000000 --- a/mock/src/test/java/seng302/Model/RaceTest.java +++ /dev/null @@ -1,315 +0,0 @@ -package seng302.Model; - - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.Mockito; -import org.xml.sax.SAXException; -import seng302.DataInput.BoatDataSource; -import seng302.DataInput.BoatXMLReader; -import seng302.DataInput.RaceDataSource; -import seng302.DataInput.RaceXMLReader; -import seng302.Exceptions.StreamedCourseXMLException; -import seng302.MockOutput; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; - - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -/** - * Created by esa46 on 15/03/17. - */ -public class RaceTest{ - - - public static final CompoundMark ORIGIN = new CompoundMark(new Mark(1, "test origin 1", new GPSCoordinate(0, 0))); - public static final CompoundMark THREE_NM_FROM_ORIGIN = new CompoundMark(new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0))); - public static final CompoundMark FIFTEEN_NM_FROM_ORIGIN = new CompoundMark(new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0))); - public static ArrayList TEST_LEGS = new ArrayList<>(); - public static final int START_LEG_DISTANCE = 3; - public static final int MIDDLE_LEG_DISTANCE = 12; - public static final Leg START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0); - public static final Leg MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1); - public static final Leg FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2); - - @Before - public void setUp() { - TEST_LEGS.add(START_LEG); - TEST_LEGS.add(MIDDLE_LEG); - TEST_LEGS.add(FINISH_LEG); - } - - @Ignore - @Test - public void countdownTimerSendsBoatLocations() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - testRace.initialiseBoats(); - testRace.countdownTimer.handle(1); - verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong()); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void countdownTimerSendsRaceStatusMessages() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - testRace.countdownTimer.handle(1); - verify(mockOutput, atLeast(1)).parseRaceStatus(any()); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void checkPositionFinishedUpdatesNumberFinishedBoats() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(FINISH_LEG); - testBoat.setDistanceTravelledInLeg(1); - testRace.checkPosition(testBoat, 1); - - assertEquals(testRace.getNumberOfActiveBoats(), 0); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void checkPositionSetFinishedBoatVelocityTo0() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(FINISH_LEG); - testBoat.setDistanceTravelledInLeg(1); - testRace.checkPosition(testBoat, 1); - - assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void checkPositionSetsFinishTime() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(FINISH_LEG); - testBoat.setDistanceTravelledInLeg(1); - testRace.checkPosition(testBoat, 1); - - assertEquals(testBoat.getTimeFinished(), 1, 1e-8); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(START_LEG); - testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE); - testRace.checkPosition(testBoat, 1); - - assertEquals(testRace.getNumberOfActiveBoats(), 1); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - - @Ignore - @Test - public void distanceTravelledBeforeUpdatingLegIsRetained() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); - Race testRace = new Race(dataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(START_LEG); - testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); - testRace.checkPosition(testBoat, 0); - - assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void doNotFinishAnswersYesIf100PercentChance() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - - testRace.setDnfChance(100); - assertTrue(testRace.doNotFinish()); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void doNotFinishAnswersNoIf0PercentChance() { - - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - testRace.setDnfChance(0); - assertFalse(testRace.doNotFinish()); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void boatsAreSetToDNF() { - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - testRace.setDnfChance(100); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(START_LEG); - testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); - testRace.checkPosition(testBoat, 1); - assertEquals(testBoat.getCurrentLeg().getName(), "DNF"); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - - } - - @Ignore - @Test - public void updatePositionIgnoresFinishedBoats() { - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(FINISH_LEG); - testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); - testRace.updatePosition(testBoat, 1, 1); - assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate()); - - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void updatePositionChangesBoatPosition() { - try { - MockOutput mockOutput = Mockito.mock(MockOutput.class); - BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); - RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); - Race testRace = new Race(raceDataSource, mockOutput); - testRace.initialiseBoats(); - Boat testBoat = testRace.getBoats().get(0); - testBoat.setCurrentLeg(START_LEG); - testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1); - testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); - testRace.updatePosition(testBoat, 100, 100); - assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate()); - } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { - e.printStackTrace(); - fail(); - } - } - - @Ignore - @Test - public void windDirectionCorrectValues(){ -// try { -// MockOutput mockOutput = Mockito.mock(MockOutput.class); -// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml"); -// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); -// Race testRace = new Race(raceDataSource, mockOutput); -// testRace.setChangeWind(1); -// testRace.setWindDir(65535); -// testRace.changeWindDir(); -// assertEquals(100, testRace.getWind()); -// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { -// e.printStackTrace(); -// fail(); -// } - } - - -} diff --git a/network/pom.xml b/network/pom.xml index 40087f2d..8d9b6324 100644 --- a/network/pom.xml +++ b/network/pom.xml @@ -14,12 +14,6 @@ - - seng302 - sharedModel - 1.0-SNAPSHOT - - junit diff --git a/network/src/main/java/seng302/Networking/MessageEncoders/XMLMessageEncoder.java b/network/src/main/java/seng302/Networking/MessageEncoders/XMLMessageEncoder.java deleted file mode 100644 index 55c55375..00000000 --- a/network/src/main/java/seng302/Networking/MessageEncoders/XMLMessageEncoder.java +++ /dev/null @@ -1,60 +0,0 @@ -package seng302.Networking.MessageEncoders; - -import java.nio.ByteBuffer; - -import static seng302.Networking.Utils.ByteConverter.intToBytes; -import static seng302.Networking.Utils.ByteConverter.longToBytes; -import static seng302.Networking.Utils.ByteConverter.shortToBytes; - -/** - * Encodes a XML file into a message of AC35 format - */ -public class XMLMessageEncoder { - private byte[] messageVersionNumber; - private short ackNumber; - private long timeStamp; - private byte[] xmlMsgSubType; - private short sequenceNumber; - private short xmlMsgLength; - private String xmlMessage; - - public XMLMessageEncoder(short ackNumber, long timeStamp, int xmlMsgSubType, short sequenceNumber, short xmlMsgLength, String xmlMessage) { - this.messageVersionNumber = intToBytes(1, 1); - this.ackNumber = ackNumber; - this.timeStamp = timeStamp; - this.xmlMsgSubType = intToBytes(xmlMsgSubType, 1); - this.sequenceNumber = sequenceNumber; - this.xmlMsgLength = xmlMsgLength; - this.xmlMessage = xmlMessage; - } - - public byte[] encode() { - byte[] messageBytes = xmlMessage.getBytes(); - if (messageBytes.length > this.xmlMsgLength) { - //System.err.println("Xml message is to big"); - return null; - } - ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); - - //ackNumber converted to bytes - byte[] ackNumberBytes = shortToBytes(ackNumber, 2); - - //sequenceNumber converted to bytes - byte[] sequenceNumberBytes = shortToBytes(sequenceNumber, 2); - - //xmlMsgLength converted to bytes - byte[] xmlMsgLengthBytes = shortToBytes(xmlMsgLength, 2); - - - tempOutputByteBuffer.put(messageVersionNumber); - tempOutputByteBuffer.put(ackNumberBytes); - tempOutputByteBuffer.put(longToBytes(timeStamp, 6)); - tempOutputByteBuffer.put(xmlMsgSubType); - tempOutputByteBuffer.put(sequenceNumberBytes); - tempOutputByteBuffer.put(xmlMsgLengthBytes); - tempOutputByteBuffer.put(messageBytes); - - return tempOutputByteBuffer.array(); - } - -} diff --git a/network/src/main/java/seng302/Networking/Messages/XMLMessage.java b/network/src/main/java/seng302/Networking/Messages/XMLMessage.java deleted file mode 100644 index 856d4bc3..00000000 --- a/network/src/main/java/seng302/Networking/Messages/XMLMessage.java +++ /dev/null @@ -1,57 +0,0 @@ -package seng302.Networking.Messages; - -import seng302.Networking.Messages.Enums.MessageType; - -import java.io.InputStream; - -/** - * Created by fwy13 on 25/04/17. - */ -public class XMLMessage extends AC35Data { - - private int ackNumber; - private long timeStamp; - private int xmlMsgSubType; - private int sequenceNumber; - private int xmlMsgLength; - private InputStream xmlMessage; - - public static int XMLTypeRegatta = 5; - public static int XMLTypeRace = 6; - public static int XMLTypeBoat = 7; - - /** - * Constructor for an XML Message - * @param ackNumber Number for acknowledgement inherited for the AC35Data Packet - * @param timeStamp Time received - * @param xmlMsgSubType Type of XML message - * @param sequenceNumber Order that it has arrived in - * @param xmlMsgLength Length of the xml message - * @param xmlMessage XML message - */ - public XMLMessage(int ackNumber, long timeStamp, int xmlMsgSubType, int sequenceNumber, int xmlMsgLength, InputStream xmlMessage){ - super(MessageType.XMLMESSAGE); - this.ackNumber = ackNumber; - this.timeStamp = timeStamp; - this.xmlMsgSubType = xmlMsgSubType; - this.sequenceNumber = sequenceNumber; - this.xmlMsgLength = xmlMsgLength; - this.xmlMessage = xmlMessage; - } - - /** - * Get the XML Message - * @return the XML message as an input stream - */ - public InputStream getXmlMessage() { - return xmlMessage; - } - - /** - * Get the type of message - * @return Gets the type of message the XML message is - */ - public int getXmlMsgSubType() { - return xmlMsgSubType; - } -} diff --git a/network/src/test/java/seng302/Networking/MessageDecoders/XMLMessageDecoderTest.java b/network/src/test/java/seng302/Networking/MessageDecoders/XMLMessageDecoderTest.java deleted file mode 100644 index 1b0a572e..00000000 --- a/network/src/test/java/seng302/Networking/MessageDecoders/XMLMessageDecoderTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package seng302.Networking.MessageDecoders; - -import org.junit.Assert; -import org.junit.Test; -import seng302.Networking.MessageEncoders.XMLMessageEncoder; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -/** - * Created by hba56 on 20/04/17. - */ -public class XMLMessageDecoderTest { - @Test - public void getByteArrayTest(){ - try{ - StringBuilder xmlString; - BufferedReader br = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(("../../../raceXML/Regatta.xml")))); - String line; - xmlString = new StringBuilder(); - while((line=br.readLine())!= null){ - xmlString.append(line.trim()); - } - long time = System.currentTimeMillis(); - XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, time, (byte)7, (short)1, (short)xmlString.length(), xmlString.toString()); - - byte[] encodedXML = testEncoder.encode(); - - XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); - decoderXML.decode(); - - - Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); - Assert.assertEquals((short)1, decoderXML.getAckNumber()); - Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals((byte)7, decoderXML.getXmlMsgSubType()); - Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); - Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); - - }catch (IOException e){ - e.printStackTrace(); - } - } -} diff --git a/network/src/test/java/seng302/Networking/XMLMessageEncoderTest.java b/network/src/test/java/seng302/Networking/XMLMessageEncoderTest.java deleted file mode 100644 index 38e7f6e9..00000000 --- a/network/src/test/java/seng302/Networking/XMLMessageEncoderTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package seng302.Networking; - -import org.junit.Assert; -import org.junit.Test; -import seng302.Networking.MessageEncoders.XMLMessageEncoder; - -import java.io.*; - -/** - * Created by hba56 on 19/04/17. - */ -public class XMLMessageEncoderTest { - @Test - public void getByteArrayTest(){ - StringBuilder xmlString; - try{ - BufferedReader br = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml")))); - String line; - xmlString = new StringBuilder(); - while((line=br.readLine())!= null){ - xmlString.append(line.trim()); - } - XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)xmlString.length(), xmlString.toString()); - - byte[] encodedXML = testEncoder.encode(); - - //1 + 2 + 6 + 1 + 2 + 2 + 374 - Assert.assertEquals(388, encodedXML.length); - }catch (IOException e){ - e.printStackTrace(); - } - } - - @Test - public void getByteArrayNullTest(){ - StringBuilder xmlString; - try{ - BufferedReader br = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml")))); - String line; - xmlString = new StringBuilder(); - while((line=br.readLine())!= null){ - xmlString.append(line.trim()); - } - XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, System.currentTimeMillis(), (byte)7, (short)1, (short)1, xmlString.toString()); - - byte[] encodedXML = testEncoder.encode(); - - Assert.assertEquals(null, encodedXML); - }catch (IOException e){ - e.printStackTrace(); - } - } -} diff --git a/pom.xml b/pom.xml index 2003bb14..444c0faa 100644 --- a/pom.xml +++ b/pom.xml @@ -8,10 +8,10 @@ team-7 + racevisionGame mock visualiser network - sharedModel https://eng-git.canterbury.ac.nz/SENG302-2016/team-7 diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml new file mode 100644 index 00000000..efc88c09 --- /dev/null +++ b/racevisionGame/pom.xml @@ -0,0 +1,218 @@ + + 4.0.0 + + seng302 + team-7 + 1.0-SNAPSHOT + + + jar + racevisionGame + racevisionGame + 1.0-SNAPSHOT + + + + junit + junit + 4.12 + test + + + + + + + org.mockito + mockito-all + 1.9.5 + + + + + + org.testng + testng + 6.11 + test + + + + + + org.jetbrains + annotations + 15.0 + + + + + + org.geotools + gt-referencing + 9.0 + + + + + + + + + maven2-repository.dev.java.net + Java.net repository + http://download.java.net/maven/2 + + + + + osgeo + Open Source Geospatial Foundation Repository + http://download.osgeo.org/webdav/geotools/ + + + + + + + + + 1.8 + 1.8 + + + + + + + mock + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + + + mock.app.App + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + package + + shade + + + + + + + + + + + visualiser + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + + + visualiser.app.App + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + + package + + shade + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.5 + + + org.apache.maven.plugins + maven-pmd-plugin + 3.6 + + true + ${maven.compiler.target} + + /rulesets/java/basic.xml + /rulesets/java/imports.xml + /rulesets/java/codesize.xml + /rulesets/java/design.xml + /rulesets/java/empty.xml + /rulesets/java/junit.xml + /rulesets/java/unusedcode.xml + + true + utf-8 + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.19.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.8.1 + + + + diff --git a/racevisionGame/src/main/java/mock/app/App.java b/racevisionGame/src/main/java/mock/app/App.java new file mode 100644 index 00000000..06f628d5 --- /dev/null +++ b/racevisionGame/src/main/java/mock/app/App.java @@ -0,0 +1,54 @@ +package mock.app; + + +import javafx.application.Application; +import javafx.stage.Stage; +import mock.dataInput.PolarParser; +import mock.model.Polars; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import shared.dataInput.XMLReader; +import shared.enums.XMLFileType; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class App extends Application { + + /** + * Entry point for running the programme + * + * @param args for starting the programme + */ + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) { + try { + Polars boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); + + String regattaXML = XMLReader.readXMLFileToString("mock/mockXML/regattaTest.xml", StandardCharsets.UTF_8); + String raceXML = XMLReader.readXMLFileToString("mock/mockXML/raceTest.xml", StandardCharsets.UTF_8); + String boatXML = XMLReader.readXMLFileToString("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8); + + Event raceEvent = new Event(raceXML, regattaXML, boatXML, XMLFileType.Contents, boatPolars); + raceEvent.start(); + + } catch (Exception e) { + //Catch all exceptions, print, and exit. + e.printStackTrace(); + System.exit(1); + } + } + + + +} diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java new file mode 100644 index 00000000..080e8405 --- /dev/null +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -0,0 +1,124 @@ +package mock.app; + +import mock.model.MockRace; +import mock.model.Polars; +import network.Messages.LatestMessages; +import shared.dataInput.*; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.InvalidRaceDataException; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; +import shared.model.Constants; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + + +/** + * A Race Event, this holds all of the race's information as well as handling the connection to its clients. + */ +public class Event { + + private String raceXML; + private String regattaXML; + private String boatXML; + + private XMLFileType xmlFileType; + + private Polars boatPolars; + + private MockOutput mockOutput; + private LatestMessages latestMessages; + + + /** + * Constructs an event, using various XML files. + * @param raceXML The race.xml file. + * @param regattaXML The regatta.xml file. + * @param boatXML The boat.xml file. + * @param type How to read the file - e.g., load as resource. + * @param boatPolars polars that the boat uses + */ + public Event(String raceXML, String regattaXML, String boatXML, XMLFileType type, Polars boatPolars) { + + this.raceXML = getRaceXMLAtCurrentTime(raceXML); + this.boatXML = boatXML; + this.regattaXML = regattaXML; + + this.xmlFileType = type; + + this.boatPolars = boatPolars; + + this.latestMessages = new LatestMessages(); + + + try { + this.mockOutput = new MockOutput(this.latestMessages); + new Thread(mockOutput).start(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Sends the initial race data and then begins race simulation. + * @throws InvalidRaceDataException Thrown if the race xml file cannot be parsed. + * @throws XMLReaderException Thrown if any of the xml files cannot be parsed. + * @throws InvalidBoatDataException Thrown if the boat xml file cannot be parsed. + * @throws InvalidRegattaDataException Thrown if the regatta xml file cannot be parsed. + */ + public void start() throws InvalidRaceDataException, XMLReaderException, InvalidBoatDataException, InvalidRegattaDataException { + + sendXMLs(); + + //Parse the XML files into data sources. + RaceDataSource raceDataSource = new RaceXMLReader(this.raceXML, this.xmlFileType); + BoatDataSource boatDataSource = new BoatXMLReader(this.boatXML, this.xmlFileType); + RegattaDataSource regattaDataSource = new RegattaXMLReader(this.regattaXML, this.xmlFileType); + + //Create and start race. + MockRace newRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, this.latestMessages, this.boatPolars, Constants.RaceTimeScale); + + new Thread(newRace).start(); + + } + + /** + * Sends out each xml string, via the mock output + */ + private void sendXMLs() { + + mockOutput.setRegattaXml(regattaXML); + + mockOutput.setRaceXml(raceXML); + + mockOutput.setBoatsXml(boatXML); + } + + /** + * Sets the xml description of the race to show the race was created now, and starts in 4 minutes + * @param raceXML The race.xml contents. + * @return String containing edited xml + */ + private String getRaceXMLAtCurrentTime(String raceXML) { + + //The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute. + long millisecondsToAdd = Constants.RacePreStartTime + (1 * 60 * 1000); + long secondsToAdd = millisecondsToAdd / 1000; + //Scale the time using our time scalar. + secondsToAdd = secondsToAdd / Constants.RaceTimeScale; + + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); + ZonedDateTime creationTime = ZonedDateTime.now(); + raceXML = raceXML.replace("CREATION_TIME", dateFormat.format(creationTime)); + + raceXML = raceXML.replace("START_TIME", dateFormat.format(creationTime.plusSeconds(secondsToAdd))); + + return raceXML; + + } + +} diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java new file mode 100644 index 00000000..e217accf --- /dev/null +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -0,0 +1,431 @@ +package mock.app; + + + +import network.BinaryMessageEncoder; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.*; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.XMLMessageType; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; + +/** + * TCP server to send race information to connected clients. + */ +public class MockOutput implements Runnable +{ + /** + * Timestamp of the last sent heartbeat message. + */ + private long lastHeartbeatTime; + + /** + * Period for the heartbeat - that is, how often we send it. + */ + private double heartbeatPeriod = 5.0; + + /** + * Port to expose server on. + */ + private int serverPort = 4942; + /** + * Socket used to listen for clients on. + */ + private ServerSocket serverSocket; + /** + * Socket used to communicate with a client. + */ + private Socket mockSocket; + /** + * Output stream which wraps around mockSocket outstream. + */ + private DataOutputStream outToVisualiser; + + + /** + * An object containing the set of latest messages to send. + * Every server frame, MockOutput reads messages from this, and send them. + */ + private LatestMessages latestMessages; + + + + /** + * Ack numbers used in messages. + */ + private int ackNumber = 1; + + + /** + * Sequence number for race xml messages. + */ + private short raceXMLSequenceNumber = 1; + + /** + * Sequence number for boat xml messages. + */ + private short boatXMLSequenceNumber = 1; + + /** + * Sequence number for regatta xml messages. + */ + private short regattaXMLSequenceNumber = 1; + + /** + * Sequence number for heartbeat messages. + */ + private int heartbeatSequenceNum = 1; + + + private boolean stop = false; //whether or not hte thread keeps running + + /** + * Ctor. + * @param latestMessages The collection of messages to send to connected clients. + * @throws IOException if server socket cannot be opened. + */ + public MockOutput(LatestMessages latestMessages) throws IOException { + + this.lastHeartbeatTime = System.currentTimeMillis(); + this.serverSocket = new ServerSocket(serverPort); + + this.latestMessages = latestMessages; + + } + + + /** + * Increments the ackNumber value, and returns it. + * @return Incremented ackNumber. + */ + private int getNextAckNumber(){ + this.ackNumber++; + + return this.ackNumber; + } + + + /** + * Calculates the time since last heartbeat message, in seconds. + * @return Time since last heartbeat message, in seconds. + */ + private double timeSinceHeartbeat() { + long now = System.currentTimeMillis(); + return (now - lastHeartbeatTime) / 1000.0; + } + + + /** + * Generates the next heartbeat message and returns it. Increments the heartbeat sequence number. + * @return The next heartbeat message. + */ + private Heartbeat createHeartbeatMessage() { + + //Create the heartbeat message. + Heartbeat heartbeat = new Heartbeat(this.heartbeatSequenceNum); + heartbeatSequenceNum++; + + return heartbeat; + } + + /** + * Serializes a heartbeat message into a packet to be sent, and returns the byte array. + * @param heartbeat The heartbeat message to serialize. + * @return Byte array containing the next heartbeat message. + */ + private byte[] parseHeartbeat(Heartbeat heartbeat) { + + //Serializes the heartbeat message. + byte[] heartbeatMessage = RaceVisionByteEncoder.heartBeat(heartbeat); + + //Places the serialized message in a packet. + BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( + MessageType.HEARTBEAT, + System.currentTimeMillis(), + getNextAckNumber(), + (short) heartbeatMessage.length, + heartbeatMessage ); + + return binaryMessageEncoder.getFullMessage(); + + } + + + /** + * Creates an XMLMessage of a specified subtype using the xml contents string. + * @param xmlString The contents of the xml file. + * @param messageType The subtype of xml message (race, regatta, boat). + * @return The created XMLMessage object. + */ + private XMLMessage createXMLMessage(String xmlString, XMLMessageType messageType) { + + //Get the correct sequence number to use, and increment it. + short sequenceNumber = 0; + if (messageType == XMLMessageType.RACE) { + sequenceNumber = this.raceXMLSequenceNumber; + this.raceXMLSequenceNumber++; + + } else if (messageType == XMLMessageType.BOAT) { + sequenceNumber = this.boatXMLSequenceNumber; + this.boatXMLSequenceNumber++; + + } else if (messageType == XMLMessageType.REGATTA) { + sequenceNumber = this.regattaXMLSequenceNumber; + this.regattaXMLSequenceNumber++; + + } + + //Create the message. + XMLMessage message = new XMLMessage( + XMLMessage.currentVersionNumber, + getNextAckNumber(), + System.currentTimeMillis(), + messageType, + sequenceNumber, + xmlString ); + + + return message; + } + + /** + * Encodes/serialises a XMLMessage message, and returns it. + * @param xmlMessage The XMLMessage message to serialise. + * @return The XMLMessage message in a serialised form. + */ + private synchronized byte[] parseXMLMessage(XMLMessage xmlMessage) { + + //Serialize the xml message. + byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(xmlMessage); + + //Place the message in a packet. + BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( + MessageType.XMLMESSAGE, + System.currentTimeMillis(), + xmlMessage.getAckNumber(), //We use the ack number from the xml message. + (short) encodedXML.length, + encodedXML ); + + + return binaryMessageEncoder.getFullMessage(); + + } + + + + /** + * Encodes/serialises a BoatLocation message, and returns it. + * @param boatLocation The BoatLocation message to serialise. + * @return The BoatLocation message in a serialised form. + */ + private synchronized byte[] parseBoatLocation(BoatLocation boatLocation){ + + + //Encodes the message. + byte[] encodedBoatLoc = RaceVisionByteEncoder.boatLocation(boatLocation); + + //Encodes the full message with header. + BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( + MessageType.BOATLOCATION, + System.currentTimeMillis(), + getNextAckNumber(), + (short) encodedBoatLoc.length, + encodedBoatLoc ); + + + return binaryMessageEncoder.getFullMessage(); + + } + + /** + * Encodes/serialises a RaceStatus message, and returns it. + * @param raceStatus The RaceStatus message to serialise. + * @return The RaceStatus message in a serialised form. + */ + private synchronized byte[] parseRaceStatus(RaceStatus raceStatus){ + + //Encodes the messages. + byte[] encodedRaceStatus = RaceVisionByteEncoder.raceStatus(raceStatus); + + //Encodes the full message with header. + BinaryMessageEncoder binaryMessageEncoder = new BinaryMessageEncoder( + MessageType.RACESTATUS, + System.currentTimeMillis(), + getNextAckNumber(), + (short) encodedRaceStatus.length, + encodedRaceStatus ); + + + return binaryMessageEncoder.getFullMessage(); + + + } + + + /** + * Sending loop of the Server + */ + public void run() { + + try { + while (!stop){ + //Wait for a client to connect. + System.out.println("Waiting for a connection...");//TEMP DEBUG REMOVE + mockSocket = serverSocket.accept(); + + outToVisualiser = new DataOutputStream(mockSocket.getOutputStream()); + + //Wait until all of the xml files have been set. + if (!this.latestMessages.hasAllXMLMessages()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + continue; + } + + + long previousFrameTime = System.currentTimeMillis(); + boolean sentXMLs = false; + + + while(true) { + try { + + long currentFrameTime = System.currentTimeMillis(); + + //This is the time elapsed, in milliseconds, since the last server "frame". + long framePeriod = currentFrameTime - previousFrameTime; + + //We only attempt to send packets every X milliseconds. + long minimumFramePeriod = 16; + if (framePeriod >= minimumFramePeriod) { + + //Sends a heartbeat every so often. + if (timeSinceHeartbeat() >= heartbeatPeriod) { + outToVisualiser.write(parseHeartbeat(createHeartbeatMessage())); + lastHeartbeatTime = System.currentTimeMillis(); + } + + //Send XML messages. + if (!sentXMLs) { + //Serialise them. + byte[] raceXMLBlob = parseXMLMessage(latestMessages.getRaceXMLMessage()); + byte[] regattaXMLBlob = parseXMLMessage(latestMessages.getRegattaXMLMessage()); + byte[] boatsXMLBlob = parseXMLMessage(latestMessages.getBoatXMLMessage()); + + //Send them. + outToVisualiser.write(raceXMLBlob); + outToVisualiser.write(regattaXMLBlob); + outToVisualiser.write(boatsXMLBlob); + sentXMLs = true; + } + + //Sends the RaceStatus message. + if (this.latestMessages.getRaceStatus() != null) { + byte[] raceStatusBlob = this.parseRaceStatus(this.latestMessages.getRaceStatus()); + + this.outToVisualiser.write(raceStatusBlob); + } + + //Send all of the BoatLocation messages. + for (int sourceID : this.latestMessages.getBoatLocationMap().keySet()) { + + //Get the message. + BoatLocation boatLocation = this.latestMessages.getBoatLocation(sourceID); + if (boatLocation != null) { + + //Encode. + byte[] boatLocationBlob = this.parseBoatLocation(boatLocation); + + //Write it. + this.outToVisualiser.write(boatLocationBlob); + + } + } + + previousFrameTime = currentFrameTime; + + + } else { + //Wait until the frame period will be large enough. + long timeToWait = minimumFramePeriod - framePeriod; + + try { + Thread.sleep(timeToWait); + } catch (InterruptedException e) { + //If we get interrupted, exit the function. + e.printStackTrace(); + //Re-set the interrupt flag. + Thread.currentThread().interrupt(); + return; + } + + } + + } catch (SocketException e) { + break; + } + + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void stop(){ + stop = true; + } + + /** + * Sets the Race XML to send. + * @param raceXml XML to send to the Client. + */ + public void setRaceXml(String raceXml) { + //Create the message. + XMLMessage message = this.createXMLMessage(raceXml, XMLMessageType.RACE); + + //Place it in LatestMessages. + this.latestMessages.setRaceXMLMessage(message); + } + + /** + * Sets the Regatta XMl to send. + * @param regattaXml XML to send to Client. + */ + public void setRegattaXml(String regattaXml) { + //Create the message. + XMLMessage message = this.createXMLMessage(regattaXml, XMLMessageType.REGATTA); + + //Place it in LatestMessages. + this.latestMessages.setRegattaXMLMessage(message); + } + + /** + * Sets the Boats XML to send. + * @param boatsXml XMl to send to the Client. + */ + public void setBoatsXml(String boatsXml) { + //Create the message. + XMLMessage message = this.createXMLMessage(boatsXml, XMLMessageType.BOAT); + + //Place it in LatestMessages. + this.latestMessages.setBoatXMLMessage(message); + } + + + public static void main(String argv[]) throws Exception + { + MockOutput client = new MockOutput(new LatestMessages()); + client.run(); + } + +} diff --git a/mock/src/main/java/seng302/DataInput/PolarParser.java b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java similarity index 96% rename from mock/src/main/java/seng302/DataInput/PolarParser.java rename to racevisionGame/src/main/java/mock/dataInput/PolarParser.java index 222cdbbf..d33c0ac5 100644 --- a/mock/src/main/java/seng302/DataInput/PolarParser.java +++ b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java @@ -1,8 +1,10 @@ -package seng302.DataInput; +package mock.dataInput; -import seng302.Exceptions.InvalidPolarFileException; -import seng302.Model.Bearing; -import seng302.Model.Polars; + + +import mock.exceptions.InvalidPolarFileException; +import mock.model.Polars; +import shared.model.Bearing; import java.io.*; import java.util.ArrayList; diff --git a/mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java b/racevisionGame/src/main/java/mock/exceptions/InvalidPolarFileException.java similarity index 95% rename from mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java rename to racevisionGame/src/main/java/mock/exceptions/InvalidPolarFileException.java index d2e302e7..58c65656 100644 --- a/mock/src/main/java/seng302/Exceptions/InvalidPolarFileException.java +++ b/racevisionGame/src/main/java/mock/exceptions/InvalidPolarFileException.java @@ -1,4 +1,4 @@ -package seng302.Exceptions; +package mock.exceptions; /** * An exception thrown when we cannot parse a polar data file. diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java new file mode 100644 index 00000000..c8c6825b --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/MockBoat.java @@ -0,0 +1,206 @@ +package mock.model; + + +import shared.model.*; + + +/** + * Represents a Boat on the mock side of a race. + * This adds mock specific functionality to a boat. + */ +public class MockBoat extends Boat { + + + /** + * This stores a boat's polars table. + * Can be used to calculate VMG. + */ + private Polars polars; + + /** + * This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds. + */ + private long timeSinceTackChange = 0; + + + + /** + * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. + * + * @param sourceID The id of the boat + * @param name The name of the Boat. + * @param country The abbreviation or country code for the boat. + * @param polars The polars table to use for this boat. + */ + public MockBoat(int sourceID, String name, String country, Polars polars) { + super(sourceID, name, country); + + this.polars = polars; + } + + + /** + * Constructs a mock boat object from a given boat and polars table. + * + * @param boat The boat to convert into a MockBoat. + * @param polars The polars table to use for this boat. + */ + public MockBoat(Boat boat, Polars polars) { + super(boat.getSourceID(), boat.getName(), boat.getCountry()); + + this.polars = polars; + } + + + + + /** + * Calculate the bearing of the boat to its next marker. + * @return The bearing to the next marker. + */ + public Bearing calculateBearingToNextMarker() { + + //Get the start and end points. + GPSCoordinate currentPosition = this.getCurrentPosition(); + GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); + + //Calculate bearing. + Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); + + return bearing; + } + + + + /** + * Calculates the distance between the boat and its target marker in nautical miles. + * @return The distance (in nautical miles) between the boat and its target marker. + */ + public double calculateDistanceToNextMarker() { + + //Get start and end markers. + GPSCoordinate startPosition = this.getCurrentPosition(); + + //When boats finish, their "current leg" doesn't have an end marker. + if (this.getCurrentLeg().getEndCompoundMark() == null) { + return 0d; + } + + GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); + + + //Calculate distance. + double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker); + + return distanceNauticalMiles; + } + + + + + /** + * Returns the polars table for this boat. + * @return The polars table for this boat. + */ + public Polars getPolars() { + return polars; + } + + /** + * Sets the polars table for this boat. + * @param polars The new polars table for this boat. + */ + public void setPolars(Polars polars) { + this.polars = polars; + } + + + /** + * Returns the time since the boat changed its tack, in milliseconds. + * @return Time since the boat changed its tack, in milliseconds. + */ + public long getTimeSinceTackChange() { + return timeSinceTackChange; + } + + /** + * Sets the time since the boat changed it's tack, in milliseconds. + * @param timeSinceTackChange Time since the boat changed its tack, in milliseconds. + */ + public void setTimeSinceTackChange(long timeSinceTackChange) { + this.timeSinceTackChange = timeSinceTackChange; + } + + + /** + * Moves the boat meters forward in the direction that it is facing + * @param meters The number of meters to move forward. + * @param milliseconds The number of milliseconds to advance the boat's timers by. + */ + public void moveForwards(double meters, long milliseconds) { + + + //Update the boat's time since last tack. + this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds); + + //Update the time into the current leg. + this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds); + + //Update the distance into the current leg. + this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters); + + //Updates the current position of the boat. + GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing())); + this.setCurrentPosition(newPosition); + + } + + + /** + * Sets the boats speed and bearing to those in the given VMG. + * @param newVMG The new VMG to use for the boat - contains speed and bearing. + */ + public void setVMG(VMG newVMG) { + this.setBearing(newVMG.getBearing()); + this.setCurrentSpeed(newVMG.getSpeed()); + this.setTimeSinceTackChange(0); + } + + + /** + * Calculates the number of nautical miles the boat will travel in a given time slice. + * E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles. + * @param timeSlice The timeslice to use. + * @return The distance travelled, in nautical miles, over the given timeslice. + */ + public double calculateNauticalMilesTravelled(long timeSlice) { + + //The proportion of one hour the current timeslice is. + //This will be a low fractional number, so we need to go from long -> double. + double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds; + + //Calculates the distance travelled, in nautical miles, in the current timeslice. + //distanceTravelledNM = speed (nm p hr) * time taken to update loop + double distanceTravelledNM = this.getCurrentSpeed() * hourProportion; + + return distanceTravelledNM; + } + + /** + * Calculates the number of meters the boat will travel in a given time slice. + * E.g., in 53 milliseconds a boat may travel 0.02 meters. + * @param timeSlice The timeslice to use. + * @return The distance travelled, in meters, over the given timeslice. + */ + public double calculateMetersTravelled(long timeSlice) { + + //Calculate the distance travelled, in nautical miles. + double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice); + + //Convert to meters. + double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion; + + return distanceTravelledMeters; + } + +} diff --git a/mock/src/main/java/seng302/Model/Race.java b/racevisionGame/src/main/java/mock/model/MockRace.java similarity index 68% rename from mock/src/main/java/seng302/Model/Race.java rename to racevisionGame/src/main/java/mock/model/MockRace.java index b42608a7..ad259bdc 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -1,22 +1,22 @@ -package seng302.Model; +package mock.model; import javafx.animation.AnimationTimer; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seng302.Constants; -import seng302.DataInput.RaceDataSource; -import seng302.MockOutput; -import seng302.Networking.Messages.BoatStatus; -import seng302.Networking.Messages.Enums.BoatStatusEnum; -import seng302.Networking.Messages.Enums.RaceStatusEnum; -import seng302.Networking.Messages.Enums.RaceTypeEnum; -import seng302.Networking.Messages.RaceStatus; -import seng302.Networking.Utils.AC35UnitConverter; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Random; +import network.Messages.BoatLocation; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.LatestMessages; +import network.Messages.RaceStatus; +import network.Utils.AC35UnitConverter; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import network.Messages.Enums.RaceStatusEnum; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; +import java.util.*; import static java.lang.Math.cos; @@ -26,64 +26,27 @@ import static java.lang.Math.cos; * Has a course, boats, boundaries, etc... * Is responsible for simulating the race, and sending messages to a MockOutput instance. */ -public class Race implements Runnable { +public class MockRace extends Race { /** * An observable list of boats in the race. */ - private ObservableList boats; + private List boats; - /** - * An observable list of compound marks in the race. - */ - private ObservableList compoundMarks; - - /** - * A list of legs in the race. - */ - private List legs; - /** - * A list of coordinates describing the boundary of the course. - */ - private List boundary; /** * A copy of the boundary list, except "shrunk" inwards by 50m. */ private List shrinkBoundary; - /** - * The elapsed time, in milliseconds, of the race. - */ - private long totalTimeElapsed; - - /** - * The starting timestamp, in milliseconds, of the race. - */ - private long startTime; /** * The scale factor of the race. - * Frame periods are multiplied by this to get the amount of time a single frame represents. - * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. - */ - private int scaleFactor = 5; - - /** - * The race ID of the course. + * See {@link Constants#RaceTimeScale}. */ - private int raceId; + private int scaleFactor; - /** - * The current status of the race. - */ - private RaceStatusEnum raceStatusEnum; - - /** - * The type of race this is. - */ - private RaceTypeEnum raceType; /** * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint. @@ -92,56 +55,50 @@ public class Race implements Runnable { private int dnfChance = 0; + /** - * The mockOutput to send messages to. + * Used to generate random numbers when changing the wind direction. */ - private MockOutput mockOutput; + private int changeWind = 4; + /** + * The bearing the wind direction starts at. + */ + private static final Bearing windBaselineBearing = Bearing.fromDegrees(225); /** - * Wind direction bearing. + * The lower bearing angle that the wind may have. */ - private Bearing windDirection; + private static final Bearing windLowerBound = Bearing.fromDegrees(215); /** - * Wind speed (knots). - * Convert this to millimeters per second before passing to RaceStatus. + * The upper bearing angle that the wind may have. */ - private double windSpeed; + private static final Bearing windUpperBound = Bearing.fromDegrees(235); - private double windDirDegrees; - private double windDir; - private int changeWind = 4; - private static final int windUpperBound = 235; - private static final int windLowerBound = 215; /** - * Constructs a race object with a given RaceDataSource and sends events to the given mockOutput. - * @param raceData Data source for race related data (boats, legs, etc...). - * @param mockOutput The mockOutput to send events to. + * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput. + * @param boatDataSource Data source for boat related data (yachts and marker boats). + * @param raceDataSource Data source for race related data (participating boats, legs, etc...). + * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...). + * @param latestMessages The LatestMessages to send events to. + * @param polars The polars table to be used for boat simulation. + * @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}. */ - public Race(RaceDataSource raceData, MockOutput mockOutput) { + public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale) { - this.mockOutput = mockOutput; - - this.boats = FXCollections.observableArrayList(raceData.getBoats()); - this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks()); - this.boundary = raceData.getBoundary(); - this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); + super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); + this.scaleFactor = timeScale; - this.legs = raceData.getLegs(); - this.legs.add(new Leg("Finish", this.legs.size())); + this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); - this.raceId = raceData.getRaceId(); + this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); - //The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another. - this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor); - this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); - this.raceType = RaceTypeEnum.FLEET_RACE; this.windSpeed = 12; this.windDirection = Bearing.fromDegrees(180); @@ -149,15 +106,45 @@ public class Race implements Runnable { } + /** + * Generates a list of MockBoats given a list of Boats, and a list of participating boats. + * @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat. + * @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating. + * @param polars The polars table to be used for boat simulation. + * @return A list of MockBoats that are participating in the race. + */ + private List generateMockBoats(Map boats, List sourceIDs, Polars polars) { + + List mockBoats = new ArrayList<>(sourceIDs.size()); + + //For each sourceID participating... + for (int sourceID : sourceIDs) { + + //Get the boat associated with the sourceID. + Boat boat = boats.get(sourceID); + + //Construct a MockBoat using the Boat and Polars. + MockBoat mockBoat = new MockBoat(boat, polars); + + mockBoats.add(mockBoat); + + } + + return mockBoats; + + } + + /** * Runnable for the thread. */ public void run() { initialiseBoats(); - initialiseWindDir(); - countdownTimer.start(); + initialiseWindDirection(); + this.countdownTimer.start(); } + /** * Parse the compound marker boats through mock output. */ @@ -186,7 +173,20 @@ public class Race implements Runnable { */ private void parseIndividualMark(Mark mark) { - this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime); + //Create message. + BoatLocation boatLocation = new BoatLocation( + mark.getSourceID(), + mark.getPosition().getLatitude(), + mark.getPosition().getLongitude(), + this.boatLocationSequenceNumber, + 0, 0, + this.raceClock.getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); + } @@ -196,7 +196,7 @@ public class Race implements Runnable { private void parseBoatLocations() { //Parse each boat. - for (Boat boat : this.boats) { + for (MockBoat boat : this.boats) { this.parseIndividualBoatLocation(boat); @@ -208,42 +208,52 @@ public class Race implements Runnable { * Parses an individual boat, and sends it to mockOutput. * @param boat The boat to parse. */ - private void parseIndividualBoatLocation(Boat boat) { + private void parseIndividualBoatLocation(MockBoat boat) { - this.mockOutput.parseBoatLocation( + BoatLocation boatLocation = new BoatLocation( boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), + this.boatLocationSequenceNumber, boat.getBearing().degrees(), boat.getCurrentSpeed(), - startTime + totalTimeElapsed - ); + this.raceClock.getCurrentTimeMilli()); + + //Iterates the sequence number. + this.boatLocationSequenceNumber++; + + this.latestMessages.setBoatLocation(boatLocation); } /** - * Updates the race status enumeration based on the current time, in milliseconds. - * @param currentTime The current time, in milliseconds. + * Updates the race time to a specified value, in milliseconds since the unix epoch. + * @param currentTime Milliseconds since unix epoch. */ - private void updateRaceStatusEnum(long currentTime) { + private void updateRaceTime(long currentTime) { + this.raceClock.setUTCTime(currentTime); + } - //The amount of milliseconds until the race starts. - long timeToStart = this.startTime - currentTime; - //Scale the time to start based on the scale factor. - long timeToStartScaled = timeToStart / this.scaleFactor; + /** + * Updates the race status enumeration based on the current time. + */ + private void updateRaceStatusEnum() { + + //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. + long timeToStart = - this.raceClock.getDurationMilli(); - if (timeToStartScaled > Constants.RacePreStartTime) { + if (timeToStart > Constants.RacePreStartTime) { //Time > 3 minutes is the prestart period. this.setRaceStatusEnum(RaceStatusEnum.PRESTART); - } else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) { + } else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) { //Time between [1, 3] minutes is the warning period. this.setRaceStatusEnum(RaceStatusEnum.WARNING); - } else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) { + } else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) { //Time between (0, 1] minutes is the preparatory period. this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY); @@ -265,23 +275,35 @@ public class Race implements Runnable { List boatStatuses = new ArrayList<>(); //Add each boat status to the status list. - for (Boat boat : boats) { + for (MockBoat boat : this.boats) { - BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime()); + BoatStatus boatStatus = new BoatStatus( + boat.getSourceID(), + boat.getStatus(), + boat.getCurrentLeg().getLegNumber(), + boat.getEstimatedTimeAtNextMark().toInstant().toEpochMilli() ); boatStatuses.add(boatStatus); } - //TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue. //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees()); - int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond); + int windSpeedInt = (int) (this.windSpeed * Constants.KnotsToMMPerSecond); //Create race status object, and send it. - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses); + RaceStatus raceStatus = new RaceStatus( + System.currentTimeMillis(), + this.raceId, + this.getRaceStatusEnum().getValue(), + this.raceClock.getStartingTimeMilli(), + windDirectionInt, + windSpeedInt, + this.getRaceType().getValue(), + boatStatuses); - mockOutput.parseRaceStatus(raceStatus); + + this.latestMessages.setRaceStatus(raceStatus); } @@ -292,12 +314,24 @@ public class Race implements Runnable { */ private void setBoatsStatusToRacing() { - for (Boat boat : this.boats) { + for (MockBoat boat : this.boats) { boat.setStatus(BoatStatusEnum.RACING); } } + /** + * Sets the estimated time at next mark for each boat to a specified time. This is used during the countdown timer to provide this value to boat before the race starts. + * @param time The time to provide to each boat. + */ + private void setBoatsTimeNextMark(ZonedDateTime time) { + + for (MockBoat boat : this.boats) { + boat.setEstimatedTimeAtNextMark(time); + } + } + + /** * Countdown timer until race starts. */ @@ -309,8 +343,14 @@ public class Race implements Runnable { @Override public void handle(long arg0) { + //Update race time. + updateRaceTime(currentTime); + //Update the race status based on the current time. - updateRaceStatusEnum(this.currentTime); + updateRaceStatusEnum(); + + //Provide boat's with an estimated time at next mark until the race starts. + setBoatsTimeNextMark(raceClock.getCurrentTime()); //Parse the boat locations. parseBoatLocations(); @@ -319,14 +359,13 @@ public class Race implements Runnable { parseMarks(); // Change wind direction - changeWindDir(); + changeWindDirection(); //Parse the race status. parseRaceStatus(); if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { - System.setProperty("javafx.animation.fullspeed", "true"); setBoatsStatusToRacing(); raceTimer.start(); this.stop(); @@ -348,6 +387,11 @@ public class Race implements Runnable { */ long timeRaceStarted = System.currentTimeMillis(); + /** + * Current time during a loop iteration. + */ + long currentTime = System.currentTimeMillis(); + /** * The time of the previous frame, in milliseconds. */ @@ -357,27 +401,25 @@ public class Race implements Runnable { public void handle(long arg0) { //Get the current time. - long currentTime = System.currentTimeMillis(); + currentTime = System.currentTimeMillis(); + + //Update race time. + updateRaceTime(currentTime); - //Update the total elapsed time. - totalTimeElapsed = currentTime - this.timeRaceStarted; //As long as there is at least one boat racing, we still simulate the race. if (getNumberOfActiveBoats() != 0) { //Get the time period of this frame. long framePeriod = currentTime - lastFrameTime; - //We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues. - framePeriod = 20; - //For each boat, we update its position, and generate a BoatLocationMessage. - for (Boat boat : boats) { + for (MockBoat boat : boats) { //If it is still racing, update its position. if (boat.getStatus() == BoatStatusEnum.RACING) { - updatePosition(boat, framePeriod, totalTimeElapsed); + updatePosition(boat, framePeriod, raceClock.getDurationMilli()); } @@ -392,7 +434,7 @@ public class Race implements Runnable { if (getNumberOfActiveBoats() != 0) { // Change wind direction - changeWindDir(); + changeWindDirection(); //Parse the boat locations. parseBoatLocations(); @@ -417,34 +459,36 @@ public class Race implements Runnable { int iters = 0; @Override public void handle(long now) { - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>()); - mockOutput.parseRaceStatus(raceStatus); - if (iters > 500){ - mockOutput.stop(); + + parseRaceStatus(); + + if (iters > 500) { stop(); } iters++; } }; + /** * Initialise the boats in the race. * This sets their starting positions and current legs. */ - public void initialiseBoats() { + @Override + protected void initialiseBoats() { //Gets the starting positions of the boats. List startingPositions = getSpreadStartingPositions(); //Get iterators for our boat and position lists. - Iterator boatIt = this.boats.iterator(); + Iterator boatIt = this.boats.iterator(); Iterator startPositionIt = startingPositions.iterator(); //Iterate over the pair of lists. while (boatIt.hasNext() && startPositionIt.hasNext()) { //Get the next boat and position. - Boat boat = boatIt.next(); + MockBoat boat = boatIt.next(); GPSCoordinate startPosition = startPositionIt.next(); @@ -524,7 +568,7 @@ public class Race implements Runnable { * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. * @return VMG for the specified boat. */ - private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) { + private VMG calculateVMG(MockBoat boat, Bearing[] bearingBounds) { //Get the lower and upper acceptable bounds. Bearing lowerAcceptableBound = bearingBounds[0]; @@ -535,7 +579,6 @@ public class Race implements Runnable { VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); - return bestVMG; } @@ -574,7 +617,7 @@ public class Race implements Runnable { * @param vmg The new VMG to test. * @return True if the new VMG is improves velocity, false otherwise. */ - private boolean improvesVelocity(Boat boat, VMG vmg) { + private boolean improvesVelocity(MockBoat boat, VMG vmg) { //Get the boats "current" VMG. VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); @@ -592,7 +635,7 @@ public class Race implements Runnable { * @param updatePeriodMilliseconds The time, in milliseconds, since the last update. * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race. */ - protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { + protected void updatePosition(MockBoat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { //Checks if the current boat has finished the race or not. boolean finish = this.isLastLeg(boat.getCurrentLeg()); @@ -640,7 +683,7 @@ public class Race implements Runnable { //Check the boats position (update leg and stuff). - this.checkPosition(boat, totalTimeElapsed); + this.checkPosition(boat, totalElapsedMilliseconds); } @@ -651,7 +694,7 @@ public class Race implements Runnable { * @param boat The boat to check. * @return An array of bearings. The first is the lower bound, the second is the upper bound. */ - private Bearing[] calculateBearingBounds(Boat boat) { + private Bearing[] calculateBearingBounds(MockBoat boat) { Bearing[] bearings = new Bearing[2]; @@ -734,7 +777,7 @@ public class Race implements Runnable { * @param boat The boat to check. * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. */ - protected void checkPosition(Boat boat, long timeElapsed) { + protected void checkPosition(MockBoat boat, long timeElapsed) { //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. @@ -766,6 +809,7 @@ public class Race implements Runnable { boat.setTimeFinished(timeElapsed); boat.setCurrentSpeed(0); boat.setStatus(BoatStatusEnum.FINISHED); + } else if (doNotFinish()) { //Boat has pulled out of race. boat.setTimeFinished(timeElapsed); @@ -780,26 +824,6 @@ public class Race implements Runnable { } - /** - * Determines whether or not a specific leg is the last leg in the race. - * @param leg The leg to check. - * @return Returns true if it is the last, false otherwse. - */ - private boolean isLastLeg(Leg leg) { - - //Get the last leg. - Leg lastLeg = this.legs.get(this.legs.size() - 1); - - //Check its ID. - int lastLegID = lastLeg.getLegNumber(); - - //Get the specified leg's ID. - int legID = leg.getLegNumber(); - - - //Check if they are the same. - return legID == lastLegID; - } /** @@ -823,31 +847,6 @@ public class Race implements Runnable { } - /** - * Returns the current race status. - * @return The current race status. - */ - public RaceStatusEnum getRaceStatusEnum() { - return raceStatusEnum; - } - - /** - * Sets the current race status. - * @param raceStatusEnum The new status of the race. - */ - private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { - this.raceStatusEnum = raceStatusEnum; - } - - - /** - * Returns the type of race this is. - * @return The type of race this is. - */ - public RaceTypeEnum getRaceType() { - return raceType; - } - /** * Returns the number of boats that are still active in the race. @@ -856,74 +855,86 @@ public class Race implements Runnable { */ protected int getNumberOfActiveBoats() { - int numberofActiveBoats = 0; + int numberOfActiveBoats = 0; - for (Boat boat : this.boats) { + for (MockBoat boat : this.boats) { //If the boat is currently racing, count it. if (boat.getStatus() == BoatStatusEnum.RACING) { - numberofActiveBoats++; + numberOfActiveBoats++; } } - return numberofActiveBoats; + return numberOfActiveBoats; } /** - * Returns an observable list of boats in the race. + * Returns a list of boats in the race. * @return List of boats in the race. */ - public ObservableList getBoats() { + public List getBoats() { return boats; } - protected void initialiseWindDir(){ - windDirDegrees = 225; - windDir = AC35UnitConverter.convertHeading(windDirDegrees); - /*windDir = new Random().nextInt(65535+1); - windDir = BoatLocation.convertHeadingIntToDouble(255);*/ - this.windDirection = new Bearing((int)windDir); + + /** + * Initialises the wind bearing with the value of the windBaselineBearing. + */ + protected void initialiseWindDirection() { + //Set the starting bearing. + this.windDirection = Bearing.fromDegrees(MockRace.windBaselineBearing.degrees()); } - protected void changeWindDir(){ - int r = new Random().nextInt(changeWind)+1; - if(r==1){ - windDirDegrees = (0.5 + windDirDegrees) % 360; - } else if (r==2){ - windDirDegrees = ((windDirDegrees - 0.5) + 360) % 360;///keep the degrees positive when below 0 - } - if (windDirDegrees > windUpperBound){ - windDirDegrees = windUpperBound; - } - if (windDirDegrees < windLowerBound){ - windDirDegrees = windLowerBound; + + /** + * Changes the wind direction randomly, while keeping it within [windLowerBound, windUpperBound]. + */ + protected void changeWindDirection() { + + //Randomly add or remove 0.5 degrees. + int r = new Random().nextInt(changeWind) + 1; + + if (r == 1) { + //Add 0.5 degrees to the wind bearing. + this.windDirection.setDegrees(this.windDirection.degrees() + 0.5); + + } else if (r == 2) { + //Minus 0.5 degrees from the wind bearing. + this.windDirection.setDegrees(this.windDirection.degrees() - 0.5); + } - windDir = AC35UnitConverter.convertHeading(windDirDegrees); - this.windDirection = new Bearing(windDirDegrees); - } + //Ensure that the wind is in the correct bounds. + if (this.windDirection.degrees() > MockRace.windUpperBound.degrees()) { + this.windDirection.setBearing(MockRace.windUpperBound); + + } else if (this.windDirection.degrees() < MockRace.windLowerBound.degrees()) { + this.windDirection.setBearing(MockRace.windLowerBound); - protected void setChangeWind(int changeVal){ - if (changeVal>=0){ - changeWind = changeVal; } } - protected int getWind(){ - return (int)windDir; - } + /** * Updates the boat's estimated time to next mark if positive * @param boat to estimate time given its velocity */ - private void updateEstimatedTime(Boat boat) { + private void updateEstimatedTime(MockBoat boat) { + double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond; + if (velocityToMark > 0) { - long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark); - boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow); + + //Calculate milliseconds until boat reaches mark. + long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark); + + //Calculate time at which it will reach mark. + ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS); + boat.setEstimatedTimeAtNextMark(timeAtMark); } + } } diff --git a/mock/src/main/java/seng302/Model/Polars.java b/racevisionGame/src/main/java/mock/model/Polars.java similarity index 99% rename from mock/src/main/java/seng302/Model/Polars.java rename to racevisionGame/src/main/java/mock/model/Polars.java index dc770e8d..32ee8842 100644 --- a/mock/src/main/java/seng302/Model/Polars.java +++ b/racevisionGame/src/main/java/mock/model/Polars.java @@ -1,8 +1,12 @@ -package seng302.Model; +package mock.model; import javafx.util.Pair; +import shared.model.Bearing; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Encapsulates an entire polar table. Has a function to calculate VMG. diff --git a/mock/src/main/java/seng302/Model/VMG.java b/racevisionGame/src/main/java/mock/model/VMG.java similarity index 95% rename from mock/src/main/java/seng302/Model/VMG.java rename to racevisionGame/src/main/java/mock/model/VMG.java index b431ae06..905fadce 100644 --- a/mock/src/main/java/seng302/Model/VMG.java +++ b/racevisionGame/src/main/java/mock/model/VMG.java @@ -1,4 +1,6 @@ -package seng302.Model; +package mock.model; + +import shared.model.Bearing; /** * This class encapsulates VMG - that is, velocity made good. It has a speed component and a bearing component. diff --git a/network/src/main/java/seng302/Networking/BinaryMessageDecoder.java b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java similarity index 90% rename from network/src/main/java/seng302/Networking/BinaryMessageDecoder.java rename to racevisionGame/src/main/java/network/BinaryMessageDecoder.java index 1564bcd0..1c34ca85 100644 --- a/network/src/main/java/seng302/Networking/BinaryMessageDecoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageDecoder.java @@ -1,10 +1,11 @@ -package seng302.Networking; +package network; -import seng302.Networking.Exceptions.InvalidMessageException; -import seng302.Networking.MessageDecoders.*; -import seng302.Networking.Messages.*; -import seng302.Networking.Utils.*; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Exceptions.InvalidMessageException; +import network.MessageDecoders.*; +import network.Messages.*; +import network.Messages.Enums.MessageType; +import static network.Utils.ByteConverter.*; import java.nio.ByteBuffer; import java.util.Arrays; @@ -72,20 +73,20 @@ public class BinaryMessageDecoder { this.headerMessageType = this.messageHeader[2]; //Get the header timestamp. - this.headerTimeStamp = ByteConverter.bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9)); + this.headerTimeStamp = bytesToLong(Arrays.copyOfRange(this.messageHeader, 3, 9)); //Get the source ID for the message. - this.headerSourceID = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13)); + this.headerSourceID = bytesToInt(Arrays.copyOfRange(this.messageHeader, 9, 13)); //Get the length of the message body. - this.messageBodyLength = ByteConverter.bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15)); + this.messageBodyLength = bytesToInt(Arrays.copyOfRange(this.messageHeader, 13, 15)); //Get the messageBody. this.messageBody = Arrays.copyOfRange(this.fullMessage, this.headerLength, this.headerLength + this.messageBodyLength); //Get the CRC value. - this.messageCRCValue = ByteConverter.bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length)); + this.messageCRCValue = bytesToLong(Arrays.copyOfRange(this.fullMessage, this.fullMessage.length - CRCLength, this.fullMessage.length)); //Combine the header and body into a single array. ByteBuffer headerBodyByteBuffer = ByteBuffer.allocate(messageHeader.length + messageBody.length); @@ -134,7 +135,7 @@ public class BinaryMessageDecoder { //System.out.println("Decoding HeartBeat Message!"); //TODO maybe use HeartbeatDecoder.decode(message). //TODO also, decoders for each message type should encapsulate the constructing of the object. E.g., return HeartbeatDecoder.decode(message);. - return new Heartbeat(ByteConverter.bytesToLong(messageBody)); + return new Heartbeat(bytesToLong(messageBody)); case RACESTATUS: //System.out.println("Race Status Message"); @@ -150,7 +151,7 @@ public class BinaryMessageDecoder { //System.out.println("XML Message!"); XMLMessageDecoder xmdecoder = new XMLMessageDecoder(messageBody); xmdecoder.decode(); - return new XMLMessage(xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMsgLength(), xmdecoder.getXmlMessageInputStream()); + return new XMLMessage(XMLMessage.currentVersionNumber, xmdecoder.getAckNumber(), xmdecoder.getTimeStamp(), xmdecoder.getXmlMsgSubType(), xmdecoder.getSequenceNumber(), xmdecoder.getXmlMessageContents()); case RACESTARTSTATUS: //System.out.println("Race Start Status Message"); diff --git a/network/src/main/java/seng302/Networking/BinaryMessageEncoder.java b/racevisionGame/src/main/java/network/BinaryMessageEncoder.java similarity index 93% rename from network/src/main/java/seng302/Networking/BinaryMessageEncoder.java rename to racevisionGame/src/main/java/network/BinaryMessageEncoder.java index e0de7184..f7dc12a7 100644 --- a/network/src/main/java/seng302/Networking/BinaryMessageEncoder.java +++ b/racevisionGame/src/main/java/network/BinaryMessageEncoder.java @@ -1,14 +1,13 @@ -package seng302.Networking; +package network; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; +import static network.Utils.ByteConverter.*; import java.nio.ByteBuffer; import java.util.zip.CRC32; -import static seng302.Networking.Utils.ByteConverter.intToBytes; -import static seng302.Networking.Utils.ByteConverter.longToBytes; -import static seng302.Networking.Utils.ByteConverter.shortToBytes; /** * This class can be used to encode/convert a byte array message body, plus header data into a byte array containing the entire message, ready to send. diff --git a/network/src/main/java/seng302/Networking/Exceptions/InvalidMessageException.java b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageException.java similarity index 93% rename from network/src/main/java/seng302/Networking/Exceptions/InvalidMessageException.java rename to racevisionGame/src/main/java/network/Exceptions/InvalidMessageException.java index 0eb9e8f9..6af32842 100644 --- a/network/src/main/java/seng302/Networking/Exceptions/InvalidMessageException.java +++ b/racevisionGame/src/main/java/network/Exceptions/InvalidMessageException.java @@ -1,4 +1,4 @@ -package seng302.Networking.Exceptions; +package network.Exceptions; /** * Exception which is thrown when a message is read, but it is invalid in some way (CRC is wrong, sync bytes, etc...). diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/AverageWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java similarity index 93% rename from network/src/main/java/seng302/Networking/MessageDecoders/AverageWindDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java index 204fcd52..13de3c80 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/AverageWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/AverageWindDecoder.java @@ -1,7 +1,8 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; -import seng302.Networking.Messages.AverageWind; -import seng302.Networking.Utils.ByteConverter; + +import network.Messages.AverageWind; +import network.Utils.ByteConverter; import java.util.Arrays; diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/BoatLocationDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java similarity index 95% rename from network/src/main/java/seng302/Networking/MessageDecoders/BoatLocationDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java index 2900cd42..db90a343 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/BoatLocationDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/BoatLocationDecoder.java @@ -1,10 +1,14 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; -import seng302.Networking.Messages.BoatLocation; + +import network.Messages.BoatLocation; import java.util.Arrays; -import static seng302.Networking.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.bytesToInt; +import static network.Utils.ByteConverter.bytesToLong; +import static network.Utils.ByteConverter.bytesToShort; + /** * Created by hba56 on 21/04/17. diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/CourseWindDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java similarity index 91% rename from network/src/main/java/seng302/Networking/MessageDecoders/CourseWindDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java index bb3b06d9..038a79d2 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/CourseWindDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/CourseWindDecoder.java @@ -1,11 +1,14 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; -import seng302.Networking.Messages.CourseWind; + +import network.Messages.CourseWind; import java.util.ArrayList; import java.util.Arrays; -import static seng302.Networking.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.bytesToInt; +import static network.Utils.ByteConverter.bytesToLong; + /** * Created by hba56 on 23/04/17. diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/MarkRoundingDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java similarity index 92% rename from network/src/main/java/seng302/Networking/MessageDecoders/MarkRoundingDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java index da6896f3..000e86ee 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/MarkRoundingDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/MarkRoundingDecoder.java @@ -1,7 +1,8 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; -import seng302.Networking.Utils.ByteConverter; -import seng302.Networking.Messages.MarkRounding; + +import network.Messages.MarkRounding; +import network.Utils.ByteConverter; import java.util.Arrays; diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java similarity index 93% rename from network/src/main/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java index eba1e565..236c5d27 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStartStatusDecoder.java @@ -1,10 +1,10 @@ -package seng302.Networking.MessageDecoders; - +package network.MessageDecoders; import java.util.Arrays; -import static seng302.Networking.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.*; + /** * Created by hba56 on 21/04/17. diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/RaceStatusDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java similarity index 90% rename from network/src/main/java/seng302/Networking/MessageDecoders/RaceStatusDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java index 59e1622a..e4d147df 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/RaceStatusDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/RaceStatusDecoder.java @@ -1,11 +1,15 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; -import seng302.Networking.Messages.BoatStatus; + +import network.Messages.BoatStatus; import java.util.ArrayList; import java.util.Arrays; -import static seng302.Networking.Utils.ByteConverter.*; +import static network.Utils.ByteConverter.bytesToInt; +import static network.Utils.ByteConverter.bytesToLong; +import static network.Utils.ByteConverter.bytesToShort; + /** * Created by hba56 on 21/04/17. @@ -24,7 +28,7 @@ public class RaceStatusDecoder { private long time; private int race; - private int raceState; + private byte raceState; private long startTime; private int raceWindDir; private short raceWindSpeed; @@ -47,7 +51,7 @@ public class RaceStatusDecoder { time = bytesToLong(timeBytes); race = bytesToInt(raceID); - raceState = bytesToInt(raceStatus); + raceState = raceStatus; startTime = bytesToLong(expectedStart); raceWindDir = bytesToInt(raceWind); raceWindSpeed = bytesToShort(windSpeed); @@ -87,7 +91,7 @@ public class RaceStatusDecoder { return race; } - public int getRaceState() { + public byte getRaceState() { return raceState; } diff --git a/network/src/main/java/seng302/Networking/MessageDecoders/XMLMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java similarity index 72% rename from network/src/main/java/seng302/Networking/MessageDecoders/XMLMessageDecoder.java rename to racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java index 413ca359..18ead92e 100644 --- a/network/src/main/java/seng302/Networking/MessageDecoders/XMLMessageDecoder.java +++ b/racevisionGame/src/main/java/network/MessageDecoders/XMLMessageDecoder.java @@ -1,12 +1,15 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; + +import network.Messages.Enums.XMLMessageType; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static seng302.Networking.Utils.ByteConverter.bytesToLong; -import static seng302.Networking.Utils.ByteConverter.bytesToShort; +import static network.Utils.ByteConverter.bytesToLong; +import static network.Utils.ByteConverter.bytesToShort; + /** * Created by hba56 on 20/04/17. @@ -41,7 +44,7 @@ public class XMLMessageDecoder { this.sequenceNumber = bytesToShort(sequenceNumberBytes); this.xmlMsgLength = bytesToShort(xmlMsgLengthBytes); - this.xmlMessage = new String(xmlMessagebytes); + this.xmlMessage = new String(xmlMessagebytes).trim(); } public byte getMessageVersionNumber() { @@ -56,8 +59,8 @@ public class XMLMessageDecoder { return timeStamp; } - public byte getXmlMsgSubType() { - return xmlMsgSubType; + public XMLMessageType getXmlMsgSubType() { + return XMLMessageType.fromByte(xmlMsgSubType); } public short getSequenceNumber() { @@ -68,14 +71,14 @@ public class XMLMessageDecoder { return xmlMsgLength; } + + /** - * this will be used latter for the vis - * @return xml string as inputsource + * Returns the contents of the XML message (e.g., the contents of a race.xml file). + * @return The contents of the XML message. */ - public InputStream getXmlMessageInputStream() { - InputStream is = new ByteArrayInputStream(xmlMessage.trim().getBytes(StandardCharsets.UTF_8)); -// InputSource is = new InputSource(new StringReader(xmlMessage.trim())); - return is; + public String getXmlMessageContents() { + return xmlMessage; } } diff --git a/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java similarity index 82% rename from network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java rename to racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java index cc7a4ae6..0808d16d 100644 --- a/network/src/main/java/seng302/Networking/MessageEncoders/RaceVisionByteEncoder.java +++ b/racevisionGame/src/main/java/network/MessageEncoders/RaceVisionByteEncoder.java @@ -1,13 +1,16 @@ -package seng302.Networking.MessageEncoders; +package network.MessageEncoders; -import seng302.Networking.Messages.*; +import network.Messages.*; + +import static network.Utils.ByteConverter.*; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import static seng302.Networking.Utils.ByteConverter.*; + /** * Created by fwy13 on 19/04/17. @@ -16,13 +19,16 @@ public class RaceVisionByteEncoder { /** * Serializes a heartbeat message. - * @param seq Heartbeat value. + * @param heartbeat Heartbeat message. * @return Serialized message. */ - public static byte[] heartBeat(long seq){ + public static byte[] heartBeat(Heartbeat heartbeat) { + ByteBuffer heartBeat = ByteBuffer.allocate(4); - heartBeat.put(longToBytes(seq, 4)); - byte [] result = heartBeat.array(); + heartBeat.put(longToBytes(heartbeat.getSequenceNumber(), 4)); + + byte[] result = heartBeat.array(); + return result; } @@ -78,7 +84,7 @@ public class RaceVisionByteEncoder { return raceStatusMessage.array(); } - public byte[] displayTextMessage(RaceMessage[] message){ + public static byte[] displayTextMessage(RaceMessage[] message){ //ByteBuffer result = ByteBuffer.allocate(4 + numLines * 32); int messageVersionNumber = 0b1;//version number short ackNum = 0;//no clue what this does just a placeholder for 2 bytes. @@ -119,7 +125,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){ + public static byte[] raceStartStatus(long time, short ack, long startTime, int raceID, char notification){ int messageVersion = 0b1; byte[] timestamp = longToBytes(time, 6); byte[] ackNumber = intToBytes(ack, 2); @@ -138,7 +144,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID, + public static byte[] yachtEventCode(long time, short acknowledgeNumber, int raceID, int destSourceID, int incidentID, int eventID){ int messageVersion = 0b10; byte[] encodeTime = longToBytes(time, 6); @@ -159,7 +165,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] chatterText(int messageType, String message){ + public static byte[] chatterText(int messageType, String message){ int messageVersion = 0b1; byte[] type = intToBytes(messageType, 1); byte[] text = message.getBytes(); @@ -174,6 +180,44 @@ public class RaceVisionByteEncoder { return result.array(); } + + /** + * Serializes an xml message into a byte array. + * @param xmlMessage The message to serialize. + * @return byte array contaning serialized message. + */ + public static byte[] xmlMessage(XMLMessage xmlMessage) { + + + byte[] messageBytes = xmlMessage.getXmlMessage().getBytes(StandardCharsets.UTF_8); + + ByteBuffer tempOutputByteBuffer = ByteBuffer.allocate(14 + messageBytes.length); + + //ackNumber converted to bytes + byte[] ackNumberBytes = intToBytes(xmlMessage.getAckNumber(), 2); + + //Timestamp converted to bytes. + byte[] timestampBytes = longToBytes(xmlMessage.getTimeStamp(), 6); + + //sequenceNumber converted to bytes + byte[] sequenceNumberBytes = intToBytes(xmlMessage.getSequenceNumber(), 2); + + //xmlMsgLength converted to bytes + byte[] xmlMsgLengthBytes = intToBytes(xmlMessage.getXmlMsgLength(), 2); + + + tempOutputByteBuffer.put(xmlMessage.getVersionNumber()); + tempOutputByteBuffer.put(ackNumberBytes); + tempOutputByteBuffer.put(timestampBytes); + tempOutputByteBuffer.put(xmlMessage.getXmlMsgSubType().getValue()); + tempOutputByteBuffer.put(sequenceNumberBytes); + tempOutputByteBuffer.put(xmlMsgLengthBytes); + tempOutputByteBuffer.put(messageBytes); + + return tempOutputByteBuffer.array(); + + } + public static byte[] boatLocation(BoatLocation boatLocation){ int messageVersionNumber = 0b1; byte[] time = longToBytes(boatLocation.getTime(), 6); @@ -224,7 +268,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ + public static byte[] markRounding(int time, int ackNumber, int raceID, int sourceID, int boatStatus, int roundingSide, int markType, int markID){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time, 6); byte[] byteAck = intToBytes(ackNumber, 2); @@ -248,7 +292,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] courseWind(byte windID, ArrayList courseWinds){ + public static byte[] courseWind(byte windID, ArrayList courseWinds){ int messageVersionNumber = 0b1; byte byteWindID = windID; byte[] loopcount = intToBytes(courseWinds.size(), 1); @@ -269,7 +313,7 @@ public class RaceVisionByteEncoder { return result.array(); } - public byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ + public static byte[] averageWind(int time, int rawPeriod, int rawSampleSpeed, int period2, int speed2, int period3, int speed3, int period4, int speed4){ int messageVersionNumber = 0b1; byte[] byteTime = longToBytes(time,6); byte[] byteRawPeriod = intToBytes(rawPeriod, 2); diff --git a/network/src/main/java/seng302/Networking/Messages/AC35Data.java b/racevisionGame/src/main/java/network/Messages/AC35Data.java similarity index 77% rename from network/src/main/java/seng302/Networking/Messages/AC35Data.java rename to racevisionGame/src/main/java/network/Messages/AC35Data.java index a42a92be..4b9ed952 100644 --- a/network/src/main/java/seng302/Networking/Messages/AC35Data.java +++ b/racevisionGame/src/main/java/network/Messages/AC35Data.java @@ -1,14 +1,16 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; +import network.Messages.Enums.MessageType; /** * The base class for all message types. */ public abstract class AC35Data { - ///Message type from the header. + /** + * Message type from the header. + */ private MessageType type; diff --git a/network/src/main/java/seng302/Networking/Messages/AverageWind.java b/racevisionGame/src/main/java/network/Messages/AverageWind.java similarity index 90% rename from network/src/main/java/seng302/Networking/Messages/AverageWind.java rename to racevisionGame/src/main/java/network/Messages/AverageWind.java index a700c6d5..1ba17bd5 100644 --- a/network/src/main/java/seng302/Networking/Messages/AverageWind.java +++ b/racevisionGame/src/main/java/network/Messages/AverageWind.java @@ -1,6 +1,7 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Created by fwy13 on 25/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java b/racevisionGame/src/main/java/network/Messages/BoatLocation.java similarity index 95% rename from network/src/main/java/seng302/Networking/Messages/BoatLocation.java rename to racevisionGame/src/main/java/network/Messages/BoatLocation.java index 85f9891e..301584b4 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java +++ b/racevisionGame/src/main/java/network/Messages/BoatLocation.java @@ -1,19 +1,19 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; -import seng302.Networking.Utils.AC35UnitConverter; -import static seng302.Networking.Utils.AC35UnitConverter.convertGPS; -import static seng302.Networking.Utils.AC35UnitConverter.convertGPSToInt; +import network.Messages.Enums.MessageType; +import network.Utils.AC35UnitConverter; +import shared.model.Constants; + +import static network.Utils.AC35UnitConverter.convertGPS; +import static network.Utils.AC35UnitConverter.convertGPSToInt; /** * Represents the information in a boat location message (AC streaming spec: 4.9). */ public class BoatLocation extends AC35Data { - //Knots x this = meters per second. - public static final double KnotsToMetersPerSecondConversionFactor = - 0.514444; + //TODO move these to an enum. public static final byte Unknown = 0; public static final byte RacingYacht = 1; public static final byte CommitteeBoat = 2; @@ -28,6 +28,7 @@ public class BoatLocation extends AC35Data { public static final byte WeatherStation = 11; public static final byte Helicopter = 12; public static final byte DataProcessingApplication = 13; + ///Version number of the message - is always 1. private byte messageVersionNumber = 1; ///Time of the event - milliseconds since jan 1 1970. Proper type is 6 byte int. @@ -263,11 +264,8 @@ public class BoatLocation extends AC35Data { * @return Speed in millimeters per second, stored as an int (using only the two least significant bytes). */ public static int convertBoatSpeedDoubleToInt(double speed) { - //Calculate meters per second. - double metersPerSecond = speed * KnotsToMetersPerSecondConversionFactor; - //Calculate millimeters per second. - double millimetersPerSecond = metersPerSecond * 1000.0; + double millimetersPerSecond = speed * Constants.KnotsToMMPerSecond; //Convert to an int. int millimetersPerSecondInt = (int) Math.round(millimetersPerSecond); @@ -282,11 +280,9 @@ public class BoatLocation extends AC35Data { * @return Speed in knots, stored as a double. */ public static double convertBoatSpeedIntToDouble(int speed) { - //Calculate meters per second. - double metersPerSecond = speed / 1000.0; //Calculate knots. - double knots = metersPerSecond / KnotsToMetersPerSecondConversionFactor; + double knots = speed / Constants.KnotsToMMPerSecond; return knots; } diff --git a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java similarity index 92% rename from network/src/main/java/seng302/Networking/Messages/BoatStatus.java rename to racevisionGame/src/main/java/network/Messages/BoatStatus.java index 291bc50b..54996726 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -1,7 +1,8 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Utils.ByteConverter; -import seng302.Networking.Messages.Enums.BoatStatusEnum; + +import network.Messages.Enums.BoatStatusEnum; +import network.Utils.ByteConverter; /** * Created by hba56 on 23/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/CourseWind.java b/racevisionGame/src/main/java/network/Messages/CourseWind.java similarity index 93% rename from network/src/main/java/seng302/Networking/Messages/CourseWind.java rename to racevisionGame/src/main/java/network/Messages/CourseWind.java index a1261343..727d5fcc 100644 --- a/network/src/main/java/seng302/Networking/Messages/CourseWind.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWind.java @@ -1,6 +1,7 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Created by fwy13 on 21/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/CourseWinds.java b/racevisionGame/src/main/java/network/Messages/CourseWinds.java similarity index 55% rename from network/src/main/java/seng302/Networking/Messages/CourseWinds.java rename to racevisionGame/src/main/java/network/Messages/CourseWinds.java index 08b01993..fc575867 100644 --- a/network/src/main/java/seng302/Networking/Messages/CourseWinds.java +++ b/racevisionGame/src/main/java/network/Messages/CourseWinds.java @@ -1,8 +1,9 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; -import java.util.ArrayList; +import network.Messages.Enums.MessageType; + +import java.util.List; /** * Created by fwy13 on 25/04/17. @@ -11,9 +12,9 @@ public class CourseWinds extends AC35Data { private int msgVerNum; private int selectedWindID; - private ArrayList courseWinds; + private List courseWinds; - public CourseWinds(int msgVerNum, int selectedWindID, ArrayList courseWinds){ + public CourseWinds(int msgVerNum, int selectedWindID, List courseWinds){ super(MessageType.COURSEWIND); this.msgVerNum = msgVerNum; this.selectedWindID = selectedWindID; diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/BoatStatusEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/BoatStatusEnum.java similarity index 97% rename from network/src/main/java/seng302/Networking/Messages/Enums/BoatStatusEnum.java rename to racevisionGame/src/main/java/network/Messages/Enums/BoatStatusEnum.java index 8e8f06b2..f9268101 100644 --- a/network/src/main/java/seng302/Networking/Messages/Enums/BoatStatusEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/BoatStatusEnum.java @@ -1,4 +1,4 @@ -package seng302.Networking.Messages.Enums; +package network.Messages.Enums; import java.util.HashMap; import java.util.Map; diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java similarity index 97% rename from network/src/main/java/seng302/Networking/Messages/Enums/MessageType.java rename to racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 84ab6a0a..6b8fd775 100644 --- a/network/src/main/java/seng302/Networking/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -1,4 +1,4 @@ -package seng302.Networking.Messages.Enums; +package network.Messages.Enums; import java.util.HashMap; import java.util.Map; diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RaceStatusEnum.java similarity index 97% rename from network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java rename to racevisionGame/src/main/java/network/Messages/Enums/RaceStatusEnum.java index 3904eb58..2c187d0e 100644 --- a/network/src/main/java/seng302/Networking/Messages/Enums/RaceStatusEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RaceStatusEnum.java @@ -1,4 +1,4 @@ -package seng302.Networking.Messages.Enums; +package network.Messages.Enums; import java.util.HashMap; @@ -20,6 +20,10 @@ public enum RaceStatusEnum { * Less than 1:00 minutes before start. */ PREPARATORY(2), + + /** + * Race has started. + */ STARTED(3), /** diff --git a/network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java similarity index 69% rename from network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java rename to racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java index 57bfa0cf..6a03b37e 100644 --- a/network/src/main/java/seng302/Networking/Messages/Enums/RaceTypeEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java @@ -1,4 +1,4 @@ -package seng302.Networking.Messages.Enums; +package network.Messages.Enums; import java.util.HashMap; import java.util.Map; @@ -22,7 +22,7 @@ public enum RaceTypeEnum { /** * Used to indicate that a given byte value is invalid. */ - NOT_A_STATUS(-1); + NOT_A_RACE_TYPE(-1); /** @@ -48,6 +48,29 @@ public enum RaceTypeEnum { } + /** + * Attempts to convert a string into a RaceTypeEnum. + * Ignores case. + * Treats anything starting with "fleet" as {@link #FLEET_RACE}, and anything starting with "match" as {@link #MATCH_RACE}. + * @param value The string to convert. + * @return The RaceTypeEnum. + */ + public static RaceTypeEnum fromString(String value) { + + //Convert to lower case. + value = value.toLowerCase(); + + if (value.startsWith("fleet")) { + return FLEET_RACE; + } else if (value.startsWith("match")) { + return MATCH_RACE; + } else { + return NOT_A_RACE_TYPE; + } + + } + + /** * Stores a mapping between Byte values and RaceStatusEnum values. */ @@ -74,8 +97,8 @@ public enum RaceTypeEnum { RaceTypeEnum type = RaceTypeEnum.byteToStatusMap.get(raceTypeEnum); if (type == null) { - //If the byte value wasn't found, return the NOT_A_STATUS RaceTypeEnum. - return RaceTypeEnum.NOT_A_STATUS; + //If the byte value wasn't found, return the NOT_A_RACE_TYPE RaceTypeEnum. + return RaceTypeEnum.NOT_A_RACE_TYPE; } else { //Otherwise, return the RaceTypeEnum. return type; diff --git a/racevisionGame/src/main/java/network/Messages/Enums/XMLMessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/XMLMessageType.java new file mode 100644 index 00000000..94c523fc --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/Enums/XMLMessageType.java @@ -0,0 +1,87 @@ +package network.Messages.Enums; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration that encapsulates the various types of XML messages that can be sent. + */ +public enum XMLMessageType { + + /** + * A regatta.xml message. + */ + REGATTA(5), + + /** + * A race.xml message. + */ + RACE(6), + + /** + * A boats.xml message. + */ + BOAT(7), + + /** + * Used for unrecognised byte values. + */ + NOT_A_MESSAGE_TYPE(0); + + + ///Primitive value of the enum. + private byte value; + + /** + * Ctor. Creates a XMLMessageType enum from a given primitive integer value, cast to a byte. + * @param value Integer, which is cast to byte, to construct from. + */ + private XMLMessageType(int value) { + this.value = (byte)value; + } + + /** + * Returns the primitive value of the enum. + * @return Primitive value of the enum. + */ + public byte getValue() { + return value; + } + + + ///Stores a mapping between Byte values and XMLMessageType values. + private static final Map byteToTypeMap = new HashMap<>(); + + + /* + Static initialization block. Initializes the byteToTypeMap. + */ + static { + for (XMLMessageType type : XMLMessageType.values()) { + byteToTypeMap.put(type.value, type); + } + } + + + /** + * Returns the enumeration value which corresponds to a given byte value. + * @param messageTypeByte Byte value to convert to a XMLMessageType value. + * @return The XMLMessageType value which corresponds to the given byte value. + */ + public static XMLMessageType fromByte(byte messageTypeByte) { + //Gets the corresponding XMLMessageType from the map. + XMLMessageType type = byteToTypeMap.get(messageTypeByte); + + if (type == null) { + //If the byte value wasn't found, return the NOTAMESSAGE XMLMessageType. + return XMLMessageType.NOT_A_MESSAGE_TYPE; + } + else { + //Otherwise, return the XMLMessageType. + return type; + } + + } + + +} diff --git a/network/src/main/java/seng302/Networking/Messages/Heartbeat.java b/racevisionGame/src/main/java/network/Messages/Heartbeat.java similarity index 80% rename from network/src/main/java/seng302/Networking/Messages/Heartbeat.java rename to racevisionGame/src/main/java/network/Messages/Heartbeat.java index 3a8c960c..fb1dd23f 100644 --- a/network/src/main/java/seng302/Networking/Messages/Heartbeat.java +++ b/racevisionGame/src/main/java/network/Messages/Heartbeat.java @@ -1,13 +1,16 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Represents a Heartbeat message. */ public class Heartbeat extends AC35Data { - ///Sequence number of the heartbeat. + /** + * Sequence number of the heartbeat. + */ private long sequenceNumber; /** diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java new file mode 100644 index 00000000..c0da436b --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -0,0 +1,303 @@ +package network.Messages; + +import network.Messages.Enums.XMLMessageType; +import shared.dataInput.RaceDataSource; + +import java.util.HashMap; +import java.util.Map; +import java.util.Observable; + +/** + * This class contains a set of the latest messages received (e.g., the latest RaceStatus, the latest BoatLocation for each boat, etc...). + * Currently, LatestMessage only notifies observers of change when a new XMLMessage is received. + */ +public class LatestMessages extends Observable { + + /** + * The latest RaceStatus message. + */ + private RaceStatus raceStatus; + + /** + * A map of the last BoatStatus message received, for each boat. + */ + private final Map boatStatusMap = new HashMap<>(); + + /** + * A map of the last BoatLocation message received, for each boat. + */ + private final Map boatLocationMap = new HashMap<>(); + + /** + * A map of the last MarkRounding message received, for each boat. + */ + private final Map markRoundingMap = new HashMap<>(); + + /** + * The last AverageWind message received. + */ + private AverageWind averageWind; + + /** + * The last CourseWinds message received. + */ + private CourseWinds courseWinds; + + + /** + * The latest race data XML message. + */ + private XMLMessage raceXMLMessage; + + /** + * The latest boat data XML message. + */ + private XMLMessage boatXMLMessage; + + /** + * The latest regatta data XML message. + */ + private XMLMessage regattaXMLMessage; + + + + + /** + * Ctor. + */ + public LatestMessages() { + } + + + + + /** + * Gets the latest RaceStatus message received. + * @return The latest RaceStatus message received. + */ + public RaceStatus getRaceStatus() { + return raceStatus; + } + + /** + * Sets the latest RaceStatus message received. + * @param raceStatus The new RaceStatus message to store. + */ + public void setRaceStatus(RaceStatus raceStatus) { + this.raceStatus = raceStatus; + } + + + + /** + * Returns the latest BoatStatus message received for a given boat. + * @param sourceID Source ID of the boat. + * @return The latest BoatStatus message for the specified boat. + */ + public BoatStatus getBoatStatus(int sourceID) { + return boatStatusMap.get(sourceID); + } + + /** + * Inserts a BoatStatus message for a given boat. + * @param boatStatus The BoatStatus message to set. + */ + public void setBoatStatus(BoatStatus boatStatus) { + boatStatusMap.put(boatStatus.getSourceID(), boatStatus); + } + + + + /** + * Returns the latest BoatLocation message received for a given boat. + * @param sourceID Source ID of the boat. + * @return The latest BoatLocation message for the specified boat. + */ + public BoatLocation getBoatLocation(int sourceID) { + return boatLocationMap.get(sourceID); + } + + /** + * Inserts a BoatLocation message for a given boat. + * @param boatLocation The BoatLocation message to set. + */ + public void setBoatLocation(BoatLocation boatLocation) { + //TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one. + boatLocationMap.put(boatLocation.getSourceID(), boatLocation); + } + + /** + * Returns the latest MarkRounding message received for a given boat. + * @param sourceID Source ID of the boat. + * @return The latest MarkRounding message for the specified boat. + */ + public MarkRounding getMarkRounding(int sourceID) { + return markRoundingMap.get(sourceID); + } + + /** + * Inserts a MarkRounding message for a given boat. + * @param markRounding The MarkRounding message to set. + */ + public void setMarkRounding(MarkRounding markRounding) { + //TODO should compare the sequence number of the new markRounding with the existing boatLocation for this boat (if it exists), and use the newer one. + markRoundingMap.put(markRounding.getSourceID(), markRounding); + } + + + + /** + * Gets the latest AverageWind message received. + * @return The latest AverageWind message received. + */ + public AverageWind getAverageWind() { + return averageWind; + } + + /** + * Sets the latest AverageWind message received. + * @param averageWind The new AverageWind message to store. + */ + public void setAverageWind(AverageWind averageWind) { + this.averageWind = averageWind; + } + + + /** + * Gets the latest CourseWinds message received. + * @return The latest CourseWinds message received. + */ + public CourseWinds getCourseWinds() { + return courseWinds; + } + + /** + * Sets the latest CourseWinds message received. + * @param courseWinds The new CourseWinds message to store. + */ + public void setCourseWinds(CourseWinds courseWinds) { + this.courseWinds = courseWinds; + } + + + /** + * Returns the map of boat sourceIDs to BoatLocation messages. + * @return Map between boat sourceID and BoatLocation. + */ + public Map getBoatLocationMap() { + return boatLocationMap; + } + + /** + * Returns the map of boat sourceIDs to BoatStatus messages. + * @return Map between boat sourceID and BoatStatus. + */ + public Map getBoatStatusMap() { + return boatStatusMap; + } + + /** + * Returns the map of boat sourceIDs to MarkRounding messages. + * @return Map between boat sourceID and MarkRounding. + */ + public Map getMarkRoundingMap() { + return markRoundingMap; + } + + + + /** + * Returns the latest race xml message. + * @return The latest race xml message. + */ + public XMLMessage getRaceXMLMessage() { + return raceXMLMessage; + } + + /** + * Sets the latest race xml message to a specified race XML message. + * @param raceXMLMessage The new race XML message to use. + */ + public void setRaceXMLMessage(XMLMessage raceXMLMessage) { + this.raceXMLMessage = raceXMLMessage; + + this.setChanged(); + this.notifyObservers(); + } + + + /** + * Returns the latest boat xml message. + * @return The latest boat xml message. + */ + public XMLMessage getBoatXMLMessage() { + return boatXMLMessage; + } + + /** + * Sets the latest boat xml message to a specified boat XML message. + * @param boatXMLMessage The new boat XML message to use. + */ + public void setBoatXMLMessage(XMLMessage boatXMLMessage) { + this.boatXMLMessage = boatXMLMessage; + + this.setChanged(); + this.notifyObservers(); + } + + + /** + * Returns the latest regatta xml message. + * @return The latest regatta xml message. + */ + public XMLMessage getRegattaXMLMessage() { + return regattaXMLMessage; + } + + /** + * Sets the latest regatta xml message to a specified regatta XML message. + * @param regattaXMLMessage The new regatta XML message to use. + */ + public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) { + this.regattaXMLMessage = regattaXMLMessage; + + this.setChanged(); + this.notifyObservers(); + } + + /** + * Checks the type of xml message, and places it in this LatestMessages object. + * @param xmlMessage The new xml message to use. + */ + public void setXMLMessage(XMLMessage xmlMessage) { + + if (xmlMessage.getXmlMsgSubType() == XMLMessageType.RACE) { + this.setRaceXMLMessage(xmlMessage); + + } else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.REGATTA) { + this.setRegattaXMLMessage(xmlMessage); + + } else if (xmlMessage.getXmlMsgSubType() == XMLMessageType.BOAT) { + this.setBoatXMLMessage(xmlMessage); + + } + + } + + /** + * Returns whether or not there is an xml message for each message type. + * @return True if race, boat, and regatta have an xml message, false otherwise. + */ + public boolean hasAllXMLMessages() { + + if ((this.regattaXMLMessage == null) || (this.boatXMLMessage == null) || (this.raceXMLMessage == null)) { + return false; + + } else { + return true; + + } + + } + +} diff --git a/network/src/main/java/seng302/Networking/Messages/MarkRounding.java b/racevisionGame/src/main/java/network/Messages/MarkRounding.java similarity index 95% rename from network/src/main/java/seng302/Networking/Messages/MarkRounding.java rename to racevisionGame/src/main/java/network/Messages/MarkRounding.java index 70dd6a75..a13f0ba7 100644 --- a/network/src/main/java/seng302/Networking/Messages/MarkRounding.java +++ b/racevisionGame/src/main/java/network/Messages/MarkRounding.java @@ -1,6 +1,7 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Created by fwy13 on 25/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/RaceMessage.java b/racevisionGame/src/main/java/network/Messages/RaceMessage.java similarity index 84% rename from network/src/main/java/seng302/Networking/Messages/RaceMessage.java rename to racevisionGame/src/main/java/network/Messages/RaceMessage.java index 9dcba43a..9c44e38f 100644 --- a/network/src/main/java/seng302/Networking/Messages/RaceMessage.java +++ b/racevisionGame/src/main/java/network/Messages/RaceMessage.java @@ -1,6 +1,7 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Created by fwy13 on 19/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/RaceStartStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java similarity index 86% rename from network/src/main/java/seng302/Networking/Messages/RaceStartStatus.java rename to racevisionGame/src/main/java/network/Messages/RaceStartStatus.java index bb319fe7..8dc442ab 100644 --- a/network/src/main/java/seng302/Networking/Messages/RaceStartStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStartStatus.java @@ -1,6 +1,7 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; + +import network.Messages.Enums.MessageType; /** * Created by fwy13 on 25/04/17. diff --git a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java b/racevisionGame/src/main/java/network/Messages/RaceStatus.java similarity index 73% rename from network/src/main/java/seng302/Networking/Messages/RaceStatus.java rename to racevisionGame/src/main/java/network/Messages/RaceStatus.java index 07125c6d..42bb8208 100644 --- a/network/src/main/java/seng302/Networking/Messages/RaceStatus.java +++ b/racevisionGame/src/main/java/network/Messages/RaceStatus.java @@ -1,7 +1,9 @@ -package seng302.Networking.Messages; +package network.Messages; -import seng302.Networking.Messages.Enums.MessageType; -import seng302.Networking.Utils.AC35UnitConverter; + +import network.Messages.Enums.MessageType; +import network.Utils.AC35UnitConverter; +import shared.model.Constants; import java.util.List; @@ -12,14 +14,14 @@ public class RaceStatus extends AC35Data { private long currentTime; private int raceID; - private int raceStatus; + private byte raceStatus; private long expectedStartTime; private int windDirection; private int windSpeed; private int raceType; private List boatStatuses; - public RaceStatus(long currentTime, int raceID, int raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ + public RaceStatus(long currentTime, int raceID, byte raceStatus, long expectedStartTime, int windDirection, int windSpeed, int raceType, List boatStatuses){ super(MessageType.RACESTATUS); this.currentTime = currentTime; this.raceID = raceID; @@ -49,7 +51,7 @@ public class RaceStatus extends AC35Data { * * @return race status number */ - public int getRaceStatus() + public byte getRaceStatus() { return raceStatus; } @@ -64,6 +66,10 @@ public class RaceStatus extends AC35Data { return windDirection; } + /** + * Returns the wind speed for this race status, in millimeters per second. + * @return Wind speed in millimeters per second. + */ public int getWindSpeed() { return windSpeed; @@ -124,6 +130,14 @@ public class RaceStatus extends AC35Data { } public double getScaledWindDirection() { - return (double) AC35UnitConverter.convertHeading(windDirection); + return AC35UnitConverter.convertHeading(windDirection); + } + + /** + * Returns the wind speed for this race status, in knots. + * @return Wind speed in knots. + */ + public double getWindSpeedKnots() { + return (windSpeed / Constants.KnotsToMMPerSecond); } } diff --git a/racevisionGame/src/main/java/network/Messages/XMLMessage.java b/racevisionGame/src/main/java/network/Messages/XMLMessage.java new file mode 100644 index 00000000..e3a4aa1b --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/XMLMessage.java @@ -0,0 +1,136 @@ +package network.Messages; + + +import network.Messages.Enums.MessageType; +import network.Messages.Enums.XMLMessageType; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Created by fwy13 on 25/04/17. + */ +public class XMLMessage extends AC35Data { + + + /** + * The current version number for xml messages is 1. + */ + public static byte currentVersionNumber = 1; + + /** + * The version number of the message. + */ + private byte versionNumber; + + /** + * The ack number of the message. + */ + private int ackNumber; + + /** + * The timestamp of the message. + * Milliseconds since unix epoch. + */ + private long timeStamp; + + /** + * The subtype of the xml message (e.g., race xml message). + */ + private XMLMessageType xmlMsgSubType; + + /** + * The sequence number of this specific xml subtype. + * Increments whenever the xml contents for a specific xml subtype changes. + */ + private int sequenceNumber; + + /** + * The length of the xml message. + * Number of bytes. + */ + private int xmlMsgLength; + + /** + * The contents of the xml message. + */ + private String xmlMessage; + + + /** + * Constructor for an XML Message + * @param versionNumber The version number of the xml message. + * @param ackNumber Number for acknowledgement inherited for the AC35Data Packet + * @param timeStamp Time received + * @param xmlMsgSubType Type of XML message + * @param sequenceNumber Order that it has arrived in + * @param xmlMessage XML message + */ + public XMLMessage(byte versionNumber, int ackNumber, long timeStamp, XMLMessageType xmlMsgSubType, int sequenceNumber, String xmlMessage) { + super(MessageType.XMLMESSAGE); + this.versionNumber = versionNumber; + this.ackNumber = ackNumber; + this.timeStamp = timeStamp; + this.xmlMsgSubType = xmlMsgSubType; + this.sequenceNumber = sequenceNumber; + this.xmlMsgLength = xmlMessage.getBytes(StandardCharsets.UTF_8).length; + this.xmlMessage = xmlMessage; + } + + /** + * Get the XML Message. + * @return the XML message as string. + */ + public String getXmlMessage() { + return xmlMessage; + } + + /** + * Get the type of message + * @return Gets the type of message the XML message is + */ + public XMLMessageType getXmlMsgSubType() { + return xmlMsgSubType; + } + + + /** + * Returns the version number of this xml message. + * @return The version number of this xml message. + */ + public byte getVersionNumber() { + return versionNumber; + } + + /** + * Returns the ack number of this xml message. + * @return The ack number of this xml message. + */ + public int getAckNumber() { + return ackNumber; + } + + /** + * Returns the timestamp of this xml message. + * @return The timestamp of this xml message. + */ + public long getTimeStamp() { + return timeStamp; + } + + /** + * Returns the sequence number of this xml message. This is specific to each message subtype. + * @return The sequence number of this xml message. + */ + public int getSequenceNumber() { + return sequenceNumber; + } + + /** + * Returns the length, in number of bytes, of the xml message. + * @return The length, in bytes, of the xml message. + */ + public int getXmlMsgLength() { + return xmlMsgLength; + } +} diff --git a/network/src/main/java/seng302/Networking/PacketDump/AC35DumpReader.java b/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java similarity index 90% rename from network/src/main/java/seng302/Networking/PacketDump/AC35DumpReader.java rename to racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java index 2d8b48da..8022c374 100644 --- a/network/src/main/java/seng302/Networking/PacketDump/AC35DumpReader.java +++ b/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java @@ -1,8 +1,9 @@ -package seng302.Networking.PacketDump; +package network.PacketDump; -import seng302.Networking.BinaryMessageDecoder; -import seng302.Networking.Exceptions.InvalidMessageException; -import seng302.Networking.Messages.AC35Data; + +import network.BinaryMessageDecoder; +import network.Exceptions.InvalidMessageException; +import network.Messages.AC35Data; import java.io.IOException; import java.net.URISyntaxException; diff --git a/network/src/main/java/seng302/Networking/PacketDump/AC35Packet.java b/racevisionGame/src/main/java/network/PacketDump/AC35Packet.java similarity index 84% rename from network/src/main/java/seng302/Networking/PacketDump/AC35Packet.java rename to racevisionGame/src/main/java/network/PacketDump/AC35Packet.java index ea6e1a2c..4b978d48 100644 --- a/network/src/main/java/seng302/Networking/PacketDump/AC35Packet.java +++ b/racevisionGame/src/main/java/network/PacketDump/AC35Packet.java @@ -1,4 +1,4 @@ -package seng302.Networking.PacketDump; +package network.PacketDump; /** * Created by fwy13 on 25/04/17. diff --git a/network/src/main/java/seng302/Networking/Utils/AC35UnitConverter.java b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java similarity index 97% rename from network/src/main/java/seng302/Networking/Utils/AC35UnitConverter.java rename to racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java index 6aaba4c6..73f6d0e9 100644 --- a/network/src/main/java/seng302/Networking/Utils/AC35UnitConverter.java +++ b/racevisionGame/src/main/java/network/Utils/AC35UnitConverter.java @@ -1,4 +1,4 @@ -package seng302.Networking.Utils; +package network.Utils; /** * Created by fwy13 on 28/04/17. diff --git a/network/src/main/java/seng302/Networking/Utils/ByteConverter.java b/racevisionGame/src/main/java/network/Utils/ByteConverter.java similarity index 99% rename from network/src/main/java/seng302/Networking/Utils/ByteConverter.java rename to racevisionGame/src/main/java/network/Utils/ByteConverter.java index 7df204da..8c1172da 100644 --- a/network/src/main/java/seng302/Networking/Utils/ByteConverter.java +++ b/racevisionGame/src/main/java/network/Utils/ByteConverter.java @@ -1,4 +1,4 @@ -package seng302.Networking.Utils; +package network.Utils; import java.nio.ByteBuffer; import java.nio.ByteOrder; diff --git a/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java new file mode 100644 index 00000000..40f12c75 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java @@ -0,0 +1,25 @@ +package shared.dataInput; + + +import shared.model.Boat; +import shared.model.Mark; + +import java.util.Map; + +/** + * Provides information about the boats and marker boats in a race. + */ +public interface BoatDataSource { + + /** + * Returns a map between source ID and boat for all boats in the race. + * @return Map between source ID and boat. + */ + Map getBoats(); + + /** + * Returns a map between source ID and mark for all marks in the race. + * @return Map between source ID and mark. + */ + Map getMarkerBoats(); +} diff --git a/mock/src/main/java/seng302/DataInput/BoatXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java similarity index 64% rename from mock/src/main/java/seng302/DataInput/BoatXMLReader.java rename to racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java index f3494f55..885309c0 100644 --- a/mock/src/main/java/seng302/DataInput/BoatXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java @@ -1,47 +1,74 @@ -package seng302.DataInput; +package shared.dataInput; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.xml.sax.SAXException; -import seng302.Model.Boat; -import seng302.Model.GPSCoordinate; -import seng302.Model.Mark; -import seng302.Model.Polars; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.XMLReaderException; +import shared.model.Boat; +import shared.model.GPSCoordinate; +import shared.model.Mark; + +import java.io.InputStream; import java.util.HashMap; import java.util.Map; /** - * Xml Reader class for Boat XML used for the race + * Xml Reader class for Boat XML used for the race. */ public class BoatXMLReader extends XMLReader implements BoatDataSource { + /** + * A map of source ID to boat for all boats in the race. + */ private final Map boatMap = new HashMap<>(); + + /** + * A map of source ID to mark for all marks in the race. + */ private final Map markerMap = new HashMap<>(); + /** - * Polars table to assign to each boat. + * Constructor for Boat XML using a file. + * + * @param file The file to read. + * @param type How to read the file - e.g., load as resource. + * @throws XMLReaderException Thrown if the file cannot be parsed. + * @throws InvalidBoatDataException Thrown if the file cannot be parsed correctly. */ - Polars boatPolars; + public BoatXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidBoatDataException { + super(file, type); + + //Attempt to read boat xml file. + try { + read(); + } catch (Exception e) { + throw new InvalidBoatDataException("An error occurred while reading the boat xml file", e); + } + } /** - * Constructor for Boat XML + * Constructor for Boat XML, using an InputStream. * - * @param filePath Name/path of file to read. Read as a resource. - * @param boatPolars polars used by the boats - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * @param fileStream Stream to read boat data from. + * @throws XMLReaderException Thrown if the file cannot be parsed. + * @throws InvalidBoatDataException Thrown if the stream cannot be parsed correctly. */ - public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException { - super(filePath); - this.boatPolars = boatPolars; - read(); + public BoatXMLReader(InputStream fileStream) throws XMLReaderException, InvalidBoatDataException { + super(fileStream); + + //Attempt to read boat xml stream. + try { + read(); + } catch (Exception e) { + throw new InvalidBoatDataException("An error occurred while reading the boat xml stream", e); + } } + + /** * Read the XML */ @@ -90,6 +117,7 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { /** * Reads the information about one boat * Ignored values: ShapeID, StoweName, HullNum, Skipper, Type + * @param boatNode The node representing a boat. */ private void readBoatNode(Node boatNode) { int sourceID = Integer.parseInt(boatNode.getAttributes().getNamedItem("SourceID").getTextContent()); @@ -109,9 +137,9 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { String shortName = boatNode.getAttributes().getNamedItem("ShortName").getTextContent(); if (exists(boatNode, "Country")) { String country = boatNode.getAttributes().getNamedItem("Country").getTextContent(); - boatMap.put(sourceID, new Boat(sourceID, name, country, this.boatPolars)); + boatMap.put(sourceID, new Boat(sourceID, name, country)); } else { - boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars)); + boatMap.put(sourceID, new Boat(sourceID, name, shortName)); } } @@ -139,8 +167,8 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { } /** - * Get the marker BOats that are participating in this race - * @return Dictionary of the Markers BOats that are in this race indexed by their Source ID. + * Get the marker Boats that are participating in this race + * @return Dictionary of the Markers Boats that are in this race indexed by their Source ID. */ @Override public Map getMarkerBoats() { diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java new file mode 100644 index 00000000..668714e9 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java @@ -0,0 +1,87 @@ +package shared.dataInput; + +import network.Messages.Enums.RaceTypeEnum; +import shared.model.Boat; +import shared.model.CompoundMark; +import shared.model.GPSCoordinate; +import shared.model.Leg; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * An object that holds relevant data for a race.
+ * Information includes: {@link shared.model.Boat Boat}s, + * {@link shared.model.Leg Leg}s, {@link shared.model.CompoundMark CompoundMark}s and + * the {@link shared.model.GPSCoordinate GPSCoordinate}s. + */ +public interface RaceDataSource { + /** + * Returns the list of sourceIDs for boats competing in the race. + * @return SourceIDs for boats competing in the race. + */ + List getParticipants(); + + /** + * Returns the list of legs in the race. + * @return The list of legs in the race. + */ + List getLegs(); + + /** + * Returns a list of coordinates representing the boundary of the race. + * @return The boundary of the race. + */ + List getBoundary(); + + /** + * Returns a list of CompoundMarks in the race. + * @return The sequence of compounds marks in the race. + */ + List getCompoundMarks(); + + + /** + * Returns the ID of the race. + * @return The ID of the race. + */ + int getRaceId(); + + /** + * Returns the type of race. + * @return The type of race. + */ + RaceTypeEnum getRaceType(); + + + /** + * Returns the start time/date of the race. + * @return The race's start time. + */ + ZonedDateTime getStartDateTime(); + + /** + * Returns the creation time/date of the race xml file. + * @return The race xml file's creation time. + */ + ZonedDateTime getCreationDateTime(); + + /** + * Returns whether or not the race has been postponed. + * @return True if the race has been postponed, false otherwise. + */ + boolean getPostponed(); + + + /** + * Returns the GPS coordinate of the top left of the race map area. + * @return Top left GPS coordinate. + */ + GPSCoordinate getMapTopLeft(); + + /** + * Returns the GPS coordinate of the bottom right of the race map area. + * @return Bottom right GPS coordinate. + */ + GPSCoordinate getMapBottomRight(); +} diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java new file mode 100644 index 00000000..7e61b3de --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -0,0 +1,463 @@ +package shared.dataInput; + +import network.Messages.Enums.RaceTypeEnum; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidRaceDataException; +import shared.exceptions.XMLReaderException; +import shared.model.*; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * Xml Reader class for Race XML used for the race. + */ +public class RaceXMLReader extends XMLReader implements RaceDataSource { + + + /** + * The GPS coordinate of the top left of the race boundary. + */ + private GPSCoordinate mapTopLeft; + + /** + * The GPS coordinate of the bottom right of the race boundary. + */ + private GPSCoordinate mapBottomRight; + + + /** + * A list of GPS coordinates that make up the boundary of the race. + */ + private final List boundary = new ArrayList<>(); + + /** + * A map between compoundMarkID and a CompoundMark for all CompoundMarks in a race. + */ + private final Map compoundMarkMap = new HashMap<>(); + + /** + * A list of boat sourceIDs participating in the race. + */ + private final List participants = new ArrayList<>(); + + /** + * A list of legs in the race. + */ + private final List legs = new ArrayList<>(); + + + /** + * The time that the race.xml file was created. + */ + private ZonedDateTime creationTimeDate; + + /** + * The time that the race should start at, if it hasn't been postponed. + */ + private ZonedDateTime raceStartTime; + + /** + * Whether or not the race has been postponed. + */ + private boolean postpone; + + + /** + * The ID number of the race. + */ + private int raceID; + + /** + * The type of the race. + */ + private RaceTypeEnum raceType; + + + + + /** + * Constructor for Streamed Race XML + * @param file The file to read. + * @param type How to read the file - e.g., load as resource. + * @throws XMLReaderException Thrown if an XML reader cannot be constructed for the given file. + * @throws InvalidRaceDataException Thrown if the XML file is invalid in some way. + */ + public RaceXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidRaceDataException { + + super(file, type); + + + //Attempt to read race xml file. + try { + read(); + } catch (Exception e) { + throw new InvalidRaceDataException("An error occurred while reading the race xml file", e); + } + } + + /** + * Reads the contents of the race xml file. + * @throws InvalidRaceDataException Thrown if we cannot parse the document properly. + */ + private void read() throws InvalidRaceDataException { + readRace(); + readParticipants(); + readCourse(); + } + + /** + * Reads race related data from the race xml file. + */ + private void readRace() { + + DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + Element settings = (Element) doc.getElementsByTagName("Race").item(0); + NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes(); + + if (raceTimeTag.getNamedItem("Time") != null) { + dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); + } + + + //Race ID. + raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID")); + + //Race type. + String raceTypeString = getTextValueOfNode(settings, "RaceType"); + raceType = RaceTypeEnum.fromString(raceTypeString); + + //XML creation time. + creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat); + + //Race start time. + if (raceTimeTag.getNamedItem("Time") != null) { + raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat); + + } else { + raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat); + } + + //Postpone status. + postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent()); + } + + + /** + * Reads in the participants for this race. + */ + private void readParticipants() { + + //Gets the ".." element. + Element participants = (Element) doc.getElementsByTagName("Participants").item(0); + + //Gets the number of participants. + int numberOfParticipants = participants.getChildNodes().getLength(); + + //For each participant, read its sourceID. + for (int i = 0; i < numberOfParticipants; i++) { + + //Get the participating yacht. + Node yacht = participants.getChildNodes().item(i); + + if (yacht.getNodeName().equals("Yacht")) { + if (exists(yacht, "SourceID")) { + + //If the node is a valid yacht with a sourceID, add it to participant list. + int sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent()); + this.participants.add(sourceID); + + } + } + + } + } + + + /** + * Reads course data from the xml file. + * @throws InvalidRaceDataException Thrown if we cannot parse the document properly. + */ + private void readCourse() throws InvalidRaceDataException { + readCompoundMarks(); + readCompoundMarkSequence(); + readCourseLimits(); + readMapTopLeft(); + readMapBottomRight(); + } + + /** + * Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers. + * @throws InvalidRaceDataException thrown if we cannot create a compound mark from the document. + * @see CompoundMark + */ + private void readCompoundMarks() throws InvalidRaceDataException { + + //Gets the "..." element. + Element course = (Element) doc.getElementsByTagName("Course").item(0); + + //Get the list of CompoundMark elements. + NodeList compoundMarkList = course.getElementsByTagName("CompoundMark"); + + //Number of compound marks in the course. + int numberOfCompoundMarks = compoundMarkList.getLength(); + + //For each CompoundMark element, create a CompoundMark object. + for(int i = 0; i < numberOfCompoundMarks; i++) { + + //Get the CompoundMark element. + Element compoundMarkElement = (Element) compoundMarkList.item(i); + + + //Convert to CompoundMark object. + CompoundMark compoundMark = createCompoundMark(compoundMarkElement); + + compoundMarkMap.put(compoundMark.getId(), compoundMark); + + } + } + + /** + * Generates a CompoundMark from a given CompondMark element. + * @param compoundMarkElement The CompoundMark element to turn into a CompoundMark object. + * @return The corresponding CompoundMark object. + * @throws InvalidRaceDataException If the element cannot be converted into a CompoundMark. + */ + private CompoundMark createCompoundMark(Element compoundMarkElement) throws InvalidRaceDataException { + + //CompoundMark ID. + int compoundMarkID = getCompoundMarkID(compoundMarkElement); + + //CompoundMark name. + String compoundMarkName = getCompoundMarkName(compoundMarkElement); + + + //Get the list of marks within the compound mark. + NodeList marks = compoundMarkElement.getElementsByTagName("Mark"); + CompoundMark compoundMark; + + switch(marks.getLength()) { + case 1: { + //Create the Mark sub-object. + Mark mark1 = createMark((Element) marks.item(0)); + + //Create compound mark. + compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1); + break; + + } case 2: { + //Create the Mark sub-objects. + Mark mark1 = createMark((Element) marks.item(0)); + Mark mark2 = createMark((Element) marks.item(1)); + + //Create compound mark. + compoundMark = new CompoundMark(compoundMarkID, compoundMarkName, mark1, mark2); + break; + + } default: { + throw new InvalidRaceDataException("Cannot create CompoundMark from " + compoundMarkElement.toString()); + } + } + + return compoundMark; + + } + + + /** + * Gets a mark from an Element. + * @param mark The {@link Element} describing the {@link Mark}. + * @return The {@link Mark}. + */ + private Mark createMark(Element mark) { + + //Source ID. + int sourceID = Integer.parseInt(mark.getAttribute("SourceID")); + + //Name. + String name = mark.getAttribute("Name"); + + //Latitude. + double latitude = Double.parseDouble(mark.getAttribute("TargetLat")); + + //Longitude. + double longitude = Double.parseDouble(mark.getAttribute("TargetLng")); + + //Create mark. + return new Mark(sourceID, name, new GPSCoordinate(latitude, longitude)); + + } + + + /** + * Reads "compoundMarkID" attribute of CompoundMark or Corner element. + * @param element with "compoundMarkID" attribute. + * @return value of "compoundMarkID" attribute. + */ + private int getCompoundMarkID(Element element) { + return Integer.parseInt(element.getAttribute("CompoundMarkID")); + } + + + /** + * Reads "Name" attribute of a CompoundMark element. + * @param element The CompoundMark element with a "Name" attribute. + * @return value of "name" attribute. + */ + private String getCompoundMarkName(Element element) { + return element.getAttribute("Name"); + } + + + /** + * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. + */ + private void readCompoundMarkSequence() { + + //The "..." element. This contains a sequence of Corner elements. + Element compoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0); + + //Gets the list of Corner elements. + NodeList corners = compoundMarkSequence.getElementsByTagName("Corner"); + + //Gets the first corner. + Element cornerElement = (Element) corners.item(0); + + //Gets the ID number of this corner element. + int cornerID = getCompoundMarkID(cornerElement); + + //Gets the CompoundMark associated with this corner. + CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID); + + //The name of the leg is the name of the first compoundMark in the leg. + String legName = lastCompoundMark.getName(); + + //For each following corner, create a leg between cornerN and cornerN+1. + for(int i = 1; i < corners.getLength(); i++) { + + //Gets the next corner element. + cornerElement = (Element) corners.item(i); + + //Gets the ID number of this corner element. + cornerID = getCompoundMarkID(cornerElement); + + //Gets the CompoundMark associated with this corner. + CompoundMark currentCompoundMark = this.compoundMarkMap.get(cornerID); + + //Create a leg from these two adjacent compound marks. + Leg leg = new Leg(legName, lastCompoundMark, currentCompoundMark, i - 1); + legs.add(leg); + + //Prepare for next iteration. + lastCompoundMark = currentCompoundMark; + legName = lastCompoundMark.getName(); + + } + + } + + + /** + * Reads the boundary limits of the course. + */ + private void readCourseLimits() { + + //The "..." element. This contains a sequence of Limit elements. + Element courseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0); + + //Get the list of Limit elements. + NodeList limitList = courseLimit.getElementsByTagName("Limit"); + + //For each limit element... + for(int i = 0; i < limitList.getLength(); i++) { + + //Get the Limit element. + Element limit = (Element) limitList.item(i); + + //Convert to GPSCoordinate. + double latitude = Double.parseDouble(limit.getAttribute("Lat")); + double longitude = Double.parseDouble(limit.getAttribute("Lon")); + boundary.add(new GPSCoordinate(latitude, longitude)); + + } + } + + + /** + * Reads the gps coordinate of the top left of the map, using the course limits. + */ + private void readMapTopLeft(){ + + double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); + + double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); + + mapTopLeft = new GPSCoordinate(minLatitude, minLongitude); + + } + + /** + * Reads the gps coordinate of the bottom right of the map, using the course limits. + */ + private void readMapBottomRight(){ + + double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude(); + + double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude(); + + mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude); + + } + + + + public List getBoundary() { + return boundary; + } + + public GPSCoordinate getMapTopLeft() { + return mapTopLeft; + } + + public GPSCoordinate getMapBottomRight() { + return mapBottomRight; + } + + public List getLegs() { + return legs; + } + + public List getCompoundMarks() { + return new ArrayList<>(compoundMarkMap.values()); + } + + + public ZonedDateTime getCreationDateTime() { + return creationTimeDate; + } + + public ZonedDateTime getStartDateTime() { + return raceStartTime; + } + + public int getRaceId() { + return raceID; + } + + public RaceTypeEnum getRaceType() { + return raceType; + } + + public boolean getPostponed() { + return postpone; + } + + public List getParticipants() { + return participants; + } +} diff --git a/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java new file mode 100644 index 00000000..eb198cb2 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java @@ -0,0 +1,70 @@ +package shared.dataInput; + + + + +/** + * Provides information about a race regatta. + */ +public interface RegattaDataSource { + + /** + * Returns the ID of the regatta. + * @return The ID of the regatta. + */ + int getRegattaID(); + + /** + * Returns the name of the regatta. + * @return The name of the regatta. + */ + String getRegattaName(); + + /** + * Returns the ID of the race this regatta relates to. + * @return The ID of the race that this regatta relates to. + */ + int getRaceID(); + + /** + * Returns the name of the course. + * @return the name of the course + */ + String getCourseName(); + + + /** + * Returns the latitude of the centre of the course. + * @return The latitude of the centre of the course. + */ + double getCentralLatitude(); + + /** + * Returns the longitude of the centre of the course. + * @return The longitude of the centre of the course. + */ + double getCentralLongitude(); + + /** + * Returns the altitude of the centre of the course. + * @return The altitude of the centre of the course. + */ + double getCentralAltitude(); + + + /** + * Returns the UTC offset of the course's location. + * @return The UTC offset of the course. + */ + float getUtcOffset(); + + + /** + * Returns the magnetic variation of the course's location. + * @return The magnetic variation of the course. + */ + float getMagneticVariation(); + + + +} diff --git a/visualiser/src/main/java/seng302/Mock/RegattaXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java similarity index 60% rename from visualiser/src/main/java/seng302/Mock/RegattaXMLReader.java rename to racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java index 76de49ad..08d4a034 100644 --- a/visualiser/src/main/java/seng302/Mock/RegattaXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java @@ -1,69 +1,108 @@ -package seng302.Mock; +package shared.dataInput; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import seng302.GPSCoordinate; -import seng302.XMLReader; +import shared.dataInput.XMLReader; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; +import shared.model.GPSCoordinate; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; /** - * Created by jjg64 on 19/04/17. + * XML reader class for regatta xml file. */ -public class RegattaXMLReader extends XMLReader { +public class RegattaXMLReader extends XMLReader implements RegattaDataSource { + /** + * The regatta ID. + */ private int regattaID; + + /** + * The regatta name. + */ private String regattaName; + + /** + * The race ID. + */ private int raceID = 0; + + /** + * The course name. + */ private String courseName; + + /** + * The central latitude of the course. + */ private double centralLatitude; + + /** + * The central longitude of the course. + */ private double centralLongitude; + + /** + * The central altitude of the course. + */ private double centralAltitude; + + /** + * The UTC offset of the course. + */ private float utcOffset; - private float magneticVariation; /** - * Constructor for Regatta XML - * - * @param filePath path of the file - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * The magnetic variation of the course. */ - public RegattaXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - } + private float magneticVariation; + + /** - * Constructor for Regatta XML + * Constructor for Regatta XML using a file. * - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * @param file The file. + * @param type How to read the file - e.g., load as resource. + * @throws XMLReaderException Thrown if the file cannot be parsed. + * @throws InvalidRegattaDataException Thrown if the file cannot be parsed correctly. */ - private RegattaXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException { - super(filePath); - if (read) { + public RegattaXMLReader(String file, XMLFileType type) throws XMLReaderException, InvalidRegattaDataException { + super(file, type); + + //Attempt to read boat xml file. + try { read(); + } catch (Exception e) { + throw new InvalidRegattaDataException("An error occurred while reading the regatta xml file", e); } } + + /** - * Alternate Constructor that takes in an inputstream instead - * @param xmlString Input stream of the XML - * @throws IOException Error with input - * @throws SAXException Error with XML Format - * @throws ParserConfigurationException Error with XMl contents + * Constructor for Regatta XML using an InputStream. + * @param xmlString Input stream of the XML. + * @throws XMLReaderException Thrown if the input stream cannot be parsed. + * @throws InvalidRegattaDataException Thrown if the stream cannot be parsed correctly. */ - public RegattaXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException { + public RegattaXMLReader(InputStream xmlString) throws XMLReaderException, InvalidRegattaDataException { super(xmlString); - read(); + + //Attempt to read boat xml file. + try { + read(); + } catch (Exception e) { + throw new InvalidRegattaDataException("An error occurred while reading the regatta xml stream", e); + } } + /** * Read the XML */ @@ -78,14 +117,21 @@ public class RegattaXMLReader extends XMLReader { * @param attributes attributes to extract information form. */ private void makeRegatta(Element attributes) { + this.regattaID = Integer.parseInt(getTextValueOfNode(attributes, "RegattaID")); this.regattaName = getTextValueOfNode(attributes, "RegattaName"); + + //this.raceID = Integer.parseInt(getTextValueOfNode(attributes, "RaceID")); this.courseName = getTextValueOfNode(attributes, "CourseName"); + this.centralLatitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLatitude")); this.centralLongitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLongitude")); this.centralAltitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralAltitude")); + this.utcOffset = Float.parseFloat(getTextValueOfNode(attributes, "UtcOffset")); + this.magneticVariation = Float.parseFloat(getTextValueOfNode(attributes, "MagneticVariation")); + } public int getRegattaID() { @@ -160,6 +206,10 @@ public class RegattaXMLReader extends XMLReader { this.magneticVariation = magneticVariation; } + /** + * Returns the GPS coorindates of the centre of the regatta. + * @return The gps coordinate for the centre of the regatta. + */ public GPSCoordinate getGPSCoordinate() { return new GPSCoordinate(centralLatitude, centralLongitude); } diff --git a/racevisionGame/src/main/java/shared/dataInput/XMLReader.java b/racevisionGame/src/main/java/shared/dataInput/XMLReader.java new file mode 100644 index 00000000..04c6c1f7 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/XMLReader.java @@ -0,0 +1,189 @@ +package shared.dataInput; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import shared.enums.XMLFileType; +import shared.exceptions.XMLReaderException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Base Reader for XML Files + */ +public abstract class XMLReader { + + protected Document doc; + + + /** + * Reads an XML file. + * @param file The file to read. + * @param type How to read the file - e.g., load as resource. + * @throws XMLReaderException Throw if the file cannot be parsed. + */ + public XMLReader(String file, XMLFileType type) throws XMLReaderException { + + + InputStream xmlInputStream = null; + + //Create an input stream. Method depends on type parameter. + + if (type == XMLFileType.Contents) { + //Wrap file contents in input stream. + xmlInputStream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8)); + + } else if (type == XMLFileType.ResourcePath) { + xmlInputStream = XMLReader.class.getClassLoader().getResourceAsStream(file); + + } else if (type == XMLFileType.FilePath) { + try { + xmlInputStream = new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new XMLReaderException("Could not open file " + file, e); + } + + } + + + + this.doc = parseInputStream(xmlInputStream); + + } + + + /** + * Reads an XML file from an input stream. + * @param xmlInputStream The input stream to parse. + * @throws XMLReaderException Thrown if the input stream cannot be parsed. + */ + public XMLReader(InputStream xmlInputStream) throws XMLReaderException { + + this.doc = parseInputStream(xmlInputStream); + } + + + /** + * Parses an input stream into a document. + * @param inputStream The xml input stream to parse. + * @return The parsed document. + * @throws XMLReaderException Thrown when a document builder cannot be constructed, or the stream cannot be parsed. + */ + private static Document parseInputStream(InputStream inputStream) throws XMLReaderException { + + //Create document builder. + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + + DocumentBuilder dBuilder = null; + try { + dBuilder = dbFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new XMLReaderException("Could not create a DocumentBuilder.", e); + } + + //Parse document. + Document document = null; + try { + document = dBuilder.parse(inputStream); + } catch (SAXException | IOException e) { + throw new XMLReaderException("Could not parse the xml input stream.", e); + } + document.getDocumentElement().normalize(); + + return document; + } + + + /** + * Return Document data of the read-in XML + * @return XML document + */ + public Document getDocument() { + return doc; + } + + /** + * Get content of a tag in an element + * @param n Element to read tags from + * @param tagName Name of the tag + * @return Content of the tag + */ + public String getTextValueOfNode(Element n, String tagName) { + return n.getElementsByTagName(tagName).item(0).getTextContent(); + } + + /** + * Get attributes for an element + * @param n Element to read attributes from + * @param attr Attributes of element + * @return Attributes of element + */ + public String getAttribute(Element n, String attr) { + return n.getAttribute(attr); + } + + protected boolean exists(Node node, String attribute) { + return node.getAttributes().getNamedItem(attribute) != null; + } + + /** + * Get the contents of the XML FILe. + * @param document holds all xml information + * @return String representation of document + * @throws TransformerException when document is malformed, and cannot be turned into a string + */ + public static String getContents(Document document) throws TransformerException { + DOMSource source = new DOMSource(document); + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + StringWriter stringWriter = new StringWriter(); + StreamResult result = new StreamResult(stringWriter); + transformer.transform(source, result); + + return stringWriter.toString(); + } + + + /** + * Reads the an XML file, as a resource, into a string. + * @param path path of the XML + * @param encoding encoding of the xml + * @return A string containing the contents of the specified file. + * @throws TransformerException Issue with the XML format + * @throws XMLReaderException Thrown if file cannot be read for some reason. + */ + public static String readXMLFileToString(String path, Charset encoding) throws TransformerException, XMLReaderException { + + InputStream fileStream = XMLReader.class.getClassLoader().getResourceAsStream(path); + + //Resource can't be found. + if (fileStream == null) { + throw new XMLReaderException("Could not open resource: " + path, new IOException()); + } + + Document doc = XMLReader.parseInputStream(fileStream); + + doc.getDocumentElement().normalize(); + + return XMLReader.getContents(doc); + + } + +} diff --git a/racevisionGame/src/main/java/shared/enums/XMLFileType.java b/racevisionGame/src/main/java/shared/enums/XMLFileType.java new file mode 100644 index 00000000..6578fb0b --- /dev/null +++ b/racevisionGame/src/main/java/shared/enums/XMLFileType.java @@ -0,0 +1,24 @@ +package shared.enums; + +/** + * Represents the various ways in which we want to open/read an xml file - e.g., a string may contain the file contents, or the file path. + */ +public enum XMLFileType { + + /** + * This means that a provided string contains the contents of an XML file. + */ + Contents, + + /** + * This means that a provided string contains the path to an XML file. + */ + FilePath, + + /** + * This means that a provided string contains the path, to be loaded as a resource, of an XML file. + */ + ResourcePath; + + +} diff --git a/racevisionGame/src/main/java/shared/exceptions/InvalidBoatDataException.java b/racevisionGame/src/main/java/shared/exceptions/InvalidBoatDataException.java new file mode 100644 index 00000000..ba12215c --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/InvalidBoatDataException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when we cannot generate Boats.xml and send an XML message, or we cannot parse a Boats.xml file. + */ +public class InvalidBoatDataException extends Exception { + + public InvalidBoatDataException(String message) { + super(message); + } + + public InvalidBoatDataException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/InvalidRaceDataException.java b/racevisionGame/src/main/java/shared/exceptions/InvalidRaceDataException.java new file mode 100644 index 00000000..e181f675 --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/InvalidRaceDataException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * Exception thrown when we cannot generate Race.xml data, and send an XML message, or we cannot parse a Race.xml file. + */ +public class InvalidRaceDataException extends Exception { + + public InvalidRaceDataException(String message) { + super(message); + } + + public InvalidRaceDataException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/InvalidRegattaDataException.java b/racevisionGame/src/main/java/shared/exceptions/InvalidRegattaDataException.java new file mode 100644 index 00000000..8a5d581e --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/InvalidRegattaDataException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when we cannot generate Regatta.xml and send an XML message, or we cannot parse a Regatta.xml file. + */ +public class InvalidRegattaDataException extends Exception { + + public InvalidRegattaDataException(String message) { + super(message); + } + + public InvalidRegattaDataException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java b/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java new file mode 100644 index 00000000..7948be58 --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when an XMLReader cannot be constructed for some reason. + */ +public class XMLReaderException extends Exception { + + public XMLReaderException(String message) { + super(message); + } + + public XMLReaderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/mock/src/main/java/seng302/Model/Angle.java b/racevisionGame/src/main/java/shared/model/Angle.java similarity index 87% rename from mock/src/main/java/seng302/Model/Angle.java rename to racevisionGame/src/main/java/shared/model/Angle.java index 8fa767dd..00610b65 100644 --- a/mock/src/main/java/seng302/Model/Angle.java +++ b/racevisionGame/src/main/java/shared/model/Angle.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; /** * This represents an angle. @@ -84,6 +84,13 @@ public class Angle implements Comparable { } + @Override + public int hashCode() { + return Double.hashCode(this.degrees); + } + + + /** * Returns an int describing the ordering between this angle object, and another. * @param o Other angle to compare to. @@ -127,4 +134,23 @@ public class Angle implements Comparable { return angle; } + + + + /** + * Sets the degrees value of the angle. + * @param degrees New value of the angle. + */ + public void setDegrees(double degrees) { + this.degrees = degrees; + } + + /** + * Sets the radians value of the angle. + * @param radians New value of the angle. + */ + public void setRadians(double radians) { + this.setDegrees(Math.toDegrees(radians)); + } + } diff --git a/mock/src/main/java/seng302/Model/Azimuth.java b/racevisionGame/src/main/java/shared/model/Azimuth.java similarity index 66% rename from mock/src/main/java/seng302/Model/Azimuth.java rename to racevisionGame/src/main/java/shared/model/Azimuth.java index cbbaa2e6..0f6202f6 100644 --- a/mock/src/main/java/seng302/Model/Azimuth.java +++ b/racevisionGame/src/main/java/shared/model/Azimuth.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; @@ -65,4 +65,45 @@ public class Azimuth extends Angle{ return Azimuth.fromDegrees(bearing.degrees()); } + + /** + * Constructs an Azimuth object from another Azimuth object. + * @param azimuth Azimuth object to read value from. + * @return Azimuth object. + */ + public static Azimuth fromAzimuth(Azimuth azimuth) { + return Azimuth.fromDegrees(azimuth.degrees()); + } + + + /** + * Sets the degrees value of the azimuth. + * @param degrees New value of the azimuth. + */ + public void setDegrees(double degrees) { + //Put degree value in correct interval. + degrees = Azimuth.toAzimuthInterval(degrees); + //Update. + super.setDegrees(degrees); + } + + /** + * Sets the radians value of the azimuth. + * @param radians New value of the azimuth. + */ + public void setRadians(double radians) { + this.setDegrees(Math.toDegrees(radians)); + } + + + /** + * Sets the value of this azimuth from another azimuth. + * @param azimuth Azimuth to copy the value from. + */ + public void setAzimuth(Azimuth azimuth) { + this.setDegrees(azimuth.degrees()); + } + + + } diff --git a/mock/src/main/java/seng302/Model/Bearing.java b/racevisionGame/src/main/java/shared/model/Bearing.java similarity index 63% rename from mock/src/main/java/seng302/Model/Bearing.java rename to racevisionGame/src/main/java/shared/model/Bearing.java index 27ff1235..950c4094 100644 --- a/mock/src/main/java/seng302/Model/Bearing.java +++ b/racevisionGame/src/main/java/shared/model/Bearing.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; /** * Represents a bearing. Also known as a heading. @@ -28,7 +28,7 @@ public class Bearing extends Angle { */ public static double toBearingInterval(double degrees) { - return Angle.toPeriodicInterval(degrees, -0d, 360d, 360d); + return Angle.toPeriodicInterval(degrees, 0d, 360d, 360d); } /** @@ -63,4 +63,45 @@ public class Bearing extends Angle { return Bearing.fromDegrees(azimuth.degrees()); } + /** + * Constructs a Bearing object from another Bearing object. + * This can be used to copy a bearing. + * @param bearing Bearing object to read value from. + * @return Bearing object. + */ + public static Bearing fromBearing(Bearing bearing) { + return Bearing.fromDegrees(bearing.degrees()); + } + + + /** + * Sets the degrees value of the bearing. + * @param degrees New value of the bearing. + */ + public void setDegrees(double degrees) { + //Put degree value in correct interval. + degrees = Bearing.toBearingInterval(degrees); + //Update. + super.setDegrees(degrees); + } + + /** + * Sets the radians value of the bearing. + * @param radians New value of the bearing. + */ + public void setRadians(double radians) { + this.setDegrees(Math.toDegrees(radians)); + } + + + /** + * Sets the value of this bearing from another bearing. + * @param bearing Bearing to copy the value from. + */ + public void setBearing(Bearing bearing) { + this.setDegrees(bearing.degrees()); + } + + + } diff --git a/mock/src/main/java/seng302/Model/Boat.java b/racevisionGame/src/main/java/shared/model/Boat.java similarity index 53% rename from mock/src/main/java/seng302/Model/Boat.java rename to racevisionGame/src/main/java/shared/model/Boat.java index 1cc95a1e..d6e28783 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/racevisionGame/src/main/java/shared/model/Boat.java @@ -1,8 +1,11 @@ -package seng302.Model; +package shared.model; -import seng302.Constants; -import seng302.Networking.Messages.Enums.BoatStatusEnum; +import javafx.beans.property.*; +import network.Messages.Enums.BoatStatusEnum; +import org.jetbrains.annotations.Nullable; + +import java.time.ZonedDateTime; /** * Boat Model that is used to store information on the boats that are running in the race. @@ -11,13 +14,13 @@ public class Boat { /** * The name of the boat/team. */ - private String name; + private StringProperty name = new SimpleStringProperty(); /** * The current speed of the boat, in knots. * TODO knots */ - private double currentSpeed; + private DoubleProperty currentSpeed = new SimpleDoubleProperty(0); /** * The current bearing/heading of the boat. @@ -32,7 +35,7 @@ public class Boat { /** * The country or team abbreviation of the boat. */ - private String country; + private StringProperty country = new SimpleStringProperty(); /** * The source ID of the boat. @@ -43,7 +46,7 @@ public class Boat { /** * The leg of the race that the boat is currently on. */ - private Leg currentLeg; + private Property currentLeg = new SimpleObjectProperty<>(); /** * The distance, in meters, that the boat has travelled in the current leg. @@ -51,6 +54,12 @@ public class Boat { */ private double distanceTravelledInLeg; + /** + * The boat's position within the race (e.g., 5th). + */ + private StringProperty positionInRace = new SimpleStringProperty(); + + /** * The time, in milliseconds, that has elapsed during the current leg. * TODO milliseconds @@ -69,19 +78,17 @@ public class Boat { */ private BoatStatusEnum status; - /** - * This stores a boat's polars table. - * Can be used to calculate VMG. - */ - private Polars polars; /** - * This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds. - * TODO milliseconds - */ - private long timeSinceTackChange = 0; + * The time at which the boat is estimated to reach the next mark, in milliseconds since unix epoch. + */ + private ZonedDateTime estimatedTimeAtNextMark; - private long estimatedTime = 0; + /** + * The time at which the boat reached the previous mark. + */ + @Nullable + private ZonedDateTime timeAtLastMark; /** @@ -90,13 +97,12 @@ public class Boat { * @param sourceID The id of the boat * @param name The name of the Boat. * @param country The abbreviation or country code for the boat. - * @param polars The polars table to use for this boat. */ - public Boat(int sourceID, String name, String country, Polars polars) { - this.country = country; - this.name = name; + public Boat(int sourceID, String name, String country) { + this.sourceID = sourceID; - this.polars = polars; + this.setName(name); + this.setCountry(country); this.bearing = Bearing.fromDegrees(0d); @@ -106,54 +112,12 @@ public class Boat { - /** - * Calculate the bearing of the boat to its next marker. - * @return The bearing to the next marker. - */ - public Bearing calculateBearingToNextMarker() { - - //Get the start and end points. - GPSCoordinate currentPosition = this.getCurrentPosition(); - GPSCoordinate nextMarkerPosition = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); - - //Calculate bearing. - Bearing bearing = GPSCoordinate.calculateBearing(currentPosition, nextMarkerPosition); - - return bearing; - } - - - - /** - * Calculates the distance between the boat and its target marker in nautical miles. - * @return The distance (in nautical miles) between the boat and its target marker. - */ - public double calculateDistanceToNextMarker() { - - //Get start and end markers. - GPSCoordinate startPosition = this.getCurrentPosition(); - - //When boats finish, their "current leg" doesn't have an end marker. - if (this.getCurrentLeg().getEndCompoundMark() == null) { - return 0d; - } - - GPSCoordinate endMarker = this.getCurrentLeg().getEndCompoundMark().getAverageGPSCoordinate(); - - - //Calculate distance. - double distanceNauticalMiles = GPSCoordinate.calculateDistanceNauticalMiles(startPosition, endMarker); - - return distanceNauticalMiles; - } - - /** * Returns the name of the boat/team. * @return Name of the boat/team. */ public String getName() { - return name; + return name.getValue(); } /** @@ -161,16 +125,23 @@ public class Boat { * @param name Name of the boat/team. */ public void setName(String name) { - this.name = name; + this.name.setValue(name); } + /** + * Returns the name property of the boat. + * @return The name of the boat, in a StringProperty. + */ + public StringProperty nameProperty() { + return name; + } /** * Returns the current speed of the boat, in knots. * @return The current speed of the boat, in knots. */ public double getCurrentSpeed() { - return currentSpeed; + return currentSpeed.get(); } /** @@ -178,7 +149,15 @@ public class Boat { * @param currentSpeed The new speed of the boat, in knots. */ public void setCurrentSpeed(double currentSpeed) { - this.currentSpeed = currentSpeed; + this.currentSpeed.set(currentSpeed); + } + + /** + * Returns the current speed property of the boat. + * @return The current speed of the boat, in a DoubleProperty. + */ + public DoubleProperty currentSpeedProperty() { + return currentSpeed; } @@ -187,7 +166,7 @@ public class Boat { * @return The country/team abbreviation of the boat. */ public String getCountry() { - return country; + return country.getValue(); } /** @@ -195,9 +174,16 @@ public class Boat { * @param country The new country/team abbreviation for the boat. */ public void setCountry(String country) { - this.country = country; + this.country.setValue(country); } + /** + * Returns the country/abbreviation property of the boat. + * @return The country/abbreviation of the boat, in a StringProperty. + */ + public StringProperty countryProperty() { + return country; + } /** * Returns the source ID of the boat. @@ -220,6 +206,14 @@ public class Boat { * @return The current leg of the race the boat is in. */ public Leg getCurrentLeg() { + return currentLeg.getValue(); + } + + /** + * Returns the current leg, wrapped in a property. + * @return Current leg, wrapped in a property. + */ + public Property legProperty() { return currentLeg; } @@ -229,9 +223,12 @@ public class Boat { * @param currentLeg The new leg of the race the boat is in. */ public void setCurrentLeg(Leg currentLeg) { - this.currentLeg = currentLeg; + + this.currentLeg.setValue(currentLeg); + this.setTimeElapsedInCurrentLeg(0); this.setDistanceTravelledInLeg(0); + } @@ -252,6 +249,31 @@ public class Boat { } + /** + * Returns the position within the race the boat has (e.g., 5th). + * @return The boat's position in race. + */ + public StringProperty positionProperty() { + return positionInRace; + } + + /** + * Sets the position within the race the boat has (e.g., 5th). + * @param position The boat's position in race. + */ + public void setPosition(String position) { + this.positionInRace.set(position); + } + + /** + * Returns the position within the race the boat has (e.g., 5th). + * @return The boat's position in race. + */ + public String getPosition() { + return this.positionInRace.get(); + } + + /** * Returns the current position of the boat. * @return The current position of the boat. @@ -305,40 +327,6 @@ public class Boat { } - /** - * Returns the polars table for this boat. - * @return The polars table for this boat. - */ - public Polars getPolars() { - return polars; - } - - /** - * Sets the polars table for this boat. - * @param polars The new polars table for this boat. - */ - public void setPolars(Polars polars) { - this.polars = polars; - } - - - /** - * Returns the time since the boat changed its tack, in milliseconds. - * @return Time since the boat changed its tack, in milliseconds. - */ - public long getTimeSinceTackChange() { - return timeSinceTackChange; - } - - /** - * Sets the time since the boat changed it's tack, in milliseconds. - * @param timeSinceTackChange Time since the boat changed its tack, in milliseconds. - */ - public void setTimeSinceTackChange(long timeSinceTackChange) { - this.timeSinceTackChange = timeSinceTackChange; - } - - /** * Returns the time, in milliseconds, that has elapsed since the boat started the current leg. * @return The time, in milliseconds, that has elapsed since the boat started the current leg. @@ -374,81 +362,37 @@ public class Boat { /** - * Moves the boat meters forward in the direction that it is facing - * @param meters The number of meters to move forward. - * @param milliseconds The number of milliseconds to advance the boat's timers by. + * Returns the time at which the boat should reach the next mark. + * @return Time at which the boat should reach next mark. */ - public void moveForwards(double meters, long milliseconds) { - - - //Update the boat's time since last tack. - this.setTimeSinceTackChange(this.getTimeSinceTackChange() + milliseconds); - - //Update the time into the current leg. - this.setTimeElapsedInCurrentLeg(this.getTimeElapsedInCurrentLeg() + milliseconds); - - //Update the distance into the current leg. - this.setDistanceTravelledInLeg(this.getDistanceTravelledInLeg() + meters); - - //Updates the current position of the boat. - GPSCoordinate newPosition = GPSCoordinate.calculateNewPosition(this.getCurrentPosition(), meters, Azimuth.fromBearing(this.getBearing())); - this.setCurrentPosition(newPosition); - + public ZonedDateTime getEstimatedTimeAtNextMark() { + return estimatedTimeAtNextMark; } - /** - * Sets the boats speed and bearing to those in the given VMG. - * @param newVMG The new VMG to use for the boat - contains speed and bearing. + * Sets the time at which the boat should reach the next mark. + * @param estimatedTimeAtNextMark Time at which the boat should reach next mark. */ - public void setVMG(VMG newVMG) { - this.setBearing(newVMG.getBearing()); - this.setCurrentSpeed(newVMG.getSpeed()); - this.setTimeSinceTackChange(0); + public void setEstimatedTimeAtNextMark(ZonedDateTime estimatedTimeAtNextMark) { + this.estimatedTimeAtNextMark = estimatedTimeAtNextMark; } /** - * Calculates the number of nautical miles the boat will travel in a given time slice. - * E.g., in 53 milliseconds a boat may travel 0.0002 nautical miles. - * @param timeSlice The timeslice to use. - * @return The distance travelled, in nautical miles, over the given timeslice. + * Returns the time at which the boat reached the previous mark. + * @return The time at which the boat reached the previous mark. May be null. */ - public double calculateNauticalMilesTravelled(long timeSlice) { - - //The proportion of one hour the current timeslice is. - //This will be a low fractional number, so we need to go from long -> double. - double hourProportion = ((double) timeSlice) / Constants.OneHourMilliseconds; - - //Calculates the distance travelled, in nautical miles, in the current timeslice. - //distanceTravelledNM = speed (nm p hr) * time taken to update loop - double distanceTravelledNM = this.getCurrentSpeed() * hourProportion; - - return distanceTravelledNM; + @Nullable + public ZonedDateTime getTimeAtLastMark() { + return timeAtLastMark; } /** - * Calculates the number of meters the boat will travel in a given time slice. - * E.g., in 53 milliseconds a boat may travel 0.02 meters. - * @param timeSlice The timeslice to use. - * @return The distance travelled, in meters, over the given timeslice. + * Sets the time at which the boat reached the previous mark to a specified time. + * @param timeAtLastMark Time at which boat passed previous mark. */ - public double calculateMetersTravelled(long timeSlice) { - - //Calculate the distance travelled, in nautical miles. - double distanceTravelledNM = this.calculateNauticalMilesTravelled(timeSlice); - - //Convert to meters. - double distanceTravelledMeters = distanceTravelledNM * Constants.NMToMetersConversion; - - return distanceTravelledMeters; + public void setTimeAtLastMark(ZonedDateTime timeAtLastMark) { + this.timeAtLastMark = timeAtLastMark; } - public long getEstimatedTime() { - return estimatedTime; - } - - public void setEstimatedTime(long estimatedTime) { - this.estimatedTime = estimatedTime; - } } diff --git a/mock/src/main/java/seng302/Model/CompoundMark.java b/racevisionGame/src/main/java/shared/model/CompoundMark.java similarity index 75% rename from mock/src/main/java/seng302/Model/CompoundMark.java rename to racevisionGame/src/main/java/shared/model/CompoundMark.java index 546ed079..b9f45753 100644 --- a/mock/src/main/java/seng302/Model/CompoundMark.java +++ b/racevisionGame/src/main/java/shared/model/CompoundMark.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; /** @@ -6,6 +6,16 @@ package seng302.Model; */ public class CompoundMark { + /** + * The ID of the compound mark. + */ + private int id; + + /** + * The name of the compound mark. + */ + private String name; + /** * The first mark in the compound mark. */ @@ -24,9 +34,13 @@ public class CompoundMark { /** * Constructs a compound mark from a single mark. + * @param id the id of the compound mark + * @param name name of the compound mark * @param mark1 The individual mark that comprises this compound mark. */ - public CompoundMark(Mark mark1) { + public CompoundMark(int id, String name, Mark mark1) { + this.id = id; + this.name = name; this.mark1 = mark1; this.averageGPSCoordinate = calculateAverage(); @@ -35,10 +49,14 @@ public class CompoundMark { /** * Constructs a compound mark from a pair of marks. + * @param id the id of the compound mark + * @param name name of the compound mark * @param mark1 The first individual mark that comprises this compound mark. * @param mark2 The second individual mark that comprises this compound mark. */ - public CompoundMark(Mark mark1, Mark mark2) { + public CompoundMark(int id, String name, Mark mark1, Mark mark2) { + this.id = id; + this.name = name; this.mark1 = mark1; this.mark2 = mark2; this.averageGPSCoordinate = calculateAverage(); @@ -46,6 +64,22 @@ public class CompoundMark { } + /** + * Returns the ID of this compound mark. + * @return The ID of this compound mark. + */ + public int getId() { + return id; + } + + /** + * Returns the name of this compound mark + * @return The name of this compound mark. + */ + public String getName() { + return name; + } + /** * Returns the first mark of the compound mark. * @return The first mark of the compound mark. diff --git a/mock/src/main/java/seng302/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java similarity index 58% rename from mock/src/main/java/seng302/Constants.java rename to racevisionGame/src/main/java/shared/model/Constants.java index ab86b6d4..51666bf2 100644 --- a/mock/src/main/java/seng302/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -1,4 +1,4 @@ -package seng302; +package shared.model; /** * Constants that are used throughout the program @@ -27,6 +27,14 @@ public class Constants { public static final double KnotsToMMPerSecond = 514.444; + + /** + * The scale factor of the race. + * Frame periods are multiplied by this to get the amount of time a single frame represents. + * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. + */ + public static final int RaceTimeScale = 10; + /** * The race pre-start time, in milliseconds. 3 minutes. */ @@ -41,9 +49,22 @@ public class Constants { /** * The number of milliseconds in one hour. + *
+ * Multiply by this factor to convert milliseconds to hours. + *
+ * Divide by this factor to convert hours to milliseconds. */ public static long OneHourMilliseconds = 1 * 60 * 60 * 1000; + /** + * The number of seconds in one hour. + *
+ * Multiply by this factor to convert seconds to hours. + *
+ * Divide by this factor to convert hours to seconds. + */ + public static long OneHourSeconds = 1 * 60 * 60; + } diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java similarity index 97% rename from mock/src/main/java/seng302/Model/GPSCoordinate.java rename to racevisionGame/src/main/java/shared/model/GPSCoordinate.java index 2b80eb65..ee5eaaff 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/racevisionGame/src/main/java/shared/model/GPSCoordinate.java @@ -1,8 +1,7 @@ -package seng302.Model; +package shared.model; import javafx.util.Pair; import org.geotools.referencing.GeodeticCalculator; -import seng302.Constants; import java.awt.geom.Point2D; import java.util.ArrayList; @@ -300,6 +299,7 @@ public class GPSCoordinate { */ public static List getShrinkBoundary(List boundary) { + //TODO shrinkDistance should be a parameter. Also the code should be refactored to be smaller/simpler. double shrinkDistance = 50d; List shrunkBoundary = new ArrayList<>(boundary.size()); @@ -336,12 +336,12 @@ public class GPSCoordinate { //Get the bearing between two adjacent points. Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint); - //Calculate angle perpendicular to bearing. - Bearing perpendicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); + //Calculate angle perpindicular to bearing. + Bearing perpindicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); //Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge. - GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing)); - GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing)); + GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); //Add edge to list. shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated)); @@ -356,12 +356,12 @@ public class GPSCoordinate { //Get the bearing between two adjacent points. Bearing bearing = GPSCoordinate.calculateBearing(firstPoint, secondPoint); - //Calculate angle perpendicular to bearing. - Bearing perpendicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); + //Calculate angle perpindicular to bearing. + Bearing perpindicularBearing = Bearing.fromDegrees(bearing.degrees() + (90d * clockwiseScaleFactor)); //Translate both first and second point by 50m, using this bearing. These form our inwards shifted edge. - GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing)); - GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpendicularBearing)); + GPSCoordinate firstPointTranslated = GPSCoordinate.calculateNewPosition(firstPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); + GPSCoordinate secondPointTranslated = GPSCoordinate.calculateNewPosition(secondPoint, shrinkDistance, Azimuth.fromBearing(perpindicularBearing)); //Add edge to list. shrunkEdges.add(new Pair<>(firstPointTranslated, secondPointTranslated)); diff --git a/mock/src/main/java/seng302/Model/Leg.java b/racevisionGame/src/main/java/shared/model/Leg.java similarity index 99% rename from mock/src/main/java/seng302/Model/Leg.java rename to racevisionGame/src/main/java/shared/model/Leg.java index 10034efe..d9233f62 100644 --- a/mock/src/main/java/seng302/Model/Leg.java +++ b/racevisionGame/src/main/java/shared/model/Leg.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; /** diff --git a/mock/src/main/java/seng302/Model/Mark.java b/racevisionGame/src/main/java/shared/model/Mark.java similarity index 82% rename from mock/src/main/java/seng302/Model/Mark.java rename to racevisionGame/src/main/java/shared/model/Mark.java index e2a8c7e7..5781861a 100644 --- a/mock/src/main/java/seng302/Model/Mark.java +++ b/racevisionGame/src/main/java/shared/model/Mark.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; /** * Represents an individual mark. @@ -60,5 +60,11 @@ public class Mark { return position; } - + /** + * Sets the position of the mark to a specified GPSCoordinate. + * @param position The new GPSCoordinate to use. + */ + public void setPosition(GPSCoordinate position) { + this.position = position; + } } diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java new file mode 100644 index 00000000..415e9f77 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -0,0 +1,346 @@ +package shared.model; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import network.Messages.LatestMessages; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; + +import java.util.List; + + +/** + * Represents a yacht race. + * This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}. + * Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}. + */ +public abstract class Race implements Runnable { + + + /** + * The source of race related data. + */ + protected RaceDataSource raceDataSource; + + /** + * The source of boat related data. + */ + protected BoatDataSource boatDataSource; + + /** + * The source of regatta related data. + */ + protected RegattaDataSource regattaDataSource; + + /** + * The collection of latest race messages. + * Can be either read from or written to. + */ + protected LatestMessages latestMessages; + + /** + * The sequence number of the latest BoatLocation message sent or received. + */ + protected int boatLocationSequenceNumber = 1; + + /** + * The sequence number of the latest RaceStatus message sent or received. + */ + protected int raceStatusSequenceNumber = 1; + + + + /** + * A list of compound marks in the race. + */ + protected List compoundMarks; + + /** + * A list of legs in the race. + */ + protected List legs; + + /** + * A list of coordinates describing the boundary of the course. + */ + protected List boundary; + + + + /** + * The clock which tracks the race's start time, current time, and elapsed duration. + */ + protected RaceClock raceClock; + + + /** + * The race ID of the course. + */ + protected int raceId; + + /** + * The name of the regatta. + */ + protected String regattaName; + + /** + * The current status of the race. + */ + protected RaceStatusEnum raceStatusEnum; + + /** + * The type of race this is. + */ + protected RaceTypeEnum raceType; + + + /** + * The current wind direction bearing. + */ + protected Bearing windDirection; + + /** + * Wind speed (knots). + * Convert this to millimeters per second before passing to RaceStatus. + */ + protected double windSpeed; + + + /** + * The number of frames per second. + * We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, {@link #currentFps} is reset. + */ + private int currentFps = 0; + + /** + * The number of frames per second we generated over the last 1 second period. + */ + private IntegerProperty lastFps = new SimpleIntegerProperty(0); + + /** + * The time, in milliseconds, since we last reset our {@link #currentFps} counter. + */ + private long lastFpsResetTime; + + + + /** + * Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource. + * @param boatDataSource Data source for boat related data (yachts and marker boats). + * @param raceDataSource Data source for race related data (participating boats, legs, etc...). + * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...). + * @param latestMessages The collection of latest messages, which can be written to, or read from. + */ + public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages) { + + //Keep a reference to data sources. + this.raceDataSource = raceDataSource; + this.boatDataSource = boatDataSource; + this.regattaDataSource = regattaDataSource; + + this.latestMessages = latestMessages; + + + //Marks. + this.compoundMarks = raceDataSource.getCompoundMarks(); + + //Boundaries. + this.boundary = raceDataSource.getBoundary(); + + + //Legs. + this.useLegsList(raceDataSource.getLegs()); + + + //Race ID. + this.raceId = raceDataSource.getRaceId(); + + //Regatta name. + this.regattaName = regattaDataSource.getRegattaName(); + + //Race clock. + this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime()); + + //Race status. + this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); + //Race type. + this.raceType = raceDataSource.getRaceType(); + + //Wind speed. + this.windSpeed = 0; + //Wind direction. + this.windDirection = Bearing.fromDegrees(0); + + + } + + + + /** + * Initialise the boats in the race. + * This sets their starting positions and current legs. + */ + protected abstract void initialiseBoats(); + + + /** + * Updates the race to use a new list of legs, and adds a dummy "Finish" leg at the end. + * @param legs The new list of legs to use. + */ + protected void useLegsList(List legs) { + //We add a "dummy" leg at the end of the race. + this.legs = legs; + this.legs.add(new Leg("Finish", this.legs.size())); + } + + /** + * Determines whether or not a specific leg is the last leg in the race. + * @param leg The leg to check. + * @return Returns true if it is the last, false otherwise. + */ + protected boolean isLastLeg(Leg leg) { + + //Get the last leg. + Leg lastLeg = this.legs.get(this.legs.size() - 1); + + //Check its ID. + int lastLegID = lastLeg.getLegNumber(); + + //Get the specified leg's ID. + int legID = leg.getLegNumber(); + + + //Check if they are the same. + return legID == lastLegID; + } + + + + + + /** + * Returns the current race status. + * @return The current race status. + */ + public RaceStatusEnum getRaceStatusEnum() { + return raceStatusEnum; + } + + /** + * Sets the current race status. + * @param raceStatusEnum The new status of the race. + */ + protected void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + this.raceStatusEnum = raceStatusEnum; + } + + + /** + * Returns the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { + return raceType; + } + + /** + * Returns the name of the regatta. + * @return The name of the regatta. + */ + public String getRegattaName() { + return regattaName; + } + + /** + * Returns the wind bearing. + * @return The wind bearing. + */ + public Bearing getWindDirection() { + return windDirection; + } + + /** + * Returns the wind speed. + * Measured in knots. + * @return The wind speed. + */ + public double getWindSpeed() { + return windSpeed; + } + + /** + * Returns the RaceClock for this race. + * This is used to track the start time, current time, and elapsed duration of the race. + * @return The RaceClock for the race. + */ + public RaceClock getRaceClock() { + return raceClock; + } + + + /** + * Returns the RaceDataSource used for the race. + * @return The RaceDataSource used for the race. + */ + public RaceDataSource getRaceDataSource() { + return raceDataSource; + } + + /** + * Returns the number of legs in the race. + * @return The number of legs in the race. + */ + public int getLegCount() { + //We minus one, as we have added an extra "dummy" leg. + return legs.size() - 1; + } + + + /** + * Returns the race boundary. + * @return The race boundary. + */ + public List getBoundary() { + return boundary; + } + + /** + * Returns the number of frames generated per second. + * @return Frames per second. + */ + public int getFps() { + return lastFps.getValue(); + } + + /** + * Returns the fps property. + * @return The fps property. + */ + public IntegerProperty fpsProperty() { + return lastFps; + } + + + /** + * Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer. + * @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}. + */ + protected void incrementFps(long timePeriod) { + //Increment. + this.currentFps++; + + //Add period to timer. + this.lastFpsResetTime += timePeriod; + + //If we have reached 1 second period, snapshot the framerate and reset. + if (this.lastFpsResetTime > 1000) { + this.lastFps.set(this.currentFps); + + this.currentFps = 0; + this.lastFpsResetTime = 0; + } + } +} diff --git a/racevisionGame/src/main/java/shared/model/RaceClock.java b/racevisionGame/src/main/java/shared/model/RaceClock.java new file mode 100644 index 00000000..4c6532a9 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/RaceClock.java @@ -0,0 +1,305 @@ +package shared.model; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.jetbrains.annotations.Nullable; +import visualiser.model.ResizableRaceCanvas; + +import java.time.Duration; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * This class is used to implement a clock which keeps track of and + * displays times relevant to a race. This is displayed on the + * {@link ResizableRaceCanvas} via the + * {@link visualiser.Controllers.RaceController} and the + * {@link visualiser.Controllers.StartController}. + */ +public class RaceClock { + + /** + * The time zone of the race. + */ + private final ZoneId zoneId; + + /** + * The start time of the race. + */ + private ZonedDateTime startingTime; + + /** + * The current time of the race. + */ + private final StringProperty startingTimeProperty = new SimpleStringProperty(); + + + /** + * The current time of the race. + */ + @Nullable + private ZonedDateTime currentTime; + + /** + * The current time of the race. + */ + private final StringProperty currentTimeProperty = new SimpleStringProperty(); + + /** + * The time until the race starts, or elapsed time in the race after it has started. + */ + private StringProperty durationProperty = new SimpleStringProperty(); + + + //Format strings. + /** + * Format string used for starting time. + */ + private String startingTimeFormat = "'Starting time:' HH:mm dd/MM/YYYY"; + + /** + * Format string used for current time. + */ + private String currentTimeFormat = "'Current time:' HH:mm dd/MM/YYYY"; + + /** + * Format string used for duration before it has started. + */ + private String durationBeforeStartFormat = "Starting in: %02d:%02d:%02d"; + /** + * Format string used for duration once the race has started. + */ + private String durationAfterStartFormat = "Time: %02d:%02d:%02d"; + + + + + /** + * Constructs a RaceClock using a specified starting ZonedDateTime. + * @param startingTime The ZonedDateTime that the race starts at. + */ + public RaceClock(ZonedDateTime startingTime) { + this.zoneId = startingTime.getZone(); + + //Set start time. + setStartingTime(startingTime); + + } + + + + /** + * Sets time to given UTC time in seconds from Unix epoch, preserving timezone. + * @param time UTC time. + */ + public void setUTCTime(long time) { + Date utcTime = new Date(time); + setCurrentTime(utcTime.toInstant().atZone(this.zoneId)); + } + + + + /** + * Get ZonedDateTime corresponding to local time zone and given UTC time. + * @param time time in mills + * @return local date time + */ + public ZonedDateTime getLocalTime(long time) { + Date utcTime = new Date(time); + return utcTime.toInstant().atZone(this.zoneId); + } + + + /** + * Returns the starting time of the race. + * @return The starting time of the race. + */ + public ZonedDateTime getStartingTime() { + return startingTime; + } + + /** + * Returns the race start time, expressed as the number of milliseconds since the unix epoch. + * @return Start time expressed as milliseconds since unix epoch. + */ + public long getStartingTimeMilli() { + return startingTime.toInstant().toEpochMilli(); + } + + /** + * Sets the starting time of the race. + * @param startingTime The starting time of the race. + */ + public void setStartingTime(ZonedDateTime startingTime) { + this.startingTime = startingTime; + + //Convert time into string. + String startingTimeString = DateTimeFormatter.ofPattern(this.startingTimeFormat).format(startingTime); + + //Use it. + setStartingTimeString(startingTimeString); + } + + /** + * Returns the starting time of the race, as a string. + * @return The starting time of the race, as a string. + */ + public String getStartingTimeString() { + return startingTimeProperty.get(); + } + + /** + * Sets the starting time string of the race. + * This should only be called by {@link #setStartingTime(ZonedDateTime)}. + * @param startingTime The new value for the starting time string. + */ + private void setStartingTimeString(String startingTime) { + this.startingTimeProperty.setValue(startingTime); + } + + /** + * Returns the starting time property. + * @return The starting time property. + */ + public StringProperty startingTimeProperty() { + return startingTimeProperty; + } + + + + /** + * Returns the race duration, in milliseconds. + * A negative value means that the race has not started. + * @return Race duration in milliseconds. + */ + public long getDurationMilli() { + return getCurrentTimeMilli() - getStartingTimeMilli(); + } + + + /** + * Returns the race duration, as a string. + * @return Duration as a string. + */ + public String getDurationString() { + return durationProperty.get(); + } + + /** + * Sets the duration time string of the race. + * @param duration The new value for the duration time string. + */ + private void setDurationString(String duration) { + this.durationProperty.setValue(duration); + } + + /** + * Returns the duration property. + * @return The duration property. + */ + public StringProperty durationProperty() { + return durationProperty; + } + + + + + /** + * Returns the current time of the race. + * @return The current time of the race. + */ + @Nullable + public ZonedDateTime getCurrentTime() { + return currentTime; + } + + /** + * Returns the race current time, expressed as the number of milliseconds since the unix epoch. + * @return Current time expressed as milliseconds since unix epoch. + */ + public long getCurrentTimeMilli() { + return currentTime.toInstant().toEpochMilli(); + } + + /** + * Sets the current time of the race. + * @param currentTime The current time of the race. + */ + private void setCurrentTime(ZonedDateTime currentTime) { + this.currentTime = currentTime; + + //Convert time into string. + String currentTimeString = DateTimeFormatter.ofPattern(this.currentTimeFormat).format(currentTime); + + //Use it. + setCurrentTimeString(currentTimeString); + + + //Update the duration string. + updateDurationString(); + + } + + /** + * Updates the duration string based on the start time and current time. + * This requires {@link #currentTime} to be non-null. + */ + private void updateDurationString() { + //Calculates the duration in seconds. + long seconds = Duration.between(startingTime.toLocalDateTime(), currentTime.toLocalDateTime()).getSeconds(); + + //Check if the race has already started or not. This determines the format string used. + String formatString; + if (seconds < 0) { + //Race hasn't started. + formatString = this.durationBeforeStartFormat; + //The seconds value is negative, so we make it positive. + seconds = seconds * -1; + } else { + //Race has started. + formatString = this.durationAfterStartFormat; + } + + //Format the seconds value. + //Hours : minutes : seconds. + String formattedDuration = String.format(formatString, seconds / 3600, (seconds % 3600) / 60, seconds % 60); + + //Use it. + setDurationString(formattedDuration); + } + + /** + * Returns the current time of the race, as a string. + * @return The current time of the race, as a string. + */ + public String getCurrentTimeString() { + return currentTimeProperty.get(); + } + + /** + * Sets the current time string of the race. + * @param currentTime The new value for the current time string. + */ + private void setCurrentTimeString(String currentTime) { + this.currentTimeProperty.setValue(currentTime); + } + + /** + * Returns the current time property. + * @return The current time property. + */ + public StringProperty currentTimeProperty() { + return currentTimeProperty; + } + + + /** + * Returns the time zone of the race, as a string. + * @return The race time zone. + */ + public String getTimeZone() { + return zoneId.toString(); + } +} diff --git a/visualiser/src/main/java/seng302/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java similarity index 92% rename from visualiser/src/main/java/seng302/Controllers/ConnectionController.java rename to racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java index 40437318..7dacbd5e 100644 --- a/visualiser/src/main/java/seng302/Controllers/ConnectionController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java @@ -1,4 +1,4 @@ -package seng302.Controllers; +package visualiser.Controllers; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -8,7 +8,7 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.layout.AnchorPane; -import seng302.RaceConnection; +import visualiser.model.RaceConnection; import java.io.IOException; import java.net.Socket; @@ -22,7 +22,7 @@ public class ConnectionController extends Controller { @FXML private AnchorPane connectionWrapper; @FXML - private TableView connectionTable; + private TableView connectionTable; @FXML private TableColumn hostnameColumn; @FXML @@ -73,7 +73,7 @@ public class ConnectionController extends Controller { */ public void connectSocket() { try{ - RaceConnection connection = (RaceConnection)connectionTable.getSelectionModel().getSelectedItem(); + RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem(); Socket socket = new Socket(connection.getHostname(), connection.getPort()); connectionWrapper.setVisible(false); parent.enterLobby(socket); diff --git a/visualiser/src/main/java/seng302/Controllers/Controller.java b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java similarity index 95% rename from visualiser/src/main/java/seng302/Controllers/Controller.java rename to racevisionGame/src/main/java/visualiser/Controllers/Controller.java index ca6b922e..220b7816 100644 --- a/visualiser/src/main/java/seng302/Controllers/Controller.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java @@ -1,4 +1,4 @@ -package seng302.Controllers; +package visualiser.Controllers; import javafx.fxml.Initializable; diff --git a/visualiser/src/main/java/seng302/Controllers/FinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java similarity index 60% rename from visualiser/src/main/java/seng302/Controllers/FinishController.java rename to racevisionGame/src/main/java/visualiser/Controllers/FinishController.java index a5c32eee..b6f7dff8 100644 --- a/visualiser/src/main/java/seng302/Controllers/FinishController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java @@ -1,4 +1,4 @@ -package seng302.Controllers; +package visualiser.Controllers; import javafx.collections.ObservableList; @@ -7,14 +7,14 @@ import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.AnchorPane; -import seng302.Model.Boat; +import visualiser.model.VisualiserBoat; import java.net.URL; import java.util.ResourceBundle; /** - * Finish Screen for when the race finishs. + * Finish Screen for when the race finishes. */ public class FinishController extends Controller { @@ -22,41 +22,67 @@ public class FinishController extends Controller { AnchorPane finishWrapper; @FXML - TableView boatInfoTable; + TableView boatInfoTable; @FXML - TableColumn boatRankColumn; + TableColumn boatRankColumn; @FXML - TableColumn boatNameColumn; + TableColumn boatNameColumn; @FXML Label raceWinnerLabel; + + /** + * The boats to display on the table. + */ + private ObservableList boats; + + + /** + * Ctor. + */ + public FinishController() { + } + + + @Override + public void initialize(URL location, ResourceBundle resources){ + } + + + /** * Sets up the finish table * @param boats Boats to display */ - private void setFinishTable(ObservableList boats){ + private void setFinishTable(ObservableList boats) { + this.boats = boats; + + //Set contents. boatInfoTable.setItems(boats); - boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName()); + + //Name. + boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + + //Rank/position. boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty()); + + //Winner label. raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue()); raceWinnerLabel.setWrapText(true); } - @Override - public void initialize(URL location, ResourceBundle resources){ - } /** * Display the table * @param boats boats to display on the table. */ - public void enterFinish(ObservableList boats){ + public void enterFinish(ObservableList boats){ finishWrapper.setVisible(true); setFinishTable(boats); } diff --git a/visualiser/src/main/java/seng302/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java similarity index 63% rename from visualiser/src/main/java/seng302/Controllers/MainController.java rename to racevisionGame/src/main/java/visualiser/Controllers/MainController.java index 9937ae2c..262c3c8a 100644 --- a/visualiser/src/main/java/seng302/Controllers/MainController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java @@ -1,11 +1,11 @@ -package seng302.Controllers; +package visualiser.Controllers; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.layout.AnchorPane; -import seng302.Model.Boat; -import seng302.Model.RaceClock; -import seng302.VisualiserInput; +import visualiser.app.VisualiserInput; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRace; import java.net.Socket; import java.net.URL; @@ -15,20 +15,46 @@ import java.util.ResourceBundle; * Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage. */ public class MainController extends Controller { + @FXML private StartController startController; @FXML private RaceController raceController; @FXML private ConnectionController connectionController; @FXML private FinishController finishController; - public void beginRace(VisualiserInput visualiserInput, RaceClock raceClock) { - raceController.startRace(visualiserInput, raceClock); + + + /** + * Ctor. + */ + public MainController() { + } + + + + /** + * Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race). + * @param visualiserInput The object used to read packets from the race server. + * @param visualiserRace The object modelling the race. + */ + public void beginRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) { + raceController.startRace(visualiserInput, visualiserRace); } + /** + * Transitions from the server selection screen to the pre-race lobby for a given server. + * @param socket The server to read data from. + */ public void enterLobby(Socket socket) { startController.enterLobby(socket); } - public void enterFinish(ObservableList boats) { finishController.enterFinish(boats); } + /** + * Transitions from the {@link RaceController} screen to the {@link FinishController} screen. + * @param boats The boats to display on the finish screen. + */ + public void enterFinish(ObservableList boats) { + finishController.enterFinish(boats); + } /** * Main Controller for the applications will house the menu and the displayed pane. @@ -38,10 +64,12 @@ public class MainController extends Controller { */ @Override public void initialize(URL location, ResourceBundle resources) { + startController.setParent(this); raceController.setParent(this); connectionController.setParent(this); finishController.setParent(this); + AnchorPane.setTopAnchor(startController.startWrapper(), 0.0); AnchorPane.setBottomAnchor(startController.startWrapper(), 0.0); AnchorPane.setLeftAnchor(startController.startWrapper(), 0.0); diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java new file mode 100644 index 00000000..ffcfcb54 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -0,0 +1,383 @@ +package visualiser.Controllers; + + +import javafx.animation.AnimationTimer; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.chart.LineChart; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.util.Callback; +import network.Messages.Enums.RaceStatusEnum; +import shared.model.Leg; +import visualiser.app.VisualiserInput; +import visualiser.model.*; + +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ResourceBundle; + +/** + * Controller used to display a running race. + */ +public class RaceController extends Controller { + + /** + * The object used to read packets from the connected server. + */ + private VisualiserInput visualiserInput; + + /** + * The race object which describes the currently occurring race. + */ + private VisualiserRace visualiserRace; + + /** + * An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats. + */ + private ObservableList tableBoatList; + + /** + * The canvas that draws the race. + */ + private ResizableRaceCanvas raceCanvas; + + /** + * The sparkline graph. + */ + private Sparkline sparkline; + + + @FXML private GridPane canvasBase; + @FXML private Pane arrow; + @FXML private SplitPane race; + @FXML private StackPane arrowPane; + @FXML private Label timer; + @FXML private Label FPS; + @FXML private Label timeZone; + @FXML private CheckBox showFPS; + @FXML private TableView boatInfoTable; + @FXML private TableColumn boatPlacingColumn; + @FXML private TableColumn boatTeamColumn; + @FXML private TableColumn boatMarkColumn; + @FXML private TableColumn boatSpeedColumn; + @FXML private LineChart sparklineChart; + @FXML private AnchorPane annotationPane; + + + + /** + * Ctor. + */ + public RaceController() { + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + } + + + /** + * Initialises the various UI components to listen to the {@link #visualiserRace}. + */ + private void initialiseRace() { + //Fps display. + initialiseFps(this.visualiserRace); + + //Need to add the included arrow pane to the arrowPane container. + initialiseArrow(); + + //Information table. + initialiseInfoTable(this.visualiserRace); + + //Sparkline. + initialiseSparkline(this.visualiserRace); + + //Race canvas. + initialiseRaceCanvas(this.visualiserRace); + + //Race timezone label. + initialiseRaceTimezoneLabel(this.visualiserRace); + + //Race clock. + initialiseRaceClock(this.visualiserRace); + + + //Start the race animation timer. + raceTimer(); + } + + + + /** + * Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property. + * @param visualiserRace The race to connect the fps label to. + */ + private void initialiseFps(VisualiserRace visualiserRace) { + + //On/off toggle. + initialiseFpsToggle(); + + //Label value. + initialiseFpsLabel(visualiserRace); + } + + /** + * Initialises a listener for the fps toggle. + */ + private void initialiseFpsToggle() { + + showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { + if (showFPS.isSelected()) { + FPS.setVisible(true); + + } else { + FPS.setVisible(false); + + } + }); + + } + + /** + * Initialises the fps label to update when the race fps changes. + * @param visualiserRace The race to monitor the frame rate of. + */ + private void initialiseFpsLabel(VisualiserRace visualiserRace) { + + visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString())); + }); + + } + + + + /** + * Initialises the information table view to listen to a given race. + * @param race Race to listen to. + */ + public void initialiseInfoTable(VisualiserRace race) { + + //Copy list of boats. + this.tableBoatList = FXCollections.observableArrayList(race.getBoats()); + + + //Set up table. + boatInfoTable.setItems(this.tableBoatList); + + + //Set up each column. + + //Name. + boatTeamColumn.setCellValueFactory( + cellData -> cellData.getValue().nameProperty() ); + + //Speed. + boatSpeedColumn.setCellValueFactory( + cellData -> cellData.getValue().currentSpeedProperty() ); + + //Kind of ugly, but allows for formatting an observed speed. + boatSpeedColumn.setCellFactory( + //Callback object. + new Callback, TableCell>() { + + //Callback function. + @Override + public TableCell call(TableColumn param) { + //We return a table cell that populates itself with a Number, and formats it. + return new TableCell(){ + + //Function to update the cell text. + @Override + protected void updateItem(Number item, boolean empty) { + + if (item != null) { + super.updateItem(item, empty); + + setText(String.format("%.2fkn", item.doubleValue())); + } + } + }; + + } + + } ); + + + //Last mark. + boatMarkColumn.setCellValueFactory( + cellData -> cellData.getValue().legProperty() ); + + //Kind of ugly, but allows for turning an observed Leg into a string. + boatMarkColumn.setCellFactory( + //Callback object. + new Callback, TableCell>() { + + //Callback function. + @Override + public TableCell call(TableColumn param) { + //We return a table cell that populates itself with a Leg's name. + return new TableCell(){ + + //Function to update the cell text. + @Override + protected void updateItem(Leg item, boolean empty) { + + if (item != null) { + super.updateItem(item, empty); + + setText(item.getName()); + } + } + }; + + } + + } ); + + //Current place within race. + boatPlacingColumn.setCellValueFactory( + cellData -> cellData.getValue().positionProperty() ); + + } + + + /** + * Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}. + * @param race The race to listen to. + */ + private void initialiseSparkline(VisualiserRace race) { + //The race.getBoats() we are passing in is sorted by position in race inside the race class. + this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart); + } + + + /** + * Initialises the {@link ResizableRaceCanvas}, provides the race to read data from. + * @param race Race to read data from. + */ + private void initialiseRaceCanvas(VisualiserRace race) { + + //Create canvas. + raceCanvas = new ResizableRaceCanvas(race, arrow.getChildren().get(0)); + + //Set properties. + raceCanvas.setMouseTransparent(true); + raceCanvas.widthProperty().bind(canvasBase.widthProperty()); + raceCanvas.heightProperty().bind(canvasBase.heightProperty()); + + //Draw it and show it. + raceCanvas.draw(); + raceCanvas.setVisible(true); + + //Add to scene. + canvasBase.getChildren().add(0, raceCanvas); + + + } + + + /** + * Intialises the race time zone label with the race's time zone. + * @param race The race to get time zone from. + */ + private void initialiseRaceTimezoneLabel(VisualiserRace race) { + timeZone.setText(race.getRaceClock().getTimeZone()); + } + + /** + * Initialises the race clock to listen to the specified race. + * @param race The race to listen to. + */ + private void initialiseRaceClock(VisualiserRace race) { + + //RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update. + race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); + } + + + + /** + * Displays a specified race. + * @param visualiserInput Object used to read packets from server. + * @param visualiserRace Object modelling the race. + */ + public void startRace(VisualiserInput visualiserInput, VisualiserRace visualiserRace) { + + this.visualiserInput = visualiserInput; + this.visualiserRace = visualiserRace; + + initialiseRace(); + + //Display this controller. + race.setVisible(true); + + + // set up annotation displays + new Annotations(annotationPane, raceCanvas); + } + + /** + * Transition from the race view to the finish view. + * @param boats boats there are in the race. + */ + public void finishRace(ObservableList boats){ + race.setVisible(false); + parent.enterFinish(boats); + } + + + /** + * Adds the included arrow pane (see arrow.fxml) to the arrowPane (see race.fxml). + */ + private void initialiseArrow() { + arrowPane.getChildren().add(arrow); + } + + + + /** + * Timer which monitors the race. + */ + private void raceTimer() { + new AnimationTimer() { + @Override + public void handle(long arg0) { + + //Get the current race status. + RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum(); + + + //If the race has finished, go to finish view. + if (raceStatus == RaceStatusEnum.FINISHED) { + //Stop this timer. + stop(); + + //Hide this, and display the finish controller. + finishRace(visualiserRace.getBoats()); + + } else { + //Otherwise, render the canvas. + raceCanvas.drawRace(); + + + //Sort the tableview. Doesn't automatically work for all columns. + boatInfoTable.sort(); + + } + + } + }.start(); + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java new file mode 100644 index 00000000..2735023e --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java @@ -0,0 +1,321 @@ +package visualiser.Controllers; + +import javafx.animation.AnimationTimer; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.LatestMessages; +import shared.dataInput.*; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.InvalidRaceDataException; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; +import visualiser.app.VisualiserInput; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRace; + +import java.io.IOException; +import java.net.Socket; +import java.net.URL; +import java.util.*; + +/** + * Controller to for waiting for the race to start. + */ +public class StartController extends Controller implements Observer { + + @FXML private GridPane start; + @FXML private AnchorPane startWrapper; + + /** + * The name of the race/regatta. + */ + @FXML private Label raceTitleLabel; + + /** + * The time the race starts at. + */ + @FXML private Label raceStartLabel; + + /** + * The current time at the race location. + */ + @FXML private Label timeZoneTime; + + /** + * Time until the race starts. + */ + @FXML private Label timer; + + @FXML private TableView boatNameTable; + @FXML private TableColumn boatNameColumn; + @FXML private TableColumn boatCodeColumn; + + /** + * The status of the race. + */ + @FXML private Label raceStatusLabel; + + + + /** + * The object used to read packets from the connected server. + */ + private VisualiserInput visualiserInput; + + /** + * The race object which describes the currently occurring race. + */ + private VisualiserRace visualiserRace; + + /** + * An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor. + */ + List colors = new ArrayList<>(Arrays.asList( + Color.BLUEVIOLET, + Color.BLACK, + Color.RED, + Color.ORANGE, + Color.DARKOLIVEGREEN, + Color.LIMEGREEN, + Color.PURPLE, + Color.DARKGRAY, + Color.YELLOW + )); + + + + /** + * Ctor. + */ + public StartController() { + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + } + + + /** + * Starts the race. + * Called once we have received all XML files from the server. + * @param latestMessages The set of latest race messages to use for race. + * @throws XMLReaderException Thrown if XML file cannot be parsed. + * @throws InvalidRaceDataException Thrown if XML file cannot be parsed. + * @throws InvalidBoatDataException Thrown if XML file cannot be parsed. + * @throws InvalidRegattaDataException Thrown if XML file cannot be parsed. + */ + private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException { + + //Create data sources from latest messages for the race. + RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage(), XMLFileType.Contents); + BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage(), XMLFileType.Contents); + RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage(), XMLFileType.Contents); + + //Create race. + this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors); + new Thread(this.visualiserRace).start(); + + + //Initialise the boat table. + initialiseBoatTable(this.visualiserRace); + + //Initialise the race name. + initialiseRaceName(this.visualiserRace); + + //Initialises the race clock. + initialiseRaceClock(this.visualiserRace); + + //Starts the race countdown timer. + countdownTimer(); + } + + + + + public AnchorPane startWrapper(){ + return startWrapper; + } + + + /** + * Initialises the boat table that is to be shown on the pane. + * @param visualiserRace The race to get data from. + */ + private void initialiseBoatTable(VisualiserRace visualiserRace) { + + //Get the boats. + ObservableList boats = visualiserRace.getBoats(); + + //Populate table. + boatNameTable.setItems(boats); + boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty()); + boatCodeColumn.setCellValueFactory(cellData -> cellData.getValue().countryProperty()); + } + + /** + * Initialises the race name which is shown on the pane. + * @param visualiserRace The race to get data from. + */ + private void initialiseRaceName(VisualiserRace visualiserRace) { + + raceTitleLabel.setText(visualiserRace.getRegattaName()); + + } + + /** + * Initialises the race clock/timer labels for the start time, current time, and remaining time. + * @param visualiserRace The race to get data from. + */ + private void initialiseRaceClock(VisualiserRace visualiserRace) { + + //Start time. + initialiseRaceClockStartTime(visualiserRace); + + //Current time. + initialiseRaceClockCurrentTime(visualiserRace); + + //Remaining time. + initialiseRaceClockDuration(visualiserRace); + + } + + + /** + * Initialises the race current time label. + * @param visualiserRace The race to get data from. + */ + private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) { + + raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString()); + + visualiserRace.getRaceClock().startingTimeProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + raceStartLabel.setText(newValue); + }); + }); + + } + + + /** + * Initialises the race current time label. + * @param visualiserRace The race to get data from. + */ + private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) { + + visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timeZoneTime.setText(newValue); + }); + }); + + } + + /** + * Initialises the race duration label. + * @param visualiserRace The race to get data from. + */ + private void initialiseRaceClockDuration(VisualiserRace visualiserRace) { + + visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + timer.setText(newValue); + }); + }); + + } + + /** + * Countdown timer until race starts. + */ + private void countdownTimer() { + new AnimationTimer() { + @Override + public void handle(long arg0) { + + //TODO instead of having an AnimationTimer checking the race status, we could provide a Property, and connect a listener to that. + //Get the current race status. + RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum(); + + //Display it. + raceStatusLabel.setText("Race Status: " + raceStatus.name()); + + //If the race has reached the preparatory phase, or has started... + if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { + //Stop this timer. + stop(); + + //Hide this, and display the race controller. + startWrapper.setVisible(false); + start.setVisible(false); + + parent.beginRace(visualiserInput, visualiserRace); + } + } + }.start(); + } + + + + /** + * Function to handle changes in objects we observe. + * We observe LatestMessages. + * @param o The observed object. + * @param arg The {@link Observable#notifyObservers(Object)} parameter. + */ + @Override + public void update(Observable o, Object arg) { + + //Check that we actually have LatestMessages. + if (o instanceof LatestMessages) { + LatestMessages latestMessages = (LatestMessages) o; + + //If we've received all of the xml files, start the race. Only start it if it hasn't already been created. + if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) { + + //Need to handle it in the javafx thread. + Platform.runLater(() -> { + try { + this.startRace(latestMessages); + + } catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) { + //We currently don't handle this in meaningful way, as it should never occur. + //If we reach this point it means that malformed XML files were sent. + e.printStackTrace(); + + } + }); + } + } + + } + + /** + * Show starting information for a race given a socket. + * @param socket network source of information + */ + public void enterLobby(Socket socket) { + startWrapper.setVisible(true); + try { + //Begin reading packets from the socket/server. + this.visualiserInput = new VisualiserInput(socket); + //Store a reference to latestMessages so that we can observe it. + LatestMessages latestMessages = this.visualiserInput.getLatestMessages(); + latestMessages.addObserver(this); + new Thread(this.visualiserInput).start(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/visualiser/src/main/java/seng302/App.java b/racevisionGame/src/main/java/visualiser/app/App.java similarity index 94% rename from visualiser/src/main/java/seng302/App.java rename to racevisionGame/src/main/java/visualiser/app/App.java index 7bc8a18f..4bf544b2 100644 --- a/visualiser/src/main/java/seng302/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -1,4 +1,4 @@ -package seng302; +package visualiser.app; import javafx.application.Application; import javafx.application.Platform; @@ -29,7 +29,7 @@ public class App extends Application { System.exit(0); } }); - FXMLLoader loader = new FXMLLoader(getClass().getResource("/scenes/main.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/main.fxml")); Parent root = loader.load(); Scene scene = new Scene(root, 1200, 800); diff --git a/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java new file mode 100644 index 00000000..d86e0224 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/app/VisualiserInput.java @@ -0,0 +1,377 @@ +package visualiser.app; +import javafx.application.Platform; +import network.BinaryMessageDecoder; +import network.Exceptions.InvalidMessageException; +import network.Messages.*; +import org.xml.sax.SAXException; +import shared.dataInput.BoatXMLReader; +import shared.dataInput.RaceXMLReader; +import shared.dataInput.RegattaXMLReader; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.InvalidRaceDataException; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.DataInputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static network.Utils.ByteConverter.bytesToShort; + +/** + * TCP client which receives packets/messages from a race data source + * (e.g., mock source, official source), and exposes them to any observers. + */ +public class VisualiserInput implements Runnable { + + /** + * Timestamp of the last heartbeat. + */ + private long lastHeartbeatTime = -1; + /** + * Sequence number of the last heartbeat. + */ + private long lastHeartbeatSequenceNum = -1; + + + /** + * The socket that we have connected to. + */ + private Socket connectionSocket; + + + /** + * InputStream (from the socket). + */ + private DataInputStream inStream; + + + /** + * An object containing the set of latest messages to write to. + * Every server frame, VisualiserInput reads messages from its inputStream, and write them to this. + */ + private LatestMessages latestMessages; + + + + /** + * Ctor. + * @param socket Socket from which we will receive race data. + * @throws IOException If there is something wrong with the socket's input stream. + */ + public VisualiserInput(Socket socket) throws IOException { + + this.connectionSocket = socket; + + //We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled. + this.inStream = new DataInputStream(connectionSocket.getInputStream()); + + this.latestMessages = new LatestMessages(); + + + this.lastHeartbeatTime = System.currentTimeMillis(); + } + + + /** + * Returns the LatestMessages object, which can be queried for any received race related messages. + * @return The LatestMessages object. + */ + public LatestMessages getLatestMessages() { + return latestMessages; + } + + /** + * Calculates the time since last heartbeat, in milliseconds. + * @return Time since last heartbeat, in milliseconds.. + */ + private double timeSinceHeartbeat() { + long now = System.currentTimeMillis(); + return (now - lastHeartbeatTime); + } + + + + /** + * Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead. + * @return Encoded binary message bytes. + * @throws IOException Thrown when an error occurs while reading from the socket. + */ + private byte[] getNextMessageBytes() throws IOException { + inStream.mark(0); + short CRCLength = 4; + short headerLength = 15; + + //Read the header of the next message. + byte[] headerBytes = new byte[headerLength]; + inStream.readFully(headerBytes); + + //Read the message body length. + byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength); + short messageBodyLength = bytesToShort(messageBodyLengthBytes); + + //Read the message body. + byte[] messageBodyBytes = new byte[messageBodyLength]; + inStream.readFully(messageBodyBytes); + + //Read the message CRC. + byte[] messageCRCBytes = new byte[CRCLength]; + inStream.readFully(messageCRCBytes); + + //Put the head + body + crc into one large array. + ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length); + messageBytes.put(headerBytes); + messageBytes.put(messageBodyBytes); + messageBytes.put(messageCRCBytes); + + return messageBytes.array(); + } + + /** + * Reads and returns the next message object from the socket. + * @return The message object. Use instanceof for concrete type. + * @throws IOException Thrown when an error occurs while reading from the socket. + * @throws InvalidMessageException Thrown when the message is invalid in some way. + */ + private AC35Data getNextMessage() throws IOException, InvalidMessageException + { + //Get the next message from the socket as a block of bytes. + byte[] messageBytes = this.getNextMessageBytes(); + + //Decode the binary message into an appropriate message object. + BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes); + + return decoder.decode(); + + } + + /** + * Main loop which reads messages from the socket, and exposes them. + */ + public void run(){ + boolean receiverLoop = true; + //receiver loop that gets the input + while (receiverLoop) { + + //If no heartbeat has been received in more the heartbeat period + //then the connection will need to be restarted. + //System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE + long heartBeatPeriod = 10 * 1000; + if (timeSinceHeartbeat() > heartBeatPeriod) { + System.out.println("Connection has stopped, trying to reconnect."); + + //Attempt to reconnect the socket. + try {//This attempt doesn't really work. Under what circumstances would + this.connectionSocket = new Socket(this.connectionSocket.getInetAddress(), this.connectionSocket.getPort()); + //this.connectionSocket.connect(this.connectionSocket.getRemoteSocketAddress()); + //Reset the heartbeat timer. + this.lastHeartbeatTime = System.currentTimeMillis(); + } + catch (IOException e) { + System.err.println("Unable to reconnect."); + + //Wait 500ms. Ugly hack, should refactor. + long waitPeriod = 500; + long waitTimeStart = System.currentTimeMillis() + waitPeriod; + + while (System.currentTimeMillis() < waitTimeStart){ + //Nothing. Busyloop. + } + + //Swallow the exception. + continue; + } + + } + + //Reads the next message. + AC35Data message; + try { + message = this.getNextMessage(); + } + catch (InvalidMessageException | IOException e) { + //Prints exception to stderr, and iterate loop (that is, read the next message). + System.err.println("Unable to read message: " + e.getMessage()); + try { + inStream.reset(); + } catch (IOException e1) { + e1.printStackTrace(); + } + //Continue to the next loop iteration/message. + continue; + } + + + //Checks which message is being received and does what is needed for that message. + switch (message.getType()) { + + //Heartbeat. + case HEARTBEAT: { + Heartbeat heartbeat = (Heartbeat) message; + + //Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time. + if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) { + lastHeartbeatTime = System.currentTimeMillis(); + lastHeartbeatSequenceNum = heartbeat.getSequenceNumber(); + //System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum); + } + + break; + } + + //RaceStatus. + case RACESTATUS: { + RaceStatus raceStatus = (RaceStatus) message; + + //System.out.println("Race Status Message"); + this.latestMessages.setRaceStatus(raceStatus); + + for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) { + this.latestMessages.setBoatStatus(boatStatus); + } + + break; + } + + //DisplayTextMessage. + case DISPLAYTEXTMESSAGE: { + //System.out.println("Display Text Message"); + //No decoder for this. + + break; + } + + //XMLMessage. + case XMLMESSAGE: { + XMLMessage xmlMessage = (XMLMessage) message; + + //System.out.println("XML Message!"); + + this.latestMessages.setXMLMessage(xmlMessage); + + break; + } + + //RaceStartStatus. + case RACESTARTSTATUS: { + + //System.out.println("Race Start Status Message"); + + break; + } + + //YachtEventCode. + case YACHTEVENTCODE: { + //YachtEventCode yachtEventCode = (YachtEventCode) message; + + //System.out.println("Yacht Event Code!"); + //No decoder for this. + + break; + } + + //YachtActionCode. + case YACHTACTIONCODE: { + //YachtActionCode yachtActionCode = (YachtActionCode) message; + + //System.out.println("Yacht Action Code!"); + // No decoder for this. + + break; + } + + //ChatterText. + case CHATTERTEXT: { + //ChatterText chatterText = (ChatterText) message; + + //System.out.println("Chatter Text Message!"); + //No decoder for this. + + break; + } + + //BoatLocation. + case BOATLOCATION: { + BoatLocation boatLocation = (BoatLocation) message; + + //System.out.println("Boat Location!"); + + BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID()); + if (existingBoatLocation != null) { + //If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer). + if (boatLocation.getTime() > existingBoatLocation.getTime()) { + //If it is, replace the old message. + this.latestMessages.setBoatLocation(boatLocation); + } + } else { + //If the map _doesn't_ already contain a message for this boat, insert the message. + this.latestMessages.setBoatLocation(boatLocation); + } + + break; + } + + //MarkRounding. + case MARKROUNDING: { + MarkRounding markRounding = (MarkRounding) message; + + //System.out.println("Mark Rounding Message!"); + + MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID()); + if (existingMarkRounding != null) { + + //If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer). + if (markRounding.getTime() > existingMarkRounding.getTime()) { + //If it is, replace the old message. + this.latestMessages.setMarkRounding(markRounding); + } + + } else { + //If the map _doesn't_ already contain a message for this boat, insert the message. + this.latestMessages.setMarkRounding(markRounding); + } + + break; + } + + //CourseWinds. + case COURSEWIND: { + + //System.out.println("Course Wind Message!"); + CourseWinds courseWinds = (CourseWinds) message; + + this.latestMessages.setCourseWinds(courseWinds); + + break; + } + + //AverageWind. + case AVGWIND: { + + //System.out.println("Average Wind Message!"); + AverageWind averageWind = (AverageWind) message; + + this.latestMessages.setAverageWind(averageWind); + + break; + } + + //Unrecognised message. + default: { + System.out.println("Broken Message!"); + + break; + } + + } + } + } + + + +} diff --git a/visualiser/src/main/java/seng302/Model/Annotations.java b/racevisionGame/src/main/java/visualiser/model/Annotations.java similarity index 93% rename from visualiser/src/main/java/seng302/Model/Annotations.java rename to racevisionGame/src/main/java/visualiser/model/Annotations.java index b4f58260..09e3831a 100644 --- a/visualiser/src/main/java/seng302/Model/Annotations.java +++ b/racevisionGame/src/main/java/visualiser/model/Annotations.java @@ -1,4 +1,4 @@ -package seng302.Model; +package visualiser.model; import javafx.fxml.FXML; import javafx.scene.Node; @@ -14,13 +14,13 @@ import java.util.Map; /** * Class that processes user selected annotation visibility options to * display the requested information on the - * {@link seng302.Model.ResizableRaceMap ResizbleRaceMap}. These are displayed - * via the {@link seng302.Controllers.RaceController RaceController}.
- * Annotation options for a {@link seng302.Model.Boat Boat} include: its name, + * {@link ResizableRaceCanvas}. These are displayed + * via the {@link visualiser.Controllers.RaceController}.
+ * Annotation options for a {@link VisualiserBoat} include: its name, * abbreviation, speed, the time since it passed the last - * {@link seng302.Model.Marker Marker}, estimated time to the next marker, + * {@link shared.model.Mark}, estimated time to the next marker, * and a path it has travelled made up of - * {@link seng302.Model.TrackPoint TrackPoint}s. + * {@link TrackPoint}s. */ public class Annotations { private ResizableRaceCanvas raceMap; @@ -107,7 +107,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleAnnoName(); storeCurrentAnnotationState(nameCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); @@ -117,7 +117,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleAnnoAbbrev(); storeCurrentAnnotationState(abbrevCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); @@ -127,7 +127,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleBoatPath(); storeCurrentAnnotationState(pathCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); @@ -137,7 +137,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleAnnoSpeed(); storeCurrentAnnotationState(speedCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); @@ -147,7 +147,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleAnnoTime(); storeCurrentAnnotationState(timeCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); @@ -157,7 +157,7 @@ public class Annotations { if (old_val != new_val) { raceMap.toggleAnnoEstTime(); storeCurrentAnnotationState(estTimeCheckAnno, new_val); - raceMap.update(); + raceMap.draw(); } }); } @@ -191,7 +191,7 @@ public class Annotations { for (Map.Entry checkBox : checkBoxes.entrySet()){ checkBox.getValue().setSelected(false); } - raceMap.update(); + raceMap.draw(); buttonChecked = noBtn; prevBtnChecked = hideBtn; selectShow = true; @@ -206,7 +206,7 @@ public class Annotations { checkBox.getValue().setSelected( annoShownBeforeHide.get(checkBox.getKey())); } - raceMap.update(); + raceMap.draw(); buttonChecked = noBtn; prevBtnChecked = showBtn; } @@ -226,7 +226,7 @@ public class Annotations { } else { checkBox.getValue().setSelected(false); } } - raceMap.update(); + raceMap.draw(); buttonChecked = noBtn; prevBtnChecked = partialBtn; selectShow = true; @@ -243,6 +243,7 @@ public class Annotations { (importantAnno.get(checkBox.getKey())); } } + raceMap.draw(); buttonChecked = noBtn; prevBtnChecked = importantBtn; selectShow = true; diff --git a/visualiser/src/main/java/seng302/GraphCoordinate.java b/racevisionGame/src/main/java/visualiser/model/GraphCoordinate.java similarity index 68% rename from visualiser/src/main/java/seng302/GraphCoordinate.java rename to racevisionGame/src/main/java/visualiser/model/GraphCoordinate.java index 2b4523cc..a6a2132b 100644 --- a/visualiser/src/main/java/seng302/GraphCoordinate.java +++ b/racevisionGame/src/main/java/visualiser/model/GraphCoordinate.java @@ -1,13 +1,19 @@ -package seng302; +package visualiser.model; /** - * It is a coordinate representing a location on the - * {@link seng302.Model.ResizableRaceMap ResizableRaceMap}. - * It has been converted from a {@link seng302.GPSCoordinate GPSCoordinate} - * to display objects in their relative positions. + * It is a coordinate representing a location + * resizable race canvas */ public class GraphCoordinate { + + /** + * X (horizontal) coordinate. + */ private final int x; + + /** + * Y (vertical) coordinate. + */ private final int y; /** @@ -21,6 +27,7 @@ public class GraphCoordinate { this.y = y; } + /** * Returns the X coordinate. * diff --git a/visualiser/src/main/java/seng302/RaceConnection.java b/racevisionGame/src/main/java/visualiser/model/RaceConnection.java similarity index 85% rename from visualiser/src/main/java/seng302/RaceConnection.java rename to racevisionGame/src/main/java/visualiser/model/RaceConnection.java index da104741..848b747e 100644 --- a/visualiser/src/main/java/seng302/RaceConnection.java +++ b/racevisionGame/src/main/java/visualiser/model/RaceConnection.java @@ -1,4 +1,4 @@ -package seng302; +package visualiser.model; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -28,9 +28,10 @@ public class RaceConnection { */ @SuppressWarnings("unused") public boolean check() { + //TODO the connection needs to be moved to its own thread, so it doesn't block fx thread. InetSocketAddress i = new InetSocketAddress(hostname.get(), port); try (Socket s = new Socket()){ - s.connect(i, 5000); + s.connect(i, 750);//TODO this should be at least a second or two, once moved to its own thread status.set("Ready"); return true; } catch (IOException e) {} diff --git a/racevisionGame/src/main/java/visualiser/model/RaceMap.java b/racevisionGame/src/main/java/visualiser/model/RaceMap.java new file mode 100644 index 00000000..1583e0a5 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/RaceMap.java @@ -0,0 +1,146 @@ +package visualiser.model; + +import shared.model.GPSCoordinate; + +/** + * The base size of the map to be used for the + * resizable race map + */ +public class RaceMap { + + /** + * The longitude of the left side of the map. + */ + private final double longLeft; + + /** + * The longitude of the right side of the map. + */ + private final double longRight; + + /** + * The latitude of the top side of the map. + */ + private final double latTop; + + /** + * The latitude of the bottom side of the map. + */ + private final double latBottom; + + + /** + * The width, in pixels, of the map. + */ + private int width; + + /** + * The height, in pixels, of the map. + */ + private int height; + + + + /** + * Constructor Method. + * + * @param latTop Latitude of the top left point. + * @param longLeft Longitude of the top left point. + * @param latBottom Latitude of the top right point. + * @param longRight Longitude of the top right point. + * @param width width that the Canvas the race is to be drawn on is. + * @param height height that the Canvas the race is to be drawn on is. + */ + public RaceMap(double latTop, double longLeft, double latBottom, double longRight, int width, int height) { + + //Long/lat edges. + this.longLeft = longLeft; + this.longRight = longRight; + this.latTop = latTop; + this.latBottom = latBottom; + + //Pixel sizes. + this.width = width; + this.height = height; + } + + /** + * Converts GPS coordinates to coordinates for container. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param lat GPS latitude + * @param lon GPS longitude + * @return GraphCoordinate (pair of doubles) + * @see GraphCoordinate + */ + private GraphCoordinate convertGPS(double lat, double lon) { + + //Calculate the width/height, in gps coordinates, of the map. + double longWidth = longRight - longLeft; + double latHeight = latBottom - latTop; + + //Calculate the distance between the specified coordinate and the edge of the map. + double longDelta = lon - longLeft; + double latDelta = lat - latTop; + + //Calculate the proportion along horizontally, from the left, the coordinate should be. + double longProportion = longDelta / longWidth; + //Calculate the proportion along vertically, from the top, the coordinate should be. + double latProportion = latDelta / latHeight; + + + //Check which pixel dimension of our map is smaller. We use this to ensure that any rendered stuff retains its correct aspect ratio, and that everything is visible on screen. + int smallerDimension = Math.min(width, height); + + //Calculate the x and y pixel coordinates. + //We take the complement of latProportion to flip it. + int x = (int) (longProportion * smallerDimension); + int y = (int) ((1 - latProportion) * smallerDimension); + + //Because we try to maintain the correct aspect ratio, we will end up with "spare" pixels along the larger dimension (e.g., width 800, height 600, 200 extra pixels along width). + int extraPixels = Math.abs(width - height); + //We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels. + if (width > height) { + x += extraPixels / 2; + } else { + y += extraPixels / 2; + } + + + //Finally, create the GraphCoordinate. + GraphCoordinate graphCoordinate = new GraphCoordinate(x, y); + + + return graphCoordinate; + + } + + /** + * Converts the GPS Coordinate to GraphCoordinate. + * It is assumed that the provided GPSCoordinate will always be within the GPSCoordinate boundaries of the RaceMap. + * + * @param coordinate GPSCoordinate representation of Latitude and Longitude. + * @return GraphCoordinate that the GPS is coordinates are to be displayed on the map. + * @see GraphCoordinate + * @see GPSCoordinate + */ + public GraphCoordinate convertGPS(GPSCoordinate coordinate) { + return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); + } + + /** + * Sets the width, in pixels, of our RaceMap. + * @param width The new width, in pixels, of the RaceMap. + */ + public void setWidth(int width) { + this.width = width; + } + + /** + * Sets the height, in pixels, of our RaceMap. + * @param height The new height, in pixels, of the RaceMap. + */ + public void setHeight(int height) { + this.height = height; + } +} diff --git a/visualiser/src/main/java/seng302/Model/ResizableCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableCanvas.java similarity index 66% rename from visualiser/src/main/java/seng302/Model/ResizableCanvas.java rename to racevisionGame/src/main/java/visualiser/model/ResizableCanvas.java index 95ee7a32..1fda8173 100644 --- a/visualiser/src/main/java/seng302/Model/ResizableCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableCanvas.java @@ -1,4 +1,4 @@ -package seng302.Model; +package visualiser.model; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -7,46 +7,44 @@ import javafx.scene.canvas.GraphicsContext; * The abstract class for the resizable race canvases. */ public abstract class ResizableCanvas extends Canvas { + + /** + * The {@link GraphicsContext} to draw to. + */ protected final GraphicsContext gc; + /** + * Ctor. + */ public ResizableCanvas(){ this.gc = this.getGraphicsContext2D(); + // Redraw canvas when size changes. widthProperty().addListener(evt -> draw()); heightProperty().addListener(evt -> draw()); - } - abstract void draw(); - /** - * Set the Canvas to resizable. - * - * @return That the Canvas is resizable. + * Draws desired contents onto the canvas. + * Subclasses implement this to decide what to draw. */ + public abstract void draw(); + + + + @Override public boolean isResizable() { return true; } - /** - * Returns the preferred width of the Canvas - * - * @param width of canvas - * @return Returns the width of the Canvas - */ + @Override public double prefWidth(double width) { return getWidth(); } - /** - * Returns the preferred height of the Canvas - * - * @param height of canvas - * @return Returns the height of the Canvas - */ @Override public double prefHeight(double height) { return getHeight(); diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java new file mode 100644 index 00000000..3c7be5dc --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -0,0 +1,560 @@ +package visualiser.model; + + +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.transform.Rotate; +import network.Messages.Enums.BoatStatusEnum; +import shared.dataInput.RaceDataSource; +import shared.model.GPSCoordinate; +import shared.model.Mark; +import shared.model.RaceClock; + +import java.time.Duration; +import java.util.List; + +/** + * This JavaFX Canvas is used to update and display details for a + * {@link RaceMap} via the + * {@link visualiser.Controllers.RaceController}.
+ * It fills it's parent and cannot be downsized.
+ * Details displayed include: + * {@link VisualiserBoat}s (and their + * {@link TrackPoint}s), + * {@link shared.model.Mark}s, a + * {@link RaceClock}, a wind direction arrow and + * various user selected {@link Annotations}. + */ +public class ResizableRaceCanvas extends ResizableCanvas { + + /** + * The RaceMap used for converting GPSCoordinates to GraphCoordinates. + */ + private RaceMap map; + + /** + * The race we read data from and draw. + */ + private VisualiserRace visualiserRace; + + /** + * The background of the race. + * We render the background whenever the race boundary changes, or the screen size changes. + */ + private Image background; + + + private boolean annoName = true; + private boolean annoAbbrev = true; + private boolean annoSpeed = true; + private boolean annoPath = true; + private boolean annoEstTime = true; + private boolean annoTimeSinceLastMark = true; + + + /** + * The wind arrow node. + */ + private Node arrow; + + + /** + * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}. + * @param visualiserRace The race that data is read from in order to be drawn. + * @param arrow The wind arrow's node. + */ + public ResizableRaceCanvas(VisualiserRace visualiserRace, Node arrow) { + super(); + + this.visualiserRace = visualiserRace; + this.arrow = arrow; + + RaceDataSource raceData = visualiserRace.getRaceDataSource(); + + double lat1 = raceData.getMapTopLeft().getLatitude(); + double long1 = raceData.getMapTopLeft().getLongitude(); + double lat2 = raceData.getMapBottomRight().getLatitude(); + double long2 = raceData.getMapBottomRight().getLongitude(); + + this.map = new RaceMap( + lat1, long1, lat2, long2, + (int) getWidth(), (int) getHeight() ); + + + } + + + + /** + * Toggle name display in annotation + */ + public void toggleAnnoName() { + annoName = !annoName; + } + + /** + * Toggle boat path display in annotation + */ + public void toggleBoatPath() { + annoPath = !annoPath; + } + + public void toggleAnnoEstTime() { + annoEstTime = !annoEstTime; + } + + /** + * Toggle boat time display in annotation + */ + public void toggleAnnoTime() { + annoTimeSinceLastMark = !annoTimeSinceLastMark; + } + + /** + * Toggle abbreviation display in annotation + */ + public void toggleAnnoAbbrev() { + annoAbbrev = !annoAbbrev; + } + + /** + * Toggle speed display in annotation + */ + public void toggleAnnoSpeed() { + annoSpeed = !annoSpeed; + } + + + + + /** + * Rotates things on the canvas Note: this must be called in between gc.save() and gc.restore() else they will rotate everything + * + * @param degrees Bearing degrees to rotate. + * @param px Pivot point x of rotation. + * @param py Pivot point y of rotation. + */ + private void rotate(double degrees, double px, double py) { + Rotate r = new Rotate(degrees, px, py); + gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy()); + } + + + + /** + * Draws a circle with a given diameter, centred on a given graph coordinate. + * @param center The center coordinate of the circle. + * @param diameter The diameter of the circle. + */ + private void drawCircle(GraphCoordinate center, double diameter) { + + //The graphCoordinates are for the center of the point, so we offset them to get the corner coordinate. + gc.fillOval( + center.getX() - (diameter / 2), + center.getY() - (diameter / 2), + diameter, diameter ); + + } + + /** + * Displays a line on the map with rectangles on the starting and ending point of the line. + * + * @param graphCoordinateA Starting Point of the line in GraphCoordinate. + * @param graphCoordinateB End Point of the line in GraphCoordinate. + * @param paint Colour the line is to coloured. + */ + private void drawLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) { + + gc.setStroke(paint); + gc.setFill(paint); + + + double endPointDiameter = 6; + + //Draw first end-point. + drawCircle(graphCoordinateA, endPointDiameter); + + //Draw second end-point. + drawCircle(graphCoordinateB, endPointDiameter); + + //Draw line between them. + gc.strokeLine( + graphCoordinateA.getX(), + graphCoordinateA.getY(), + graphCoordinateB.getX(), + graphCoordinateB.getY() ); + + } + + /** + * Display a point on the Canvas. It has a diameter of 10 pixels. + * + * @param graphCoordinate Coordinate that the point is to be displayed at. + * @param paint Paint to use for the point. + */ + private void drawPoint(GraphCoordinate graphCoordinate, Paint paint) { + + //Set paint. + gc.setFill(paint); + + double pointDiameter = 10; + + //Draw the point. + drawCircle(graphCoordinate, pointDiameter); + } + + + /** + * Display given name and speed of boat at a graph coordinate + * + * @param name name of the boat + * @param abbrev abbreviation of the boat name + * @param speed speed of the boat + * @param coordinate coordinate the text appears + * @param timeToNextMark The time until the boat reaches the next mark. + * @param timeSinceLastMark The time since the boat passed the last mark. + */ + private void drawText(String name, String abbrev, double speed, GraphCoordinate coordinate, String timeToNextMark, String timeSinceLastMark) { + + //The text to draw. Built during the function. + String text = ""; + + + //Draw name if annotation is enabled. + if (annoName) { + text += String.format("%s ", name); + } + + //Draw abbreviation/country if annotation is enabled. + if (annoAbbrev) { + text += String.format("%s ", abbrev); + } + + //Draw speed if annotation is enabled. + if (annoSpeed){ + text += String.format("%.2fkn ", speed); + } + + //Draw time to reach next mark if annotation is enabled. + if (annoEstTime) { + text += timeToNextMark; + } + + //Draw time since last mark if annotation is enabled. + if(annoTimeSinceLastMark) { + text += timeSinceLastMark; + } + + + //Offset by 20 pixels horizontally. + long xCoord = coordinate.getX() + 20; + long yCoord = coordinate.getY(); + + //If the text would extend out of the canvas (to the right), move it left. + if (xCoord + (text.length() * 7) >= getWidth()) { + xCoord -= text.length() * 7; + } + + + if (yCoord - (text.length() * 2) <= 0) { + yCoord += 30; + } + + //Draw text. + gc.fillText(text, xCoord, yCoord); + } + + + /** + * Draws the label for a given boat. Includes name, abbreviation, speed, time since mark, and time to next mark. + * @param boat The boat to draw text for. + */ + private void drawBoatText(VisualiserBoat boat) { + + drawText( + boat.getName(), + boat.getCountry(), + boat.getCurrentSpeed(), + this.map.convertGPS(boat.getCurrentPosition()), + boat.getTimeToNextMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime()), + boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime()) ); + + } + + + + + /** + * Draws all of the boats on the canvas. + */ + private void drawBoats() { + + for (VisualiserBoat boat : visualiserRace.getBoats()) { + + //Draw the boat. + drawBoat(boat); + + //Only draw wake if they are currently racing. + if (boat.getStatus() == BoatStatusEnum.RACING) { + drawWake(boat); + } + + + //If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts. + if ((boat.getStatus() != BoatStatusEnum.RACING) && (boat.getStatus() == BoatStatusEnum.FINISHED)) { + boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime()); + } + + //Draw boat label. + drawBoatText(boat); + + //Draw track. + drawTrack(boat); + + } + + } + + + + /** + * Draws a given boat on the canvas. + * @param boat The boat to draw. + */ + private void drawBoat(VisualiserBoat boat) { + + //The position may be null if we haven't received any BoatLocation messages yet. + if (boat.getCurrentPosition() != null) { + + //Convert position to graph coordinate. + GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition()); + + //The x coordinates of each vertex of the boat. + double[] x = { + pos.getX() - 6, + pos.getX(), + pos.getX() + 6 }; + + //The y coordinates of each vertex of the boat. + double[] y = { + pos.getY() + 12, + pos.getY() - 12, + pos.getY() + 12 }; + + //The above shape is essentially a triangle 12px wide, and 24px long. + + //Draw the boat. + gc.setFill(boat.getColor()); + + gc.save(); + rotate(boat.getBearing().degrees(), pos.getX(), pos.getY()); + gc.fillPolygon(x, y, 3); + gc.restore(); + + } + + } + + + /** + * Draws the wake for a given boat. + * @param boat Boat to draw wake for. + */ + private void drawWake(VisualiserBoat boat) { + + //Calculate either end of wake line. + GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition()); + GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake()); + + //Draw. + drawLine(wakeFrom, wakeTo, boat.getColor()); + + } + + + + /** + * Displays an arrow representing wind direction on the Canvas. + * This function accepts a wind-to bearing, but displays a wind-from bearing. + * + * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). + * @see GraphCoordinate + */ + private void displayWindArrow(double angle) { + + //We need to display wind-from, so add 180 degrees. + angle += 180d; + + //Get it within [0, 360). + while (angle >= 360d) { + angle -= 360d; + } + + //Rotate the wind arrow. + if (arrow != null && arrow.getRotate() != angle) { + arrow.setRotate(angle); + } + } + + + + + /** + * Draws all of the {@link Mark}s on the canvas. + */ + private void drawMarks() { + for (Mark mark : this.visualiserRace.getMarks()) { + drawMark(mark); + } + } + + + /** + * Draws a given mark on the canvas. + * @param mark The mark to draw. + */ + private void drawMark(Mark mark) { + + //Calculate screen position. + GraphCoordinate mark1 = this.map.convertGPS(mark.getPosition()); + + //Draw. + drawPoint(mark1, Color.LIMEGREEN); + + } + + + + /** + * Draws the Race Map. + * Called when the canvas is resized. + */ + public void draw() { + + //Clear canvas. + clear(); + + //Update our RaceMap using new canvas size. + this.map.setWidth((int) getWidth()); + this.map.setHeight((int) getHeight()); + + //Redraw the boundary. + redrawBoundaryImage(); + + //Draw the race. + drawRace(); + + + } + + + /** + * Clears the canvas. + */ + private void clear() { + gc.clearRect(0, 0, getWidth(), getHeight()); + } + + + /** + * Draws the race boundary, and saves the image to {@link #background}. + * You should call {@link #clear()} before calling this. + */ + private void redrawBoundaryImage() { + + //Prepare to draw. + gc.setLineWidth(1); + gc.setFill(Color.AQUA); + + + //Calculate the screen coordinates of the boundary. + List boundary = this.visualiserRace.getBoundary(); + double[] xpoints = new double[boundary.size()]; + double[] ypoints = new double[boundary.size()]; + + //For each boundary coordinate. + for (int i = 0; i < boundary.size(); i++) { + //Convert. + GraphCoordinate coord = map.convertGPS(boundary.get(i)); + //Use. + xpoints[i] = coord.getX(); + ypoints[i] = coord.getY(); + } + + //Draw the boundary. + gc.fillPolygon(xpoints, ypoints, xpoints.length); + + //Render boundary to image. + this.background = snapshot(null, null); + + } + + /** + * Draws the race. + * Called once per frame, and on canvas resize. + */ + public void drawRace() { + + gc.setLineWidth(2); + + clear(); + + //Race boundary. + drawBoundary(); + + //Boats. + drawBoats(); + + //Marks. + drawMarks(); + + //Wind arrow. This rotates the wind arrow node. + displayWindArrow(this.visualiserRace.getWindDirection().degrees()); + + } + + + /** + * Draws the race boundary image onto the canvas. + * See {@link #background}. + */ + private void drawBoundary() { + gc.drawImage(this.background, 0, 0); + } + + + + + + /** + * Draws all track points for a given boat. Colour is set by boat, opacity by track point. + * This checks if {@link #annoPath} is enabled. + * @param boat The boat to draw tracks for. + * @see TrackPoint + */ + private void drawTrack(VisualiserBoat boat) { + + //Check that track points are enabled. + if (this.annoPath) { + + //Apply the boat color. + gc.setFill(boat.getColor()); + + //Draw each TrackPoint. + for (TrackPoint point : boat.getTrack()) { + + //Convert the GPSCoordinate to a screen coordinate. + GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate()); + + //Draw a circle for the trackpoint. + gc.fillOval(scaledCoordinate.getX(), scaledCoordinate.getY(), point.getDiameter(), point.getDiameter()); + } + } + + } + + + +} diff --git a/racevisionGame/src/main/java/visualiser/model/Sparkline.java b/racevisionGame/src/main/java/visualiser/model/Sparkline.java new file mode 100644 index 00000000..0dda82cf --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/Sparkline.java @@ -0,0 +1,185 @@ +package visualiser.model; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.paint.Color; + +import java.util.List; + + +/** + * Class to process and modify a sparkline display. This display keeps visual + * track of {@link VisualiserBoat}s in a race and their current + * placing position as they complete each {@link shared.model.Leg} by + * passing a course {@link shared.model.Mark}.
+ * This sparkline is displayed using the + * {@link visualiser.Controllers.RaceController}. + */ +public class Sparkline { + + /** + * The race to observe. + */ + private VisualiserRace race; + + /** + * The boats to observe. + */ + private ObservableList boats; + + /** + * The number of legs in the race. + * Used to correctly scale the linechart. + */ + private Integer legNum; + + + /** + * The linchart to plot sparklines on. + */ + private LineChart sparklineChart; + + /** + * The x axis of the sparkline chart. + */ + private NumberAxis xAxis; + + /** + * The y axis of the sparkline chart. + */ + private NumberAxis yAxis; + + + /** + * Constructor to set up initial sparkline (LineChart) object + * @param race The race to listen to. + * @param sparklineChart JavaFX LineChart for the sparkline. + */ + public Sparkline(VisualiserRace race, LineChart sparklineChart) { + this.race = race; + this.boats = race.getBoats(); + this.legNum = race.getLegCount(); + + this.sparklineChart = sparklineChart; + this.yAxis = (NumberAxis) sparklineChart.getYAxis(); + this.xAxis = (NumberAxis) sparklineChart.getXAxis(); + + createSparkline(); + + } + + + /** + * Creates and sets initial display for Sparkline for race positions. + * A data series for each boat in the race is added. + * Position numbers are displayed. + */ + private void createSparkline() { + // NOTE: Y axis is in negatives to display correct positions + + //For each boat... + for (VisualiserBoat boat : this.boats) { + + //Create data series for each boat. + XYChart.Series series = new XYChart.Series<>(); + + + //All boats start in "last" place. + series.getData().add(new XYChart.Data<>(0, boats.size())); + + //Listen for changes in the boat's leg - we only update the graph when it changes leg. + boat.legProperty().addListener( + (observable, oldValue, newValue) -> { + + //Get the data to plot. + List boatOrder = race.getLegCompletionOrder().get(oldValue); + //Find boat position in list. + int boatPosition = boatOrder.indexOf(boat) + 1; + + //Get leg number. + int legNumber = oldValue.getLegNumber() + 1; + + + //Create new data point for boat's position at the new leg. + XYChart.Data dataPoint = new XYChart.Data<>(legNumber, boatPosition); + + //Add to series. + Platform.runLater(() -> series.getData().add(dataPoint)); + + + }); + + + //Add to chart. + sparklineChart.getData().add(series); + + //Color using boat's color. We need to do this after adding the series to a chart, otherwise we get null pointer exceptions. + series.getNode().setStyle("-fx-stroke: " + colourToHex(boat.getColor()) + ";"); + + + } + + sparklineChart.setCreateSymbols(false); + + //Set x axis details + xAxis.setAutoRanging(false); + xAxis.setTickMarkVisible(false); + xAxis.setTickLabelsVisible(false); + xAxis.setMinorTickVisible(false); + xAxis.setLowerBound(0); + xAxis.setUpperBound(legNum + 2); + xAxis.setTickUnit(1); + + //Set y axis details + yAxis.setLowerBound(boats.size() + 1); + yAxis.setUpperBound(0); + yAxis.setAutoRanging(false); + yAxis.setLabel("Position in Race"); + yAxis.setTickUnit(-1);//Negative tick reverses the y axis. + + yAxis.setTickMarkVisible(true); + yAxis.setTickLabelsVisible(true); + yAxis.setTickMarkVisible(true); + yAxis.setMinorTickVisible(true); + + /* TODO FIX currently this doesn't work - I broke it :( + TODO only 0 and 7 get passed in to it for some reason + //Hide minus number from displaying on y axis. + yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) { + @Override + public String toString(Number value) { + + //We only label the values between [1,boats.size()]. + System.out.print("y axis: " + value.doubleValue());//TEMP remove + if ((value.doubleValue() >= 1) + && (value.doubleValue() <= boats.size())) { + System.out.println(" is good");//TEMP + return String.format("%7.0f", value.doubleValue()); + + } else { + System.out.println(" is bad");//TEMP + return ""; + + } + } + }); + */ + } + + + /** + * Converts a color to a hex string, starting with a {@literal #} symbol. + * @param color The color to convert. + * @return Hex string of the color (e.g., {@literal "#11AB4C"}). + */ + private static String colourToHex(Color color) { + return String.format( "#%02X%02X%02X", + (int)( color.getRed() * 255 ), + (int)( color.getGreen() * 255 ), + (int)( color.getBlue() * 255 ) ); + } + +} diff --git a/racevisionGame/src/main/java/visualiser/model/TrackPoint.java b/racevisionGame/src/main/java/visualiser/model/TrackPoint.java new file mode 100644 index 00000000..4e0c76c0 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/TrackPoint.java @@ -0,0 +1,108 @@ +package visualiser.model; + + +import shared.model.GPSCoordinate; + +/** + * A TrackPoint is a point plotted to display the track a + * {@link VisualiserBoat Boat} has travelled in a race.
+ * TrackPoints are displayed on a + * {@link ResizableRaceCanvas}, via the + * {@link visualiser.Controllers.RaceController}.
+ * Track points can be made visible or hidden via the RaceController's + * {@link Annotations}. + */ +public class TrackPoint { + + /** + * The {@link GPSCoordinate} this {@link TrackPoint} corresponds to. + */ + private final GPSCoordinate coordinate; + + /** + * The time the track point was created at, in milliseconds since unix epoch. + */ + private final long timeAdded; + + /** + * The period of time, in milliseconds, over which the track point's alpha should diminish to a floor value of {@link #minAlpha}. + */ + private final long expiry; + + /** + * The minimum alpha to draw the track point with. + */ + private final double minAlpha; + + /** + * The diameter to draw the track point with. + */ + private final double diameter; + + + /** + * Creates a new track point with fixed GPS coordinates and time, to reach minimum opacity on expiry. + * + * @param coordinate position of point on physical race map + * @param timeAdded system clock at time of addition + * @param expiry time to minimum opacity after added + */ + public TrackPoint(GPSCoordinate coordinate, long timeAdded, long expiry) { + this.coordinate = coordinate; + this.timeAdded = timeAdded; + this.expiry = expiry; + + this.minAlpha = 0.1; + this.diameter = 5d; + } + + + /** + * Gets the position of the point on physical race map. + * + * @return GPS coordinate of point + */ + public GPSCoordinate getCoordinate() { + return coordinate; + } + + /** + * Gets opacity of point scaled by age in proportion to expiry, between 1 and minimum opacity inclusive. + * + * @return Greater of minimum opacity and scaled opacity. + */ + public double getAlpha() { + + //Calculate how much the alpha should be attenuated by elapsed time. + + //Elapsed time. + long elapsedTime = System.currentTimeMillis() - this.timeAdded; + + //Proportion of expiry period that has elapsed. (E.g., 2.5 means that 5 times the period has elapsed.) + double elapsedProportion = ((double) elapsedTime) / this.expiry; + + //As the alpha diminishes from 1 down to a floor, we take the complement of this value. This may be negative. + double calculatedAlpha = 1.0 - elapsedProportion; + + //We then take the max of the minAlpha and calculatedAlpha so that it doesn't go past our floor value. + return Double.max(this.minAlpha, calculatedAlpha); + } + + /** + * Gets time point was added to track. + * + * @return system clock at time of addition + */ + public long getTimeAdded() { + return timeAdded; + } + + + /** + * Returns the diameter to draw the track point with. + * @return The diameter to draw the track point with. + */ + public double getDiameter() { + return diameter; + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java new file mode 100644 index 00000000..57ba0790 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java @@ -0,0 +1,217 @@ +package visualiser.model; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.paint.Color; +import network.Messages.Enums.BoatStatusEnum; +import shared.model.Azimuth; +import shared.model.Boat; +import shared.model.Constants; +import shared.model.GPSCoordinate; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a Boat on the visualiser side of a race. + * This adds visualiser specific functionality to a boat. + * This class is used to represent and store information about a boat which may + * travel around in a race. It is displayed on the + * {@link ResizableRaceCanvas ResizableRaceCanvas} via the + * {@link visualiser.Controllers.RaceController RaceController}. + */ +public class VisualiserBoat extends Boat { + + + /** + * The collection of trackpoints generated for the boat. + */ + private final List track = new ArrayList<>(); + + /** + * The next time, in milliseconds since unix epoch, at which we may create a new track point. + */ + private long nextValidTime = 0; + + /** + * The minimum period of time, in milliseconds, between the creation of each track point. + */ + private static final long trackPointTimeInterval = 5000; + + /** + * The number of track points that should be created before fully diminishing the alpha of a given track point. + */ + private static final int trackPointLimit = 10; + + + /** + * The boat's color. + */ + private Color color; + + + /** + * Scalar used to scale the boat's wake. + */ + private static final double wakeScale = 5; + + + + + + /** + * Constructs a boat object from a given boat and color. + * + * @param boat The boat to convert into a MockBoat. + * @param color The color of the boat. + */ + public VisualiserBoat(Boat boat, Color color) { + super(boat.getSourceID(), boat.getName(), boat.getCountry()); + + this.color = color; + } + + + /** + * Returns the position of the end of the boat's wake, which is 180 degrees + * from the boat's heading, and whose length is proportional to the boat's + * speed. + * + * @return GPSCoordinate of wake endpoint. + */ + public GPSCoordinate getWake() { + + + //Calculate the reverse bearing of the boat, and convert it to an azimuth. + Azimuth reverseAzimuth = Azimuth.fromDegrees(getBearing().degrees() - 180d); + + //Calculate the distance, in meters, of the wake. + //We currently use boat's speed, in meters per second, to calculate the wake length. Could maybe move the knot -> m/s calculation somewhere else. + double speedKnots = getCurrentSpeed(); + double speedMetersPerHour = speedKnots * Constants.NMToMetersConversion; + double speedMetersPerSecond = speedMetersPerHour / Constants.OneHourSeconds; + + double wakeDistanceMeters = speedMetersPerSecond * this.wakeScale; + + + //Calculate the new coordinate. + GPSCoordinate wakeCoordinate = GPSCoordinate.calculateNewPosition(getCurrentPosition(), wakeDistanceMeters, reverseAzimuth); + + return wakeCoordinate; + } + + /** + * Attempts to add a new point to boat's track. + * It only adds a new point if the boat is still racing (see {@link #getStatus()} and {@link BoatStatusEnum#RACING}), and if at least {@link #trackPointTimeInterval} milliseconds have occurred, using race time. + * @param coordinate The {@link GPSCoordinate} of the trackpoint. + * @param currentTime The current race time. + * @see TrackPoint + */ + public void addTrackPoint(GPSCoordinate coordinate, ZonedDateTime currentTime) { + + //Get current time. + long currentTimeMilli = currentTime.toInstant().toEpochMilli(); + + //Check if enough time has passed to create a new track point. + Boolean canBeAdded = currentTimeMilli >= nextValidTime; + + //If it has, and if we are still racing, create the point. + if (canBeAdded && (getStatus() == BoatStatusEnum.RACING)) { + + //Calculate the period of time that it should take the track point to diminish over. We essentially allow for trackPointLimit number of track points to be created before it should fade out. + long expiryPeriod = trackPointTimeInterval * trackPointLimit; + + //Create and add point. + TrackPoint trackPoint = new TrackPoint(coordinate, currentTimeMilli, expiryPeriod); + track.add(trackPoint); + + //Update the nextValidTime for the next track point. + nextValidTime = currentTimeMilli + trackPointTimeInterval; + + } + } + + /** + * Returns the boat's sampled track between start of race and current time. + * @return The list of track points. + * @see TrackPoint + */ + public List getTrack() { + return track; + } + + /** + * Returns the color of the boat. + * @return The color of the boat. + */ + public Color getColor() { + return color; + } + + /** + * Print method prints the name of the boat + * + * @return Name of the boat. + */ + public String toString() { + return getName(); + } + + + + + /** + * Returns the time until the boat will reach the next mark, as a string. + * @param currentTime The current race time. + * @return The time delta until the boat reaches the next mark. + */ + public String getTimeToNextMarkFormatted(ZonedDateTime currentTime) { + + //Calculate time delta. + Duration timeUntil = Duration.between(currentTime, getEstimatedTimeAtNextMark()); + + //Convert to seconds. + long secondsUntil = timeUntil.getSeconds(); + + //This means the estimated time is in the past, or not racing. + if ((secondsUntil < 0) || (getStatus() != BoatStatusEnum.RACING)) { + return " -"; + } + + + if (secondsUntil <= 60) { + //If less than 1 minute, display seconds only. + return " " + secondsUntil + "s"; + + } else { + //Otherwise display minutes and seconds. + long seconds = secondsUntil % 60; + long minutes = (secondsUntil - seconds) / 60; + return String.format(" %dm %ds", minutes, seconds); + + } + + } + + + /** + * Returns the time since the boat passed the previous mark, as a string. + * @param currentTime The current race time. + * @return The time delta since the boat passed the previous mark. + */ + public String getTimeSinceLastMarkFormatted(ZonedDateTime currentTime) { + + if (getTimeAtLastMark() != null) { + //Calculate time delta. + Duration timeSince = Duration.between(getTimeAtLastMark(), currentTime); + + //Format it. + return String.format(" %ds ", timeSince.getSeconds()); + + } else { + return " -"; + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java new file mode 100644 index 00000000..3a76631d --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java @@ -0,0 +1,471 @@ +package visualiser.model; + +import javafx.animation.AnimationTimer; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.paint.Color; +import network.Messages.BoatLocation; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.LatestMessages; +import network.Messages.RaceStatus; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.dataInput.RegattaDataSource; +import shared.model.*; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The Class used to view the race streamed. + * Has a course, boats, boundaries, etc... + * Observes LatestMessages and updates its state based on new messages. + */ +public class VisualiserRace extends Race { + + + /** + * An observable list of boats in the race. + */ + private final ObservableList boats; + + /** + * An observable list of marker boats in the race. + */ + private ObservableList boatMarkers; + + + /** + * Maps between a Leg to a list of boats, in the order that they finished the leg. + * Used by the Sparkline to ensure it has correct information. + */ + private Map> legCompletionOrder = new HashMap<>(); + + + + /** + * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and receives events from LatestMessages. + * @param boatDataSource Data source for boat related data (yachts and marker boats). + * @param raceDataSource Data source for race related data (participating boats, legs, etc...). + * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...). + * @param latestMessages The LatestMessages to send events to. + * @param colors A collection of colors used to assign a color to each boat. + */ + public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, List colors) { + + super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); + + + this.boats = FXCollections.observableArrayList(this.generateVisualiserBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), colors)); + + this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values()); + + + //Initialise the leg completion order map. + for (Leg leg : this.legs) { + this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size())); + } + + } + + + /** + * Sets the race data source for this race to a new RaceDataSource. + * Uses the boundary and legs specified by the new RaceDataSource. + * @param raceDataSource The new RaceDataSource to use. + */ + public void setRaceDataSource(RaceDataSource raceDataSource) { + this.raceDataSource = raceDataSource; + + this.boundary = raceDataSource.getBoundary(); + + this.useLegsList(raceDataSource.getLegs()); + } + + /** + * Sets the boat data source for this race to a new BoatDataSource. + * Uses the marker boats specified by the new BoatDataSource. + * @param boatDataSource The new BoatDataSource to use. + */ + public void setBoatDataSource(BoatDataSource boatDataSource) { + this.boatDataSource = boatDataSource; + + this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values()); + } + + /** + * Sets the regatta data source for this race to a new RegattaDataSource. + * @param regattaDataSource The new RegattaDataSource to use. + */ + public void setRegattaDataSource(RegattaDataSource regattaDataSource) { + this.regattaDataSource = regattaDataSource; + } + + + /** + * Returns a list of {@link Mark} boats. + * @return List of mark boats. + */ + public ObservableList getMarks() { + return boatMarkers; + } + + /** + * Generates a list of VisualiserBoats given a list of Boats, and a list of participating boats. + * @param boats The map of Boats describing boats that are potentially in the race. Maps boat sourceID to boat. + * @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating. + * @param colors The list of colors to be used for the boats. + * @return A list of MockBoats that are participating in the race. + */ + private List generateVisualiserBoats(Map boats, List sourceIDs, List colors) { + + List visualiserBoats = new ArrayList<>(sourceIDs.size()); + + //For each sourceID participating... + int colorIndex = 0; + for (int sourceID : sourceIDs) { + + //Get the boat associated with the sourceID. + Boat boat = boats.get(sourceID); + + //Get a color for the boat. + Color color = colors.get(colorIndex); + + //Construct a VisualiserBoat using the Boat and Polars. + VisualiserBoat visualiserBoat = new VisualiserBoat(boat, color); + + visualiserBoats.add(visualiserBoat); + + //Next color. + colorIndex++; + + } + + return visualiserBoats; + + } + + + /** + * Initialise the boats in the race. + * This sets their current leg. + */ + @Override + protected void initialiseBoats() { + + Leg startingLeg = legs.get(0); + + for (VisualiserBoat boat : boats) { + + boat.setCurrentLeg(startingLeg); + boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); + + } + + } + + + /** + * Updates all of the racing boats based on messages received. + * @param boats The list of racing boats. + * @param boatLocationMap A map between boat sourceIDs and BoatLocation messages. + * @param boatStatusMap A map between boat sourceIDs and BoatStatus messages. + */ + private void updateBoats(ObservableList boats, Map boatLocationMap, Map boatStatusMap) { + + for (VisualiserBoat boat : boats) { + BoatLocation boatLocation = boatLocationMap.get(boat.getSourceID()); + BoatStatus boatStatus = boatStatusMap.get(boat.getSourceID()); + updateBoat(boat, boatLocation, boatStatus); + } + } + + + /** + * Updates an individual racing boat based on messages received. + * @param boat The boat to update. + * @param boatLocation The BoatLocation message to use. + * @param boatStatus The BoatStatus message to use. + */ + private void updateBoat(VisualiserBoat boat, BoatLocation boatLocation, BoatStatus boatStatus) { + + if (boatLocation != null && boatStatus != null) { + + //Get the new position. + double latitude = boatLocation.getLatitudeDouble(); + double longitude = boatLocation.getLongitudeDouble(); + GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); + + boat.setCurrentPosition(gpsCoordinate); + + //Bearing. + boat.setBearing(Bearing.fromDegrees(boatLocation.getHeadingDegrees())); + + //Time until next mark. + boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark())); + + //Speed. + boat.setCurrentSpeed(boatLocation.getBoatSOG() / Constants.KnotsToMMPerSecond); + + + //Boat status. + BoatStatusEnum newBoatStatusEnum = BoatStatusEnum.fromByte(boatStatus.getBoatStatus()); + + //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. + if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { + boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); + } + + boat.setStatus(newBoatStatusEnum); + + + //Leg. + int legNumber = boatStatus.getLegNumber(); + + if (legNumber >= 1 && legNumber < legs.size()) { + if (boat.getCurrentLeg() != legs.get(legNumber)) { + boatFinishedLeg(boat, legs.get(legNumber)); + } + } + + + //Attempt to add a track point. + if (newBoatStatusEnum == BoatStatusEnum.RACING) { + boat.addTrackPoint(boat.getCurrentPosition(), raceClock.getCurrentTime()); + } + + //Set finish time if boat finished. + if (newBoatStatusEnum == BoatStatusEnum.FINISHED || legNumber == this.legs.size()) { + boat.setTimeFinished(boatLocation.getTime()); + boat.setStatus(BoatStatusEnum.FINISHED); + + } + + } + } + + + /** + * Updates a boat's leg to a specified leg. Also records the order in which the boat passed the leg. + * @param boat The boat to update. + * @param leg The leg to use. + */ + private void boatFinishedLeg(VisualiserBoat boat, Leg leg) { + + //Record order in which boat finished leg. + this.legCompletionOrder.get(boat.getCurrentLeg()).add(boat); + + //Update boat. + boat.setCurrentLeg(leg); + boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); + + } + + + /** + * Updates all of the marker boats based on messages received. + * @param boatMarkers The list of marker boats. + * @param boatLocationMap A map between boat sourceIDs and BoatLocation messages. + * @param boatStatusMap A map between boat sourceIDs and BoatStatus messages. + */ + private void updateMarkers(ObservableList boatMarkers, Map boatLocationMap, Map boatStatusMap) { + + for (Mark mark : boatMarkers) { + BoatLocation boatLocation = boatLocationMap.get(mark.getSourceID()); + updateMark(mark, boatLocation); + } + } + + /** + * Updates an individual marker boat based on messages received. + * @param mark The marker boat to be updated. + * @param boatLocation The message describing the boat's new location. + */ + private void updateMark(Mark mark, BoatLocation boatLocation) { + + if (boatLocation != null) { + + //We only update the boat's position. + double latitude = boatLocation.getLatitudeDouble(); + double longitude = boatLocation.getLongitudeDouble(); + GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); + + mark.setPosition(gpsCoordinate); + + } + } + + + /** + * Updates the race status (RaceStatusEnum, wind bearing, wind speed) based on received messages. + * @param raceStatus The RaceStatus message received. + */ + private void updateRaceStatus(RaceStatus raceStatus) { + + //Race status enum. + this.raceStatusEnum = RaceStatusEnum.fromByte(raceStatus.getRaceStatus()); + + //Wind bearing. + this.windDirection.setDegrees(raceStatus.getScaledWindDirection()); + + //Wind speed. + this.windSpeed = raceStatus.getWindSpeedKnots(); + + //Current race time. + this.raceClock.setUTCTime(raceStatus.getCurrentTime()); + + } + + + + + /** + * Runnable for the thread. + */ + public void run() { + initialiseBoats(); + startRaceStream(); + } + + + + + /** + * Starts the race. + * This updates the race based on {@link #latestMessages}. + */ + private void startRaceStream() { + + new AnimationTimer() { + + long lastFrameTime = System.currentTimeMillis(); + + @Override + public void handle(long arg0) { + + //Calculate the frame period. + long currentFrameTime = System.currentTimeMillis(); + long framePeriod = currentFrameTime - lastFrameTime; + + + //Update race status. + updateRaceStatus(latestMessages.getRaceStatus()); + + + //Update racing boats. + updateBoats(boats, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap()); + //And their positions (e.g., 5th). + updateBoatPositions(boats); + + + //Update marker boats. + updateMarkers(boatMarkers, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap()); + + + + + if (getRaceStatusEnum() == RaceStatusEnum.FINISHED) { + stop(); + } + + lastFrameTime = currentFrameTime; + + //Increment fps. + incrementFps(framePeriod); + + } + }.start(); + } + + /** + * Update position of boats in race (e.g, 5th), no position if on starting leg or DNF. + * @param boats The list of boats to update. + */ + private void updateBoatPositions(ObservableList boats) { + + //Sort boats. + sortBoatsByPosition(boats); + + //Assign new positions. + for (int i = 0; i < boats.size(); i++) { + VisualiserBoat boat = boats.get(i); + + + if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) { + + boat.setPosition("-"); + + } else { + boat.setPosition(Integer.toString(i + 1)); + } + } + + } + + /** + * Sorts the list of boats by their position within the race. + * @param boats The list of boats in the race. + */ + private void sortBoatsByPosition(ObservableList boats) { + + FXCollections.sort(boats, (a, b) -> { + //Get the difference in leg numbers. + int legNumberDelta = b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber(); + + //If they're on the same leg, we need to compare time to finish leg. + if (legNumberDelta == 0) { + return (int) Duration.between(b.getEstimatedTimeAtNextMark(), a.getEstimatedTimeAtNextMark()).toMillis(); + } else { + return legNumberDelta; + } + + }); + + } + + + + + /** + * Returns the boats participating in the race. + * @return ObservableList of boats participating in the race. + */ + public ObservableList getBoats() { + return boats; + } + + /** + * Returns the order in which boats completed each leg. Maps the leg to a list of boats, ordered by the order in which they finished the leg. + * @return Leg completion order for each leg. + */ + public Map> getLegCompletionOrder() { + return legCompletionOrder; + } + + /** + * Takes an estimated time an event will occur, and converts it to the + * number of seconds before the event will occur. + * + * @param estTimeMillis The estimated time, in milliseconds. + * @param currentTime The current time, in milliseconds. + * @return int difference between time the race started and the estimated time + */ + private int convertEstTime(long estTimeMillis, long currentTime) { + + //Calculate millisecond delta. + long estElapsedMillis = estTimeMillis - currentTime; + + //Convert milliseconds to seconds. + int estElapsedSecs = Math.round(estElapsedMillis / 1000); + + return estElapsedSecs; + + } + +} diff --git a/mock/src/main/resources/mockXML/boatTest.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml similarity index 100% rename from mock/src/main/resources/mockXML/boatTest.xml rename to racevisionGame/src/main/resources/mock/mockXML/boatTest.xml diff --git a/mock/src/main/resources/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml similarity index 100% rename from mock/src/main/resources/mockXML/raceTest.xml rename to racevisionGame/src/main/resources/mock/mockXML/raceTest.xml diff --git a/mock/src/main/resources/mockXML/regattaTest.xml b/racevisionGame/src/main/resources/mock/mockXML/regattaTest.xml similarity index 100% rename from mock/src/main/resources/mockXML/regattaTest.xml rename to racevisionGame/src/main/resources/mock/mockXML/regattaTest.xml diff --git a/mock/src/main/resources/polars/acc_polars.csv b/racevisionGame/src/main/resources/mock/polars/acc_polars.csv similarity index 100% rename from mock/src/main/resources/polars/acc_polars.csv rename to racevisionGame/src/main/resources/mock/polars/acc_polars.csv diff --git a/network/src/main/resources/dataDumps/ac35.bin b/racevisionGame/src/main/resources/network/dataDumps/ac35.bin similarity index 100% rename from network/src/main/resources/dataDumps/ac35.bin rename to racevisionGame/src/main/resources/network/dataDumps/ac35.bin diff --git a/network/src/main/resources/raceXML/Boats.xml b/racevisionGame/src/main/resources/network/raceXML/Boats.xml similarity index 100% rename from network/src/main/resources/raceXML/Boats.xml rename to racevisionGame/src/main/resources/network/raceXML/Boats.xml diff --git a/network/src/main/resources/raceXML/Race.xml b/racevisionGame/src/main/resources/network/raceXML/Race.xml similarity index 100% rename from network/src/main/resources/raceXML/Race.xml rename to racevisionGame/src/main/resources/network/raceXML/Race.xml diff --git a/mock/src/test/resources/raceXML/Regatta.xml b/racevisionGame/src/main/resources/network/raceXML/Regatta.xml similarity index 100% rename from mock/src/test/resources/raceXML/Regatta.xml rename to racevisionGame/src/main/resources/network/raceXML/Regatta.xml diff --git a/visualiser/src/main/resources/images/arrow.png b/racevisionGame/src/main/resources/visualiser/images/arrow.png similarity index 100% rename from visualiser/src/main/resources/images/arrow.png rename to racevisionGame/src/main/resources/visualiser/images/arrow.png diff --git a/visualiser/src/main/resources/mockXML/boatXML/boatTest.xml b/racevisionGame/src/main/resources/visualiser/mock/mockXML/boatXML/boatTest.xml similarity index 100% rename from visualiser/src/main/resources/mockXML/boatXML/boatTest.xml rename to racevisionGame/src/main/resources/visualiser/mock/mockXML/boatXML/boatTest.xml diff --git a/visualiser/src/main/resources/mockXML/raceXML/raceTest.xml b/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTest.xml similarity index 100% rename from visualiser/src/main/resources/mockXML/raceXML/raceTest.xml rename to racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTest.xml diff --git a/visualiser/src/main/resources/mockXML/regattaXML/regattaTest.xml b/racevisionGame/src/main/resources/visualiser/mock/mockXML/regattaXML/regattaTest.xml similarity index 100% rename from visualiser/src/main/resources/mockXML/regattaXML/regattaTest.xml rename to racevisionGame/src/main/resources/visualiser/mock/mockXML/regattaXML/regattaTest.xml diff --git a/visualiser/src/main/resources/raceXML/Boats.xml b/racevisionGame/src/main/resources/visualiser/raceXML/Boats.xml similarity index 100% rename from visualiser/src/main/resources/raceXML/Boats.xml rename to racevisionGame/src/main/resources/visualiser/raceXML/Boats.xml diff --git a/mock/src/test/resources/raceXML/Race.xml b/racevisionGame/src/main/resources/visualiser/raceXML/Race.xml similarity index 100% rename from mock/src/test/resources/raceXML/Race.xml rename to racevisionGame/src/main/resources/visualiser/raceXML/Race.xml diff --git a/network/src/main/resources/raceXML/Regatta.xml b/racevisionGame/src/main/resources/visualiser/raceXML/Regatta.xml similarity index 100% rename from network/src/main/resources/raceXML/Regatta.xml rename to racevisionGame/src/main/resources/visualiser/raceXML/Regatta.xml diff --git a/visualiser/src/main/resources/raceXML/bermuda_AC35.xml b/racevisionGame/src/main/resources/visualiser/raceXML/bermuda_AC35.xml similarity index 100% rename from visualiser/src/main/resources/raceXML/bermuda_AC35.xml rename to racevisionGame/src/main/resources/visualiser/raceXML/bermuda_AC35.xml diff --git a/visualiser/src/main/resources/scenes/arrow.fxml b/racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml similarity index 100% rename from visualiser/src/main/resources/scenes/arrow.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/arrow.fxml diff --git a/visualiser/src/main/resources/scenes/connect.fxml b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml similarity index 97% rename from visualiser/src/main/resources/scenes/connect.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/connect.fxml index 3b0ed923..32384999 100644 --- a/visualiser/src/main/resources/scenes/connect.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/connect.fxml @@ -4,7 +4,7 @@ - + diff --git a/visualiser/src/main/resources/scenes/finish.fxml b/racevisionGame/src/main/resources/visualiser/scenes/finish.fxml similarity index 98% rename from visualiser/src/main/resources/scenes/finish.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/finish.fxml index 0dcaf719..4f660158 100644 --- a/visualiser/src/main/resources/scenes/finish.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/finish.fxml @@ -3,7 +3,7 @@ - + diff --git a/visualiser/src/main/resources/scenes/main.fxml b/racevisionGame/src/main/resources/visualiser/scenes/main.fxml similarity index 82% rename from visualiser/src/main/resources/scenes/main.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/main.fxml index b538e1c6..9f1ae541 100644 --- a/visualiser/src/main/resources/scenes/main.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/main.fxml @@ -1,7 +1,7 @@ - + diff --git a/visualiser/src/main/resources/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml similarity index 98% rename from visualiser/src/main/resources/scenes/race.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/race.fxml index 97168ac1..76da5379 100644 --- a/visualiser/src/main/resources/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -6,7 +6,7 @@ - + diff --git a/visualiser/src/main/resources/scenes/start.fxml b/racevisionGame/src/main/resources/visualiser/scenes/start.fxml similarity index 98% rename from visualiser/src/main/resources/scenes/start.fxml rename to racevisionGame/src/main/resources/visualiser/scenes/start.fxml index b56945e2..239a6f5d 100644 --- a/visualiser/src/main/resources/scenes/start.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/start.fxml @@ -3,7 +3,7 @@ - + @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java b/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java new file mode 100644 index 00000000..78d3364f --- /dev/null +++ b/racevisionGame/src/test/java/mock/dataInput/PolarParserTest.java @@ -0,0 +1,74 @@ +package mock.dataInput; + + +import mock.exceptions.InvalidPolarFileException; +import mock.model.Polars; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Tests the polar parser. + */ +public class PolarParserTest { + + + /** + * Tests if we can parse a valid polar data file (stored in a string), and create a polar table. + */ + @Test + public void testParseValidFile() { + + try { + //Parse data file. + Polars polars = PolarParser.parse("mock/polars/acc_polars.csv"); + + } catch (InvalidPolarFileException e) { + fail("Couldn't parse polar file."); + } + + } + + /** + * Tests if we can parse an invalid polar data file - it is expected that parsing will fail. + */ + @Test + public void testParseInvalidFile() { + + try { + //Parse data file. + Polars polars = PolarParser.parse("mock/polars/invalid_polars.csv"); + + //As the file is invalid, we shouldn't reach here. + fail("exception should have been thrown, but wasn't."); + + } catch (InvalidPolarFileException e) { + assertTrue(true); + } + + } + + /** + * Tests if we can parse an non-existent polar data file - it is expected that parsing will fail. + */ + @Test + public void testParseNonExistentFile() { + + try { + //Parse data file. + Polars polars = PolarParser.parse("mock/polars/this_does_not_exist.csv"); + + //As the file doesn't exist, we shouldn't reach here. + fail("exception should have been thrown, but wasn't."); + + } catch (InvalidPolarFileException e) { + assertTrue(true); + } + + } + + + + +} diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java new file mode 100644 index 00000000..b1ee551c --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java @@ -0,0 +1,7 @@ +package mock.model; + +import static org.junit.Assert.*; + +public class MockBoatTest { +//TODO +} diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java new file mode 100644 index 00000000..cf530665 --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java @@ -0,0 +1,7 @@ +package mock.model; + +import static org.junit.Assert.*; + +public class MockRaceTest { +//TODO +} diff --git a/mock/src/test/java/seng302/Model/PolarsTest.java b/racevisionGame/src/test/java/mock/model/PolarsTest.java similarity index 93% rename from mock/src/test/java/seng302/Model/PolarsTest.java rename to racevisionGame/src/test/java/mock/model/PolarsTest.java index f7835662..491e5935 100644 --- a/mock/src/test/java/seng302/Model/PolarsTest.java +++ b/racevisionGame/src/test/java/mock/model/PolarsTest.java @@ -1,19 +1,34 @@ -package seng302.Model; +package mock.model; +import mock.dataInput.PolarParser; +import mock.exceptions.InvalidPolarFileException; import org.junit.Before; import org.junit.Test; -import seng302.DataInput.PolarParser; -import seng302.Exceptions.InvalidPolarFileException; +import shared.model.Bearing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import static org.testng.Assert.*; /** - * Created by f123 on 10-May-17. + * Tests the polars class, to see if various VMGs can be calculated. */ public class PolarsTest { + /** + * The polars table used in each test. + */ private Polars polars = null; + + /** + * The epsilon, in degrees, for computed bearing angles. + */ private double angleEpsilon = 2; + + /** + * The epsilon, in knots, for computed speeds. + */ private double speedEpsilon = 0.5; @@ -25,10 +40,10 @@ public class PolarsTest { //Read data. try { //Parse data file. - polars = PolarParser.parse("polars/acc_polars.csv"); + polars = PolarParser.parse("mock/polars/acc_polars.csv"); } catch (InvalidPolarFileException e) { - assertTrue(false); + fail("Couldn't parse polar file."); } } diff --git a/racevisionGame/src/test/java/mock/model/VMGTest.java b/racevisionGame/src/test/java/mock/model/VMGTest.java new file mode 100644 index 00000000..173df3ee --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/VMGTest.java @@ -0,0 +1,58 @@ +package mock.model; + +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; + +import static org.junit.Assert.*; + +public class VMGTest { + + /** + * VMG object to test. + */ + private VMG vmg; + + /** + * Speed of the VMG, in knots. + */ + private double speed; + + private double speedEpsilon = 0.001; + + /** + * Bearing of the vmg. + */ + private Bearing bearing; + + private double bearingDegreeEpsilon = 0.001; + + + + /** + * Constructs a VMG object. + */ + @Before + public void setUp() { + + speed = 14.98; + + bearing = Bearing.fromDegrees(78.5); + + + vmg = new VMG(speed, bearing); + } + + + @Test + public void testVMGSpeed() { + + assertEquals(speed, vmg.getSpeed(), speedEpsilon); + } + + @Test + public void testVMGBearing() { + + assertEquals(bearing.degrees(), vmg.getBearing().degrees(), bearingDegreeEpsilon); + } +} diff --git a/network/src/test/java/seng302/Networking/BinaryMessageDecoderTest.java b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java similarity index 63% rename from network/src/test/java/seng302/Networking/BinaryMessageDecoderTest.java rename to racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java index 6877d180..7414d709 100644 --- a/network/src/test/java/seng302/Networking/BinaryMessageDecoderTest.java +++ b/racevisionGame/src/test/java/network/BinaryMessageDecoderTest.java @@ -1,15 +1,24 @@ -package seng302.Networking; +package network; +import network.Exceptions.InvalidMessageException; +import network.MessageDecoders.XMLMessageDecoder; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.AC35Data; +import network.Messages.Enums.MessageType; +import network.Messages.Enums.XMLMessageType; +import network.Messages.XMLMessage; import org.junit.Assert; import org.junit.Test; -import seng302.Networking.Exceptions.InvalidMessageException; -import seng302.Networking.MessageDecoders.XMLMessageDecoder; -import seng302.Networking.MessageEncoders.XMLMessageEncoder; -import seng302.Networking.Messages.AC35Data; -import seng302.Networking.Messages.Enums.MessageType; -import seng302.Networking.Messages.XMLMessage; +import shared.dataInput.XMLReader; +import shared.exceptions.XMLReaderException; -import java.io.*; +import javax.xml.transform.TransformerException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.fail; /** * Created by hba56 on 21/04/17. @@ -18,18 +27,19 @@ public class BinaryMessageDecoderTest { @Test public void decodeTest(){ try{ - StringBuilder xmlString; - BufferedReader br = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(("../../raceXML/Regatta.xml")))); - String line; - xmlString = new StringBuilder(); - while((line=br.readLine())!= null){ - xmlString.append(line.trim()); - } + String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + long time = System.currentTimeMillis(); - XMLMessageEncoder testEncoder = new XMLMessageEncoder((short)1, time, (byte)7, (short)1, (short)xmlString.length(), xmlString.toString()); - byte[] encodedMessage = testEncoder.encode(); + XMLMessage xmlMessagePre = new XMLMessage( + (byte)1, + 1, + time, + XMLMessageType.REGATTA, + (short)1, + xmlString ); + + byte[] encodedMessage = RaceVisionByteEncoder.xmlMessage(xmlMessagePre); BinaryMessageEncoder testMessage = new BinaryMessageEncoder(MessageType.XMLMESSAGE, time, 1, (short)encodedMessage.length, encodedMessage); @@ -65,7 +75,7 @@ public class BinaryMessageDecoderTest { Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); Assert.assertEquals((short)1, decoderXML.getAckNumber()); Assert.assertEquals(time, decoderXML.getTimeStamp()); - Assert.assertEquals((byte)7, decoderXML.getXmlMsgSubType()); + Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); @@ -77,8 +87,8 @@ public class BinaryMessageDecoderTest { // } // Assert.assertEquals(xmlString.toString(), contents); - }catch (IOException e){ - Assert.assertFalse(e.getMessage(), true); + } catch (XMLReaderException | TransformerException e){ + fail("couldn't read file" + e.getMessage()); } } } diff --git a/network/src/test/java/seng302/Networking/MessageDecoders/BoatLocationDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java similarity index 88% rename from network/src/test/java/seng302/Networking/MessageDecoders/BoatLocationDecoderTest.java rename to racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java index fb1cc529..2f269257 100644 --- a/network/src/test/java/seng302/Networking/MessageDecoders/BoatLocationDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/BoatLocationDecoderTest.java @@ -1,9 +1,9 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatLocation; import org.junit.Assert; import org.junit.Test; -import seng302.Networking.Messages.BoatLocation; -import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; /** @@ -18,8 +18,8 @@ public class BoatLocationDecoderTest { (short) 6, (short) 7, 8, 9, 10, 11, (short) 12, 13, 14 , (short) 15, 16, 17, (short) 18); - RaceVisionByteEncoder raceVisionByteEncoder = new RaceVisionByteEncoder(); - byte [] testEncodedMessage = raceVisionByteEncoder.boatLocation(testMessage); + + byte [] testEncodedMessage = RaceVisionByteEncoder.boatLocation(testMessage); BoatLocationDecoder testDecoder = new BoatLocationDecoder(testEncodedMessage); BoatLocation decodedTest = testDecoder.getMessage(); diff --git a/network/src/test/java/seng302/Networking/MessageDecoders/CourseWindDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java similarity index 89% rename from network/src/test/java/seng302/Networking/MessageDecoders/CourseWindDecoderTest.java rename to racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java index 837c4ff4..53793b81 100644 --- a/network/src/test/java/seng302/Networking/MessageDecoders/CourseWindDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/CourseWindDecoderTest.java @@ -1,9 +1,9 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.CourseWind; import org.junit.Assert; import org.junit.Test; -import seng302.Networking.Messages.CourseWind; -import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; import java.util.ArrayList; @@ -29,9 +29,7 @@ public class CourseWindDecoderTest { testCourseWinds.add(testCourseWind2); - RaceVisionByteEncoder raceVisionByteEncoder = new RaceVisionByteEncoder(); - - byte[] testEncodedCourseWind = raceVisionByteEncoder.courseWind((byte) 1, testCourseWinds); + byte[] testEncodedCourseWind = RaceVisionByteEncoder.courseWind((byte) 1, testCourseWinds); CourseWindDecoder testDecoder = new CourseWindDecoder(testEncodedCourseWind); diff --git a/network/src/test/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java similarity index 76% rename from network/src/test/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoderTest.java rename to racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java index 1dad9635..ce2fe475 100644 --- a/network/src/test/java/seng302/Networking/MessageDecoders/RaceStartStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStartStatusDecoderTest.java @@ -1,8 +1,8 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; +import network.MessageEncoders.RaceVisionByteEncoder; import org.junit.Assert; import org.junit.Test; -import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; /** * Created by hba56 on 23/04/17. @@ -12,9 +12,8 @@ public class RaceStartStatusDecoderTest { public void getByteArrayTest(){ long time = System.currentTimeMillis(); - RaceVisionByteEncoder raceVisionByteEncoder = new RaceVisionByteEncoder(); long time2 = System.currentTimeMillis(); - byte[] encodedRaceStartStatus = raceVisionByteEncoder.raceStartStatus(time, (short)1, + byte[] encodedRaceStartStatus = RaceVisionByteEncoder.raceStartStatus(time, (short)1, time2, 2, (char)3); RaceStartStatusDecoder testDecoder = new RaceStartStatusDecoder(encodedRaceStartStatus); diff --git a/network/src/test/java/seng302/Networking/MessageDecoders/RaceStatusDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java similarity index 90% rename from network/src/test/java/seng302/Networking/MessageDecoders/RaceStatusDecoderTest.java rename to racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java index 51a17ebb..a5600446 100644 --- a/network/src/test/java/seng302/Networking/MessageDecoders/RaceStatusDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/RaceStatusDecoderTest.java @@ -1,12 +1,13 @@ -package seng302.Networking.MessageDecoders; +package network.MessageDecoders; +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.BoatStatus; +import network.Messages.RaceStatus; import org.junit.Assert; import org.junit.Test; -import seng302.Networking.MessageEncoders.RaceVisionByteEncoder; -import seng302.Networking.Messages.BoatStatus; -import seng302.Networking.Messages.RaceStatus; import java.util.ArrayList; +import java.util.List; /** * Created by hba56 on 23/04/17. @@ -36,12 +37,12 @@ public class RaceStatusDecoderTest { BoatStatus boatStatus2 = new BoatStatus(boat2SourceID, boat2Status, boat2LegNumber, boat2PenaltiesAwarded, boat2PenaltiesServed, boat2TimeAtNextMark, boat2TimeAtFinish); int raceID = 585; - int raceStatus = 3; + byte raceStatus = 3; long raceStartTime = time - (1000 * 31); int windDirection = 2341; int windSpeed = 10201; int raceType = 1; - ArrayList boatStatuses = new ArrayList<>(2); + List boatStatuses = new ArrayList<>(2); boatStatuses.add(boatStatus1); boatStatuses.add(boatStatus2); diff --git a/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java new file mode 100644 index 00000000..b0d0eee3 --- /dev/null +++ b/racevisionGame/src/test/java/network/MessageDecoders/XMLMessageDecoderTest.java @@ -0,0 +1,54 @@ +package network.MessageDecoders; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.XMLMessageType; +import network.Messages.XMLMessage; +import org.junit.Assert; +import org.junit.Test; +import shared.dataInput.XMLReader; +import shared.exceptions.XMLReaderException; + +import javax.xml.transform.TransformerException; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.fail; + +/** + * Created by hba56 on 20/04/17. + */ +public class XMLMessageDecoderTest { + @Test + public void getByteArrayTest(){ + try{ + String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + + long time = System.currentTimeMillis(); + + XMLMessage message = new XMLMessage( + (byte)1, + 1, + time, + XMLMessageType.REGATTA, + (short)1, + xmlString ); + + byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); + + + + XMLMessageDecoder decoderXML = new XMLMessageDecoder(encodedXML); + decoderXML.decode(); + + + Assert.assertEquals((byte)1, decoderXML.getMessageVersionNumber()); + Assert.assertEquals((short)1, decoderXML.getAckNumber()); + Assert.assertEquals(time, decoderXML.getTimeStamp()); + Assert.assertEquals(XMLMessageType.REGATTA, decoderXML.getXmlMsgSubType()); + Assert.assertEquals((short)1, decoderXML.getSequenceNumber()); + Assert.assertEquals((short)xmlString.length(), decoderXML.getXmlMsgLength()); + + } catch (XMLReaderException | TransformerException e){ + fail("couldn't read file" + e.getMessage()); + } + } +} diff --git a/network/src/test/java/seng302/Networking/AC35UnitConverter.java b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java similarity index 71% rename from network/src/test/java/seng302/Networking/AC35UnitConverter.java rename to racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java index 0471c8f2..24fc2be2 100644 --- a/network/src/test/java/seng302/Networking/AC35UnitConverter.java +++ b/racevisionGame/src/test/java/network/Utils/AC35UnitConverterTest.java @@ -1,14 +1,17 @@ -package seng302.Networking; +package network.Utils; import org.junit.Test; +import static network.Utils.AC35UnitConverter.convertGPS; +import static network.Utils.AC35UnitConverter.convertGPSToInt; +import static network.Utils.AC35UnitConverter.convertHeading; +import static network.Utils.AC35UnitConverter.convertTrueWindAngle; import static org.junit.Assert.assertTrue; -import static seng302.Networking.Utils.AC35UnitConverter.*; /** * Created by fwy13 on 4/05/17. */ -public class AC35UnitConverter { +public class AC35UnitConverterTest { @Test public void testConvertGPS(){ diff --git a/network/src/test/java/seng302/Networking/ByteConverterTest.java b/racevisionGame/src/test/java/network/Utils/ByteConverterTest.java similarity index 98% rename from network/src/test/java/seng302/Networking/ByteConverterTest.java rename to racevisionGame/src/test/java/network/Utils/ByteConverterTest.java index fc59cc87..32ffc6ee 100644 --- a/network/src/test/java/seng302/Networking/ByteConverterTest.java +++ b/racevisionGame/src/test/java/network/Utils/ByteConverterTest.java @@ -1,7 +1,7 @@ -package seng302.Networking; +package network.Utils; +import network.Utils.ByteConverter; import org.junit.Test; -import seng302.Networking.Utils.ByteConverter; import java.nio.ByteOrder; diff --git a/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java b/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java new file mode 100644 index 00000000..44a99997 --- /dev/null +++ b/racevisionGame/src/test/java/network/XMLMessageEncoderTest.java @@ -0,0 +1,50 @@ +package network; + +import network.MessageEncoders.RaceVisionByteEncoder; +import network.Messages.Enums.XMLMessageType; +import network.Messages.XMLMessage; +import org.junit.Assert; +import org.junit.Test; +import shared.dataInput.XMLReader; +import shared.exceptions.XMLReaderException; + +import javax.xml.transform.TransformerException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.fail; + +/** + * Created by hba56 on 19/04/17. + */ +public class XMLMessageEncoderTest { + @Test + public void getByteArrayTest() { + try { + + String xmlString = XMLReader.readXMLFileToString("network/raceXML/Regatta.xml", StandardCharsets.UTF_8); + + + XMLMessage message = new XMLMessage( + (byte)1, + 1, + System.currentTimeMillis(), + XMLMessageType.REGATTA, + (short)1, + xmlString ); + + int xmlMessageLength = xmlString.getBytes().length; + + byte[] encodedXML = RaceVisionByteEncoder.xmlMessage(message); + + //1 + 2 + 6 + 1 + 2 + 2 + xml.byteLength + Assert.assertEquals(14 + xmlMessageLength, encodedXML.length); + + } catch (XMLReaderException | TransformerException e){ + fail("couldn't read file" + e.getMessage()); + } + } + +} diff --git a/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java similarity index 55% rename from mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java rename to racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java index 89afea62..19370be0 100644 --- a/mock/src/test/java/seng302/DataInput/BoatXMLReaderTest.java +++ b/racevisionGame/src/test/java/shared/dataInput/BoatXMLReaderTest.java @@ -1,40 +1,60 @@ -package seng302.DataInput; +package shared.dataInput; + import org.junit.Before; import org.junit.Test; import org.xml.sax.SAXException; -import seng302.Model.Boat; -import seng302.Model.Mark; -import seng302.Model.Polars; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.XMLReaderException; +import shared.model.Boat; +import shared.model.Mark; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; -//import static org.testng.Assert.*; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Created by cbt24 on 10/05/17. */ public class BoatXMLReaderTest { - BoatDataSource boatData; - List boats; - List marks; + + private BoatDataSource boatData; + private List boats; + private List marks; + @Before public void setUp() { + + String boatXMLString = null; + try { - boatData = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); + boatXMLString = XMLReader.readXMLFileToString("mock/mockXML/boatTest.xml", StandardCharsets.UTF_8); + + } catch (Exception e) { + e.printStackTrace(); + fail("could not read boat xml file into a string"); + } + + + try { + + boatData = new BoatXMLReader(boatXMLString, XMLFileType.Contents); + boats = new ArrayList<>(boatData.getBoats().values()); marks = new ArrayList<>(boatData.getMarkerBoats().values()); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { + + } catch (XMLReaderException | InvalidBoatDataException e) { e.printStackTrace(); + assertTrue(false); } } @@ -57,4 +77,6 @@ public class BoatXMLReaderTest { assertEquals(names[i], marks.get(i).getName()); } } + + //TODO should add more tests for various BoatXMLReader functions. } diff --git a/visualiser/src/test/java/seng302/Mock/BoatsXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java similarity index 100% rename from visualiser/src/test/java/seng302/Mock/BoatsXMLTest.java rename to racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java diff --git a/visualiser/src/test/java/seng302/Mock/FailBoatXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java similarity index 100% rename from visualiser/src/test/java/seng302/Mock/FailBoatXMLTest.java rename to racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java diff --git a/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java new file mode 100644 index 00000000..0539a990 --- /dev/null +++ b/racevisionGame/src/test/java/shared/dataInput/RaceXMLReaderTest.java @@ -0,0 +1,5 @@ +package shared.dataInput; + +public class RaceXMLReaderTest { +//TODO +} diff --git a/visualiser/src/test/java/seng302/Mock/RegattaXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java similarity index 82% rename from visualiser/src/test/java/seng302/Mock/RegattaXMLTest.java rename to racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java index c8cadb39..ad9bcd95 100644 --- a/visualiser/src/test/java/seng302/Mock/RegattaXMLTest.java +++ b/racevisionGame/src/test/java/shared/dataInput/RegattaXMLReaderTest.java @@ -1,7 +1,8 @@ -package seng302.Mock; +package shared.dataInput; import org.junit.Before; import org.junit.Test; +import shared.enums.XMLFileType; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -10,13 +11,13 @@ import static org.junit.Assert.fail; * Created by jjg64 on 19/04/17. * Tests RegattaXMLReader */ -public class RegattaXMLTest { +public class RegattaXMLReaderTest { private RegattaXMLReader regatta; @Before public void findFile() { try { - regatta = new RegattaXMLReader("mockXML/regattaXML/regattaTest.xml"); + regatta = new RegattaXMLReader("shared/mockXML/regattaXML/regattaTest.xml", XMLFileType.ResourcePath); } catch (Exception e) { fail("Cannot find mockXML/regattaXML/regattaTest.xml in the resources folder"); } diff --git a/racevisionGame/src/test/java/shared/dataInput/XMLReaderTest.java b/racevisionGame/src/test/java/shared/dataInput/XMLReaderTest.java new file mode 100644 index 00000000..9fbb3421 --- /dev/null +++ b/racevisionGame/src/test/java/shared/dataInput/XMLReaderTest.java @@ -0,0 +1,5 @@ +package shared.dataInput; + +public class XMLReaderTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/AngleTest.java b/racevisionGame/src/test/java/shared/model/AngleTest.java new file mode 100644 index 00000000..6e03e788 --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/AngleTest.java @@ -0,0 +1,7 @@ +package shared.model; + +import static org.junit.Assert.*; + +public class AngleTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/AzimuthTest.java b/racevisionGame/src/test/java/shared/model/AzimuthTest.java new file mode 100644 index 00000000..9a3c3431 --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/AzimuthTest.java @@ -0,0 +1,7 @@ +package shared.model; + +import static org.junit.Assert.*; + +public class AzimuthTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/BearingTest.java b/racevisionGame/src/test/java/shared/model/BearingTest.java new file mode 100644 index 00000000..3855e829 --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/BearingTest.java @@ -0,0 +1,7 @@ +package shared.model; + +import static org.junit.Assert.*; + +public class BearingTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/BoatTest.java b/racevisionGame/src/test/java/shared/model/BoatTest.java new file mode 100644 index 00000000..c087708c --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/BoatTest.java @@ -0,0 +1,173 @@ +package shared.model; + + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Created by esa46 on 22/03/17. + */ +public class BoatTest { + + + private GPSCoordinate ORIGIN_COORDS; + private Boat TEST_BOAT; + + @Before + public void setUp() { + ORIGIN_COORDS = new GPSCoordinate(0, 0); + TEST_BOAT = new Boat(1, "Test", "tt"); + TEST_BOAT.setCurrentPosition(ORIGIN_COORDS); + } + + //TODO these bearing tests could be tidied up to reduce code repetition. + //TODO also, most of these tests may be more appropriate in GPSCoordinateTest + + @Test + public void calculateDueNorthAzimuthReturns0() { + + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(50, 0)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8); + } + + @Test + public void calculateDueSouthAzimuthReturns180() { + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8); + } + + + @Test + public void calculateDueEastAzimuthReturns90() { + + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(0, 50)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8); + } + + + @Test + public void calculateDueWestAzimuthReturnsNegative90() { + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(0, -50)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()).degrees(), -90, 1e-8); + + } + + @Test + public void calculateDueNorthHeadingReturns0() { + + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(50, 0)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(TEST_BOAT.getCurrentPosition(), endMarker.getAverageGPSCoordinate()).degrees(), 0, 1e-8); + } + + + @Test + public void calculateDueEastHeadingReturns90() { + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(0, 50)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(TEST_BOAT.getCurrentPosition(), endMarker.getAverageGPSCoordinate()).degrees(), 90, 1e-8); + } + + @Test + public void calculateDueSouthHeadingReturns180() { + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(-50, 0)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateAzimuth(TEST_BOAT.getCurrentPosition(), endMarker.getAverageGPSCoordinate()).degrees(), -180, 1e-8); + } + + @Test + public void calculateDueWestHeadingReturns270() { + CompoundMark startMarker = new CompoundMark( + 1, + "start", + new Mark(1, "test origin 1", ORIGIN_COORDS) ); + + CompoundMark endMarker = new CompoundMark( + 2, + "end", + new Mark(2, "test mark 2", new GPSCoordinate(0, -50)) ); + + Leg start = new Leg("Start", startMarker, endMarker, 0); + TEST_BOAT.setCurrentLeg(start); + assertEquals(GPSCoordinate.calculateBearing(TEST_BOAT.getCurrentPosition(), endMarker.getAverageGPSCoordinate()).degrees(), 270, 1e-8); + } + + + //TODO test Boat#setCurrentLeg and it's effects. +} diff --git a/mock/src/test/java/seng302/Model/CompoundMarkTest.java b/racevisionGame/src/test/java/shared/model/CompoundMarkTest.java similarity index 54% rename from mock/src/test/java/seng302/Model/CompoundMarkTest.java rename to racevisionGame/src/test/java/shared/model/CompoundMarkTest.java index 72b2a9c0..5a305bac 100644 --- a/mock/src/test/java/seng302/Model/CompoundMarkTest.java +++ b/racevisionGame/src/test/java/shared/model/CompoundMarkTest.java @@ -1,24 +1,34 @@ -package seng302.Model; +package shared.model; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -//import static org.testng.AssertJUnit.assertEquals; /** * Created by esa46 on 29/03/17. */ public class CompoundMarkTest { - GPSCoordinate ORIGIN_COORD = new GPSCoordinate(0, 0); + private GPSCoordinate ORIGIN_COORD; + + + @Before + public void setUp() throws Exception { + ORIGIN_COORD = new GPSCoordinate(0, 0); + } @Test public void averageOfSingleMarkAtOriginIsSingleMark() { - CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD)); - assertTrue(testMark.getAverageGPSCoordinate().equals(ORIGIN_COORD)); + CompoundMark testMark = new CompoundMark( + 1, + "test", + new Mark(1, "test origin 1", ORIGIN_COORD) ); + + assertTrue(testMark.getAverageGPSCoordinate().equals(ORIGIN_COORD) ); } @@ -26,7 +36,11 @@ public class CompoundMarkTest { public void averageOfSingleMarkIsSingleMark() { GPSCoordinate testCoord = new GPSCoordinate(20, 25); - CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord)); + CompoundMark testMark = new CompoundMark( + 1, + "test", + new Mark(1, "test origin 1", testCoord)); + assertTrue(testMark.getAverageGPSCoordinate().equals(testCoord)); } @@ -35,7 +49,12 @@ public class CompoundMarkTest { public void averageLatOfTwoMarksIsAccurate() { GPSCoordinate testCoord = new GPSCoordinate(0.001, 0); - CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord)); + CompoundMark testMark = new CompoundMark( + 1, + "test", + new Mark(1, "test origin 1", ORIGIN_COORD), + new Mark(2, "test origin 2", testCoord) ); + assertEquals(testMark.getAverageGPSCoordinate(), new GPSCoordinate(0.0005, 0)); } @@ -44,18 +63,27 @@ public class CompoundMarkTest { public void averageLongOfTwoMarksIsAccurate() { GPSCoordinate testCoord = new GPSCoordinate(0, 10); - CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", ORIGIN_COORD), new Mark(2, "test origin 2", testCoord)); + CompoundMark testMark = new CompoundMark( + 1, + "test", + new Mark(1, "test origin 1", ORIGIN_COORD), + new Mark(2, "test origin 2", testCoord) ); + assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(0, 5))); } - @Ignore + @Test public void averageLatAndLongOfTwoMarksIsAccurate() { GPSCoordinate testCoord1 = new GPSCoordinate(0.0, 30); GPSCoordinate testCoord2 = new GPSCoordinate(0.001, 60); - CompoundMark testMark = new CompoundMark(new Mark(1, "test origin 1", testCoord1), new Mark(2, "test origin 2", testCoord2)); + CompoundMark testMark = new CompoundMark( + 1, + "test", + new Mark(1, "test origin 1", testCoord1), + new Mark(2, "test origin 2", testCoord2) ); assertEquals(testMark.getAverageGPSCoordinate().getLatitude(), 0.00051776, 1e-8); assertEquals(testMark.getAverageGPSCoordinate().getLongitude(), 45.000000, 1e-8); diff --git a/mock/src/test/java/seng302/Model/GPSCoordinateTest.java b/racevisionGame/src/test/java/shared/model/GPSCoordinateTest.java similarity index 95% rename from mock/src/test/java/seng302/Model/GPSCoordinateTest.java rename to racevisionGame/src/test/java/shared/model/GPSCoordinateTest.java index 9f789069..66bf1f2e 100644 --- a/mock/src/test/java/seng302/Model/GPSCoordinateTest.java +++ b/racevisionGame/src/test/java/shared/model/GPSCoordinateTest.java @@ -1,4 +1,4 @@ -package seng302.Model; +package shared.model; import org.junit.Before; import org.junit.Test; @@ -6,8 +6,9 @@ import org.junit.Test; import java.util.ArrayList; import java.util.List; -import static junit.framework.TestCase.assertTrue; -import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + /** * Created by jjg64 on 11/05/17. diff --git a/mock/src/test/java/seng302/Model/LegTest.java b/racevisionGame/src/test/java/shared/model/LegTest.java similarity index 82% rename from mock/src/test/java/seng302/Model/LegTest.java rename to racevisionGame/src/test/java/shared/model/LegTest.java index cb8d70d4..689faced 100644 --- a/mock/src/test/java/seng302/Model/LegTest.java +++ b/racevisionGame/src/test/java/shared/model/LegTest.java @@ -1,20 +1,29 @@ -package seng302.Model; +package shared.model; import org.geotools.referencing.GeodeticCalculator; +import org.junit.Before; import org.junit.Test; -import seng302.Constants; - import java.awt.geom.Point2D; -import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertEquals; /** - * Created by esa46 on 22/03/17. + * */ public class LegTest { - private CompoundMark ORIGIN_COMPOUND_MARKER = new CompoundMark(new Mark(1, "test mark1", new GPSCoordinate(0, 0))); + private CompoundMark ORIGIN_COMPOUND_MARKER; + + + @Before + public void setUp() throws Exception { + ORIGIN_COMPOUND_MARKER = new CompoundMark( + 1, + "origin", + new Mark(1, "test mark1", new GPSCoordinate(0, 0)) ); + } + @Test public void calculateDistanceHandles5nmNorth() { @@ -71,7 +80,10 @@ public class LegTest { GPSCoordinate coords = new GPSCoordinate(point.getY(), point.getX()); - return new CompoundMark(new Mark(3, "test mark3", coords)); + return new CompoundMark( + 1, + "test compound 3", + new Mark(3, "test mark3", coords) ); } } diff --git a/racevisionGame/src/test/java/shared/model/MarkTest.java b/racevisionGame/src/test/java/shared/model/MarkTest.java new file mode 100644 index 00000000..a78eb512 --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/MarkTest.java @@ -0,0 +1,7 @@ +package shared.model; + +import static org.junit.Assert.*; + +public class MarkTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/RaceClockTest.java b/racevisionGame/src/test/java/shared/model/RaceClockTest.java new file mode 100644 index 00000000..2dd62ff5 --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/RaceClockTest.java @@ -0,0 +1,7 @@ +package shared.model; + +import static org.junit.Assert.*; + +public class RaceClockTest { +//TODO +} diff --git a/racevisionGame/src/test/java/shared/model/RaceTest.java b/racevisionGame/src/test/java/shared/model/RaceTest.java new file mode 100644 index 00000000..3334aefd --- /dev/null +++ b/racevisionGame/src/test/java/shared/model/RaceTest.java @@ -0,0 +1,345 @@ +//package shared.model; +// +// +//import mock.model.Polars; +//import org.junit.Before; +//import org.junit.Ignore; +//import org.junit.Test; +//import org.mockito.Mockito; +//import org.xml.sax.SAXException; +// +//import javax.xml.parsers.ParserConfigurationException; +//import java.io.IOException; +//import java.text.ParseException; +//import java.util.ArrayList; +// +//import static org.junit.Assert.*; +//import static org.mockito.Mockito.*; +// +///** +// * Created by esa46 on 15/03/17. +// */ +//public class RaceTest{ +// +// +// private CompoundMark ORIGIN; +// +// private CompoundMark THREE_NM_FROM_ORIGIN; +// +// private CompoundMark FIFTEEN_NM_FROM_ORIGIN; +// +// private ArrayList TEST_LEGS = new ArrayList<>(); +// +// private int START_LEG_DISTANCE = 3; +// private int MIDDLE_LEG_DISTANCE = 12; +// +// private Leg START_LEG; +// +// private Leg MIDDLE_LEG; +// +// private Leg FINISH_LEG; +// +// +// @Before +// public void setUp() { +// +// ORIGIN = new CompoundMark( +// 1, +// "origin compound", +// new Mark(1, "test origin 1", new GPSCoordinate(0, 0)) ); +// +// +// THREE_NM_FROM_ORIGIN = new CompoundMark( +// 2, +// "3 NM from origin compound", +// new Mark(2, "test mark 2", new GPSCoordinate(0.050246769, 0)) ); +// +// +// FIFTEEN_NM_FROM_ORIGIN = new CompoundMark( +// 3, +// "15 NM from origin compound", +// new Mark(3, "test mark 3", new GPSCoordinate(0.251233845, 0)) ); +// +// +// START_LEG = new Leg("Start", ORIGIN, THREE_NM_FROM_ORIGIN, 0); +// +// +// MIDDLE_LEG = new Leg("Middle", THREE_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 1); +// +// +// FINISH_LEG = new Leg("Finish", FIFTEEN_NM_FROM_ORIGIN, FIFTEEN_NM_FROM_ORIGIN, 2); +// +// +// TEST_LEGS.add(START_LEG); +// TEST_LEGS.add(MIDDLE_LEG); +// TEST_LEGS.add(FINISH_LEG); +// } +// +// @Ignore +// @Test +// public void countdownTimerSendsBoatLocations() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// testRace.initialiseBoats(); +// testRace.countdownTimer.handle(1); +// verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong()); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void countdownTimerSendsRaceStatusMessages() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// testRace.countdownTimer.handle(1); +// verify(mockOutput, atLeast(1)).parseRaceStatus(any()); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void checkPositionFinishedUpdatesNumberFinishedBoats() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(FINISH_LEG); +// testBoat.setDistanceTravelledInLeg(1); +// testRace.checkPosition(testBoat, 1); +// +// assertEquals(testRace.getNumberOfActiveBoats(), 0); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void checkPositionSetFinishedBoatVelocityTo0() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(FINISH_LEG); +// testBoat.setDistanceTravelledInLeg(1); +// testRace.checkPosition(testBoat, 1); +// +// assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void checkPositionSetsFinishTime() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(FINISH_LEG); +// testBoat.setDistanceTravelledInLeg(1); +// testRace.checkPosition(testBoat, 1); +// +// assertEquals(testBoat.getTimeFinished(), 1, 1e-8); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void checkPositionUnfinishedDoesntUpdateNumberFinishedBoats() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(START_LEG); +// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE); +// testRace.checkPosition(testBoat, 1); +// +// assertEquals(testRace.getNumberOfActiveBoats(), 1); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// +// @Ignore +// @Test +// public void distanceTravelledBeforeUpdatingLegIsRetained() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// RaceDataSource dataSource = new RaceXMLReader("mockXML/raceTest.xml", new BoatXMLReader("mockXML/boatTest.xml", new Polars())); +// Race testRace = new Race(dataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(START_LEG); +// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); +// testRace.checkPosition(testBoat, 0); +// +// assertEquals(testBoat.getDistanceTravelledInLeg(), 1, 1e-7); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void doNotFinishAnswersYesIf100PercentChance() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// +// testRace.setDnfChance(100); +// assertTrue(testRace.doNotFinish()); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void doNotFinishAnswersNoIf0PercentChance() { +// +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// testRace.setDnfChance(0); +// assertFalse(testRace.doNotFinish()); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void boatsAreSetToDNF() { +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// testRace.setDnfChance(100); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(START_LEG); +// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE + 1); +// testRace.checkPosition(testBoat, 1); +// assertEquals(testBoat.getCurrentLeg().getName(), "DNF"); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// +// } +// +// @Ignore +// @Test +// public void updatePositionIgnoresFinishedBoats() { +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(FINISH_LEG); +// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); +// testRace.updatePosition(testBoat, 1, 1); +// assertEquals(testBoat.getCurrentPosition(), ORIGIN.getAverageGPSCoordinate()); +// +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void updatePositionChangesBoatPosition() { +// try { +// MockOutput mockOutput = Mockito.mock(MockOutput.class); +// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml", new Polars()); +// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +// Race testRace = new Race(raceDataSource, mockOutput); +// testRace.initialiseBoats(); +// Boat testBoat = testRace.getBoats().get(0); +// testBoat.setCurrentLeg(START_LEG); +// testBoat.setDistanceTravelledInLeg(START_LEG_DISTANCE - 1); +// testBoat.setCurrentPosition(ORIGIN.getAverageGPSCoordinate()); +// testRace.updatePosition(testBoat, 100, 100); +// assertFalse(testBoat.getCurrentPosition() == ORIGIN.getAverageGPSCoordinate()); +// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +// e.printStackTrace(); +// fail(); +// } +// } +// +// @Ignore +// @Test +// public void windDirectionCorrectValues(){ +//// try { +//// MockOutput mockOutput = Mockito.mock(MockOutput.class); +//// BoatDataSource boatDataSource = new BoatXMLReader("mockXML/boatTest.xml"); +//// RaceDataSource raceDataSource = new RaceXMLReader("mockXML/raceTest.xml", boatDataSource); +//// Race testRace = new Race(raceDataSource, mockOutput); +//// testRace.setChangeWind(1); +//// testRace.setWindDir(65535); +//// testRace.changeWindDir(); +//// assertEquals(100, testRace.getWind()); +//// } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { +//// e.printStackTrace(); +//// fail(); +//// } +// } +// +// +//} diff --git a/racevisionGame/src/test/java/visualiser/model/GraphCoordinateTest.java b/racevisionGame/src/test/java/visualiser/model/GraphCoordinateTest.java new file mode 100644 index 00000000..b4c0d61d --- /dev/null +++ b/racevisionGame/src/test/java/visualiser/model/GraphCoordinateTest.java @@ -0,0 +1,5 @@ +package visualiser.model; + +public class GraphCoordinateTest { +//TODO +} diff --git a/visualiser/src/test/java/seng302/RaceConnectionTest.java b/racevisionGame/src/test/java/visualiser/model/RaceConnectionTest.java similarity index 96% rename from visualiser/src/test/java/seng302/RaceConnectionTest.java rename to racevisionGame/src/test/java/visualiser/model/RaceConnectionTest.java index 47b3cdae..8e38f2ee 100644 --- a/visualiser/src/test/java/seng302/RaceConnectionTest.java +++ b/racevisionGame/src/test/java/visualiser/model/RaceConnectionTest.java @@ -1,4 +1,4 @@ -package seng302; +package visualiser.model; import org.junit.Before; import org.junit.Ignore; @@ -34,4 +34,4 @@ public class RaceConnectionTest { public void offlineConnectionStatusOffline() { assertFalse(offlineConnection.check()); } -} \ No newline at end of file +} diff --git a/racevisionGame/src/test/java/visualiser/model/RaceMapTest.java b/racevisionGame/src/test/java/visualiser/model/RaceMapTest.java new file mode 100644 index 00000000..170024dd --- /dev/null +++ b/racevisionGame/src/test/java/visualiser/model/RaceMapTest.java @@ -0,0 +1,5 @@ +package visualiser.model; + +public class RaceMapTest { +//TODO +} diff --git a/racevisionGame/src/test/java/visualiser/model/TrackPointTest.java b/racevisionGame/src/test/java/visualiser/model/TrackPointTest.java new file mode 100644 index 00000000..db577d71 --- /dev/null +++ b/racevisionGame/src/test/java/visualiser/model/TrackPointTest.java @@ -0,0 +1,5 @@ +package visualiser.model; + +public class TrackPointTest { +//TODO +} diff --git a/racevisionGame/src/test/java/visualiser/model/VisualiserBoatTest.java b/racevisionGame/src/test/java/visualiser/model/VisualiserBoatTest.java new file mode 100644 index 00000000..db2093c9 --- /dev/null +++ b/racevisionGame/src/test/java/visualiser/model/VisualiserBoatTest.java @@ -0,0 +1,5 @@ +package visualiser.model; + +public class VisualiserBoatTest { +//TODO +} diff --git a/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java b/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java new file mode 100644 index 00000000..ef5e691a --- /dev/null +++ b/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java @@ -0,0 +1,100 @@ +//package visualiser.model; +// +// +//import org.junit.Before; +//import org.junit.Test; +//import shared.dataInput.RaceXMLReader; +//import shared.dataInput.XMLReader; +//import shared.exceptions.InvalidRaceDataException; +//import shared.model.GPSCoordinate; +// +//import java.nio.charset.StandardCharsets; +//import java.util.List; +// +//import static org.junit.Assert.assertEquals; +//import static org.junit.Assert.fail; +// +///** +// * Tests only work on the current version of mockXML/raceXML/raceTest.xml +// */ +//public class VisualiserRaceTest { +// private RaceXMLReader streamedCourseXMLReader; +// private List boundary; +// +// @Before +// public void setup() { +// try { +// streamedCourseXMLReader = new RaceXMLReader(XMLReader.readXMLFileToString("mockXML/raceXML/raceTest.xml", StandardCharsets.UTF_8)); +// boundary = streamedCourseXMLReader.getBoundary(); +// } catch (InvalidRaceDataException e) { +// e.printStackTrace(); +// fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder"); +// } +// } +// +// @Test +// public void testAllBoundaryPointsRead() { +// assertEquals(boundary.size(), 10); +// } +// +// @Test +// public void testBoundaryPointData() { +// // First point +// assertEquals(boundary.get(0).getLatitude(), -36.8325, 1e-6); +// assertEquals(boundary.get(0).getLongitude(), 174.8325, 1e-6); +// +// // Last point +// assertEquals(boundary.get(boundary.size() - 1).getLatitude(), -36.83417, 1e-6); +// assertEquals(boundary.get(boundary.size() - 1).getLongitude(), 174.84767, 1e-6); +// } +// +// @Test +// public void testMapEdges() { +// double maxLatitude = streamedCourseXMLReader.getMapBottomRight().getLatitude() - streamedCourseXMLReader.getPadding(); +// double maxLongitude = streamedCourseXMLReader.getMapBottomRight().getLongitude() - streamedCourseXMLReader.getPadding(); +// double minLatitude = streamedCourseXMLReader.getMapTopLeft().getLatitude() - streamedCourseXMLReader.getPadding(); +// double minLongitude = streamedCourseXMLReader.getMapTopLeft().getLongitude() - streamedCourseXMLReader.getPadding(); +// +// assertEquals(maxLatitude, -36.81033, 1e-6); +// assertEquals(maxLongitude, 174.88217, 1e-6); +// assertEquals(minLatitude, -36.83417, 1e-6); +// assertEquals(minLongitude, 174.81983, 1e-6); +// } +// +// @Test +// public void testRaceSettings() { +// +// } +// +// @Test +// public void correctLegSequence() { +// List legs = streamedCourseXMLReader.getLegs(); +// String[] expectedNames = { +// "StartLine", +// "M1", +// "M2", +// "Gate" +// }; +// for(int i = 0; i < legs.size(); i++) { +// assertEquals(expectedNames[i], legs.get(i).getName()); +// } +// } +// +// /** +// * raceTest.xml is not compliant with this test. Markers are positioned far out of bounds. +// */ +// @Test +// @Ignore +// public void markersWithinRaceBoundaries() { +// GPSCoordinate topLeft = streamedCourseXMLReader.getMapTopLeft(); +// GPSCoordinate bottomRight = streamedCourseXMLReader.getMapBottomRight(); +// +// for(Marker compoundMark : streamedCourseXMLReader.getMarkers()) { +// GPSCoordinate centre = compoundMark.getAverageGPSCoordinate(); +// assertTrue(centre.getLatitude() < bottomRight.getLatitude()); +// assertTrue(centre.getLatitude() > topLeft.getLatitude()); +// assertTrue(centre.getLongitude() > bottomRight.getLongitude()); +// assertTrue(centre.getLongitude() < topLeft.getLongitude()); +// } +// } +//} diff --git a/mock/src/test/resources/mockXML/boatTest.xml b/racevisionGame/src/test/resources/mock/mockXML/boatTest.xml similarity index 100% rename from mock/src/test/resources/mockXML/boatTest.xml rename to racevisionGame/src/test/resources/mock/mockXML/boatTest.xml diff --git a/mock/src/test/resources/mockXML/raceTest.xml b/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml similarity index 100% rename from mock/src/test/resources/mockXML/raceTest.xml rename to racevisionGame/src/test/resources/mock/mockXML/raceTest.xml diff --git a/mock/src/test/resources/mockXML/regattaTest.xml b/racevisionGame/src/test/resources/mock/mockXML/regattaTest.xml similarity index 100% rename from mock/src/test/resources/mockXML/regattaTest.xml rename to racevisionGame/src/test/resources/mock/mockXML/regattaTest.xml diff --git a/mock/src/test/resources/polars/acc_polars.csv b/racevisionGame/src/test/resources/mock/polars/acc_polars.csv similarity index 100% rename from mock/src/test/resources/polars/acc_polars.csv rename to racevisionGame/src/test/resources/mock/polars/acc_polars.csv diff --git a/racevisionGame/src/test/resources/mock/polars/invalid_polars.csv b/racevisionGame/src/test/resources/mock/polars/invalid_polars.csv new file mode 100644 index 00000000..7174d973 --- /dev/null +++ b/racevisionGame/src/test/resources/mock/polars/invalid_polars.csv @@ -0,0 +1,8 @@ +Tws,Twa0,Bsp0,Twa1,Bsp1,UpTwa,UpBsp,Twa2,Bsp2,Twa3,Bsp3,Twa4,Bsp4,Twa5,Bsp5,Twa6,Bsp6,DnTwa,DnBsp,Twa7,Bsp7,Twa4 +4,0,0,30,4,45,8,60,9,75,10,90www,10,115,10,145,10,155,10,175,4,179 +8,0,0,30,7,43,10,60,11,75,11,90,11,115,12,145,12,153,12,175,10,179 +12,0,0,30,11,43,14.4,60,16,75,20,90,23,115,24,145,23,153,21.6,175,14,179 +16,0,0,30,12,42,19.2,60,25,75,27,90,31,115,32,145,30,153,28.8,175,20,179 +20,0,0,30,13,41,24,60,29,75,37,90,39,115,40,145,38,153,36,175,24,179 +25,0,0,30,15,40,30,60,38,75,44,90,49,115,50,145,49,151,47,175,30,179 +30,0,0,30,15,42,30,60,37,75,42,90,48,115,49,145,48,150,46,175,32,179 diff --git a/mock/src/test/resources/raceXML/Boats.xml b/racevisionGame/src/test/resources/mock/raceXML/Boats.xml similarity index 100% rename from mock/src/test/resources/raceXML/Boats.xml rename to racevisionGame/src/test/resources/mock/raceXML/Boats.xml diff --git a/visualiser/src/main/resources/raceXML/Race.xml b/racevisionGame/src/test/resources/mock/raceXML/Race.xml similarity index 100% rename from visualiser/src/main/resources/raceXML/Race.xml rename to racevisionGame/src/test/resources/mock/raceXML/Race.xml diff --git a/network/src/test/resources/raceXML/Regatta.xml b/racevisionGame/src/test/resources/mock/raceXML/Regatta.xml similarity index 100% rename from network/src/test/resources/raceXML/Regatta.xml rename to racevisionGame/src/test/resources/mock/raceXML/Regatta.xml diff --git a/mock/src/test/resources/raceXML/bermuda_AC35.xml b/racevisionGame/src/test/resources/mock/raceXML/bermuda_AC35.xml similarity index 100% rename from mock/src/test/resources/raceXML/bermuda_AC35.xml rename to racevisionGame/src/test/resources/mock/raceXML/bermuda_AC35.xml diff --git a/visualiser/src/main/resources/raceXML/Regatta.xml b/racevisionGame/src/test/resources/network/raceXML/Regatta.xml similarity index 100% rename from visualiser/src/main/resources/raceXML/Regatta.xml rename to racevisionGame/src/test/resources/network/raceXML/Regatta.xml diff --git a/visualiser/src/test/resources/mockXML/boatXML/boatTest.xml b/racevisionGame/src/test/resources/shared/mockXML/boatXML/boatTest.xml similarity index 100% rename from visualiser/src/test/resources/mockXML/boatXML/boatTest.xml rename to racevisionGame/src/test/resources/shared/mockXML/boatXML/boatTest.xml diff --git a/visualiser/src/test/resources/mockXML/boatXML/insufficientInformation.xml b/racevisionGame/src/test/resources/shared/mockXML/boatXML/insufficientInformation.xml similarity index 100% rename from visualiser/src/test/resources/mockXML/boatXML/insufficientInformation.xml rename to racevisionGame/src/test/resources/shared/mockXML/boatXML/insufficientInformation.xml diff --git a/visualiser/src/test/resources/mockXML/boatXML/invalidSourceID.xml b/racevisionGame/src/test/resources/shared/mockXML/boatXML/invalidSourceID.xml similarity index 100% rename from visualiser/src/test/resources/mockXML/boatXML/invalidSourceID.xml rename to racevisionGame/src/test/resources/shared/mockXML/boatXML/invalidSourceID.xml diff --git a/visualiser/src/test/resources/mockXML/raceXML/raceTest.xml b/racevisionGame/src/test/resources/shared/mockXML/raceXML/raceTest.xml similarity index 100% rename from visualiser/src/test/resources/mockXML/raceXML/raceTest.xml rename to racevisionGame/src/test/resources/shared/mockXML/raceXML/raceTest.xml diff --git a/visualiser/src/test/resources/mockXML/regattaXML/regattaTest.xml b/racevisionGame/src/test/resources/shared/mockXML/regattaXML/regattaTest.xml similarity index 100% rename from visualiser/src/test/resources/mockXML/regattaXML/regattaTest.xml rename to racevisionGame/src/test/resources/shared/mockXML/regattaXML/regattaTest.xml diff --git a/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/boatTest.xml b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/boatTest.xml new file mode 100644 index 00000000..51f788b0 --- /dev/null +++ b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/boatTest.xml @@ -0,0 +1,251 @@ + + + + + 2012-05-17T07:49:40+0200 + + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/insufficientInformation.xml b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/insufficientInformation.xml new file mode 100644 index 00000000..a821565b --- /dev/null +++ b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/insufficientInformation.xml @@ -0,0 +1,249 @@ + + + + + 2012-05-17T07:49:40+0200 + + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/invalidSourceID.xml b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/invalidSourceID.xml new file mode 100644 index 00000000..5c61eec5 --- /dev/null +++ b/racevisionGame/src/test/resources/visualiser/mockXML/boatXML/invalidSourceID.xml @@ -0,0 +1,251 @@ + + + + + 2012-05-17T07:49:40+0200 + + 12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/visualiser/mockXML/raceXML/raceTest.xml b/racevisionGame/src/test/resources/visualiser/mockXML/raceXML/raceTest.xml new file mode 100644 index 00000000..1711e196 --- /dev/null +++ b/racevisionGame/src/test/resources/visualiser/mockXML/raceXML/raceTest.xml @@ -0,0 +1,91 @@ + + + + + 11080703 + + Match + + 2011-08-06T13:25:00-0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/test/resources/visualiser/mockXML/regattaXML/regattaTest.xml b/racevisionGame/src/test/resources/visualiser/mockXML/regattaXML/regattaTest.xml new file mode 100644 index 00000000..9ec88ccc --- /dev/null +++ b/racevisionGame/src/test/resources/visualiser/mockXML/regattaXML/regattaTest.xml @@ -0,0 +1,20 @@ + + + + 3 + + New Zealand Test + + North Head + + -36.82791529 + + 174.81218919 + + 0.00 + + 12 + + 14.1 + + \ No newline at end of file diff --git a/visualiser/src/test/resources/raceXML/bermuda_AC35.xml b/racevisionGame/src/test/resources/visualiser/raceXML/bermuda_AC35.xml similarity index 100% rename from visualiser/src/test/resources/raceXML/bermuda_AC35.xml rename to racevisionGame/src/test/resources/visualiser/raceXML/bermuda_AC35.xml diff --git a/sharedModel/pom.xml b/sharedModel/pom.xml deleted file mode 100644 index 4b814efa..00000000 --- a/sharedModel/pom.xml +++ /dev/null @@ -1,155 +0,0 @@ - - 4.0.0 - - seng302 - team-7 - 1.0-SNAPSHOT - - - jar - sharedModel - sharedModel - 1.0-SNAPSHOT - - - - junit - junit - 4.12 - test - - - - - - - org.mockito - mockito-all - 1.9.5 - - - - - - org.testng - testng - 6.11 - test - - - - - org.geotools - gt-referencing - 9.0 - - - - - - - - maven2-repository.dev.java.net - Java.net repository - http://download.java.net/maven/2 - - - - - osgeo - Open Source Geospatial Foundation Repository - http://download.osgeo.org/webdav/geotools/ - - - - - - - - - 1.8 - 1.8 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - - - seng302.App - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - package - - shade - - - - - - - - - - org.apache.maven.plugins - maven-jxr-plugin - 2.5 - - - org.apache.maven.plugins - maven-pmd-plugin - 3.6 - - true - ${maven.compiler.target} - - /rulesets/java/basic.xml - /rulesets/java/imports.xml - /rulesets/java/codesize.xml - /rulesets/java/design.xml - /rulesets/java/empty.xml - /rulesets/java/junit.xml - /rulesets/java/unusedcode.xml - - true - utf-8 - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.3 - - - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.19.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.8.1 - - - - diff --git a/sharedModel/src/main/java/seng302/App.java b/sharedModel/src/main/java/seng302/App.java deleted file mode 100644 index 3f8ee22d..00000000 --- a/sharedModel/src/main/java/seng302/App.java +++ /dev/null @@ -1,13 +0,0 @@ -package seng302; - -/** - * Created by f123 on 27-Apr-17. - */ -public class App { - - public static void main(String[] args) { - - } - - -} diff --git a/visualiser/pom.xml b/visualiser/pom.xml index 156c8e60..df395f2f 100644 --- a/visualiser/pom.xml +++ b/visualiser/pom.xml @@ -15,69 +15,16 @@ https://eng-git.canterbury.ac.nz/SENG302-2016/team-7 - - junit - junit - 4.12 - test - - - - org.geotools - gt-referencing - 9.0 - - - - - org.mockito - mockito-all - 1.9.5 - - - - com.github.bfsmith - geotimezone - 1.0.3 - - - - org.testng - testng - 6.11 - test - seng302 - network + racevisionGame 1.0-SNAPSHOT - - - maven2-repository.dev.java.net - Java.net repository - http://download.java.net/maven/2 - - - osgeo - Open Source Geospatial Foundation Repository - http://download.osgeo.org/webdav/geotools/ - - - - true - - opengeo - OpenGeo Maven Repository - http://repo.opengeo.org - - - 1.8 1.8 @@ -99,7 +46,7 @@ - seng302.App + visualiser.app.App ${maven.compiler.source} ${maven.compiler.target} @@ -163,4 +110,4 @@ - \ No newline at end of file + diff --git a/visualiser/src/main/java/seng302/Controllers/RaceController.java b/visualiser/src/main/java/seng302/Controllers/RaceController.java deleted file mode 100644 index 3d27586d..00000000 --- a/visualiser/src/main/java/seng302/Controllers/RaceController.java +++ /dev/null @@ -1,200 +0,0 @@ -package seng302.Controllers; - - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.chart.LineChart; -import javafx.scene.control.*; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; -import seng302.Mock.StreamedRace; -import seng302.Model.*; -import seng302.VisualiserInput; - -import java.net.URL; -import java.util.ResourceBundle; - -/** - * Created by fwy13 on 15/03/2017. - */ -public class RaceController extends Controller { - private ResizableRaceCanvas raceMap; - private ResizableRaceMap raceBoundaries; - - private RaceClock raceClock; - private Sparkline sparkline; - - private int legNum; - - @FXML GridPane canvasBase; - @FXML Pane arrow; - @FXML SplitPane race; - @FXML StackPane arrowPane; - @FXML Label timer; - @FXML Label FPS; - @FXML Label timeZone; - @FXML CheckBox showFPS; - @FXML TableView boatInfoTable; - @FXML TableColumn boatPlacingColumn; - @FXML TableColumn boatTeamColumn; - @FXML TableColumn boatMarkColumn; - @FXML TableColumn boatSpeedColumn; - @FXML LineChart sparklineChart; - @FXML AnchorPane annotationPane; - - /** - * Updates the ResizableRaceCanvas (raceMap) with most recent data - * - * @param boats boats that are to be displayed in the race - * @param boatMarkers Markers for boats - * @see ResizableRaceCanvas - */ - public void updateMap(ObservableList boats, ObservableList boatMarkers) { - raceMap.setBoats(boats); - raceMap.setBoatMarkers(boatMarkers); - raceMap.update(); - raceBoundaries.draw(); - //stop if the visualiser is no longer running - } - - /** - * Updates the array listened by the TableView (boatInfoTable) that displays the boat information. - * - * @param race Race to listen to. - */ - public void setInfoTable(StreamedRace race) { - boatInfoTable.setItems(race.getStartingBoats()); - boatTeamColumn.setCellValueFactory(cellData -> cellData.getValue().getName()); - boatSpeedColumn.setCellValueFactory(cellData -> cellData.getValue().getVelocityProp()); - boatMarkColumn.setCellValueFactory(cellData -> cellData.getValue().getCurrentLegName()); - boatPlacingColumn.setCellValueFactory(cellData -> cellData.getValue().positionProperty()); - } - - - @Override - public void initialize(URL location, ResourceBundle resources) { - //listener for fps - showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { - if (showFPS.isSelected()) { - FPS.setVisible(true); - } else { - FPS.setVisible(false); - } - }); - } - - /** - * Creates and sets initial display for Sparkline for race positions. - * @param boats boats to display on the sparkline - */ - public void createSparkLine(ObservableList boats){ - sparkline = new Sparkline(boats, legNum, sparklineChart); - } - - /** - * Updates the sparkline to display current boat positions. - * @param boatsInRace used for current boat positions. - */ - public void updateSparkline(ObservableList boatsInRace){ - sparkline.updateSparkline(boatsInRace); - } - - /** - * Initializes and runs the race, based on the user's chosen scale factor - * Currently uses an example racecourse - * - * @param visualiserInput input from network - * @param raceClock The RaceClock to use for the race's countdown/elapsed duration + timezone. - */ - public void startRace(VisualiserInput visualiserInput, RaceClock raceClock) { - - legNum = visualiserInput.getCourse().getLegs().size()-1; - - makeArrow(); - - raceMap = new ResizableRaceCanvas(visualiserInput.getCourse()); - raceMap.setMouseTransparent(true); - raceMap.widthProperty().bind(canvasBase.widthProperty()); - raceMap.heightProperty().bind(canvasBase.heightProperty()); - raceMap.draw(); - raceMap.setVisible(true); - raceMap.setArrow(arrow.getChildren().get(0)); - - canvasBase.getChildren().add(0, raceMap); - - raceBoundaries = new ResizableRaceMap(visualiserInput.getCourse()); - raceBoundaries.setMouseTransparent(true); - raceBoundaries.widthProperty().bind(canvasBase.widthProperty()); - raceBoundaries.heightProperty().bind(canvasBase.heightProperty()); - raceBoundaries.draw(); - raceBoundaries.setVisible(true); - - canvasBase.getChildren().add(0, raceBoundaries); - - race.setVisible(true); - - timeZone.setText(raceClock.getTimeZone()); - - //RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update. - raceClock.durationProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - timer.setText(newValue); - }); - }); - - this.raceClock = raceClock; - raceMap.setRaceClock(raceClock); - - StreamedRace newRace = new StreamedRace(visualiserInput, this); - - initializeFPS(); - - // set up annotation displays - new Annotations(annotationPane, raceMap); - - new Thread((newRace)).start(); - } - - /** - * Finish Race View - * @param boats boats there are in the race. - */ - public void finishRace(ObservableList boats){ - race.setVisible(false); - parent.enterFinish(boats); - } - - /** - * Set the value for the fps label - * - * @param fps fps that the label will be updated to - */ - public void setFrames(String fps) { - FPS.setText((fps)); - } - - /** - * Set up FPS display at bottom of screen - */ - private void initializeFPS() { - showFPS.setVisible(true); - showFPS.selectedProperty().addListener((ov, old_val, new_val) -> { - if (showFPS.isSelected()) { - FPS.setVisible(true); - } else { - FPS.setVisible(false); - } - }); - } - - private void makeArrow() { - arrowPane.getChildren().add(arrow); - } - - public RaceClock getRaceClock() { - return raceClock; - } -} diff --git a/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java deleted file mode 100644 index db8091ff..00000000 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ /dev/null @@ -1,212 +0,0 @@ -package seng302.Controllers; - -import javafx.animation.AnimationTimer; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.cell.PropertyValueFactory; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.GridPane; -import seng302.Mock.StreamedCourse; -import seng302.Model.Boat; -import seng302.Model.RaceClock; -import seng302.VisualiserInput; - -import java.io.IOException; -import java.net.Socket; -import java.net.URL; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Observable; -import java.util.Observer; -import java.util.ResourceBundle; - -/** - * Controller to for waiting for the race to start - */ -public class StartController extends Controller implements Observer { - - @FXML private GridPane start; - @FXML private AnchorPane startWrapper; - @FXML private Label raceTitleLabel; - @FXML private Label raceStartLabel; - - @FXML private TableView boatNameTable; - @FXML private TableColumn boatNameColumn; - @FXML private TableColumn boatCodeColumn; - @FXML private Label timeZoneTime; - @FXML private Label timer; - @FXML private Label raceStatusLabel; - - //@FXML Button fifteenMinButton; - - private RaceClock raceClock; - - private StreamedCourse raceData; - private int raceStat; - - private VisualiserInput visualiserInput; - - ///Tracks whether the race has been started (that is, has startRaceNoScaling() be called). - private boolean hasRaceStarted = false; - - //Tracks whether or not a clock has been created and setup, which occurs after receiving enough information. - private boolean hasCreatedClock = false; - - /** - * Begins the race with a scale factor of 1 - */ - private void startRaceNoScaling() { - //while(visualiserInput.getRaceStatus() == null);//TODO probably remove this. - - countdownTimer(); - } - - @Override - public void initialize(URL location, ResourceBundle resources){ - raceData = new StreamedCourse(); - raceData.addObserver(this); - } - - public AnchorPane startWrapper(){ - return startWrapper; - } - - /** - * Initiliases the tables that are to be shown on the pane - */ - private void initialiseTables() { - List boats = raceData.getBoats(); - ObservableList observableBoats = FXCollections.observableArrayList(boats); - - boatNameTable.setItems(observableBoats); - boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().getName()); - boatCodeColumn.setCellValueFactory(new PropertyValueFactory<>("abbrev")); - } - - /** - * Countdown timer until race starts. - */ - private void countdownTimer() { - new AnimationTimer() { - @Override - public void handle(long arg0) { - raceStat = visualiserInput.getRaceStatus().getRaceStatus(); - raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus()); - if (raceStat==2 || raceStat == 3) { - stop(); - - startWrapper.setVisible(false); - start.setVisible(false); - - parent.beginRace(visualiserInput, raceClock); - } - } - }.start(); - } - - /** - * Sets the clock that displays the time of at the current race venue. - */ - private void setRaceClock() { - raceClock = new RaceClock(raceData.getZonedDateTime()); - - raceClock.timeStringProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - timeZoneTime.setText(newValue); - }); - }); -//TEMP REMOVE - //timeZoneTime.textProperty().bind(raceClock.timeStringProperty()); - raceClock.run(); - } - - /** - * Sets the time that the race is going to start. - */ - private void setStartingTime() { - String dateFormat = "'Starting time:' HH:mm dd/MM/YYYY"; - Platform.runLater(()-> { - long utcTime = visualiserInput.getRaceStatus().getExpectedStartTime(); - raceClock.setStartingTime(raceClock.getLocalTime(utcTime)); - raceStartLabel.setText(DateTimeFormatter.ofPattern(dateFormat).format(raceClock.getStartingTime())); - - raceClock.durationProperty().addListener((observable, oldValue, newValue) -> { - Platform.runLater(() -> { - timer.setText(newValue); - }); - }); - }); - } - - /** - * set the current time, may be used to update the time on the clock. - */ - private void setCurrentTime() { - Platform.runLater(()-> - raceClock.setUTCTime(visualiserInput.getRaceStatus().getCurrentTime()) - ); - } - - @Override - public void update(Observable o, Object arg) { - if(o instanceof StreamedCourse) { - StreamedCourse streamedCourse = (StreamedCourse) o; - if(streamedCourse.hasReadBoats()) { - initialiseTables(); - } - if(streamedCourse.hasReadRegatta()) { - Platform.runLater(() -> raceTitleLabel.setText(streamedCourse.getRegattaName())); - } - if (streamedCourse.hasReadCourse()) { - Platform.runLater(() -> { - if (!this.hasCreatedClock) { - this.hasCreatedClock = true; - setRaceClock(); - if (visualiserInput.getRaceStatus() == null) { - return;//TEMP BUG FIX if the race isn't sending race status messages (our mock currently doesn't), then it would block the javafx thread with the previous while loop. - }// TODO - replace with observer on VisualiserInput - setStartingTime(); - setCurrentTime(); - } - }); - } - - //TODO this is a somewhat temporary fix for when not all of the race data (boats, course, regatta) is received in time. - //Previously, startRaceNoScaling was called in the enterLobby function after the visualiserInput was started, but when connecting to the official data source it sometimes didn't send all of the race data, causing startRaceNoScaling to start, even though we didn't have enough information to start it. - if (streamedCourse.hasReadBoats() && streamedCourse.hasReadCourse() && streamedCourse.hasReadRegatta()) { - Platform.runLater(() -> { - if (!this.hasRaceStarted) { - if(visualiserInput.getRaceStatus() == null) { - } - else { - this.hasRaceStarted = true; - startRaceNoScaling(); - } - } - }); - } - - } - } - - /** - * Show starting information for a race given a socket. - * @param socket network source of information - */ - public void enterLobby(Socket socket) { - startWrapper.setVisible(true); - try { - visualiserInput = new VisualiserInput(socket, raceData); - new Thread(visualiserInput).start(); - - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/visualiser/src/main/java/seng302/GPSCoordinate.java b/visualiser/src/main/java/seng302/GPSCoordinate.java deleted file mode 100644 index 96519dbe..00000000 --- a/visualiser/src/main/java/seng302/GPSCoordinate.java +++ /dev/null @@ -1,74 +0,0 @@ -package seng302; - -/** - * GPS Coordinate for the world map. - * It is converted to a {@link seng302.GraphCoordinate GraphCoordinate} via - * the {@link seng302.RaceMap RaceMap} to display objects in their relative - * positions on the {@link seng302.Model.ResizableRaceMap ResizableRaceMap}. - */ -public class GPSCoordinate { - - private final double latitude; - private final double longitude; - - /** - * Constructor Method - * - * @param latitude latitude the coordinate is located at. - * @param longitude Longitude that the coordinate is located at. - */ - public GPSCoordinate(double latitude, double longitude) { - this.latitude = latitude; - this.longitude = longitude; - } - - /** - * Gets the Latitude that the Coordinate is at. - * - * @return Returns the latitude of the Coordinate. - */ - public double getLatitude() { - return latitude; - } - - /** - * Gets the Longitude that the Coordinate is at. - * - * @return Returns the longitude of the Coordinate. - */ - public double getLongitude() { - return longitude; - } - - /** - * To String method of the Coordinate in the form Latitude: $f, Longitude: $f. - * - * @return A String representation of the GPSCoordinate Class. - */ - public String toString() { - return String.format("Latitude: %f, Longitude: %f", latitude, longitude); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GPSCoordinate that = (GPSCoordinate) o; - - if (Math.abs(this.latitude - latitude) > 1e-6) return false; - return (Math.abs(this.longitude - longitude) < 1e-6); - } - - @Override - public int hashCode() { - int result; - long temp; - temp = Double.doubleToLongBits(latitude); - result = (int) (temp ^ (temp >>> 32)); - temp = Double.doubleToLongBits(longitude); - result = 31 * result + (int) (temp ^ (temp >>> 32)); - return result; - } -} - diff --git a/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java b/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java deleted file mode 100644 index 33c7d5ea..00000000 --- a/visualiser/src/main/java/seng302/Mock/BoatXMLReader.java +++ /dev/null @@ -1,157 +0,0 @@ -package seng302.Mock; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; -import seng302.Model.Boat; -import seng302.XMLReader; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * XML Reader that reads in a file and initializes - * {@link seng302.Mock.StreamedBoat StreamedBoat}s that will be participating - * in a race. - */ -public class BoatXMLReader extends XMLReader { - private final Map streamedBoatMap = new HashMap<>(); - private Map participants = new HashMap<>(); - - /** - * Constructor for Boat XML Reader - * @param filePath path of the file - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - } - - /** - * Constructor for Boat XML Reader - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException { - super(filePath); - if (read) { - read(); - } - } - - /** - * Constructor for Boat XML Reader - * @param xmlString sting to read - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException { - super(xmlString); - read(); - } - - public void read() { - readSettings(); - readShapes(); - readBoats(); - } - - /** - * Reads boats settings. - * INFORMATION FROM HERE IS IGNORED FOR NOW - */ - private void readSettings() { - - } - - /** - * Reads different kinds of boat. - * INFORMATION FROM HERE IS IGNORED FOR NOW - */ - private void readShapes() { - - } - - /** - * Reads the boats in the race - */ - private void readBoats() { - Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0); - for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) { - Node boat = nBoats.getChildNodes().item(i); - if (boat.getNodeName().equals("Boat") && boat.getAttributes().getNamedItem("Type").getTextContent().equals("Yacht")) { - readSingleBoat(boat); - } - } - } - - /** - * Reads the information about one boat - * Ignored values: ShapeID, StoweName, HullNum, Skipper, Type - * @param boat The node to read boat data from. - */ - private void readSingleBoat(Node boat) { - StreamedBoat streamedBoat; - String country = null; - int sourceID = Integer.parseInt(boat.getAttributes().getNamedItem("SourceID").getTextContent()); - String boatName = boat.getAttributes().getNamedItem("BoatName").getTextContent(); - String shortName = boat.getAttributes().getNamedItem("ShortName").getTextContent(); - if (exists(boat, "Country")) country = boat.getAttributes().getNamedItem("Country").getTextContent(); - - // Ignore all non participating boats - if (participants.containsKey(sourceID)) { - - if (!streamedBoatMap.containsKey(sourceID)) { - if (country != null) { - streamedBoat = new StreamedBoat(sourceID, boatName, country); - } else { - streamedBoat = new StreamedBoat(sourceID, boatName, shortName); - } - streamedBoatMap.put(sourceID, streamedBoat); - // Override boat with new boat - participants.put(sourceID, streamedBoat); - } - - for (int i = 0; i < boat.getChildNodes().getLength(); i++) { - Node GPSposition = boat.getChildNodes().item(i); - if (GPSposition.getNodeName().equals("GPSposition")) - readBoatPositionInformation(sourceID, GPSposition); - } - } - } - - - /** - * Reads the positional information about a boat - * Ignored values: FlagPosition, MastTop, Z value of GPSPosition - * @param sourceID The source ID of the boat. - * @param GPSposition The relative GPS position of the boat. - */ - private void readBoatPositionInformation(int sourceID, Node GPSposition) { - // TODO Get relative point before implementing. (GPSposition is based - // off a relative point). - } - - /** - * Sets the participants - * @param participants boats participating the race mapped by their source ID's - */ - public void setParticipants(Map participants) { - this.participants = participants; - } - - public List getBoats() { - return new ArrayList<>(streamedBoatMap.values()); - } -} diff --git a/visualiser/src/main/java/seng302/Mock/StreamedBoat.java b/visualiser/src/main/java/seng302/Mock/StreamedBoat.java deleted file mode 100644 index 3b2b2ee0..00000000 --- a/visualiser/src/main/java/seng302/Mock/StreamedBoat.java +++ /dev/null @@ -1,36 +0,0 @@ -package seng302.Mock; - -import seng302.Model.Boat; - -/** - * Boats that are been received. - */ -public class StreamedBoat extends Boat { - private final int sourceID; - private boolean dnf = false; - - private void init() { - this.velocity = 0; - } - - /** - * COnstructor - * @param sourceID ID of the BOat - * @param name Name of the Boat - * @param abbrev Boat's name shortened. - */ - public StreamedBoat(int sourceID, String name, String abbrev) { - super(name, abbrev); - this.sourceID = sourceID; - this.init(); - } - - public StreamedBoat(int sourceID) { - this(sourceID, "None", "None"); - } - - public int getSourceID() { - return sourceID; - } - -} diff --git a/visualiser/src/main/java/seng302/Mock/StreamedCourse.java b/visualiser/src/main/java/seng302/Mock/StreamedCourse.java deleted file mode 100644 index 83c3d89f..00000000 --- a/visualiser/src/main/java/seng302/Mock/StreamedCourse.java +++ /dev/null @@ -1,106 +0,0 @@ -package seng302.Mock; - -import seng302.GPSCoordinate; -import seng302.Model.Boat; -import seng302.Model.Leg; -import seng302.Model.Marker; -import seng302.RaceDataSource; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Observable; - -/** - * COurse that the is being received. - */ -public class StreamedCourse extends Observable implements RaceDataSource { - private StreamedCourseXMLReader streamedCourseXMLReader = null; - private BoatXMLReader boatXMLReader = null; - private RegattaXMLReader regattaXMLReader = null; - private double windDirection = 0; - - public StreamedCourse() {} - - /** - * Read and set the new XML that has been received. - * @param boatXMLReader new XMl of the boats. - */ - public void setBoatXMLReader(BoatXMLReader boatXMLReader) { - this.boatXMLReader = boatXMLReader; - if (streamedCourseXMLReader != null && boatXMLReader != null) { - this.boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants()); - - boatXMLReader.read(); - } - setChanged(); - notifyObservers(); - } - - public StreamedCourseXMLReader getStreamedCourseXMLReader() { - return streamedCourseXMLReader; - } - - /** - * Read and sets the new Course that has been received - * @param streamedCourseXMLReader COurse XML that has been received - */ - public void setStreamedCourseXMLReader(StreamedCourseXMLReader streamedCourseXMLReader) { - this.streamedCourseXMLReader = streamedCourseXMLReader; - if (streamedCourseXMLReader != null && boatXMLReader != null) { - boatXMLReader.setParticipants(streamedCourseXMLReader.getParticipants()); - boatXMLReader.read(); - } - } - - /** - * Reads and sets the new Regatta that has been received - * @param regattaXMLReader Regatta XMl that has been received. - */ - public void setRegattaXMLReader(RegattaXMLReader regattaXMLReader) { - this.regattaXMLReader = regattaXMLReader; - setChanged(); - notifyObservers(); - } - - public void setWindDirection(double windDirection) { - this.windDirection = windDirection; - } - - public double getWindDirection() { - return windDirection; - } - - public boolean hasReadRegatta() { return regattaXMLReader != null; } - - public boolean hasReadBoats() { return boatXMLReader != null; } - - public boolean hasReadCourse() { return streamedCourseXMLReader != null; } - - public String getRegattaName() { return regattaXMLReader.getRegattaName(); } - - public List getBoats() { - return boatXMLReader.getBoats(); - } - - public List getLegs() { - return streamedCourseXMLReader.getLegs(); - } - - public List getMarkers() { return streamedCourseXMLReader.getMarkers(); } - - public List getBoundary() { - return streamedCourseXMLReader.getBoundary(); - } - - public ZonedDateTime getZonedDateTime() { - return streamedCourseXMLReader.getRaceStartTime(); - } - - public GPSCoordinate getMapTopLeft() { - return streamedCourseXMLReader.getMapTopLeft(); - } - - public GPSCoordinate getMapBottomRight() { - return streamedCourseXMLReader.getMapBottomRight(); - } -} diff --git a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLException.java b/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLException.java deleted file mode 100644 index 5bebaa09..00000000 --- a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLException.java +++ /dev/null @@ -1,7 +0,0 @@ -package seng302.Mock; - -/** - * Created by cbt24 on 25/04/17. - */ -public class StreamedCourseXMLException extends Throwable { -} diff --git a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java b/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java deleted file mode 100644 index 3e2f03f4..00000000 --- a/visualiser/src/main/java/seng302/Mock/StreamedCourseXMLReader.java +++ /dev/null @@ -1,291 +0,0 @@ -package seng302.Mock; - -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; -import seng302.GPSCoordinate; -import seng302.Model.Leg; -import seng302.Model.Marker; -import seng302.XMLReader; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - -/** - * XML Read for the Course that is being received - */ -public class StreamedCourseXMLReader extends XMLReader { - private static final double COORDINATEPADDING = 0.000; - private GPSCoordinate mapTopLeft, mapBottomRight; - private final List boundary = new ArrayList<>(); - private final Map compoundMarks = new HashMap<>(); - private final Map participants = new HashMap<>(); - private final List legs = new ArrayList<>(); - private final List markers = new ArrayList<>(); - private ZonedDateTime creationTimeDate; - private ZonedDateTime raceStartTime; - private int raceID; - private String raceType; - private boolean postpone; - - /** - * Constructor for Streamed Race XML - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - * @throws StreamedCourseXMLException error - */ - public StreamedCourseXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException { - super(filePath); - if (read) { - read(); - } - } - - /** - * Constructor for Streamed Race XML - * @param xmlString string to read - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - * @throws StreamedCourseXMLException error - */ - public StreamedCourseXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException, StreamedCourseXMLException { - super(xmlString); - read(); - } - - /** - * reads - * @throws StreamedCourseXMLException error - */ - private void read() throws StreamedCourseXMLException { - readRace(); - readParticipants(); - readCourse(); - } - - /** - * reads a race - */ - private void readRace() { - DateTimeFormatter dateFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - Element settings = (Element) doc.getElementsByTagName("Race").item(0); - NamedNodeMap raceTimeTag = doc.getElementsByTagName("RaceStartTime").item(0).getAttributes(); - - if (raceTimeTag.getNamedItem("Time") != null) dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); - - - raceID = Integer.parseInt(getTextValueOfNode(settings, "RaceID")); - raceType = getTextValueOfNode(settings, "RaceType"); - - creationTimeDate = ZonedDateTime.parse(getTextValueOfNode(settings, "CreationTimeDate"), dateFormat); - - if (raceTimeTag.getNamedItem("Time") != null) raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Time").getTextContent(), dateFormat); - else raceStartTime = ZonedDateTime.parse(raceTimeTag.getNamedItem("Start").getTextContent(), dateFormat); - - postpone = Boolean.parseBoolean(raceTimeTag.getNamedItem("Postpone").getTextContent()); - } - - /** - * Reads the participants of the race. - */ - private void readParticipants() { - Element nParticipants = (Element) doc.getElementsByTagName("Participants").item(0); - nParticipants.getChildNodes().getLength(); - for (int i = 0; i < nParticipants.getChildNodes().getLength(); i++) { - int sourceID; - Node yacht = nParticipants.getChildNodes().item(i); - if (yacht.getNodeName().equals("Yacht")) { - if (exists(yacht, "SourceID")) { - sourceID = Integer.parseInt(yacht.getAttributes().getNamedItem("SourceID").getTextContent()); - participants.put(sourceID, new StreamedBoat(sourceID)); - } - } - } - } - - /** - * reads a course - * @throws StreamedCourseXMLException error - */ - private void readCourse() throws StreamedCourseXMLException { - readCompoundMarks(); - readCompoundMarkSequence(); - readCourseLimit(); - } - - /** - * Indexes CompoundMark elements by their ID for use in generating the course, and populates list of Markers. - * @throws StreamedCourseXMLException if a CompoundMark element contains an unhandled number of compoundMarks. - * @see seng302.Model.Marker - */ - private void readCompoundMarks() throws StreamedCourseXMLException { - Element nCourse = (Element) doc.getElementsByTagName("Course").item(0); - for(int i = 0; i < nCourse.getChildNodes().getLength(); i++) { - Node compoundMark = nCourse.getChildNodes().item(i); - if(compoundMark.getNodeName().equals("CompoundMark")) { - int compoundMarkID = getCompoundMarkID((Element) compoundMark); - compoundMarks.put(compoundMarkID, (Element)compoundMark); - markers.add(getMarker(compoundMarkID)); - } - } - } - - /** - * Generates a Marker from the CompoundMark element with given ID. - * @param compoundMarkID index of required CompoundMark element - * @return generated Marker - * @throws StreamedCourseXMLException if CompoundMark element contains unhandled number of compoundMarks - * @see seng302.Model.Marker - */ - private Marker getMarker(int compoundMarkID) throws StreamedCourseXMLException { - Element compoundMark = compoundMarks.get(compoundMarkID); - NodeList nMarks = compoundMark.getElementsByTagName("Mark"); - Marker marker; - - switch(nMarks.getLength()) { - case 1: marker = new Marker(getCoordinate((Element)nMarks.item(0)),getSourceId((Element)nMarks.item(0))); break; - case 2: marker = new Marker(getCoordinate((Element)nMarks.item(0)), getCoordinate((Element)nMarks.item(1)), - getSourceId((Element)nMarks.item(0)), getSourceId((Element)nMarks.item(1))); break; - default: throw new StreamedCourseXMLException(); - } - - return marker; - } - - /** - * Extracts the GPS Coordinates from a XMl Element - * @param mark Element to Extract from - * @return the GPS coordinate that it reflects - */ - private GPSCoordinate getCoordinate(Element mark) { - double lat = Double.parseDouble(mark.getAttribute("TargetLat")); - double lon = Double.parseDouble(mark.getAttribute("TargetLng")); - return new GPSCoordinate(lat,lon); - } - - /** - * Extracts the SourceID from a XML ELement - * @param mark Element to Extract from - * @return the Source ID of the Extracted Element. - */ - private int getSourceId(Element mark) { - String sourceId = mark.getAttribute("SourceID"); - if (sourceId.isEmpty()){ - return 0; - } - return Integer.parseInt(sourceId); - } - - /** - * Reads "compoundMarkID" attribute of CompoundMark or Corner element - * @param element with "compoundMarkID" attribute - * @return value of "compoundMarkID" attribute - */ - private int getCompoundMarkID(Element element) { - return Integer.parseInt(element.getAttribute("CompoundMarkID")); - } - - /** - * Reads "name" attribute of CompoundMark element with corresponding CompoundMarkID - * @param compoundMarkID unique ID for CompoundMark element - * @return value of "name" attribute - */ - private String getCompoundMarkName(int compoundMarkID) { - return compoundMarks.get(compoundMarkID).getAttribute("Name"); - } - - /** - * Populates list of legs given CompoundMarkSequence element and referenced CompoundMark elements. - * @throws StreamedCourseXMLException if markers cannot be resolved from CompoundMark - */ - private void readCompoundMarkSequence() throws StreamedCourseXMLException { - Element nCompoundMarkSequence = (Element) doc.getElementsByTagName("CompoundMarkSequence").item(0); - NodeList nCorners = nCompoundMarkSequence.getElementsByTagName("Corner"); - Element markXML = (Element)nCorners.item(0); - Marker lastMarker = getMarker(getCompoundMarkID(markXML)); - String legName = getCompoundMarkName(getCompoundMarkID(markXML)); - for(int i = 1; i < nCorners.getLength(); i++) { - markXML = (Element)nCorners.item(i); - Marker currentMarker = getMarker(getCompoundMarkID(markXML)); - legs.add(new Leg(legName, lastMarker, currentMarker, i-1)); - lastMarker = currentMarker; - legName = getCompoundMarkName(getCompoundMarkID(markXML)); - } - } - - /** - * Reads the course boundary limitations of the course recieved. - */ - private void readCourseLimit() { - Element nCourseLimit = (Element) doc.getElementsByTagName("CourseLimit").item(0); - for(int i = 0; i < nCourseLimit.getChildNodes().getLength(); i++) { - Node limit = nCourseLimit.getChildNodes().item(i); - if (limit.getNodeName().equals("Limit")) { - double lat = Double.parseDouble(limit.getAttributes().getNamedItem("Lat").getTextContent()); - double lon = Double.parseDouble(limit.getAttributes().getNamedItem("Lon").getTextContent()); - boundary.add(new GPSCoordinate(lat, lon)); - } - } - - - double maxLatitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING; - double maxLongitude = boundary.stream().max(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING; - double minLatitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLatitude)).get().getLatitude() + COORDINATEPADDING; - double minLongitude = boundary.stream().min(Comparator.comparingDouble(GPSCoordinate::getLongitude)).get().getLongitude() + COORDINATEPADDING; - - mapTopLeft = new GPSCoordinate(minLatitude, minLongitude); - mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude); - } - - public List getBoundary() { - return boundary; - } - - public GPSCoordinate getMapTopLeft() { - return mapTopLeft; - } - - public GPSCoordinate getMapBottomRight() { - return mapBottomRight; - } - - public List getLegs() { - return legs; - } - - public List getMarkers() { return markers; } - - public Double getPadding() { - return COORDINATEPADDING; - } - - public ZonedDateTime getRaceStartTime() { - return raceStartTime; - } - - public int getRaceID() { - return raceID; - } - - public String getRaceType() { - return raceType; - } - - public boolean isPostpone() { - return postpone; - } - - public Map getParticipants() { - return participants; - } -} diff --git a/visualiser/src/main/java/seng302/Mock/StreamedRace.java b/visualiser/src/main/java/seng302/Mock/StreamedRace.java deleted file mode 100644 index 9098e0bd..00000000 --- a/visualiser/src/main/java/seng302/Mock/StreamedRace.java +++ /dev/null @@ -1,285 +0,0 @@ -package seng302.Mock; - -import javafx.animation.AnimationTimer; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seng302.Controllers.FinishController; -import seng302.Controllers.RaceController; -import seng302.GPSCoordinate; -import seng302.Model.Boat; -import seng302.Model.Leg; -import seng302.Model.Marker; -import seng302.Networking.Messages.BoatLocation; -import seng302.Networking.Messages.BoatStatus; -import seng302.Networking.Messages.Enums.BoatStatusEnum; -import seng302.VisualiserInput; - -import java.util.List; - -/** - * The Class used to view the race streamed. - */ -public class StreamedRace implements Runnable { - private final VisualiserInput visualiserInput; - private final ObservableList startingBoats; - private final ObservableList boatMarkers; - private final List legs; - private RaceController controller; - protected FinishController finishController; - private int boatsFinished = 0; - private long totalTimeElapsed; - - private int lastFPS = 20; - - public StreamedRace(VisualiserInput visualiserInput, RaceController controller) { - StreamedCourse course = visualiserInput.getCourse(); - - this.startingBoats = FXCollections.observableArrayList(course.getBoats()); - this.boatMarkers = FXCollections.observableArrayList(course.getMarkers()); - this.legs = course.getLegs(); - this.legs.add(new Leg("Finish", this.legs.size())); - this.controller = controller; - if (startingBoats != null && startingBoats.size() > 0) { - initialiseBoats(); - } - this.visualiserInput = visualiserInput; - } - - private void initialiseBoats() { - Leg officialStart = legs.get(0); - String name = officialStart.getName(); - Marker endCompoundMark = officialStart.getEndMarker(); - - for (Boat boat : startingBoats) { - if (boat != null) { - Leg startLeg = new Leg(name, 0); - startLeg.setEndMarker(endCompoundMark); - boat.setCurrentLeg(startLeg, controller.getRaceClock()); - boat.setTimeSinceLastMark(controller.getRaceClock().getTime()); - } - } - } - - /** - * Checks if the boat cannot finish the race - * @return True if boat cannot finish the race - */ - protected boolean doNotFinish() { - // DNF is no longer random and is now determined by a dnf packet - return false; - } - - /** - * Checks the position of the boat. - * - * @param boat Boat that the position is to be updated for. - * @param timeElapsed Time that has elapse since the start of the the race. - */ - private void checkPosition(Boat boat, long timeElapsed) { - boolean legChanged = false; - StreamedCourse raceData = visualiserInput.getCourse(); - BoatStatus boatStatusMessage = visualiserInput.getBoatStatusMap().get(boat.getSourceID()); - if (boatStatusMessage != null) { - BoatStatusEnum boatStatusEnum = BoatStatusEnum.fromByte(boatStatusMessage.getBoatStatus()); - - int legNumber = boatStatusMessage.getLegNumber(); - - - if (legNumber >= 1 && legNumber < legs.size()) { - if (boat.getCurrentLeg() != legs.get(legNumber)){ - boat.setCurrentLeg(legs.get(legNumber), controller.getRaceClock()); - legChanged = true; - } - } - - if (boatStatusEnum == BoatStatusEnum.RACING) { - boat.addTrackPoint(boat.getCurrentPosition()); - } else if (boatStatusEnum == BoatStatusEnum.DNF) { - boat.setDnf(true); - } else if (boatStatusEnum == BoatStatusEnum.FINISHED || legNumber == raceData.getLegs().size()) { - boatsFinished++; - boat.setTimeFinished(timeElapsed); - boat.setFinished(true); - } - } - if (legChanged) { - //Update the boat display table in the GUI to reflect the leg change - updatePositions(); - controller.updateSparkline(startingBoats); - } - } - - /** - * Updates the boat's gps coordinates - * - * @param boat to be updated - */ - private void updatePosition(Boat boat) { - int sourceID = boat.getSourceID(); - BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID); - BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID); - if(boatLocation != null) { - double lat = boatLocation.getLatitudeDouble(); - double lon = boatLocation.getLongitudeDouble(); - boat.setCurrentPosition(new GPSCoordinate(lat, lon)); - boat.setHeading(boatLocation.getHeadingDegrees()); - boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime())); - double MMPS_TO_KN = 0.001944; - boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN); - } - } - - /** - * Updates the boat's gps coordinates - * - * @param mark to be updated - */ - private void updateMarker(Marker mark) { - int sourceID = mark.getSourceId1(); - BoatLocation boatLocation1 = visualiserInput.getBoatLocationMessage(sourceID); - if(boatLocation1 != null) { - double lat = boatLocation1.getLatitudeDouble(); - double lon = boatLocation1.getLongitudeDouble(); - mark.setCurrentPosition1(new GPSCoordinate(lat, lon)); - } - int sourceID2 = mark.getSourceId2(); - BoatLocation boatLocation2 = visualiserInput.getBoatLocationMessage(sourceID2); - if(boatLocation2 != null) { - double lat = boatLocation2.getLatitudeDouble(); - double lon = boatLocation2.getLongitudeDouble(); - mark.setCurrentPosition2(new GPSCoordinate(lat, lon)); - } - } - - public void setController(RaceController controller) { - this.controller = controller; - } - - - /** - * Runnable for the thread. - */ - public void run() { - setControllerListeners(); - Platform.runLater(() -> controller.createSparkLine(startingBoats)); - initialiseBoats(); - startRaceStream(); - } - - - /** - * Update the calculated fps to the fps label - * - * @param fps The new calculated fps value - */ - private void updateFPS(int fps) { - Platform.runLater(() -> controller.setFrames("FPS: " + fps)); - } - - /** - * Starts the Race Simulation, playing the race start to finish with the timescale. - * This prints the boats participating, the order that the events occur in time order, and the respective information of the events. - */ - private void startRaceStream() { - - System.setProperty("javafx.animation.fullspeed", "true"); - - - - new AnimationTimer() { - - final long timeRaceStarted = System.currentTimeMillis(); //start time of loop - int fps = 0; //init fps value - long timeCurrent = System.currentTimeMillis(); //current time - - @Override - public void handle(long arg0) { - totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; - - - //Check if the race has actually started. - if (visualiserInput.getRaceStatus().isStarted()) { - //Set all boats to started. - for (Boat boat : startingBoats) { - boat.setStarted(true); - } - } - - for (Boat boat : startingBoats) { - if (boat != null && !boat.isFinished()) { - updatePosition(boat); - checkPosition(boat, totalTimeElapsed); - } - - } - for (Marker mark: boatMarkers){ - if (mark != null){ - updateMarker(mark); - } - } - - if (visualiserInput.getRaceStatus().isFinished()) { - controller.finishRace(startingBoats); - stop(); - } - controller.updateMap(startingBoats, boatMarkers); - fps++; - if ((System.currentTimeMillis() - timeCurrent) > 1000) { - updateFPS(fps); - lastFPS = fps; - fps = 0; - timeCurrent = System.currentTimeMillis(); - } - } - }.start(); - } - - /** - * Update position of boats in race, no position if on starting leg or DNF. - */ - private void updatePositions() { - FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber()); - for(Boat boat: startingBoats) { - if(boat != null) { - boat.setPosition(Integer.toString(startingBoats.indexOf(boat) + 1)); - if (boat.isDnf() || !boat.isStarted() || boat.getCurrentLeg().getLegNumber() < 0) - boat.setPosition("-"); - } - } - } - - /** - * Update call for the controller. - */ - private void setControllerListeners() { - if (controller != null) controller.setInfoTable(this); - } - - /** - * Returns the boats that have started the race. - * - * @return ObservableList of Boat class that participated in the race. - * @see ObservableList - * @see Boat - */ - public ObservableList getStartingBoats() { - return startingBoats; - } - - /** - * Takes an estimated time an event will occur, and converts it to the - * number of seconds before the event will occur. - * - * @param estTimeMillis estimated time in milliseconds - * @return int difference between time the race started and the estimated time - */ - private int convertEstTime(long estTimeMillis, long currentTime) { - - long estElapsedMillis = estTimeMillis - currentTime; - int estElapsedSecs = Math.round(estElapsedMillis/1000); - return estElapsedSecs; - - } - -} diff --git a/visualiser/src/main/java/seng302/Model/Boat.java b/visualiser/src/main/java/seng302/Model/Boat.java deleted file mode 100644 index 3d0613ca..00000000 --- a/visualiser/src/main/java/seng302/Model/Boat.java +++ /dev/null @@ -1,272 +0,0 @@ -package seng302.Model; - -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import org.geotools.referencing.GeodeticCalculator; -import seng302.GPSCoordinate; - -import java.awt.geom.Point2D; -import java.time.ZonedDateTime; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * This class is used to represent and store information about a boat which may - * travel around in a race. It is displayed on the - * {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas} via the - * {@link seng302.Controllers.RaceController RaceController}. - */ -public class Boat { - - private final StringProperty name; - protected double velocity; - private final StringProperty velocityProp; - private final String abbrev; - private GPSCoordinate currentPosition; - protected double heading; - private Leg currentLeg; - private final StringProperty currentLegName; - private boolean finished = false; - private long timeFinished; - private final StringProperty position; - private boolean started = false; - private boolean dnf = false; - private int sourceID; - private int estTime; - private final Queue track = new ConcurrentLinkedQueue<>(); - private long nextValidTime = 0; - - private ZonedDateTime timeSinceLastMark; - - /** - * Boat initializer which keeps all of the information of the boat. - * - * @param name Name of the Boat. - * @param velocity Speed in m/s that the boat travels at. - * @param abbrev nam abbreviation - */ - public Boat(String name, double velocity, String abbrev) { - this.velocity = velocity; - this.velocityProp = new SimpleStringProperty(String.valueOf(Math.round(velocity))); - this.abbrev = abbrev; - this.name = new SimpleStringProperty(name); - currentLegName = new SimpleStringProperty(""); - position = new SimpleStringProperty("-"); - } - - /** - * Boat initializer which keeps all of the information of the boat. - * - * @param name Name of the Boat. - * @param abbrev nam abbreviation - */ - protected Boat(String name, String abbrev) { - this(name, 0, abbrev); - } - - /** - * Returns the position of the end of the boat's wake, which is 180 degrees - * from the boat's heading, and whose length is proportional to the boat's - * speed. - * - * @return GPSCoordinate of wake endpoint. - */ - public GPSCoordinate getWake() { - double reverseHeading = getHeading() - 180; - double wakeScale = 5; - double distance = wakeScale * getVelocity(); - - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint( - new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude()) - ); - calc.setDirection(reverseHeading, distance); - Point2D endpoint = calc.getDestinationGeographicPoint(); - return new GPSCoordinate(endpoint.getY(), endpoint.getX()); - } - - /** - * Adds a new point to boat's track. - * @param coordinate of point on track - * @see seng302.Model.TrackPoint - */ - public void addTrackPoint(GPSCoordinate coordinate) { - Boolean added = System.currentTimeMillis() >= nextValidTime; - long currentTime = System.currentTimeMillis(); - if (added && this.started) { - float trackPointTimeInterval = 5000; - nextValidTime = currentTime + (long) trackPointTimeInterval; - int TRACK_POINT_LIMIT = 10; - track.add(new TrackPoint(coordinate, currentTime, TRACK_POINT_LIMIT * (long) trackPointTimeInterval)); - } - } - - /** - * Returns the boat's sampled track between start of race and current time. - * @return queue of track points - * @see seng302.Model.TrackPoint - */ - public Queue getTrack() { - return track; - } - - /** - * @return Name of the boat - */ - public StringProperty getName() { - return name; - } - - /** - * Sets the boat name - * - * @param name of boat - */ - public void setName(String name) { - this.name.setValue(name); - } - - - - /** - * @return Speed of the boat. - */ - public double getVelocity() { - return velocity; - } - - /** - * Sets the speed of the boat in knots. - * - * @param velocity speed in knots - */ - public void setVelocity(double velocity) { - this.velocity = velocity; - this.velocityProp.setValue(String.valueOf(Math.round(velocity))); - } - - /** - * Print method prints the name of the boat - * - * @return Name of the boat. - */ - public String toString() { - return getName().getValue(); - } - - /** - * @return Velocity String Property of the boat - */ - public StringProperty getVelocityProp() { - return velocityProp; - } - - /** - * @return Abbreviation of the boat - */ - public String getAbbrev() { - return abbrev; - } - - public GPSCoordinate getCurrentPosition() { - return currentPosition; - } - - public void setCurrentPosition(GPSCoordinate currentPosition) { - this.currentPosition = currentPosition; - } - - public double getHeading() { - return heading; - } - - public void setHeading(double heading) { - this.heading = heading; - } - - public Leg getCurrentLeg() { - return currentLeg; - } - - public void setCurrentLeg(Leg currentLeg, RaceClock raceClock) { - this.currentLeg = currentLeg; - this.currentLegName.setValue(currentLeg.getName()); - this.setTimeSinceLastMark(raceClock.getTime()); - } - - public boolean isFinished() { - return finished; - } - - public void setFinished(boolean finished) { - this.finished = finished; - } - - public long getTimeFinished() { - return timeFinished; - } - - public void setTimeFinished(long timeFinished) { - this.timeFinished = timeFinished; - } - - public StringProperty positionProperty() { - return position; - } - - public void setPosition(String position) { - this.position.set(position); - } - - public boolean isStarted() { - return started; - } - - public void setStarted(boolean started) { - this.started = started; - } - - /** - * @return Name of boat's current leg - */ - public StringProperty getCurrentLegName() { - return currentLegName; - } - - public int getSourceID() { - return this.sourceID; - } - - public boolean isDnf() { - return dnf; - } - - public void setDnf(boolean dnf) { - this.dnf = dnf; - } - - public ZonedDateTime getTimeSinceLastMark() { - return timeSinceLastMark; - } - - public void setTimeSinceLastMark(ZonedDateTime timeSinceLastMark) { - this.timeSinceLastMark = timeSinceLastMark; - } - - public void setEstTime(int estTime) { this.estTime = estTime; } - - public String getFormattedEstTime() { - if (estTime < 0) { - return " -"; - } - if (estTime <= 60) { - return " " + estTime + "s"; - } else { - int seconds = estTime % 60; - int minutes = (estTime - seconds) / 60; - return String.format(" %dm %ds", minutes, seconds); - } - - - } -} diff --git a/visualiser/src/main/java/seng302/Model/BoatInRace.java b/visualiser/src/main/java/seng302/Model/BoatInRace.java deleted file mode 100644 index b949900e..00000000 --- a/visualiser/src/main/java/seng302/Model/BoatInRace.java +++ /dev/null @@ -1,311 +0,0 @@ -//package seng302.Model; -// -//import javafx.beans.property.SimpleStringProperty; -//import javafx.beans.property.StringProperty; -//import javafx.scene.paint.Color; -//import org.geotools.referencing.GeodeticCalculator; -//import seng302.GPSCoordinate; -// -//import java.awt.geom.Point2D; -//import java.util.Queue; -//import java.util.concurrent.ConcurrentLinkedQueue; -// -///** -// * Boat in the Race extends {@link seng302.Model.Boat Boat}. -// * The extended properties are related to the boats current race position. -// * @See seng302.Model.Boat -// */ -//public class BoatInRace extends Boat { -// -// private Leg currentLeg; -// private double scaledVelocity; -// private double distanceTravelledInLeg; -// private GPSCoordinate currentPosition; -// private long timeFinished; -// private Color colour; -// private boolean finished = false; -// private final StringProperty currentLegName; -// private boolean started = false; -// private final StringProperty position; -// private double heading; -// private static final double WAKE_SCALE = 10; -// -// private final Queue track = new ConcurrentLinkedQueue<>(); -// private long nextValidTime = 0; -// -// private static final float BASE_TRACK_POINT_TIME_INTERVAL = 5000; -// private static float trackPointTimeInterval = 5000; // every 1 seconds -// -// /** -// * Constructor method. -// * -// * @param name Name of the boat. -// * @param velocity Speed that the boat travels. -// * @param colour Colour the boat will be displayed as on the map -// * @param abbrev of boat -// */ -// public BoatInRace(String name, double velocity, Color colour, String abbrev) { -// super(name, velocity, abbrev); -// setColour(colour); -// currentLegName = new SimpleStringProperty(""); -// position = new SimpleStringProperty("-"); -// } -// -// /** -// * Calculates the azimuth of the travel via map coordinates of the raceMarkers -// * -// * @return the direction that the boat is heading towards in degrees (-180 to 180). -// */ -// public double calculateAzimuth() { -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// GPSCoordinate start = currentLeg.getStartMarker().getAverageGPSCoordinate(); -// GPSCoordinate end = currentLeg.getEndMarker().getAverageGPSCoordinate(); -// -// calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); -// calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); -// -// return calc.getAzimuth(); -// } -// -// /** -// * Converts an azimuth to a bearing -// * -// * @param azimuth azimuth value to be converted -// * @return the bearings in degrees (0 to 360). -// */ -// private static double calculateHeading(double azimuth) { -// if (azimuth >= 0) { -// return azimuth; -// } else { -// return azimuth + 360; -// } -// } -// -// public double getHeading() { -// return heading; -// } -// -// public void setHeading(double heading) { -// this.heading = heading; -// } -// -// /** -// * Calculates the bearing of the travel via map coordinates of the raceMarkers -// * -// * @return the direction that the boat is heading towards in degrees (0 to 360). -// */ -// public double calculateHeading() { -// double azimuth = calculateAzimuth(); -// return calculateHeading(azimuth); -// } -// -// /** -// * Returns the position of the end of the boat's wake, which is 180 degrees -// * from the boat's heading, and whose length is proportional to the boat's -// * speed. -// * -// * @return GPSCoordinate of wake endpoint. -// */ -// public GPSCoordinate getWake() { -// double reverseHeading = getHeading() - 180; -// double distance = WAKE_SCALE * getVelocity(); -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// calc.setStartingGeographicPoint( -// new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude()) -// ); -// calc.setDirection(reverseHeading, distance); -// Point2D endpoint = calc.getDestinationGeographicPoint(); -// return new GPSCoordinate(endpoint.getY(), endpoint.getX()); -// } -// -// /** -// * @return Scaled velocity of the boat -// */ -// public double getScaledVelocity() { -// return scaledVelocity; -// } -// -// /** -// * Sets the boat's scaled velocity -// * -// * @param velocity of boat -// */ -// public void setScaledVelocity(double velocity) { -// this.scaledVelocity = velocity; -// } -// -// /** -// * @return Returns the current position of the boat in a GPSCoordinate Class. -// * @see GPSCoordinate -// */ -// public GPSCoordinate getCurrentPosition() { -// return currentPosition; -// } -// -// /** -// * Sets the current position on the GPS that the boat. -// * -// * @param position GPSCoordinate of the position that the boat is currently on. -// * @see GPSCoordinate -// */ -// public void setCurrentPosition(GPSCoordinate position) { -// this.currentPosition = position; -// } -// -// /** -// * @return Returns the time that the boat finished the race. -// */ -// public long getTimeFinished() { -// return timeFinished; -// } -// -// /** -// * Sets the time that the boat finished the race. -// * -// * @param timeFinished Time the boat finished the race. -// */ -// public void setTimeFinished(long timeFinished) { -// this.timeFinished = timeFinished; -// } -// -// /** -// * @return Returns the colour of the boat. -// */ -// public Color getColour() { -// return colour; -// } -// -// /** -// * Sets the colour that boat will be shown as when drawn on the ResizableRaceCanvas. -// * -// * @param colour Colour that the boat is to be set to. -// * @see ResizableRaceCanvas -// */ -// private void setColour(Color colour) { -// this.colour = colour; -// } -// -// /** -// * Gets the current leg that the boat is on. -// * -// * @return returns the leg the boat is on in a Leg class -// * @see Leg -// */ -// public Leg getCurrentLeg() { -// return currentLeg; -// } -// -// /** -// * Sets the boat's current leg. -// * -// * @param currentLeg Leg class that the boat is currently on. -// * @see Leg -// */ -// public void setCurrentLeg(Leg currentLeg) { -// this.currentLeg = currentLeg; -// this.currentLegName.setValue(currentLeg.getName()); -// } -// -// /** -// * @return Name of boat's current leg -// */ -// public StringProperty getCurrentLegName() { -// return currentLegName; -// } -// -// /** -// * Gets the distance travelled by the boat in the leg. -// * -// * @return Returns the value in nautical miles (1.852km) that the boat has traversed. -// */ -// public double getDistanceTravelledInLeg() { -// return distanceTravelledInLeg; -// } -// -// /** -// * Sets the distance travelled by the boat in the leg in nautical miles (1.852km) -// * -// * @param distanceTravelledInLeg Distance travelled by the boat in nautical miles. -// */ -// public void setDistanceTravelledInLeg(double distanceTravelledInLeg) { -// this.distanceTravelledInLeg = distanceTravelledInLeg; -// } -// -// /** -// * @return true if boat has finished, false if not -// */ -// public boolean isFinished() { -// return this.finished; -// } -// -// /** -// * Sets whether boat is finished or not -// * -// * @param bool is finished value -// */ -// public void setFinished(boolean bool) { -// this.finished = bool; -// } -// -// public boolean isStarted() { -// return started; -// } -// -// public void setStarted(boolean started) { -// this.started = started; -// } -// -// public String getPosition() { -// return position.get(); -// } -// -// public StringProperty positionProperty() { -// return position; -// } -// -// public void setPosition(String position) { -// this.position.set(position); -// } -// -// /** -// * Adds a new point to boat's track. -// * @param coordinate of point on track -// * @see seng302.Model.TrackPoint -// */ -// public void addTrackPoint(GPSCoordinate coordinate) { -// Boolean added = System.currentTimeMillis() >= nextValidTime; -// long currentTime = System.currentTimeMillis(); -// if (added && this.started) { -// nextValidTime = currentTime + (long) trackPointTimeInterval; -// int TRACK_POINT_LIMIT = 10; -// track.add(new TrackPoint(coordinate, currentTime, TRACK_POINT_LIMIT * (long) trackPointTimeInterval)); -// } -// } -// -// /** -// * Returns the boat's sampled track between start of race and current time. -// * @return queue of track points -// * @see seng302.Model.TrackPoint -// */ -// public Queue getTrack() { -// return track; -// } -// -// /** -// * Get base track point time interval -// * @return base track point time interval -// */ -// public static float getBaseTrackPointTimeInterval() { -// return BASE_TRACK_POINT_TIME_INTERVAL; -// } -// -// /** -// * Set track point time interval -// * @param value track point time interval value -// */ -// public static void setTrackPointTimeInterval(float value) { -// trackPointTimeInterval = value; -// } -//} diff --git a/visualiser/src/main/java/seng302/Model/ConstantVelocityRace.java b/visualiser/src/main/java/seng302/Model/ConstantVelocityRace.java deleted file mode 100644 index 79b76dbc..00000000 --- a/visualiser/src/main/java/seng302/Model/ConstantVelocityRace.java +++ /dev/null @@ -1,208 +0,0 @@ -//package seng302.Model; -// -//import org.geotools.referencing.GeodeticCalculator; -//import seng302.Constants; -//import seng302.Controllers.RaceController; -//import seng302.GPSCoordinate; -//import seng302.RaceDataSource; -// -//import java.awt.geom.Point2D; -//import java.util.ArrayList; -//import java.util.Arrays; -//import java.util.List; -//import java.util.Random; -// -///** -// * Created by cbt24 on 6/03/17. -// * -// * @deprecated -// */ -//public class ConstantVelocityRace extends Race { -// -// private int dnfChance = 0; //%percentage chance a boat fails at each checkpoint -// -// /** -// * Initializer for a constant velocity race without standard data source -// * -// * @param startingBoats in race -// * @param legs in race -// * @param controller for graphics -// * @param scaleFactor of timer -// */ -// public ConstantVelocityRace(List startingBoats, List legs, RaceController controller, int scaleFactor) { -// super(startingBoats, legs, controller, scaleFactor); -// } -// -// /** -// * Initializer for legacy tests -// * -// * @param startingBoats in race -// * @param legs in race -// * @param controller for graphics -// * @param scaleFactor of timer -// * -// * @deprecated Please use {@link #ConstantVelocityRace(List, List, RaceController, int) } for future tests. -// */ -// public ConstantVelocityRace(BoatInRace[] startingBoats, List legs, RaceController controller, int scaleFactor) { -// super(Arrays.asList(startingBoats), legs, controller, scaleFactor); -// } -// -// /** -// * Initializer for constant velocity race with standard data source -// * @param raceData for race -// * @param controller for graphics -// * @param scaleFactor of timer -// */ -// public ConstantVelocityRace(RaceDataSource raceData, RaceController controller, int scaleFactor) { -// super(raceData, controller, scaleFactor); -// } -// -// public void initialiseBoats() { -// Leg officialStart = legs.get(0); -// String name = officialStart.getName(); -// CompoundMark endMarker = officialStart.getEndCompoundMark(); -// -// BoatInRace.setTrackPointTimeInterval(BoatInRace.getBaseTrackPointTimeInterval() / scaleFactor); -// -// ArrayList startMarkers = getSpreadStartingPositions(); -// for (int i = 0; i < startingBoats.size(); i++) { -// BoatInRace boat = startingBoats.get(i); -// if (boat != null) { -// boat.setScaledVelocity(boat.getVelocity() * scaleFactor); -// Leg startLeg = new Leg(name, 0); -// boat.setCurrentPosition(startMarkers.get(i).getAverageGPSCoordinate()); -// startLeg.setStartCompoundMark(startMarkers.get(i)); -// startLeg.setEndCompoundMark(endMarker); -// startLeg.calculateDistance(); -// boat.setCurrentLeg(startLeg); -// boat.setHeading(boat.calculateHeading()); -// } -// } -// } -// -// /** -// * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line -// * -// * @return list of starting positions -// */ -// public ArrayList getSpreadStartingPositions() { -// -// int nBoats = startingBoats.size(); -// CompoundMark marker = legs.get(0).getStartCompoundMark(); -// -// GeodeticCalculator initialCalc = new GeodeticCalculator(); -// initialCalc.setStartingGeographicPoint(marker.getMark1().getLongitude(), marker.getMark1().getLatitude()); -// initialCalc.setDestinationGeographicPoint(marker.getMark2().getLongitude(), marker.getMark2().getLatitude()); -// -// double azimuth = initialCalc.getAzimuth(); -// double distanceBetweenMarkers = initialCalc.getOrthodromicDistance(); -// double distanceBetweenBoats = distanceBetweenMarkers / (nBoats + 1); -// -// GeodeticCalculator positionCalc = new GeodeticCalculator(); -// positionCalc.setStartingGeographicPoint(marker.getMark1().getLongitude(), marker.getMark1().getLatitude()); -// ArrayList positions = new ArrayList<>(); -// -// for (int i = 0; i < nBoats; i++) { -// positionCalc.setDirection(azimuth, distanceBetweenBoats); -// Point2D position = positionCalc.getDestinationGeographicPoint(); -// positions.add(new CompoundMark(new GPSCoordinate(position.getY(), position.getX()))); -// -// positionCalc = new GeodeticCalculator(); -// positionCalc.setStartingGeographicPoint(position); -// } -// return positions; -// } -// -// /** -// * Sets the chance each boat has of failing at a gate or marker -// * @param chance percentage chance a boat has of failing per checkpoint. -// */ -// protected void setDnfChance(int chance) { -// if (chance >= 0 && chance <= 100) { -// dnfChance = chance; -// } -// } -// -// protected boolean doNotFinish() { -// Random rand = new Random(); -// return rand.nextInt(100) < dnfChance; -// } -// -// /** -// * Calculates the distance a boat has travelled and updates its current position according to this value. -// * -// * @param boat to be updated -// * @param millisecondsElapsed since last update -// */ -// protected void updatePosition(BoatInRace boat, int millisecondsElapsed) { -// -// //distanceTravelled = velocity (nm p hr) * time taken to update loop -// double distanceTravelled = (boat.getScaledVelocity() * millisecondsElapsed) / 3600000; -// -// double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg(); -// -// boolean finish = boat.getCurrentLeg().getName().equals("Finish"); -// if (!finish) { -// boat.setHeading(boat.calculateHeading()); -// //update boat's distance travelled -// boat.setDistanceTravelledInLeg(totalDistanceTravelled); -// //Calculate boat's new position by adding the distance travelled onto the start point of the leg -// boat.setCurrentPosition(calculatePosition(boat.getCurrentLeg().getStartCompoundMark().getAverageGPSCoordinate(), -// totalDistanceTravelled, boat.calculateAzimuth())); -// } -// } -// -// protected void checkPosition(BoatInRace boat, long timeElapsed) { -// if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()) { -// //boat has passed onto new leg -// if (boat.getCurrentLeg().getName().equals("Finish")) { -// //boat has finished -// boatsFinished++; -// boat.setFinished(true); -// boat.setTimeFinished(timeElapsed); -// } else if (doNotFinish()) { -// boatsFinished++; -// boat.setFinished(true); -// boat.setCurrentLeg(new Leg("DNF", -1)); -// boat.setVelocity(0); -// boat.setScaledVelocity(0); -// } else { -// //Calculate how much the boat overshot the marker by -// boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); -// //Move boat on to next leg -// Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1); -// -// boat.setCurrentLeg(nextLeg); -// //Add overshoot distance into the distance travelled for the next leg -// boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg()); -// } -// //Update the boat display table in the GUI to reflect the leg change -// updatePositions(); -// } -// } -// -// /** -// * Calculates the boats next GPS position based on its distance travelled and heading -// * -// * @param oldCoordinates GPS coordinates of the boat's starting position -// * @param distanceTravelled distance in nautical miles -// * @param azimuth boat's current direction. Value between -180 and 180 -// * @return The boat's new coordinate -// */ -// public static GPSCoordinate calculatePosition(GPSCoordinate oldCoordinates, double distanceTravelled, double azimuth) { -// -// //Find new coordinate using current heading and distance -// -// GeodeticCalculator geodeticCalculator = new GeodeticCalculator(); -// //Load start point into calculator -// Point2D startPoint = new Point2D.Double(oldCoordinates.getLongitude(), oldCoordinates.getLatitude()); -// geodeticCalculator.setStartingGeographicPoint(startPoint); -// //load direction and distance tranvelled into calculator -// geodeticCalculator.setDirection(azimuth, distanceTravelled * Constants.NMToMetersConversion); -// //get new point -// Point2D endPoint = geodeticCalculator.getDestinationGeographicPoint(); -// -// return new GPSCoordinate(endPoint.getY(), endPoint.getX()); -// } -// -//} \ No newline at end of file diff --git a/visualiser/src/main/java/seng302/Model/Leg.java b/visualiser/src/main/java/seng302/Model/Leg.java deleted file mode 100644 index 92b6117a..00000000 --- a/visualiser/src/main/java/seng302/Model/Leg.java +++ /dev/null @@ -1,106 +0,0 @@ -package seng302.Model; - -import org.geotools.referencing.GeodeticCalculator; -import seng302.GPSCoordinate; - -/** - * This class represents a leg, which is a portion of a race between two - * {@link seng302.Model.Marker Marker}s. A leg is used to identify a - * {@link seng302.Model.Boat Boat}'s progress through a race. - */ -public class Leg { - private final String name; //nautical miles - private double distance; - private Marker startMarker; - private Marker endMarker; - private final int legNumber; - // 1 nautical mile = 1852 meters - public static final int NM_TO_METERS = 1852; - - /** - * Leg Initializer - * - * @param name Name of the Leg - * @param start marker - * @param end marker - * @param number Leg's position in race - */ - public Leg(String name, Marker start, Marker end, int number) { - this.name = name; - this.startMarker = start; - this.endMarker = end; - this.legNumber = number; - calculateDistance(); - } - - /** - * Construction Method - * - * @param name Name of the Leg - * @param number leg number - */ - public Leg(String name, int number) { - this.name = name; - this.legNumber = number; - } - - /** - * Returns the name of the Leg - * - * @return Returns the name of the Leg - */ - public String getName() { - return name; - } - - /** - * Get the distance in nautical miles - * - * @return Returns the total distance of the leg. - */ - public double getDistance() { - return distance; - } - - /** - * Returns the leg number that the leg exists in the Race - * - * @return Returns the Leg - */ - public int getLegNumber() { - return legNumber; - } - - - public Marker getStartMarker() { - return startMarker; - } - - public void setStartMarker(Marker startMarker) { - this.startMarker = startMarker; - } - - public Marker getEndMarker() { - return endMarker; - } - - public void setEndMarker(Marker endMarker) { - this.endMarker = endMarker; - } - - /** - * Calculates the distance that the legs are in nautical miles (1.852 km). - */ - private void calculateDistance() { - - GeodeticCalculator calc = new GeodeticCalculator(); - //Load start and end of leg - GPSCoordinate startMarker = this.startMarker.getAverageGPSCoordinate(); - GPSCoordinate endMarker = this.endMarker.getAverageGPSCoordinate(); - calc.setStartingGeographicPoint(startMarker.getLongitude(), startMarker.getLatitude()); - calc.setDestinationGeographicPoint(endMarker.getLongitude(), endMarker.getLatitude()); - this.distance = calc.getOrthodromicDistance() / NM_TO_METERS; - } - - -} diff --git a/visualiser/src/main/java/seng302/Model/Marker.java b/visualiser/src/main/java/seng302/Model/Marker.java deleted file mode 100644 index 7c0b7296..00000000 --- a/visualiser/src/main/java/seng302/Model/Marker.java +++ /dev/null @@ -1,105 +0,0 @@ -package seng302.Model; - -import org.geotools.referencing.GeodeticCalculator; -import seng302.GPSCoordinate; - -import java.awt.geom.Point2D; - -/** - * A marker on a race course that a boat can pass, to be displayed on the - * {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}. This is - * displayed via the {@link seng302.Controllers.RaceController RaceController}. - *
Markers are also used to calculate a {@link seng302.Model.Leg Leg} - * distance. - */ -public class Marker { - private final GPSCoordinate averageGPSCoordinate; - private GPSCoordinate mark1; - private GPSCoordinate mark2; - private final int sourceId1; - private final int sourceId2; - - public Marker(GPSCoordinate mark1, int sourceId) { - this(mark1, mark1, sourceId, sourceId); - } - - public Marker(GPSCoordinate mark1, GPSCoordinate mark2, int sourceId1, int sourceId2) { - - this.mark1 = mark1; - this.mark2 = mark2; - this.averageGPSCoordinate = calculateAverage(); - this.sourceId1 = sourceId1; - this.sourceId2 = sourceId2; - - } - - /** - * - * @param mark1 Mark coordinates. - */ - public Marker(GPSCoordinate mark1) { - this(mark1, mark1, 0,0); - } - - /** - * - * @param mark1 Mark one coordinate - * @param mark2 Mark two coordinate - */ - public Marker(GPSCoordinate mark1, GPSCoordinate mark2) { - this(mark1, mark2, 0,0); - } - - public GPSCoordinate getMark1() { - return mark1; - } - - public GPSCoordinate getMark2() { - return mark2; - } - - /** - * Returns true if mark consists of two points, e.g. it is a gate. - * @return boolean - */ - public boolean isCompoundMark() { - return mark1 != mark2; - } - - public GPSCoordinate getAverageGPSCoordinate() { - return averageGPSCoordinate; - } - - - private GPSCoordinate calculateAverage() { - - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude()); - calc.setDestinationGeographicPoint(mark2.getLongitude(), mark2.getLatitude()); - double azimuth = calc.getAzimuth(); - double distance = calc.getOrthodromicDistance(); - - GeodeticCalculator middleCalc = new GeodeticCalculator(); - middleCalc.setStartingGeographicPoint(mark1.getLongitude(), mark1.getLatitude()); - middleCalc.setDirection(azimuth, distance / 2); - Point2D middlePoint = middleCalc.getDestinationGeographicPoint(); - return new GPSCoordinate(middlePoint.getY(), middlePoint.getX()); - - } - - public void setCurrentPosition1(GPSCoordinate gpsCoordinate){ - mark1 = gpsCoordinate; - } - - public void setCurrentPosition2(GPSCoordinate gpsCoordinate){ - mark2 = gpsCoordinate; - } - - public int getSourceId1() { - return sourceId1; - } - - public int getSourceId2() { - return sourceId2; - } -} diff --git a/visualiser/src/main/java/seng302/Model/RaceClock.java b/visualiser/src/main/java/seng302/Model/RaceClock.java deleted file mode 100644 index 1800a239..00000000 --- a/visualiser/src/main/java/seng302/Model/RaceClock.java +++ /dev/null @@ -1,133 +0,0 @@ -package seng302.Model; - -import com.github.bfsmith.geotimezone.TimeZoneLookup; -import com.github.bfsmith.geotimezone.TimeZoneResult; -import javafx.animation.AnimationTimer; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import seng302.GPSCoordinate; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.Date; - -/** - * This class is used to implement a clock which keeps track of and - * displays times relevant to a race. This is displayed on the - * {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas} via the - * {@link seng302.Controllers.RaceController RaceController} and the - * {@link seng302.Controllers.StartController StartController}. - */ -public class RaceClock implements Runnable { - private long lastTime; - private final ZoneId zoneId; - private ZonedDateTime time; - private ZonedDateTime startingTime; - private final StringProperty timeString; - private StringProperty duration; - - public RaceClock(ZonedDateTime zonedDateTime) { - this.zoneId = zonedDateTime.getZone(); - this.timeString = new SimpleStringProperty(); - this.duration = new SimpleStringProperty(); - this.time = zonedDateTime; - setTime(time); - } - - public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) { - TimeZoneLookup timeZoneLookup = new TimeZoneLookup(); - TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude()); - ZoneId zone = ZoneId.of(timeZoneResult.getResult()); - return LocalDateTime.now(zone).atZone(zone); - } - - public void run() { - new AnimationTimer() { - @Override - public void handle(long now) { - updateTime(); - } - }.start(); - } - - /** - * Sets time to arbitrary zoned time. - * - * @param time arbitrary time with timezone. - */ - private void setTime(ZonedDateTime time) { - this.time = time; - this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time)); - this.lastTime = System.currentTimeMillis(); - - - if(startingTime != null) { - long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds(); - if(seconds < 0) - duration.set(String.format("Starting in: %02d:%02d:%02d", -seconds/3600, -(seconds%3600)/60, -seconds%60)); - else - duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60)); - } - - } - - /** - * Sets time to given UTC time in seconds from Unix epoch, preserving timezone. - * @param time UTC time - */ - public void setUTCTime(long time) { - Date utcTime = new Date(time); - setTime(utcTime.toInstant().atZone(this.zoneId)); - } - - public ZonedDateTime getStartingTime() { - return startingTime; - } - - public void setStartingTime(ZonedDateTime startingTime) { - this.startingTime = startingTime; - } - - /** - * Get ZonedDateTime corresponding to local time zone and given UTC time. - * @param time time in mills - * @return local date time - */ - public ZonedDateTime getLocalTime(long time) { - Date utcTime = new Date(time); - return utcTime.toInstant().atZone(this.zoneId); - } - - /** - * Updates time by duration elapsed since last update. - */ - private void updateTime() { - this.time = this.time.plus(Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS)); - this.lastTime = System.currentTimeMillis(); - setTime(time); - } - - public String getDuration() { - return duration.get(); - } - - public StringProperty durationProperty() { - return duration; - } - - public String getTimeZone() { - return zoneId.toString(); - } - - public ZonedDateTime getTime() { - return time; - } - - public StringProperty timeStringProperty() { - return timeString; - } -} diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java deleted file mode 100644 index cb5107b0..00000000 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ /dev/null @@ -1,393 +0,0 @@ -package seng302.Model; - - -import javafx.collections.ObservableList; -import javafx.scene.Node; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.transform.Rotate; -import seng302.GraphCoordinate; -import seng302.Mock.StreamedCourse; -import seng302.RaceDataSource; -import seng302.RaceMap; - -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.*; - -/** - * This JavaFX Canvas is used to update and display details for a - * {@link seng302.RaceMap RaceMap} via the - * {@link seng302.Controllers.RaceController RaceController}.
- * It fills it's parent and cannot be downsized.
- * Details displayed include: - * {@link seng302.Model.Boat Boats} (and their - * {@link seng302.Model.TrackPoint TrackPoint}s), - * {@link seng302.Model.Marker Markers}, a - * {@link seng302.Model.RaceClock RaceClock}, a wind direction arrow and - * various user selected {@link seng302.Model.Annotations Annotations}. - */ -public class ResizableRaceCanvas extends ResizableCanvas { - private RaceMap map; - private List boats; - private List boatMarkers; - private boolean annoName = true; - private boolean annoAbbrev = true; - private boolean annoSpeed = true; - private boolean annoPath = true; - private boolean annoEstTime = true; - private boolean annoTimeSinceLastMark = true; - private List colours; - private final List markers; - private final RaceDataSource raceData; - private Map boatColours = new HashMap<>(); - private Node arrow; - - private RaceClock raceClock; - - public ResizableRaceCanvas(RaceDataSource raceData) { - super(); - - double lat1 = raceData.getMapTopLeft().getLatitude(); - double long1 = raceData.getMapTopLeft().getLongitude(); - double lat2 = raceData.getMapBottomRight().getLatitude(); - double long2 = raceData.getMapBottomRight().getLongitude(); - - setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight())); - - this.markers = raceData.getMarkers(); - makeColours(); - this.raceData = raceData; - } - - /** - * Sets the boats that are to be displayed in this race. - * - * @param boats in race - */ - public void setBoats(List boats) { - this.boats = boats; - mapBoatColours(); - } - - - /** - * Sets the boat markers that are to be displayed in this race. - * - * @param boatMarkers in race - */ - public void setBoatMarkers(ObservableList boatMarkers) { - this.boatMarkers = boatMarkers; - } - - /** - * Sets the RaceMap that the RaceCanvas is to be displaying for. - * - * @param map race map - */ - private void setMap(RaceMap map) { - this.map = map; - } - - private void displayBoat(Boat boat, double angle, Color colour) { - if (boat.getCurrentPosition() != null) { - GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition()); - - double[] x = {pos.getX() - 6, pos.getX(), pos.getX() + 6}; - double[] y = {pos.getY() + 12, pos.getY() - 12, pos.getY() + 12}; - gc.setFill(colour); - - gc.save(); - rotate(angle, pos.getX(), pos.getY()); - gc.fillPolygon(x, y, 3); - gc.restore(); - } - } - - /** - * Displays a line on the map with rectangles on the starting and ending point of the line. - * - * @param graphCoordinateA Starting Point of the line in GraphCoordinate. - * @param graphCoordinateB End Point of the line in GraphCoordinate. - * @param paint Colour the line is to coloured. - * @see GraphCoordinate - * @see Color - * @see Paint - */ - private void displayLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) { - gc.setStroke(paint); - gc.setFill(paint); - gc.fillOval(graphCoordinateA.getX() - 3, graphCoordinateA.getY() - 3, 6, 6); - gc.fillOval(graphCoordinateB.getX() - 3, graphCoordinateB.getY() - 3, 6, 6); - gc.strokeLine(graphCoordinateA.getX(), graphCoordinateA.getY(), graphCoordinateB.getX(), graphCoordinateB.getY()); - } - - /** - * Display a point on the Canvas - * - * @param graphCoordinate Coordinate that the point is to be displayed at. - * @param paint Colour that the boat is to be coloured. - * @see GraphCoordinate - * @see Paint - * @see Color - */ - private void displayPoint(GraphCoordinate graphCoordinate, Paint paint) { - gc.setFill(paint); - gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 10, 10); - } - - - /** - * Displays an arrow representing wind direction on the Canvas - * - * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). - * @see GraphCoordinate - */ - private void displayWindArrow(double angle) { - angle = angle % 360; - - // show direction wind is coming from - if (angle<180){angle = angle + 180;} - else {angle = angle - 180;} - - if (arrow != null && arrow.getRotate() != angle) { - arrow.setRotate(angle); - } - } - - /** - * Rotates things on the canvas Note: this must be called in between gc.save() and gc.restore() else they will rotate everything - * - * @param angle Bearing angle to rotate at in degrees - * @param px Pivot point x of rotation. - * @param py Pivot point y of rotation. - */ - private void rotate(double angle, double px, double py) { - Rotate r = new Rotate(angle, px, py); - gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy()); - } - - /** - * Display given name and speed of boat at a graph coordinate - * - * @param name name of the boat - * @param abbrev abbreviation of the boat name - * @param speed speed of the boat - * @param coordinate coordinate the text appears - * @param timeSinceLastMark time since the last mark was passed - */ - private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, String estTime, ZonedDateTime timeSinceLastMark) { - String text = ""; - //Check name toggle value - if (annoName){ - text += String.format("%s ", name); - } - //Check abbreviation toggle value - if (annoAbbrev){ - text += String.format("%s ", abbrev); - } - //Check speed toggle value - if (annoSpeed){ - text += String.format("%.2fkn ", speed); - } - if (annoEstTime) { - text += estTime; - } - //Check time since last mark toggle value - if(annoTimeSinceLastMark){ - Duration timeSince = Duration.between(timeSinceLastMark, raceClock.getTime()); - text += String.format(" %ds ", timeSince.getSeconds()); - } - //String text = String.format("%s, %2$.2fkn", name, speed); - long xCoord = coordinate.getX() + 20; - long yCoord = coordinate.getY(); - if (xCoord + (text.length() * 7) >= getWidth()) { - xCoord -= text.length() * 7; - } - if (yCoord - (text.length() * 2) <= 0) { - yCoord += 30; - } - gc.fillText(text, xCoord, yCoord); - } - - /** - * Draws race map with up to date data. - */ - public void update() { - this.draw(); - this.updateBoats(); - } - - /** - * Draw race markers - */ - private void drawMarkers() { - for(Marker marker: markers) { - GraphCoordinate mark1 = this.map.convertGPS(marker.getMark1()); - // removed drawing of lines between the marks as only - // the start and finish line should have a line drawn - if(marker.isCompoundMark()) { - GraphCoordinate mark2 = this.map.convertGPS(marker.getMark2()); - displayPoint(mark1, Color.LIMEGREEN); - displayPoint(mark2, Color.LIMEGREEN); - } else { - displayPoint(mark1, Color.GREEN); - } - } - } - - /** - * Draws the Race Map - */ - public void draw() { - - double width = getWidth(); - double height = getHeight(); - - gc.clearRect(0, 0, width, height); - - if (map == null) { - return;//TODO this should return a exception in the future - } - this.map.setHeight((int) height); - this.map.setWidth((int) width); - - gc.setLineWidth(2); - updateBoats(); - drawMarkers(); - - //display wind direction arrow - specify origin point and angle - angle now set to random angle - if (raceData instanceof StreamedCourse) { - displayWindArrow(((StreamedCourse) raceData).getWindDirection()); - } else { - displayWindArrow(150); - } - } - - - /** - * Toggle name display in annotation - */ - public void toggleAnnoName() { - annoName = !annoName; - } - - /** - * Toggle boat path display in annotation - */ - public void toggleBoatPath() { - annoPath = !annoPath; - } - - public void toggleAnnoEstTime() { - annoEstTime = !annoEstTime; - } - - /** - * Toggle boat time display in annotation - */ - public void toggleAnnoTime() { annoTimeSinceLastMark = !annoTimeSinceLastMark;} - - /** - * Toggle abbreviation display in annotation - */ - public void toggleAnnoAbbrev() { - annoAbbrev = !annoAbbrev; - } - - /** - * Toggle speed display in annotation - */ - public void toggleAnnoSpeed() { - annoSpeed = !annoSpeed; - } - - /** - * Draws boats while race in progress, when leg heading is set. - */ - private void updateBoats() { - if (boats != null) { - if (boatColours.size() < boats.size()) mapBoatColours(); - for (Boat boat : boats) { - boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF"); - boolean isStart = boat.isStarted(); - int sourceID = boat.getSourceID(); - if (!finished && isStart) { - displayBoat(boat, boat.getHeading(), boatColours.get(sourceID)); - GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition()); - GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake()); - displayLine(wakeFrom, wakeTo, boatColours.get(sourceID)); - } else if (!isStart) { - displayBoat(boat, boat.getHeading(), boatColours.get(sourceID)); - } else { - displayBoat(boat, 0, boatColours.get(sourceID)); - } - - if (Duration.between(boat.getTimeSinceLastMark(), raceClock.getTime()).getSeconds() < 0) { - boat.setTimeSinceLastMark(raceClock.getTime()); - } - - //If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts. - if (boat.isStarted() == false) { - boat.setTimeSinceLastMark(raceClock.getTime()); - } - - displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getFormattedEstTime(), boat.getTimeSinceLastMark()); - //TODO this needs to be fixed. - drawTrack(boat, boatColours.get(sourceID)); - } - } - } - - /** - * Draws all track points for a given boat. Colour is set by boat, opacity by track point. - * @param boat whose track is displayed - * @param colour The color to use for the track. - * @see seng302.Model.TrackPoint - */ - private void drawTrack(Boat boat, Color colour) { - if (annoPath) { - for (TrackPoint point : boat.getTrack()) { - GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate()); - gc.setFill(new Color(colour.getRed(), colour.getGreen(), colour.getBlue(), point.getAlpha())); - gc.fillOval(scaledCoordinate.getX(), scaledCoordinate.getY(), 5, 5); - } - } - } - - /** - * makes colours - */ - private void makeColours() { - colours = new ArrayList<>(Arrays.asList( - Color.BLUEVIOLET, - Color.BLACK, - Color.RED, - Color.ORANGE, - Color.DARKOLIVEGREEN, - Color.LIMEGREEN, - Color.PURPLE, - Color.DARKGRAY, - Color.YELLOW - )); - } - - public void setArrow(Node arrow) { - this.arrow = arrow; - } - - public void setRaceClock(RaceClock raceClock) { - this.raceClock = raceClock; - } - - private void mapBoatColours() { - int currentColour = 0; - for (Boat boat : boats) { - if (!boatColours.containsKey(boat.getSourceID())) { - boatColours.put(boat.getSourceID(), colours.get(currentColour)); - } - currentColour = (currentColour + 1) % colours.size(); - } - } - -} diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceMap.java b/visualiser/src/main/java/seng302/Model/ResizableRaceMap.java deleted file mode 100644 index 01b97af9..00000000 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceMap.java +++ /dev/null @@ -1,94 +0,0 @@ -package seng302.Model; - -import javafx.scene.paint.Color; -import seng302.GPSCoordinate; -import seng302.GraphCoordinate; -import seng302.RaceDataSource; -import seng302.RaceMap; - -import java.util.List; - -/** - * This JavaFX Canvas is used to generate the size of a - * {@link seng302.RaceMap RaceMap} using co-ordinates from a - * {@link seng302.RaceDataSource RaceDataSource}. This is done via the - * {@link seng302.Controllers.RaceController RaceController}. - */ -public class ResizableRaceMap extends ResizableCanvas { - private RaceMap map; - private final List raceBoundaries; - private double[] xpoints = {}; - private double[] ypoints = {}; - - /** - * Constructor - * @param raceData Race which it is taking its information to be displayed from - */ - public ResizableRaceMap(RaceDataSource raceData){ - super(); - raceBoundaries = raceData.getBoundary(); - - double lat1 = raceData.getMapTopLeft().getLatitude(); - double long1 = raceData.getMapTopLeft().getLongitude(); - double lat2 = raceData.getMapBottomRight().getLatitude(); - double long2 = raceData.getMapBottomRight().getLongitude(); - setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight())); - //draw(); - } - - /** - * Sets the map race that it is supposed to be viewing. - * @param map the map to be set - */ - private void setMap(RaceMap map) { - this.map = map; - } - - /** - * Draw boundary of the race. - */ - private void drawBoundaries() { - if (this.raceBoundaries == null) { - return; - } - gc.setFill(Color.AQUA); - setRaceBoundCoordinates(); - - gc.setLineWidth(1); - gc.fillPolygon(xpoints, ypoints, xpoints.length); - } - - /** - * Sets the coordinately of the race boundaries - */ - private void setRaceBoundCoordinates() { - xpoints = new double[this.raceBoundaries.size()]; - ypoints = new double[this.raceBoundaries.size()]; - for (int i = 0; i < raceBoundaries.size(); i++) { - GraphCoordinate coord = map.convertGPS(raceBoundaries.get(i)); - xpoints[i] = coord.getX(); - ypoints[i] = coord.getY(); - } - } - - /** - * Draw update for the canvas - */ - public void draw(){ - - double width = getWidth(); - double height = getHeight(); - - gc.clearRect(0, 0, width, height); - - if (map == null) { - return;//TODO this should return a exception in the future - } - this.map.setHeight((int) height); - this.map.setWidth((int) width); - - gc.setLineWidth(2); - drawBoundaries(); - } - -} diff --git a/visualiser/src/main/java/seng302/Model/Sparkline.java b/visualiser/src/main/java/seng302/Model/Sparkline.java deleted file mode 100644 index 0c4e503d..00000000 --- a/visualiser/src/main/java/seng302/Model/Sparkline.java +++ /dev/null @@ -1,179 +0,0 @@ -package seng302.Model; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.XYChart; -import javafx.scene.paint.Color; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -/** - * Class to process and modify a sparkline display. This display keeps visual - * track of {@link seng302.Model.Boat Boats}s in a race and their current - * placing position as they complete each {@link seng302.Model.Leg Leg} by - * passing a course {@link seng302.Model.Marker Marker}.
- * This sparkline is displayed using the - * {@link seng302.Controllers.RaceController RaceController}. - */ -public class Sparkline { - private ArrayList colours; - private ArrayList startBoats = new ArrayList<>(); - private Map boatColours = new HashMap<>(); - private Integer legNum; - private Integer sparkLineNumber = 0; - @FXML LineChart sparklineChart; - @FXML NumberAxis xAxis; - @FXML NumberAxis yAxis; - - - /** - * Constructor to set up initial sparkline (LineChart) object - * @param boats boats to display on the sparkline - * @param legNum total number of legs in the race - * @param sparklineChart javaFX LineChart for the sparkline - */ - public Sparkline(ObservableList boats, Integer legNum, - LineChart sparklineChart) { - this.sparklineChart = sparklineChart; - this.legNum = legNum; - this.yAxis = (NumberAxis)sparklineChart.getYAxis(); - this.xAxis = (NumberAxis)sparklineChart.getXAxis(); - startBoats.addAll(boats); - - makeColours(); - mapBoatColours(); - createSparkline(); - } - - - /** - * Creates and sets initial display for Sparkline for race positions. - * A data series for each boat in the race is added. - * Position numbers are displayed. - */ - public void createSparkline(){ - // NOTE: Y axis is in negatives to display correct positions - - // all boats start in 'last' place - for (int i=0; i series = new XYChart.Series(); - series.getData().add(new XYChart.Data(0, -startBoats.size())); - series.getData().add(new XYChart.Data(0, -startBoats.size())); - sparklineChart.getData().add(series); - sparklineChart.getData().get(i).getNode().setStyle("-fx-stroke: " + - ""+boatColours.get(startBoats.get(i).getSourceID())+";"); - } - - sparklineChart.setCreateSymbols(false); - - // set x axis details - xAxis.setAutoRanging(false); - xAxis.setTickMarkVisible(false); - xAxis.setTickLabelsVisible(false); - xAxis.setMinorTickVisible(false); - xAxis.setUpperBound((startBoats.size()+1)*legNum); - xAxis.setTickUnit((startBoats.size()+1)*legNum); - - // set y axis details - yAxis.setLowerBound(-(startBoats.size()+1)); - yAxis.setUpperBound(0); - yAxis.setAutoRanging(false); - yAxis.setLabel("Position in Race"); - yAxis.setTickUnit(1); - yAxis.setTickMarkVisible(false); - yAxis.setMinorTickVisible(false); - - // hide minus number from displaying on axis - yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) { - @Override - public String toString(Number value) { - if ((Double)value == 0.0 - || (Double)value < -startBoats.size()){ - return ""; - } - else { - return String.format("%7.0f", -value.doubleValue()); - } - } - }); - } - - /** - * Updates the sparkline to display current boat positions. - * New points are plotted to represent each boat when required. - * @param boatsInRace current position of the boats in race - */ - public void updateSparkline(ObservableList boatsInRace){ - int placingVal = boatsInRace.size(); - sparkLineNumber++; - - for (int i = boatsInRace.size() - 1; i >= 0; i--){ - for (int j = startBoats.size() - 1; j >= 0; j--){ - if (boatsInRace.get(i)==startBoats.get(j)){ - - // when a boat is on its first leg - if (boatsInRace.get(i).getCurrentLeg().getLegNumber()==0){ - // adjust boats latest point on X axis - sparklineChart.getData().get(j).getData().get(1) - .setXValue(sparkLineNumber); - } - - // when a boat first enters its second leg - else if (boatsInRace.get(i).getCurrentLeg().getLegNumber - ()==1 && sparklineChart.getData().get(j).getData - ().size()==2){ - // adjust boats position from start mark - sparklineChart.getData().get(j).getData().get(1) - .setYValue(-placingVal); - sparklineChart.getData().get(j).getData().get(1) - .setXValue(sparkLineNumber); - sparklineChart.getData().get(j).getData().add(new XYChart.Data<> - (sparkLineNumber, -placingVal)); - } - - // plot new point for boats current position - else { - sparklineChart.getData().get(j).getData().add - (new XYChart.Data<>(sparkLineNumber, -placingVal)); - } - placingVal-=1; - } - } - } - } - - private void makeColours() { - colours = new ArrayList<>(Arrays.asList( - colourToHex(Color.BLUEVIOLET), - colourToHex(Color.BLACK), - colourToHex(Color.RED), - colourToHex(Color.ORANGE), - colourToHex(Color.DARKOLIVEGREEN), - colourToHex(Color.LIMEGREEN), - colourToHex(Color.PURPLE), - colourToHex(Color.DARKGRAY), - colourToHex(Color.YELLOW) - )); - } - - private String colourToHex(Color color) { - return String.format( "#%02X%02X%02X", - (int)( color.getRed() * 255 ), - (int)( color.getGreen() * 255 ), - (int)( color.getBlue() * 255 ) ); - } - - private void mapBoatColours() { - int currentColour = 0; - for (Boat boat : startBoats) { - if (!boatColours.containsKey(boat.getSourceID())) { - boatColours.put(boat.getSourceID(), colours.get(currentColour)); - } - currentColour = (currentColour + 1) % colours.size(); - } - } -} diff --git a/visualiser/src/main/java/seng302/Model/TrackPoint.java b/visualiser/src/main/java/seng302/Model/TrackPoint.java deleted file mode 100644 index 12eab755..00000000 --- a/visualiser/src/main/java/seng302/Model/TrackPoint.java +++ /dev/null @@ -1,60 +0,0 @@ -package seng302.Model; - -import seng302.GPSCoordinate; - -/** - * A TrackPoint is a point plotted to display the track a - * {@link seng302.Model.Boat Boat} has travelled in a race.
- * TrackPoints are displayed on a - * {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}, via the - * {@link seng302.Controllers.RaceController RaceController}.
- * Track points can be made visible or hidden via the RaceController's - * {@link seng302.Model.Annotations Annotations}. - */ -public class TrackPoint { - private final GPSCoordinate coordinate; - private final long timeAdded; - private final long expiry; - private final double minAlpha; - - /** - * Creates a new track point with fixed GPS coordinates and time, to reach minimum opacity on expiry. - * - * @param coordinate position of point on physical race map - * @param timeAdded system clock at time of addition - * @param expiry time to minimum opacity after added - */ - public TrackPoint(GPSCoordinate coordinate, long timeAdded, long expiry) { - this.coordinate = coordinate; - this.timeAdded = timeAdded; - this.expiry = expiry; - this.minAlpha = 0.1; - } - - /** - * Gets the position of the point on physical race map. - * - * @return GPS coordinate of point - */ - public GPSCoordinate getCoordinate() { - return coordinate; - } - - /** - * Gets opacity of point scaled by age in proportion to expiry, between 1 and minimum opacity inclusive. - * - * @return greater of minimum opacity and scaled opacity - */ - public double getAlpha() { - return Double.max(minAlpha, 1.0 - (double) (System.currentTimeMillis() - timeAdded) / expiry); - } - - /** - * Gets time point was added to track. - * - * @return system clock at time of addition - */ - public long getTimeAdded() { - return timeAdded; - } -} diff --git a/visualiser/src/main/java/seng302/RaceDataSource.java b/visualiser/src/main/java/seng302/RaceDataSource.java deleted file mode 100644 index e3bb7634..00000000 --- a/visualiser/src/main/java/seng302/RaceDataSource.java +++ /dev/null @@ -1,26 +0,0 @@ -package seng302; - -import seng302.Model.Boat; -import seng302.Model.Leg; -import seng302.Model.Marker; - -import java.time.ZonedDateTime; -import java.util.List; - -/** - * An object that holds relevant data for a race.
- * Information includes: {@link seng302.Model.Boat Boat}s, - * {@link seng302.Model.Leg Leg}s, {@link seng302.Model.Marker Marker}s and - * the {@link seng302.GPSCoordinate GPSCoordinate}s to create a - * {@link seng302.Model.ResizableRaceMap ResizableRaceMap}. - */ -public interface RaceDataSource { - List getBoats(); - List getLegs(); - List getMarkers(); - List getBoundary(); - - ZonedDateTime getZonedDateTime(); - GPSCoordinate getMapTopLeft(); - GPSCoordinate getMapBottomRight(); -} diff --git a/visualiser/src/main/java/seng302/RaceMap.java b/visualiser/src/main/java/seng302/RaceMap.java deleted file mode 100644 index 6d55b0a9..00000000 --- a/visualiser/src/main/java/seng302/RaceMap.java +++ /dev/null @@ -1,76 +0,0 @@ -package seng302; - -/** - * The base size of the map to be used for the - * {@link seng302.Model.ResizableRaceMap ResizableRaceMap} and - * {@link seng302.Model.ResizableRaceCanvas ResizableRaceCanvas}. It is used - * to convert {@link seng302.GPSCoordinate GPSCoordinate}s to relative - * {@link seng302.GraphCoordinate GraphCoordinate}s. - */ -public class RaceMap { - private final double x1; - private final double x2; - private final double y1; - private final double y2; - private int width, height; - - /** - * Constructor Method. - * - * @param x1 Longitude of the top left point. - * @param y1 Latitude of the top left point. - * @param x2 Longitude of the top right point. - * @param y2 Latitude of the top right point. - * @param width width that the Canvas the race is to be drawn on is. - * @param height height that the Canvas the race is to be drawn on is. - */ - public RaceMap(double y1, double x1, double y2, double x2, int height, int width) { - this.x1 = x1; - this.x2 = x2; - this.y1 = y1; - this.y2 = y2; - this.width = width; - this.height = height; - } - - /** - * Converts GPS coordinates to coordinates for container - * - * @param lat GPS latitude - * @param lon GPS longitude - * @return GraphCoordinate (pair of doubles) - * @see GraphCoordinate - */ - private GraphCoordinate convertGPS(double lat, double lon) { - int difference = Math.abs(width - height); - int size = width; - if (width > height) { - size = height; - return new GraphCoordinate((int) ((size * (lon - x1) / (x2 - x1)) + difference / 2), (int) (size - (size * (lat - y1) / (y2 - y1)))); - } else { - return new GraphCoordinate((int) (size * (lon - x1) / (x2 - x1)), (int) ((size - (size * (lat - y1) / (y2 - y1))) + difference / 2)); - } - - //return new GraphCoordinate((int) (width * (lon - x1) / (x2 - x1)), (int) (height - (height * (lat - y1) / (y2 - y1)))); - } - - /** - * Converts the GPS Coordinate to GraphCoordinates - * - * @param coordinate GPSCoordinate representation of Latitude and Longitude. - * @return GraphCoordinate that the GPS is coordinates are to be displayed on the map. - * @see GraphCoordinate - * @see GPSCoordinate - */ - public GraphCoordinate convertGPS(GPSCoordinate coordinate) { - return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); - } - - public void setWidth(int width) { - this.width = width; - } - - public void setHeight(int height) { - this.height = height; - } -} diff --git a/visualiser/src/main/java/seng302/VisualiserInput.java b/visualiser/src/main/java/seng302/VisualiserInput.java deleted file mode 100644 index f5a3f561..00000000 --- a/visualiser/src/main/java/seng302/VisualiserInput.java +++ /dev/null @@ -1,434 +0,0 @@ -package seng302; -import javafx.application.Platform; -import org.xml.sax.SAXException; -import seng302.Mock.*; -import seng302.Networking.BinaryMessageDecoder; -import seng302.Networking.Exceptions.InvalidMessageException; -import seng302.Networking.Messages.*; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.DataInputStream; -import java.io.IOException; -import java.net.Socket; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static seng302.Networking.Utils.ByteConverter.bytesToShort; - -/** - * TCP client which receives packets/messages from a race data source - * (e.g., mock source, official source), and exposes them to any observers. - * @see seng302.Mock.StreamedCourse - */ -public class VisualiserInput implements Runnable { - - ///Timestamp of the last heartbeat. - private long lastHeartbeatTime = -1; - ///Sequence number of the last heartbeat. - private long lastHeartbeatSequenceNum = -1; - - ///The socket that we have connected to. - private Socket connectionSocket; - - ///Object to store parsed course data. //TODO comment? - private StreamedCourse course; - - ///The last RaceStatus message received. - private RaceStatus raceStatus; - - ///A map of the last BoatStatus message received, for each boat. - private final Map boatStatusMap = new HashMap<>(); - - ///A map of the last BoatLocation message received, for each boat. - private final Map boatLocationMap = new HashMap<>(); - - ///The last AverageWind message received. - private AverageWind averageWind; - - ///The last CourseWinds message received. - private CourseWinds courseWinds; - - ///A map of the last MarkRounding message received, for each boat. - private final Map markRoundingMap = new HashMap<>(); - - ///InputStream (from the socket). - private DataInputStream inStream; - - /** - * Ctor. - * @param socket Socket from which we will receive race data. - * @param course TODO comment? - * @throws IOException If there is something wrong with the socket's input stream. - */ - public VisualiserInput(Socket socket, StreamedCourse course) throws IOException { - this.connectionSocket = socket; - //We wrap a DataInputStream around the socket's InputStream because it has the stream.readFully(buffer) function, which is a blocking read until the buffer has been filled. - this.inStream = new DataInputStream(connectionSocket.getInputStream()); - this.course = course; - - this.lastHeartbeatTime = System.currentTimeMillis(); - } - - /** - * Provides StreamedCourse container for fixed course data. - * @return Course for current VisualiserInput instance. - * @see seng302.Mock.StreamedCourse - */ - public StreamedCourse getCourse() { - return course; - } - - /** - * Returns the last boat location message associated with the given boat source ID. - * @param sourceID Unique global identifier for the boat. - * @return The most recent location message. - */ - public BoatLocation getBoatLocationMessage(int sourceID) { - return boatLocationMap.get(sourceID); - } - - public BoatStatus getBoatStatusMessage(int sourceID) { - return boatStatusMap.get(sourceID); - } - - /** - * Calculates the time since last heartbeat, in milliseconds. - * @return Time since last heartbeat, in milliseconds.. - */ - private double timeSinceHeartbeat() { - long now = System.currentTimeMillis(); - return (now - lastHeartbeatTime); - } - - /** - * Returns the boat locations map. Maps from Integer (Boat ID) to BoatLocation. - * @return Map of boat locations. - */ - public Map getBoatLocationMap() { - return boatLocationMap; - } - - /** - * Gets the status of the race. - * @return The status of the race. - */ - public RaceStatus getRaceStatus() { - return raceStatus; - } - - /** - * Returns the boat statuses map. Maps from Integer (Boat ID) to BoatStatus. - * @return Map of boat statuses. - */ - public Map getBoatStatusMap() { - return boatStatusMap; - } - - /** - * Returns the average wind of the race. - * @return Average wind in the race. - */ - public AverageWind getAverageWind() { - return averageWind; - } - - /** - * Returns winds in the course. - * @return Winds that are in the course. - */ - public CourseWinds getCourseWinds() { - return courseWinds; - } - - - /** - * Returns the mark roundings map. Maps from Integer (Boat ID) to MarkRounding. - * @return Map of mark roundings. - */ - public Map getMarkRoundingMap() { - return markRoundingMap; - } - - /** - * Sets the wind direction for the current course. - * @param direction The new wind direction for the course. - */ - private void setCourseWindDirection(double direction) { - this.course.setWindDirection(direction); - } - - /** - * Reads and returns the next message as an array of bytes from the socket. Use getNextMessage() to get the actual message object instead. - * @return Encoded binary message bytes. - * @throws IOException Thrown when an error occurs while reading from the socket. - */ - private byte[] getNextMessageBytes() throws IOException { - inStream.mark(0); - short CRCLength = 4; - short headerLength = 15; - - //Read the header of the next message. - byte[] headerBytes = new byte[headerLength]; - inStream.readFully(headerBytes); - - //Read the message body length. - byte[] messageBodyLengthBytes = Arrays.copyOfRange(headerBytes, headerLength - 2, headerLength); - short messageBodyLength = bytesToShort(messageBodyLengthBytes); - - //Read the message body. - byte[] messageBodyBytes = new byte[messageBodyLength]; - inStream.readFully(messageBodyBytes); - - //Read the message CRC. - byte[] messageCRCBytes = new byte[CRCLength]; - inStream.readFully(messageCRCBytes); - - //Put the head + body + crc into one large array. - ByteBuffer messageBytes = ByteBuffer.allocate(headerBytes.length + messageBodyBytes.length + messageCRCBytes.length); - messageBytes.put(headerBytes); - messageBytes.put(messageBodyBytes); - messageBytes.put(messageCRCBytes); - - return messageBytes.array(); - } - - /** - * Reads and returns the next message object from the socket. - * @return The message object. Use instanceof for concrete type. - * @throws IOException Thrown when an error occurs while reading from the socket. - * @throws InvalidMessageException Thrown when the message is invalid in some way. - */ - private AC35Data getNextMessage() throws IOException, InvalidMessageException - { - //Get the next message from the socket as a block of bytes. - byte[] messageBytes = this.getNextMessageBytes(); - - //Decode the binary message into an appropriate message object. - BinaryMessageDecoder decoder = new BinaryMessageDecoder(messageBytes); - - return decoder.decode(); - - } - - /** - * Main loop which reads messages from the socket, and exposes them. - */ - public void run(){ - boolean receiverLoop = true; - //receiver loop that gets the input - while (receiverLoop) { - - //If no heartbeat has been received in more the heartbeat period - //then the connection will need to be restarted. - //System.out.println("time since last heartbeat: " + timeSinceHeartbeat());//TEMP REMOVE - long heartBeatPeriod = 10 * 1000; - if (timeSinceHeartbeat() > heartBeatPeriod) { - System.out.println("Connection has stopped, trying to reconnect."); - - //Attempt to reconnect the socket. - try {//This attempt doesn't really work. Under what circumstances would - this.connectionSocket = new Socket(this.connectionSocket.getInetAddress(), this.connectionSocket.getPort()); - //this.connectionSocket.connect(this.connectionSocket.getRemoteSocketAddress()); - //Reset the heartbeat timer. - this.lastHeartbeatTime = System.currentTimeMillis(); - } - catch (IOException e) { - System.err.println("Unable to reconnect."); - - //Wait 500ms. Ugly hack, should refactor. - long waitPeriod = 500; - long waitTimeStart = System.currentTimeMillis() + waitPeriod; - - while (System.currentTimeMillis() < waitTimeStart){ - //Nothing. Busyloop. - } - - //Swallow the exception. - continue; - } - - } - - //Reads the next message. - AC35Data message; - try { - message = this.getNextMessage(); - } - catch (InvalidMessageException | IOException e) { - //Prints exception to stderr, and iterate loop (that is, read the next message). - System.err.println("Unable to read message: " + e.getMessage()); - try { - inStream.reset(); - } catch (IOException e1) { - e1.printStackTrace(); - } - //Continue to the next loop iteration/message. - continue; - }/* - - //Add it to message queue. - this.messagesReceivedQueue.add(message);*/ - - - //Checks which message is being received and does what is needed for that message. - //Heartbeat. - if (message instanceof Heartbeat) { - Heartbeat heartbeat = (Heartbeat) message; - - //Check that the heartbeat number is greater than the previous value, and then set the last heartbeat time. - if (heartbeat.getSequenceNumber() > this.lastHeartbeatSequenceNum) { - lastHeartbeatTime = System.currentTimeMillis(); - lastHeartbeatSequenceNum = heartbeat.getSequenceNumber(); - //System.out.println("HeartBeat Message! " + lastHeartbeatSequenceNum); - } - } - //RaceStatus. - else if (message instanceof RaceStatus) { - RaceStatus raceStatus = (RaceStatus) message; - - //System.out.println("Race Status Message"); - this.raceStatus = raceStatus; - for (BoatStatus boatStatus: this.raceStatus.getBoatStatuses()) { - this.boatStatusMap.put(boatStatus.getSourceID(), boatStatus); - } - setCourseWindDirection(raceStatus.getScaledWindDirection()); - } - //DisplayTextMessage. - /*else if (message instanceof DisplayTextMessage) { - //System.out.println("Display Text Message"); - //No decoder for this. - }*/ - //XMLMessage. - else if (message instanceof XMLMessage) { - XMLMessage xmlMessage = (XMLMessage) message; - - //System.out.println("XML Message!"); - - Platform.runLater(()-> { - if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRegatta) { - //System.out.println("Setting Regatta"); - try { - course.setRegattaXMLReader(new RegattaXMLReader(xmlMessage.getXmlMessage())); - - } - //TODO REFACTOR should put all of these exceptions behind a RegattaXMLReaderException. - catch (IOException | SAXException | ParserConfigurationException e) { - System.err.println("Error creating RegattaXMLReader: " + e.getMessage()); - //Continue to the next loop iteration/message. - } - - } else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeRace) { - //System.out.println("Setting Course"); - try { - course.setStreamedCourseXMLReader(new StreamedCourseXMLReader(xmlMessage.getXmlMessage())); - } - //TODO REFACTOR should put all of these exceptions behind a StreamedCourseXMLReaderException. - catch (IOException | SAXException | ParserConfigurationException | StreamedCourseXMLException e) { - System.err.println("Error creating StreamedCourseXMLReader: " + e.getMessage()); - //Continue to the next loop iteration/message. - } - - } else if (xmlMessage.getXmlMsgSubType() == XMLMessage.XMLTypeBoat) { - //System.out.println("Setting Boats"); - try { - course.setBoatXMLReader(new BoatXMLReader(xmlMessage.getXmlMessage())); - } - //TODO REFACTOR should put all of these exceptions behind a BoatXMLReaderException. - catch (IOException | SAXException | ParserConfigurationException e) { - System.err.println("Error creating BoatXMLReader: " + e.getMessage()); - //Continue to the next loop iteration/message. - } - - } - }); - } - //RaceStartStatus. - else if (message instanceof RaceStartStatus) { - - //System.out.println("Race Start Status Message"); - } - //YachtEventCode. - /*else if (message instanceof YachtEventCode) { - YachtEventCode yachtEventCode = (YachtEventCode) message; - - //System.out.println("Yacht Event Code!"); - //No decoder for this. - - }*/ - //YachtActionCode. - /*else if (message instanceof YachtActionCode) { - YachtActionCode yachtActionCode = (YachtActionCode) message; - - //System.out.println("Yacht Action Code!"); - //No decoder for this. - - }*/ - //ChatterText. - /*else if (message instanceof ChatterText) { - ChatterText chatterText = (ChatterText) message; - - //System.out.println("Chatter Text Message!"); - //No decoder for this. - - }*/ - //BoatLocation. - else if (message instanceof BoatLocation) { - BoatLocation boatLocation = (BoatLocation) message; - - //System.out.println("Boat Location!"); - if (this.boatLocationMap.containsKey(boatLocation.getSourceID())) { - //If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer). - if (boatLocation.getTime() > this.boatLocationMap.get(boatLocation.getSourceID()).getTime()){ - //If it is, replace the old message. - this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation); - } - }else{ - //If the map _doesn't_ already contain a message for this boat, insert the message. - this.boatLocationMap.put(boatLocation.getSourceID(), boatLocation); - } - } - //MarkRounding. - else if (message instanceof MarkRounding) { - MarkRounding markRounding = (MarkRounding) message; - - //System.out.println("Mark Rounding Message!"); - - if (this.markRoundingMap.containsKey(markRounding.getSourceID())) { - //If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer). - if (markRounding.getTime() > this.markRoundingMap.get(markRounding.getSourceID()).getTime()){ - //If it is, replace the old message. - this.markRoundingMap.put(markRounding.getSourceID(), markRounding); - } - }else{ - //If the map _doesn't_ already contain a message for this boat, insert the message. - this.markRoundingMap.put(markRounding.getSourceID(), markRounding); - } - - } - //CourseWinds. - else if (message instanceof CourseWinds) { - - //System.out.println("Course Wind Message!"); - this.courseWinds = (CourseWinds) message; - - } - //AverageWind. - else if (message instanceof AverageWind) { - - //System.out.println("Average Wind Message!"); - this.averageWind = (AverageWind) message; - - } - //Unrecognised message. - else { - System.out.println("Broken Message!"); - } - - } - } - - - -} diff --git a/visualiser/src/main/java/seng302/XMLReader.java b/visualiser/src/main/java/seng302/XMLReader.java deleted file mode 100644 index f250e268..00000000 --- a/visualiser/src/main/java/seng302/XMLReader.java +++ /dev/null @@ -1,52 +0,0 @@ -package seng302; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; - -/** - * The abstract class for reading in XML race data. - */ -public abstract class XMLReader { - - protected Document doc; - - protected XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException { - InputStream fXmlFile = getClass().getClassLoader().getResourceAsStream(filePath); - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(fXmlFile); - doc.getDocumentElement().normalize(); - } - - protected XMLReader(InputStream xmlInput) throws ParserConfigurationException, IOException, SAXException { - - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(xmlInput); - } - - public Document getDocument() { - return doc; - } - - protected String getTextValueOfNode(Element n, String tagName) { - return n.getElementsByTagName(tagName).item(0).getTextContent(); - } - - protected boolean exists(Node node, String attribute) { - return node.getAttributes().getNamedItem(attribute) != null; - } - - public String getAttribute(Element n, String attr) { - return n.getAttribute(attr); - } - -} diff --git a/visualiser/src/test/java/seng302/Mock/StreamedRaceTest.java b/visualiser/src/test/java/seng302/Mock/StreamedRaceTest.java deleted file mode 100644 index bca938c7..00000000 --- a/visualiser/src/test/java/seng302/Mock/StreamedRaceTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package seng302.Mock; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import seng302.GPSCoordinate; -import seng302.Model.Leg; -import seng302.Model.Marker; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Tests only work on the current version of mockXML/raceXML/raceTest.xml - */ -public class StreamedRaceTest { - private StreamedCourseXMLReader streamedCourseXMLReader; - private List boundary; - - @Before - public void setup() { - try { - streamedCourseXMLReader = new StreamedCourseXMLReader("mockXML/raceXML/raceTest.xml", true); - boundary = streamedCourseXMLReader.getBoundary(); - } catch (Exception | StreamedCourseXMLException e) { - e.printStackTrace(); - //fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder"); - } - } - - @Test - public void testAllBoundaryPointsRead() { - assertEquals(boundary.size(), 10); - } - - @Test - public void testBoundaryPointData() { - // First point - assertEquals(boundary.get(0).getLatitude(), -36.8325, 1e-6); - assertEquals(boundary.get(0).getLongitude(), 174.8325, 1e-6); - - // Last point - assertEquals(boundary.get(boundary.size() - 1).getLatitude(), -36.83417, 1e-6); - assertEquals(boundary.get(boundary.size() - 1).getLongitude(), 174.84767, 1e-6); - } - - @Test - public void testMapEdges() { - double maxLatitude = streamedCourseXMLReader.getMapBottomRight().getLatitude() - streamedCourseXMLReader.getPadding(); - double maxLongitude = streamedCourseXMLReader.getMapBottomRight().getLongitude() - streamedCourseXMLReader.getPadding(); - double minLatitude = streamedCourseXMLReader.getMapTopLeft().getLatitude() - streamedCourseXMLReader.getPadding(); - double minLongitude = streamedCourseXMLReader.getMapTopLeft().getLongitude() - streamedCourseXMLReader.getPadding(); - - assertEquals(maxLatitude, -36.81033, 1e-6); - assertEquals(maxLongitude, 174.88217, 1e-6); - assertEquals(minLatitude, -36.83417, 1e-6); - assertEquals(minLongitude, 174.81983, 1e-6); - } - - @Test - public void testRaceSettings() { - - } - - @Test - public void correctLegSequence() { - List legs = streamedCourseXMLReader.getLegs(); - String[] expectedNames = { - "StartLine", - "M1", - "M2", - "Gate" - }; - for(int i = 0; i < legs.size(); i++) { - assertEquals(expectedNames[i], legs.get(i).getName()); - } - } - - /** - * raceTest.xml is not compliant with this test. Markers are positioned far out of bounds. - */ - @Test - @Ignore - public void markersWithinRaceBoundaries() { - GPSCoordinate topLeft = streamedCourseXMLReader.getMapTopLeft(); - GPSCoordinate bottomRight = streamedCourseXMLReader.getMapBottomRight(); - - for(Marker compoundMark : streamedCourseXMLReader.getMarkers()) { - GPSCoordinate centre = compoundMark.getAverageGPSCoordinate(); - assertTrue(centre.getLatitude() < bottomRight.getLatitude()); - assertTrue(centre.getLatitude() > topLeft.getLatitude()); - assertTrue(centre.getLongitude() > bottomRight.getLongitude()); - assertTrue(centre.getLongitude() < topLeft.getLongitude()); - } - } -} diff --git a/visualiser/src/test/java/seng302/Model/BoatInRaceTest.java b/visualiser/src/test/java/seng302/Model/BoatInRaceTest.java deleted file mode 100644 index 22c2caaf..00000000 --- a/visualiser/src/test/java/seng302/Model/BoatInRaceTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package seng302.Model; - -/** - * Tests various aspects of a boat in race perform correctly. - */ -public class BoatInRaceTest { - // TODO change this test to use Boat and not BoatInRace ?? - // TODO delete BoatInRace class - -// private final GPSCoordinate ORIGIN_COORDS = new GPSCoordinate(0, 0); -// private final BoatInRace TEST_BOAT = new BoatInRace("Test", 1, Color.ALICEBLUE, "tt"); -// -// -// @Test -// public void calculateDueNorthAzimuthReturns0() { -// -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(50, 0)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateAzimuth(), 0, 1e-8); -// } -// -// @Test -// public void calculateDueSouthAzimuthReturns180() { -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateAzimuth(), 180, 1e-8); -// } -// -// -// @Test -// public void calculateDueEastAzimuthReturns90() { -// -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(0, 50)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateAzimuth(), 90, 1e-8); -// } -// -// -// @Test -// public void calculateDueWestAzimuthReturnsNegative90() { -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(0, -50)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateAzimuth(), -90, 1e-8); -// -// } -// -// @Test -// public void calculateDueNorthHeadingReturns0() { -// -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(50, 0)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateHeading(), 0, 1e-8); -// } -// -// -// @Test -// public void calculateDueEastHeadingReturns90() { -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(0, 50)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateHeading(), 90, 1e-8); -// } -// -// @Test -// public void calculateDueSouthHeadingReturns180() { -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateHeading(), 180, 1e-8); -// } -// -// @Test -// public void calculateDueWestHeadingReturns270() { -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(0, -50)); -// Leg start = new Leg("Start", startMarker, endMarker, 0); -// TEST_BOAT.setCurrentLeg(start); -// assertEquals(TEST_BOAT.calculateHeading(), 270, 1e-8); -// } -// -// @Test -// public void createNewBoatCratesInstanceOfSuperClass() { -// -// BoatInRace testBoat = new BoatInRace("Boat", 20, Color.ALICEBLUE, "tt"); -// testBoat.setName("Name can change"); -// assertTrue(testBoat instanceof Boat); -// assertTrue(testBoat.getCurrentLeg() == null); -// assertTrue(testBoat.getCurrentPosition() == null); -// assertTrue(testBoat.toString().contains("Name can change")); -// assertEquals(testBoat.getVelocity(), 20.0); -// assertTrue(testBoat.getVelocityProp().toString().contains("20")); -// assertTrue(testBoat.getAbbrev().equals("tt")); -// assertTrue(testBoat.getColour().equals(Color.ALICEBLUE)); -// assertFalse(testBoat.isFinished()); -// } -// -// -// @Test -// public void getWakeAtProperHeading() throws Exception { -// BoatInRace boat = new BoatInRace("Test", 1, Color.ALICEBLUE, "tt"); -// -// // Construct leg of 0 degrees -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(50, 0)); -// Leg leg0deg = new Leg("Start", startMarker, endMarker, 0); -// boat.setCurrentLeg(leg0deg); -// boat.setCurrentPosition(new GPSCoordinate(0, 0)); -// -// assertEquals(0, boat.calculateHeading(), 1e-8); -// -// // Construct leg from wake - heading should be 180 degrees -// Leg leg180deg = new Leg("Start", startMarker, new Marker(boat.getWake()), 0); -// boat.setCurrentLeg(leg180deg); -// -// assertEquals(180, boat.calculateHeading(), 1e-8); -// } -// -// -// @Test -// public void getWakeProportionalToVelocity() throws Exception { -// BoatInRace boat = new BoatInRace("Test", 10, Color.ALICEBLUE, "tt"); -// -// // Construct leg of 0 degrees at 0 N -// Marker startMarker = new Marker(ORIGIN_COORDS); -// Marker endMarker = new Marker(new GPSCoordinate(50, 0)); -// Leg leg0deg = new Leg("Start", startMarker, endMarker, 0); -// boat.setCurrentLeg(leg0deg); -// boat.setCurrentPosition(new GPSCoordinate(0, 0)); -// -// // Get latitude of endpoint of wake at 10 kn (longitude is 0) -// double endpointAt10Kn = boat.getWake().getLatitude(); -// -// // Latitude of endpoint at 20 kn should be twice endpoint at 10 kn -// boat.setVelocity(20); -// assertEquals(2 * endpointAt10Kn, boat.getWake().getLatitude(), 1e-8); -// } -} \ No newline at end of file diff --git a/visualiser/src/test/java/seng302/Model/ConstantVelocityRaceTest.java b/visualiser/src/test/java/seng302/Model/ConstantVelocityRaceTest.java deleted file mode 100644 index 4985c69f..00000000 --- a/visualiser/src/test/java/seng302/Model/ConstantVelocityRaceTest.java +++ /dev/null @@ -1,140 +0,0 @@ -//package seng302.Model; -// -// -//import javafx.scene.paint.Color; -//import org.geotools.referencing.GeodeticCalculator; -//import org.junit.Test; -//import seng302.Constants; -//import seng302.GPSCoordinate; -// -//import java.lang.reflect.Array; -//import java.util.ArrayList; -// -//import static org.junit.Assert.assertEquals; -// -///** -// * Created by esa46 on 16/03/17. -// */ -//public class ConstantVelocityRaceTest { -// -// CompoundMark START_MARKER = new CompoundMark(new GPSCoordinate(0, 0)); -// CompoundMark END_MARKER = new CompoundMark(new GPSCoordinate(10, 10)); -// Leg START_LEG = new Leg("Start", START_MARKER, END_MARKER, 0); -// -// int ONE_HOUR = 3600000; //1 hour in milliseconds -// -// -// private ArrayList generateLegsArray() { -// ArrayList legs = new ArrayList<>(); -// legs.add(START_LEG); -// return legs; -// } -// -// @Test -// public void updatePositionChangesDistanceTravelled() { -// ArrayList legs = generateLegsArray(); -// BoatInRace boat = new BoatInRace("Test", 1, Color.ALICEBLUE, "tt"); -// boat.setCurrentLeg(legs.get(0)); -// boat.setDistanceTravelledInLeg(0); -// BoatInRace[] boats = new BoatInRace[]{boat}; -// -// ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, null, 1); -// -// race.updatePosition(boat, ONE_HOUR); -// assertEquals(boat.getDistanceTravelledInLeg(), boat.getVelocity(), 1e-8); -// } -// -// -// @Test -// public void updatePositionHandlesNoChangeToDistanceTravelled() { -// -// ArrayList legs = generateLegsArray(); -// BoatInRace boat = new BoatInRace("Test", 0, Color.ALICEBLUE, "tt"); -// boat.setCurrentLeg(legs.get(0)); -// boat.setDistanceTravelledInLeg(0); -// BoatInRace[] boats = new BoatInRace[]{boat}; -// -// ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, null, 1); -// -// race.updatePosition(boat, ONE_HOUR); -// assertEquals(boat.getDistanceTravelledInLeg(), 0, 1e-8); -// } -// -// @Test -// public void changesToDistanceTravelledAreAdditive() { -// -// ArrayList legs = generateLegsArray(); -// BoatInRace boat = new BoatInRace("Test", 5, Color.ALICEBLUE, "tt"); -// boat.setCurrentLeg(legs.get(0)); -// boat.setDistanceTravelledInLeg(50); -// BoatInRace[] boats = new BoatInRace[]{boat}; -// -// ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, null, 1); -// -// race.updatePosition(boat, ONE_HOUR); -// assertEquals(boat.getDistanceTravelledInLeg(), boat.getVelocity() + 50, 1e-8); -// } -// -// @Test -// public void travelling10nmNorthGivesCorrectNewCoordinates() { -// GPSCoordinate oldPos = new GPSCoordinate(0, 0); -// GPSCoordinate newPos = ConstantVelocityRace.calculatePosition(oldPos, 10, 0); -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// calc.setStartingGeographicPoint(0, 0); -// calc.setDirection(0, 10 * Constants.NMToMetersConversion); -// -// assertEquals(newPos.getLongitude(), 0, 1e-8); -// assertEquals(newPos.getLatitude(), calc.getDestinationGeographicPoint().getY(), 1e-8); -// assertEquals(newPos.getLongitude(), calc.getDestinationGeographicPoint().getX(), 1e-8); -// } -// -// -// @Test -// public void travelling10nmEastGivesCorrectNewCoordinates() { -// GPSCoordinate oldPos = new GPSCoordinate(0, 0); -// GPSCoordinate newPos = ConstantVelocityRace.calculatePosition(oldPos, 10, 90); -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// calc.setStartingGeographicPoint(0, 0); -// calc.setDirection(90, 10 * Constants.NMToMetersConversion); -// -// -// assertEquals(newPos.getLatitude(), 0, 1e-8); -// assertEquals(newPos.getLatitude(), calc.getDestinationGeographicPoint().getY(), 1e-8); -// assertEquals(newPos.getLongitude(), calc.getDestinationGeographicPoint().getX(), 1e-8); -// } -// -// -// @Test -// public void travelling10nmWestGivesCorrectNewCoordinates() { -// GPSCoordinate oldPos = new GPSCoordinate(0, 0); -// GPSCoordinate newPos = ConstantVelocityRace.calculatePosition(oldPos, 10, -90); -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// calc.setStartingGeographicPoint(0, 0); -// calc.setDirection(-90, 10 * Constants.NMToMetersConversion); -// -// -// assertEquals(newPos.getLatitude(), 0, 1e-8); -// assertEquals(newPos.getLatitude(), calc.getDestinationGeographicPoint().getY(), 1e-8); -// assertEquals(newPos.getLongitude(), calc.getDestinationGeographicPoint().getX(), 1e-8); -// } -// -// -// @Test -// public void travelling10nmSouthGivesCorrectNewCoordinates() { -// GPSCoordinate oldPos = new GPSCoordinate(0, 0); -// GPSCoordinate newPos = ConstantVelocityRace.calculatePosition(oldPos, 10, 180); -// -// GeodeticCalculator calc = new GeodeticCalculator(); -// calc.setStartingGeographicPoint(0, 0); -// calc.setDirection(180, 10 * Constants.NMToMetersConversion); -// -// -// assertEquals(newPos.getLongitude(), 0, 1e-8); -// assertEquals(newPos.getLatitude(), calc.getDestinationGeographicPoint().getY(), 1e-8); -// assertEquals(newPos.getLongitude(), calc.getDestinationGeographicPoint().getX(), 1e-8); -// } -// -//} diff --git a/visualiser/src/test/java/seng302/Model/LegTest.java b/visualiser/src/test/java/seng302/Model/LegTest.java deleted file mode 100644 index 56176a61..00000000 --- a/visualiser/src/test/java/seng302/Model/LegTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package seng302.Model; - -import org.geotools.referencing.GeodeticCalculator; -import org.junit.Test; -import seng302.GPSCoordinate; - -import java.awt.geom.Point2D; - -import static junit.framework.TestCase.assertEquals; -import static seng302.Model.Leg.NM_TO_METERS; - -/** - * Tests that using a start and end mark initialises the correct leg between - * the markers. - */ -public class LegTest { - - private final Marker ORIGIN_Marker = new Marker(new GPSCoordinate(0, 0)); - - @Test - public void calculateDistanceHandles5nmNorth() { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(0, 0); - calc.setDirection(0, 5 * NM_TO_METERS); - - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); - Leg test = new Leg("Test", ORIGIN_Marker, endMarker, 0); - assertEquals(test.getDistance(), 5, 1e-8); - } - - @Test - public void calculateDistanceHandles12nmEast() { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(0, 0); - calc.setDirection(90, 12 * NM_TO_METERS); - - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); - Leg test = new Leg("Test", ORIGIN_Marker, endMarker, 0); - assertEquals(test.getDistance(), 12, 1e-8); - } - - @Test - public void calculateDistanceHandlesHalfnmSouth() { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(0, 0); - calc.setDirection(180, 0.5 * NM_TO_METERS); - - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); - Leg test = new Leg("Test", ORIGIN_Marker, endMarker, 0); - assertEquals(test.getDistance(), 0.5, 1e-8); - } - - @Test - public void calculateDistanceHandlesPoint1nmWest() { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(0, 0); - calc.setDirection(-90, 0.1 * NM_TO_METERS); - - Marker endMarker = getEndMarker(calc.getDestinationGeographicPoint()); - Leg test = new Leg("Test", ORIGIN_Marker, endMarker, 0); - assertEquals(test.getDistance(), 0.1, 1e-8); - } - - @Test - public void calculateDistanceHandlesZeroDifference() { - - Leg test = new Leg("Test", ORIGIN_Marker, ORIGIN_Marker, 0); - assertEquals(test.getDistance(), 0, 1e-8); - } - - private Marker getEndMarker(Point2D point) { - - GPSCoordinate coords = new GPSCoordinate(point.getY(), point.getX()); - - return new Marker(coords); - - } -} diff --git a/visualiser/src/test/java/seng302/Model/MarkerTest.java b/visualiser/src/test/java/seng302/Model/MarkerTest.java deleted file mode 100644 index 420c5eff..00000000 --- a/visualiser/src/test/java/seng302/Model/MarkerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package seng302.Model; - -import org.junit.Test; -import seng302.GPSCoordinate; - -import static org.junit.Assert.assertTrue; - -/** - * Created by esa46 on 29/03/17. - */ -public class MarkerTest { - - private final GPSCoordinate ORIGIN_COORD = new GPSCoordinate(0, 0); - - @Test - public void averageOfSingleMarkAtOriginIsSingleMark() { - - Marker testMark = new Marker(ORIGIN_COORD); - assertTrue(testMark.getAverageGPSCoordinate().equals(ORIGIN_COORD)); - - } - - @Test - public void averageOfSingleMarkIsSingleMark() { - - GPSCoordinate testCoord = new GPSCoordinate(20, 25); - Marker testMark = new Marker(testCoord); - assertTrue(testMark.getAverageGPSCoordinate().equals(testCoord)); - - } - - @Test - public void averageLatOfTwoMarksIsAccurate() { - - GPSCoordinate testCoord = new GPSCoordinate(10, 0); - Marker testMark = new Marker(ORIGIN_COORD, testCoord); - assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(5, 0))); - } - - @Test - public void averageLongOfTwoMarksIsAccurate() { - - GPSCoordinate testCoord = new GPSCoordinate(0, 10); - Marker testMark = new Marker(ORIGIN_COORD, testCoord); - assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(0, 5))); - } - - @Test - public void averageLatAndLongOfTwoMarksIsAccurate() { - - GPSCoordinate testCoord1 = new GPSCoordinate(10, 30); - GPSCoordinate testCoord2 = new GPSCoordinate(30, 60); - Marker testMark = new Marker(testCoord1, testCoord2); - assertTrue(testMark.getAverageGPSCoordinate().equals(new GPSCoordinate(020.644102, 44.014817))); - } - -} diff --git a/visualiser/src/test/java/seng302/Model/RaceTest.java b/visualiser/src/test/java/seng302/Model/RaceTest.java deleted file mode 100644 index 6f3a8911..00000000 --- a/visualiser/src/test/java/seng302/Model/RaceTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package seng302.Model; - -/** - * Created by esa46 on 15/03/17. - */ -public class RaceTest { - -} diff --git a/visualiser/src/test/java/seng302/Model/RaceXMLTest.java b/visualiser/src/test/java/seng302/Model/RaceXMLTest.java deleted file mode 100644 index e69de29b..00000000