diff --git a/dedicatedServer/pom.xml b/dedicatedServer/pom.xml index 8702f4f9..c7a7d668 100644 --- a/dedicatedServer/pom.xml +++ b/dedicatedServer/pom.xml @@ -106,6 +106,9 @@ org.apache.maven.plugins maven-project-info-reports-plugin 2.8.1 + + false + diff --git a/dedicatedServer/src/main/java/app/App.java b/dedicatedServer/src/main/java/app/App.java index 34ee2c5b..3b36c43e 100644 --- a/dedicatedServer/src/main/java/app/App.java +++ b/dedicatedServer/src/main/java/app/App.java @@ -21,7 +21,7 @@ public class App extends Application { public void start(Stage primaryStage) { try { //TODO should read a configuration file to configure server? - Event raceEvent = new Event(false); + Event raceEvent = new Event(false, 0); } catch (Exception e) { diff --git a/pom.xml b/pom.xml index 4f4983fc..ee4cba6f 100644 --- a/pom.xml +++ b/pom.xml @@ -14,4 +14,12 @@ https://eng-git.canterbury.ac.nz/SENG302-2016/team-7 + + + central + Maven Central + http://repo1.maven.org/maven2/ + + + diff --git a/racevisionGame/pom.xml b/racevisionGame/pom.xml index 41157414..98ccaf82 100644 --- a/racevisionGame/pom.xml +++ b/racevisionGame/pom.xml @@ -25,10 +25,30 @@ org.mockito - mockito-all - 1.9.5 + mockito-core + 2.9.0 + + + net.bytebuddy + byte-buddy + 1.7.0 + runtime + + + net.bytebuddy + byte-buddy-agent + 1.7.0 + runtime + + + org.objenesis + objenesis + 2.6 + runtime + + @@ -46,14 +66,17 @@ 15.0 - - org.geotools gt-referencing 9.0 + + JavaInteractiveMesh + STLImporter + 0.7 + @@ -72,7 +95,11 @@ http://download.osgeo.org/webdav/geotools/ - + + interactivemesh + Interactive Mesh + http://umbrasheep.com:8888/repository/internal/ + @@ -163,6 +190,9 @@ org.apache.maven.plugins maven-project-info-reports-plugin 2.8.1 + + false + diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index ee8ea216..b4ed1d2a 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -61,12 +61,7 @@ public class ConnectionAcceptor implements Runnable { */ private SourceIdAllocator sourceIdAllocator; - //race xml sequence number - private short raceXMLSequenceNumber; - //boat xml sequence number - private short boatXMLSequenceNumber; - //regatta xml sequence number - private short regattaXMLSequenceNumber; + // private RaceLogic raceLogic = null; @@ -209,77 +204,6 @@ public class ConnectionAcceptor implements Runnable { } } } - - /** - * 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); - } - - /** - * 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, - AckSequencer.getNextAckNum(), - System.currentTimeMillis(), - messageType, - sequenceNumber, - xmlString); - - - return message; - } } diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index c054e2fb..93f33ab5 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -4,6 +4,9 @@ import mock.dataInput.PolarParser; import mock.exceptions.EventConstructionException; import mock.model.*; import mock.model.commandFactory.CompositeCommand; +import mock.model.wind.RandomWindGenerator; +import mock.model.wind.ShiftingWindGenerator; +import mock.model.wind.WindGenerator; import mock.xml.RaceXMLCreator; import network.Messages.LatestMessages; import org.xml.sax.SAXException; @@ -15,16 +18,16 @@ import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import shared.model.Bearing; import shared.model.Constants; +import shared.xml.XMLUtilities; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; import java.io.IOException; -import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -68,6 +71,8 @@ public class Event { private Thread connectionThread; + private int mapIndex; + @@ -77,11 +82,32 @@ public class Event { * @param singlePlayer Whether or not to create a single player event. * @throws EventConstructionException Thrown if we cannot create an Event for any reason. */ - public Event(boolean singlePlayer) throws EventConstructionException { + public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException { - String raceXMLFile = "mock/mockXML/raceTest.xml"; +// System.out.println(XMLUtilities.validateXML(this.getClass().getClassLoader().getResource("mock/mockXML/iMapLayout.xml").toString() +// , this.getClass().getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"))); + this.mapIndex = mapIndex; + String raceXMLFile; String boatsXMLFile = "mock/mockXML/boatTest.xml"; String regattaXMLFile = "mock/mockXML/regattaTest.xml"; + switch (mapIndex){ + case 0:raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + break; + case 1:raceXMLFile = "mock/mockXML/oMapLayout.xml"; + break; + case 2: raceXMLFile = "mock/mockXML/iMapLayout.xml"; + break; + case 3: raceXMLFile = "mock/mockXML/mMapLayout.xml"; + break; + case 4: + raceXMLFile = "mock/mockXML/raceTutorial.xml"; + boatsXMLFile = "mock/mockXML/boatTutorial.xml"; + regattaXMLFile = "mock/mockXML/regattaTutorial.xml"; + break; + default: raceXMLFile = "mock/mockXML/raceSixPlayers.xml"; + + } + if (singlePlayer) { raceXMLFile = "mock/mockXML/raceSinglePlayer.xml"; @@ -93,7 +119,9 @@ public class Event { //this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90); this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8); this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8)); - + if(mapIndex==4){ + this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000); + } this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8); this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8); @@ -104,6 +132,7 @@ public class Event { this.xmlFileType = XMLFileType.Contents; this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv"); + PolarParser.parseNewPolars("mock/polars/acc_polars.csv"); //Parse the XML files into data sources. @@ -118,18 +147,14 @@ public class Event { } - this.sourceIdAllocator = new SourceIdAllocator(raceDataSource.getParticipants()); + this.compositeCommand = new CompositeCommand(); this.latestMessages = new LatestMessages(); - //Create and start race. - WindGenerator windGenerator = new RandomWindGenerator( + WindGenerator windGenerator = new ShiftingWindGenerator( Bearing.fromDegrees(225), - Bearing.fromDegrees(215), - Bearing.fromDegrees(235), - 12d, - 8d, - 16d ); + 12 + ); RaceLogic newRace = new RaceLogic( new MockRace( boatDataSource, @@ -146,6 +171,7 @@ public class Event { //Create connection acceptor. + this.sourceIdAllocator = new SourceIdAllocator(newRace.getRace()); try { this.connectionAcceptor = new ConnectionAcceptor(latestMessages, compositeCommand, sourceIdAllocator, newRace); @@ -158,8 +184,6 @@ public class Event { this.connectionThread = new Thread(connectionAcceptor, "Event.Start()->ConnectionAcceptor thread"); connectionThread.start(); - sendXMLs(); - } @@ -172,18 +196,6 @@ public class Event { - /** - * Sends out each xml string, via the mock output - */ - private void sendXMLs() { - - connectionAcceptor.setRegattaXml(regattaXML); - - connectionAcceptor.setRaceXml(raceXML); - - connectionAcceptor.setBoatsXml(boatXML); - } - //TODO remove this after demo on 18th august! /** @@ -192,9 +204,15 @@ public class Event { * @return String containing edited xml */ public static String setRaceXMLAtCurrentTimeToNow(String raceXML) { + return setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime); + } + public static String setRaceXMLAtCurrentTimeToNow(String raceXML, long racePreStartTime, long racePreparatoryTime){ //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 millisecondsToAdd = racePreStartTime + racePreparatoryTime; + + long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; @@ -204,7 +222,6 @@ public class Event { raceXML = raceXML.replace("RACE_CREATION_TIME", dateFormat.format(creationTime)); raceXML = raceXML.replace("RACE_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 index 68136ea0..023235cc 100644 --- a/racevisionGame/src/main/java/mock/app/MockOutput.java +++ b/racevisionGame/src/main/java/mock/app/MockOutput.java @@ -27,7 +27,12 @@ public class MockOutput implements RunnableWithFramePeriod { */ private LatestMessages latestMessages; + //These sequence number track the last race/boat/regatta xml message we've sent. + private int lastSentRaceNumber = -1; + private int lastSentBoatNumber = -1; + + private int lastSentRegattaNumber = -1; @@ -69,7 +74,6 @@ public class MockOutput implements RunnableWithFramePeriod { long previousFrameTime = System.currentTimeMillis(); - boolean sentXMLs = false; @@ -82,16 +86,26 @@ public class MockOutput implements RunnableWithFramePeriod { previousFrameTime = currentFrameTime; - //Send XML messages. - if (!sentXMLs) { + //Send XML messages if needed. + + if (lastSentRaceNumber != latestMessages.getRaceXMLMessage().getSequenceNumber()) { + lastSentRaceNumber = latestMessages.getRaceXMLMessage().getSequenceNumber(); outgoingMessages.put(latestMessages.getRaceXMLMessage()); - outgoingMessages.put(latestMessages.getRegattaXMLMessage()); + } + + if (lastSentBoatNumber != latestMessages.getBoatXMLMessage().getSequenceNumber()) { + lastSentBoatNumber = latestMessages.getBoatXMLMessage().getSequenceNumber(); outgoingMessages.put(latestMessages.getBoatXMLMessage()); + } - sentXMLs = true; + if (lastSentRegattaNumber != latestMessages.getRegattaXMLMessage().getSequenceNumber()) { + lastSentRegattaNumber = latestMessages.getRegattaXMLMessage().getSequenceNumber(); + outgoingMessages.put(latestMessages.getRegattaXMLMessage()); } + + List snapshot = latestMessages.getSnapshot(); for (AC35Data message : snapshot) { outgoingMessages.put(message); diff --git a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java index d33c0ac5..9a94d1f0 100644 --- a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java +++ b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java @@ -3,6 +3,7 @@ package mock.dataInput; import mock.exceptions.InvalidPolarFileException; +import mock.model.NewPolars; import mock.model.Polars; import shared.model.Bearing; @@ -104,4 +105,90 @@ public class PolarParser { return polarTable; } + /** + * Given a filename, this function parses it and generates a Polar object, which can be queried for polar information. + * @param filename The filename to load and read data from (loaded as a resource). + */ + public static void parseNewPolars(String filename) throws InvalidPolarFileException { + NewPolars newPolars = new NewPolars(); + + + //Open the file for reading. + InputStream fileStream = PolarParser.class.getClassLoader().getResourceAsStream(filename); + if (fileStream == null) { + throw new InvalidPolarFileException("Could not open polar data file: " + filename); + } + //Wrap it with buffered input stream to set encoding and buffer. + InputStreamReader in = null; + try { + in = new InputStreamReader(fileStream, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InvalidPolarFileException("Unsupported encoding: UTF-8", e); + } + BufferedReader inputStream = new BufferedReader(in); + + + //We expect the polar data file to have the column headings: + // Tws, Twa0, Bsp0, Twa1, Bsp1, UpTwa, UpBsp, Twa2, Bsp2, Twa3, Bsp3, Twa4, Bsp4, Twa5, Bsp5, Twa6, Bsp6, DnTwa, DnBsp, Twa7, Bsp7 + //and to have 7 rows of data. + //Angles are expected to be in degrees, and velocities in knots. + + + //We read data rows, and split them into arrays of elements. + ArrayList dataRows = new ArrayList<>(7); + try { + //Heading row. + //We skip the heading row by reading it. + String headingRow = inputStream.readLine(); + + //Data rows. + while (inputStream.ready()) { + //Read line. + String dataRow = inputStream.readLine(); + + //Split line. + String[] dataElements = dataRow.split(","); + + //Add to collection. + dataRows.add(dataElements); + + } + + } catch (IOException e) { + throw new InvalidPolarFileException("Could not read from polar data file: " + filename, e); + } + + //Finished reading in data, now we need to construct polar rows and table from it. + //For each row... + int rowNumber = 0; + for (String[] row : dataRows) { + + //For each pair of columns (the pair is angle, speed). + //We start at column 1 since column 0 is the wind speed column. + for (int i = 1; i < row.length; i += 2) { + + //Add angle+speed=velocity estimate to polar table. + try { + + //Add the polar value to the polar table + double windSpeedKnots = Double.parseDouble(row[0]); + double angleDegrees = Double.parseDouble(row[i]); + Bearing angle = Bearing.fromDegrees(angleDegrees); + double boatSpeedKnots = Double.parseDouble(row[i + 1]); + newPolars.addPolars(windSpeedKnots, angle, boatSpeedKnots); + + } catch (NumberFormatException e) { + throw new InvalidPolarFileException("Could not convert (Row,Col): (" + rowNumber + "," + i +") = " + row[i] + " to a double.", e); + + } + } + + //Increment row number. + rowNumber++; + + } + newPolars.linearInterpolatePolars(); + + } + } diff --git a/racevisionGame/src/main/java/mock/model/ClientConnection.java b/racevisionGame/src/main/java/mock/model/ClientConnection.java index d2ca7609..2b0caacf 100644 --- a/racevisionGame/src/main/java/mock/model/ClientConnection.java +++ b/racevisionGame/src/main/java/mock/model/ClientConnection.java @@ -109,6 +109,11 @@ public class ClientConnection implements Runnable { */ private ConnectionStateEnum connectionState = ConnectionStateEnum.UNKNOWN; + /** + * The source ID that has been allocated to the client. + * 0 means not allocated. + */ + private int allocatedSourceID = 0; @@ -178,7 +183,7 @@ public class ClientConnection implements Runnable { RequestToJoin requestToJoin = waitForRequestToJoin(); - int allocatedSourceID = 0; + allocatedSourceID = 0; //If they want to participate, give them a source ID number. if (requestToJoin.getRequestType() == RequestToJoinEnum.PARTICIPANT) { @@ -283,6 +288,10 @@ public class ClientConnection implements Runnable { if (this.controllerServerThread != null) { this.controllerServerThread.interrupt(); } + + if (allocatedSourceID != 0) { + sourceIdAllocator.returnSourceID(allocatedSourceID); + } } } diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 379123af..a9f11770 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -1,6 +1,8 @@ package mock.model; +import mock.model.wind.WindGenerator; import javafx.animation.AnimationTimer; +import mock.model.collider.ColliderRegistry; import mock.xml.*; import network.Messages.BoatLocation; import network.Messages.BoatStatus; @@ -13,7 +15,6 @@ import shared.exceptions.BoatNotFoundException; import shared.enums.RoundingType; import shared.model.*; import shared.model.Bearing; -import shared.model.Race; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -27,7 +28,7 @@ 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 MockRace extends Race { +public class MockRace extends RaceState { /** * An observable list of boats in the race. @@ -40,6 +41,12 @@ public class MockRace extends Race { private List shrinkBoundary; + /** + * Registry for all collider object in this race + */ + private ColliderRegistry colliderRegistry; + + /** * The scale factor of the race. * See {@link Constants#RaceTimeScale}. @@ -51,6 +58,14 @@ public class MockRace extends Race { */ private WindGenerator windGenerator; + + /** + * The polars file to use for each boat. + */ + private Polars polars; + + + /** * 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). @@ -62,13 +77,16 @@ public class MockRace extends Race { */ public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, int timeScale, WindGenerator windGenerator) { - super(boatDataSource, raceDataSource, regattaDataSource); + this.setBoatDataSource(boatDataSource); + this.setRaceDataSource(raceDataSource); + this.setRegattaDataSource(regattaDataSource); + this.polars = polars; this.scaleFactor = timeScale; - this.boats = this.generateMockBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), polars); + this.boats = new ArrayList<>(); - this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); + this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.getBoundary()); this.windGenerator = windGenerator; @@ -76,35 +94,53 @@ public class MockRace extends Race { //Wind. this.setWind(windGenerator.generateBaselineWind()); + + // Set up colliders + this.colliderRegistry = new ColliderRegistry(); + + for(CompoundMark mark: this.getCompoundMarks()) { + colliderRegistry.addCollider(mark.getMark1()); + if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2()); + } + this.colliderRegistry.addAllColliders(boats); } + /** - * 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. + * Generates a MockBoat from the BoatDataSource, given a source ID. Also adds it to the participant list. + * @param sourceID The source ID to assign the boat. */ - private List generateMockBoats(Map boats, List sourceIDs, Polars polars) { + public void generateMockBoat(Integer sourceID) { - List mockBoats = new ArrayList<>(sourceIDs.size()); + //Get the boat associated with the sourceID. + Boat boat = getBoatDataSource().getBoats().get(sourceID); - //For each sourceID participating... - for (int sourceID : sourceIDs) { + //Construct a MockBoat using the Boat and Polars. + MockBoat mockBoat = new MockBoat(boat, polars); + mockBoat.setCurrentLeg(this.getLegs().get(0)); - //Get the boat associated with the sourceID. - Boat boat = boats.get(sourceID); + //Update participant list. + getRaceDataSource().getParticipants().add(sourceID); - //Construct a MockBoat using the Boat and Polars. - MockBoat mockBoat = new MockBoat(boat, polars); + this.boats.add(mockBoat); - mockBoats.add(mockBoat); + getRaceDataSource().incrementSequenceNumber(); + } - } + /** + * Removes a MockBoat from the race, by sourceID. Also removes it from the participant list. + * @param sourceID Source ID of boat to remove. + */ + public void removeMockBoat(Integer sourceID) { + this.boats.removeIf(mockBoat -> mockBoat.getSourceID() == sourceID); + getRaceDataSource().getParticipants().remove(sourceID); + getRaceDataSource().incrementSequenceNumber(); + } - return mockBoats; + public ColliderRegistry getColliderRegistry() { + return colliderRegistry; } @@ -113,7 +149,7 @@ public class MockRace extends Race { * @param currentTime Milliseconds since unix epoch. */ public void updateRaceTime(long currentTime) { - this.raceClock.setUTCTime(currentTime); + this.getRaceClock().setUTCTime(currentTime); } @@ -123,7 +159,7 @@ public class MockRace extends Race { public void updateRaceStatusEnum() { //The millisecond duration of the race. Negative means it hasn't started, so we flip sign. - long timeToStart = - this.raceClock.getDurationMilli(); + long timeToStart = - this.getRaceClock().getDurationMilli(); if (timeToStart > Constants.RacePreStartTime) { @@ -194,7 +230,7 @@ public class MockRace extends Race { //The boat starts on the first leg of the race. - boat.setCurrentLeg(this.legs.get(0)); + boat.setCurrentLeg(this.getLegs().get(0)); //Boats start with 0 knots speed. boat.setCurrentSpeed(0d); @@ -209,7 +245,9 @@ public class MockRace extends Race { boat.setStatus(BoatStatusEnum.PRESTART); //We set a large time since tack change so that it calculates a new VMG when the simulation starts. - boat.setTimeSinceTackChange(Long.MAX_VALUE); + //boat.setTimeSinceTackChange(Long.MAX_VALUE); + boat.setTimeSinceTackChange(0); + } } @@ -223,7 +261,7 @@ public class MockRace extends Race { private List getSpreadStartingPositions() { //The first compound marker of the race - the starting gate. - CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); + CompoundMark compoundMark = this.getLegs().get(0).getStartCompoundMark(); //The position of the two markers from the compound marker. GPSCoordinate mark1Position = compoundMark.getMark1Position(); @@ -341,6 +379,7 @@ public class MockRace extends Race { if (boat.getAutoVMG()) { newOptimalVMG(boat); + boat.setAutoVMG(false); } } else { @@ -353,31 +392,38 @@ public class MockRace extends Race { private void newOptimalVMG(MockBoat boat) { long tackPeriod = 1000; - if (boat.getTimeSinceTackChange() > tackPeriod) { + //System.out.println("optim called"); //Calculate the new VMG. - VMG newVMG = boat.getPolars().calculateVMG( - this.getWindDirection(), - this.getWindSpeed(), - boat.calculateBearingToNextMarker(), - Bearing.fromDegrees(0d), - Bearing.fromDegrees(359.99999d)); - - +// VMG newVMG = boat.getPolars().calculateVMG( +// this.getWindDirection(), +// this.getWindSpeed(), +// boat.calculateBearingToNextMarker(), +// Bearing.fromDegrees(0d), +// Bearing.fromDegrees(359.99999d)); + + VMG newVMG = NewPolars.setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing()); + //System.out.println(newVMG); //If the new vmg improves velocity, use it. - if (improvesVelocity(boat, newVMG)) { - boat.setVMG(newVMG); - } + /*if (improvesVelocity(boat, newVMG)) { + }*/ + boat.setVMG(newVMG); } } private void setBoatSpeed(MockBoat boat) { - VMG vmg = boat.getPolars().calculateVMG( +// VMG vmg = boat.getPolars().calculateVMG( +// this.getWindDirection(), +// this.getWindSpeed(), +// boat.getBearing(), +// Bearing.fromDegrees(boat.getBearing().degrees() - 1), +// Bearing.fromDegrees(boat.getBearing().degrees() + 1)); + //VMG vmg = boat.getPolars().setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing()); + VMG vmg = new VMG(NewPolars.calculateSpeed( this.getWindDirection(), this.getWindSpeed(), - boat.getBearing(), - Bearing.fromDegrees(boat.getBearing().degrees() - 1), - Bearing.fromDegrees(boat.getBearing().degrees() + 1)); + boat.getBearing() + ), boat.getBearing()) ; if (vmg.getSpeed() > 0) { boat.setCurrentSpeed(vmg.getSpeed()); } @@ -485,7 +531,7 @@ public class MockRace extends Race { roundingChecks.get(0), boat.getPosition(), legBearing) && gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { boat.increaseRoundingStatus(); - if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){ + if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){ //boat has finished race boat.increaseRoundingStatus(); } @@ -503,7 +549,7 @@ public class MockRace extends Race { case 2://has traveled 180 degrees around the mark //Move boat on to next leg. boat.resetRoundingStatus(); - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1); boat.setCurrentLeg(nextLeg); break; } @@ -527,10 +573,10 @@ public class MockRace extends Race { if (boat.isStarboardSide(roundingMark) && GPSCoordinate.passesLine(roundingMark.getPosition(), roundingChecks.get(0), boat.getPosition(), legBearing) && - gateCheck && + gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) { boat.increaseRoundingStatus(); - if (boat.getCurrentLeg().getLegNumber() + 2 >= legs.size()){ + if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){ //boat has finished race boat.increaseRoundingStatus(); } @@ -547,7 +593,7 @@ public class MockRace extends Race { case 2://has traveled 180 degrees around the mark //Move boat on to next leg. boat.resetRoundingStatus(); - Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + Leg nextLeg = this.getLegs().get(boat.getCurrentLeg().getLegNumber() + 1); boat.setCurrentLeg(nextLeg); break; } @@ -585,7 +631,7 @@ public class MockRace extends Race { GPSCoordinate roundCheck2; try{ - Leg nextLeg = legs.get(legs.indexOf(boat.getCurrentLeg()) + 1); + Leg nextLeg = getLegs().get(getLegs().indexOf(boat.getCurrentLeg()) + 1); GPSCoordinate startNextDirectionLinePoint = nextLeg.getStartCompoundMark().getMark1Position(); GPSCoordinate endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position(); @@ -681,7 +727,7 @@ public class MockRace extends Race { */ public void changeWindDirection() { - Wind nextWind = windGenerator.generateNextWind(raceWind.getValue()); + Wind nextWind = windGenerator.generateNextWind(windProperty().getValue()); setWind(nextWind); } @@ -702,13 +748,10 @@ public class MockRace extends Race { long timeFromNow = (long) (1000 * boat.calculateDistanceToNextMarker() / velocityToMark); //Calculate time at which it will reach mark. - ZonedDateTime timeAtMark = this.raceClock.getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS); + ZonedDateTime timeAtMark = this.getRaceClock().getCurrentTime().plus(timeFromNow, ChronoUnit.MILLIS); boat.setEstimatedTimeAtNextMark(timeAtMark); } } - public List getCompoundMarks() { - return compoundMarks; - } -} \ No newline at end of file +} diff --git a/racevisionGame/src/main/java/mock/model/NewPolars.java b/racevisionGame/src/main/java/mock/model/NewPolars.java new file mode 100644 index 00000000..c525316c --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/NewPolars.java @@ -0,0 +1,204 @@ +package mock.model; + +import shared.model.Bearing; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * New Polars are the revampe of the old Polars class which interpolates the data after being parsed from the Polar Parser + * There can only be one NewPolars instance stored statically however if a boat does happen to have a special case it can be assigned. + */ +public class NewPolars { + + + //true wind speed, + private static Map> polars = new TreeMap<>(); + + public static NewPolars newPolars = null; + + public NewPolars(){ + newPolars = this; + } + + /** + * Add polars from the polar table + * @param trueWindSpeed True Wind Speed that the true wind angle and speed corresponds to + * @param trueWindAngle True Wind Angle of the race + * @param boatSpeed The speed the boat should be going at given the true wind angle + */ + public static void addPolars(double trueWindSpeed, Bearing trueWindAngle, double boatSpeed){ + double tws = trueWindSpeed; + double bs = boatSpeed; + double twa = trueWindAngle.degrees(); + if (!polars.containsKey(tws)){ + polars.put(tws, new TreeMap<>()); + } + polars.get(tws).putIfAbsent(twa, bs); + polars.get(tws).putIfAbsent(360d - twa, bs); + } + + /** + * Linearly Interpolates this should only be called once per parsing of a polar table + */ + public static void linearInterpolatePolars(){ + TreeMap prevTWS = null; + TreeMap> iterablePolars = new TreeMap<>(polars); + //this loop averages out the speed between tow angles + //Example: Pair one: 0 degrees, 0 knots + // Pair two: 3 degrees, 6 knots + //This loop will add + //Pair one: 0 degrees, 0 knots + //Pair two: 1 degrees, 2 knots + //Pair three: 2 degrees, 4 knots + //Pair four: 3 degrees, 6 knots + for (double windSpeed: iterablePolars.keySet()){ + TreeMap tws = iterablePolars.get(windSpeed); + + if (prevTWS == null){ + prevTWS = tws; + continue; + } + + double previousTWA = -1; + TreeMap iterableTWS = new TreeMap<>(tws); + + for (double twa: iterableTWS.keySet()){ + if (previousTWA == -1){ + previousTWA = twa; + continue; + } + double twaDiff = twa - previousTWA; + double speedDiff = iterableTWS.get(twa) - iterableTWS.get(previousTWA); + double prevSpeed = iterableTWS.get(previousTWA); + double diff = speedDiff/twaDiff; + + for (double i = previousTWA; i < twa; i ++){ + double mult = i - previousTWA; + double newSpeed = diff * mult + prevSpeed; + tws.put(i, newSpeed); + } + previousTWA = twa; + } + } + } + + private static double getClosest(double value, Set set){ + double closestVal = 0; + double smallestDiff = Double.MAX_VALUE; + for (double d: set){ + double diff = Math.abs(value - d); + if (diff < smallestDiff){ + closestVal = d; + smallestDiff = diff; + } + } + return closestVal; + } + + /** + * Determines which quadrant degrees are in + * 0/360 Degrees + * Quadrant 4 | Quadrant 1 + * ----------------------- + * Quadrant 3 | Quadrant 2 + * @param degrees + * @return + */ + private static int getQuadrant(double degrees){ + return (int) modulateAngle(degrees) / 90 + 1; + } + + private static double getBestSpeedInQuadrant(int quad, Map set){ + double min = (quad - 1)* 90; + double max = quad * 90; + double maxAngle = 0; + double maxSpeed = 0; + double dupAngle = 0; + //DupAngle will average the angle between maxAngles that have the same speed + //Example: if 150 degrees, 180 degrees, and 210 degrees all go at 10 knots + //then the average will be taken as (150 + 210) / 2 and the angle will be returned on that. + for (Double s: set.keySet()){ + if (s >= min && s < max){ + if (set.get(s) > maxSpeed){ + dupAngle = 0; + maxAngle = s; + maxSpeed = set.get(s); + } else if (set.get(s) == maxSpeed){ + dupAngle = s; + } + } + } + if (dupAngle != 0 ){ + return getClosest((dupAngle + maxAngle) / 2, set.keySet()); + } + return maxAngle; + } + + /** + * Returns the best VMG that the boat can change to given it's current diagonal heading direction. + * @param trueWindAngle True wind angle of the race + * @param trueWindSpeed True wind speed of the race + * @param boatAngle Angle that the boat is currently at + * @return the best vmg that the boat can change to + */ + public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){ + //System.out.println("VMG AUTO CALLED"); + //speed + double closestSpeed = getClosest(trueWindSpeed, polars.keySet()); + + double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees()); + int quad = getQuadrant(angle); + double bestAngle = getBestSpeedInQuadrant(quad, polars.get(closestSpeed)); + + double boatSpeed = polars.get(closestSpeed).get(bestAngle); + + double newAngle = modulateAngle(bestAngle + trueWindAngle.degrees()); + + return new VMG(boatSpeed, Bearing.fromDegrees(newAngle)); + } + + /** + * Calculates the speed that a certain angle should be doing + * @param trueWindAngle TrueWind Angle of the race + * @param trueWindSpeed True Wind Speed of the race + * @param boatAngle Angle that the boat is current at + * @return the speed that the boat should be traveling at. + */ + public static double calculateSpeed(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){ + //speed + double closestSpeed = getClosest(trueWindSpeed, polars.keySet()); + + double angleDiff = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees()); + double closestAngle = getClosest(angleDiff, polars.get(closestSpeed).keySet()); + + double boatSpeed = polars.get(closestSpeed).get(closestAngle); + + return boatSpeed; + + + } + + public static double modulateAngle(double angle){ + return (angle % 360 + 360) % 360; + } + + private Map> getPolars(){ + //this function is just for testing so therefore it is private + return polars; + } + + private void printOutLinearInterpolated(){ + for (double tws: polars.keySet()){ + System.out.println("=================================================="); + System.out.println("Speed: " + tws); + System.out.println("=================================================="); + for (double twa: polars.get(tws).keySet()){ + System.out.println("TWA: " + twa + ", Boat Speed: " + polars.get(tws).get(twa)); + } + } + } + +} diff --git a/racevisionGame/src/main/java/mock/model/Polars.java b/racevisionGame/src/main/java/mock/model/Polars.java index 32ee8842..943214cf 100644 --- a/racevisionGame/src/main/java/mock/model/Polars.java +++ b/racevisionGame/src/main/java/mock/model/Polars.java @@ -3,10 +3,7 @@ package mock.model; import javafx.util.Pair; import shared.model.Bearing; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Encapsulates an entire polar table. Has a function to calculate VMG. @@ -78,8 +75,6 @@ public class Polars { } - - /** * Calculates the VMG for a given wind angle, wind speed, and angle to destination. Will only return VMGs that have a true bearing (angle) within a given bound - this is to ensure that you can calculate VMGs without going out of bounds. *
diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java index d70b6152..0b3dffb2 100644 --- a/racevisionGame/src/main/java/mock/model/RaceLogic.java +++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java @@ -49,6 +49,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { */ @Override public void run() { + + prestartCountdown(); + race.initialiseBoats(); countdown(); @@ -61,6 +64,37 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer { } + /** + * The countdown timer until the prestart period is finished. This timer will stop 3 minutes before the race starts, and players can no longer start participating. + */ + private void prestartCountdown() { + + long previousFrameTime = System.currentTimeMillis(); + + while (((race.getRaceStatusEnum() == RaceStatusEnum.PRESTART) + || (race.getRaceStatusEnum() == RaceStatusEnum.NOT_ACTIVE) + || (race.getRaceStatusEnum() == RaceStatusEnum.WARNING)) && loopBool) { + + long currentTime = System.currentTimeMillis(); + + //Update race time. + race.updateRaceTime(currentTime); + + //Update the race status based on the current time. + race.updateRaceStatusEnum(); + + //Provide boat's with an estimated time at next mark until the race starts. + race.setBoatsTimeNextMark(race.getRaceClock().getCurrentTime()); + + //Parse the race snapshot. + server.parseSnapshot(); + + + waitForFramePeriod(previousFrameTime, currentTime, 50); + previousFrameTime = currentTime; + } + } + /** * Countdown timer until race starts. */ diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java index 279d2742..80ec5e8b 100644 --- a/racevisionGame/src/main/java/mock/model/RaceServer.java +++ b/racevisionGame/src/main/java/mock/model/RaceServer.java @@ -1,14 +1,22 @@ package mock.model; +import network.AckSequencer; import network.Messages.*; import network.Messages.Enums.BoatLocationDeviceEnum; import network.Messages.Enums.YachtEventEnum; +import network.Messages.Enums.XMLMessageType; import shared.model.Bearing; import shared.model.CompoundMark; import shared.model.Mark; +import shared.xml.Race.RaceDataSourceToXML; +import shared.xml.boats.BoatDataSourceToXML; +import shared.xml.regatta.RegattaDataSourceToXML; +import javax.xml.bind.JAXBException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Created by connortaylorbrown on 2/08/17. @@ -24,6 +32,21 @@ public class RaceServer { */ private int boatLocationSequenceNumber = 1; + /** + * The sequence number of race XML messages. + */ + private int raceXMLSeqNumber = -1; + + /** + * The sequence number of boat XML messages. + */ + private int boatXMLSeqNumber = -1; + + /** + * The sequence number of regatta XML messages. + */ + private int regattaXMLSeqNumber = -1; + public RaceServer(MockRace race, LatestMessages latestMessages) { this.race = race; @@ -53,6 +76,8 @@ public class RaceServer { latestMessages.setSnapshot(snapshotMessages); + updateXMLFiles(); + //Reset collision list collisionEvents.clear(); //System.out.println(collisionEvents.size()); @@ -60,6 +85,73 @@ public class RaceServer { } + /** + * Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations. + */ + private void updateXMLFiles() { + updateRaceXMLFile(); + updateBoatXMLFile(); + updateRegattaXMLFile(); + } + + /** + * Checks if the race data source has changed, and if it has, updates LatestMessages' race xml message. + */ + private void updateRaceXMLFile() { + if (raceXMLSeqNumber != race.getRaceDataSource().getSequenceNumber()) { + + raceXMLSeqNumber = race.getRaceDataSource().getSequenceNumber(); + try { + String raceXMLString = RaceDataSourceToXML.toString(race.getRaceDataSource()); + XMLMessage message = createXMLMessage(raceXMLString, XMLMessageType.RACE); + latestMessages.setXMLMessage(message); + + } catch (JAXBException e) { + Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRaceDataSource(), e); + } + + } + } + + /** + * Checks if the boat data source has changed, and if it has, updates LatestMessages' boat xml message. + */ + private void updateBoatXMLFile() { + if (boatXMLSeqNumber != race.getBoatDataSource().getSequenceNumber()) { + + boatXMLSeqNumber = race.getBoatDataSource().getSequenceNumber(); + try { + String boatXMLString = BoatDataSourceToXML.toString(race.getBoatDataSource()); + XMLMessage message = createXMLMessage(boatXMLString, XMLMessageType.BOAT); + latestMessages.setXMLMessage(message); + + } catch (JAXBException e) { + Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getBoatDataSource(), e); + } + + } + } + + /** + * Checks if the regatta data source has changed, and if it has, updates LatestMessages' regatta xml message. + */ + private void updateRegattaXMLFile() { + if (regattaXMLSeqNumber != race.getRegattaDataSource().getSequenceNumber()) { + + regattaXMLSeqNumber = race.getRegattaDataSource().getSequenceNumber(); + + try { + String regattaXMLString = RegattaDataSourceToXML.toString(race.getRegattaDataSource()); + XMLMessage message = createXMLMessage(regattaXMLString, XMLMessageType.REGATTA); + latestMessages.setXMLMessage(message); + + } catch (JAXBException e) { + Logger.getGlobal().log(Level.WARNING, "Could not serialise: " + race.getRegattaDataSource(), e); + } + + } + } + /** * Parses an individual marker boat, and returns it. * @param mark The marker boat to parse. @@ -191,6 +283,43 @@ public class RaceServer { return raceStatus; } + + + + /** + * 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. + int sequenceNumber = 0; + if (messageType == XMLMessageType.RACE) { + sequenceNumber = this.raceXMLSeqNumber; + + } else if (messageType == XMLMessageType.BOAT) { + sequenceNumber = this.boatXMLSeqNumber; + + } else if (messageType == XMLMessageType.REGATTA) { + sequenceNumber = this.regattaXMLSeqNumber; + + } + + //Create the message. + XMLMessage message = new XMLMessage( + XMLMessage.currentVersionNumber, + AckSequencer.getNextAckNum(), + System.currentTimeMillis(), + messageType, + sequenceNumber, + xmlString); + + + return message; + } + /** * Parse the yacht event and return it * @param boat yacht with event diff --git a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java index 3b62a8a7..f590d9b7 100644 --- a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java +++ b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java @@ -2,6 +2,7 @@ package mock.model; import mock.exceptions.SourceIDAllocationException; +import network.Messages.Enums.RaceStatusEnum; import java.util.ArrayList; import java.util.List; @@ -13,24 +14,18 @@ public class SourceIdAllocator { /** - * This list contains all unallocated source IDs. + * The race we are allocating for. */ - List unallocatedIDs = new ArrayList<>(); + private MockRace mockRace; - /** - * This list contains all allocated source IDs. - */ - List allocatedIDs = new ArrayList<>(); - /** - * Creates a source ID allocator, using the given list of unallocated source IDs. - * @param unallocatedIDs List of unallocated source IDs. + * Creates a SourceIdAllocator for a given race. + * @param mockRace Race to allocate source IDs for. */ - public SourceIdAllocator(List unallocatedIDs) { - //We need to copy the list. - this.unallocatedIDs.addAll(unallocatedIDs); + public SourceIdAllocator(MockRace mockRace) { + this.mockRace = mockRace; } @@ -41,11 +36,25 @@ public class SourceIdAllocator { */ public synchronized int allocateSourceID() throws SourceIDAllocationException { + if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART) + || (mockRace.getRaceStatusEnum() == RaceStatusEnum.WARNING))) { + throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start period. It is currently: " + mockRace.getRaceStatusEnum()); + } + + List allocatedIDs = mockRace.getRaceDataSource().getParticipants(); + + List allIDs = new ArrayList<>(mockRace.getBoatDataSource().getBoats().keySet()); + + //Get list of unallocated ids. + List unallocatedIDs = new ArrayList<>(allIDs); + unallocatedIDs.removeAll(allocatedIDs); + + if (!unallocatedIDs.isEmpty()) { int sourceID = unallocatedIDs.remove(0); - allocatedIDs.add(sourceID); + mockRace.generateMockBoat(sourceID); return sourceID; @@ -61,10 +70,6 @@ public class SourceIdAllocator { * @param sourceID Source ID to return. */ public void returnSourceID(Integer sourceID) { - - //We remove an Integer, not an int, so that we remove by value not by index. - allocatedIDs.remove(sourceID); - - unallocatedIDs.add(sourceID); + mockRace.removeMockBoat(sourceID); } } diff --git a/racevisionGame/src/main/java/mock/model/VMG.java b/racevisionGame/src/main/java/mock/model/VMG.java index 905fadce..370df835 100644 --- a/racevisionGame/src/main/java/mock/model/VMG.java +++ b/racevisionGame/src/main/java/mock/model/VMG.java @@ -45,4 +45,8 @@ public class VMG { return bearing; } + public String toString(){ + return String.format("VMG Object: Speed %f, Bearing %f.", speed, bearing.degrees()); + } + } diff --git a/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java similarity index 96% rename from racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java rename to racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java index ae14daac..6cdfafe9 100644 --- a/racevisionGame/src/main/java/mock/model/ConstantWindGenerator.java +++ b/racevisionGame/src/main/java/mock/model/wind/ConstantWindGenerator.java @@ -1,11 +1,9 @@ -package mock.model; +package mock.model.wind; import shared.model.Bearing; import shared.model.Wind; -import java.util.Random; - /** * This class generates Wind objects for use in a MockRace. * Initialised with a baseline wind speed and direction, and keeps it constant. diff --git a/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java similarity index 99% rename from racevisionGame/src/main/java/mock/model/RandomWindGenerator.java rename to racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java index 4f981b8d..bd7b13c3 100644 --- a/racevisionGame/src/main/java/mock/model/RandomWindGenerator.java +++ b/racevisionGame/src/main/java/mock/model/wind/RandomWindGenerator.java @@ -1,4 +1,4 @@ -package mock.model; +package mock.model.wind; import shared.model.Bearing; diff --git a/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java new file mode 100644 index 00000000..4ceff627 --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java @@ -0,0 +1,152 @@ +package mock.model.wind; + +import shared.model.Bearing; +import shared.model.Wind; + +import java.util.Random; + +public class ShiftingWindGenerator implements WindGenerator { + private Bearing baselineBearing; + private double baseLineSpeed; + private double windSpeedVariance = 5; + private double bearingVariance = 5; // In degrees + private double oscillationVariance = 0.25; + private double oscillationPeriod = 1e3 * 60 * 1; // In milliseconds + private double shiftTime = 1e3 * 60; + private double shiftedSoFar = 0; + + private double timeOfLastOscillationReset = 0; + private double timeOfLastChange = 0; + private double timeOfLastShift = 0; // Back / veer + + private boolean anticlockwise = false; + private boolean shiftAnticlockwise = false;//true for Back, false for veer + private boolean shiftThisRace = Math.random() > 0.5; + + /** + * Constructor + * @param baselineBearing baseline bearing for wind + * @param baseLineSpeed base line speed for wind + */ + public ShiftingWindGenerator(Bearing baselineBearing, double baseLineSpeed) { + this.baselineBearing = baselineBearing; + this.baseLineSpeed = baseLineSpeed; + initialiseOscillationDirection(); + } + + @Override + public Wind generateBaselineWind() { + return new Wind(baselineBearing, baseLineSpeed); + } + + @Override + public Wind generateNextWind(Wind currentWind) { + return changeWind(currentWind); + } + + /** + * @param wind the wind to change + * @return the changed wind + */ + private Wind changeWind(Wind wind) { + Wind newWind = new Wind(wind.getWindDirection(), wind.getWindSpeed()); + oscillateWind(newWind); + if (shiftThisRace){shiftWind(newWind);} + changeWindSpeed(newWind); + timeOfLastChange = System.currentTimeMillis(); + return newWind; + } + + /** + * moves the wind 5 degrees up and down + * @param wind the wind to oscillate + */ + private void oscillateWind(Wind wind) { + double timeSinceLastOscillationReset = System.currentTimeMillis() - timeOfLastOscillationReset; + double timeSinceLastChange = System.currentTimeMillis() - timeOfLastChange; + double newBearing = wind.getWindDirection().degrees(); + double degreeChange = timeSinceLastChange * 2 * bearingVariance / oscillationPeriod; + degreeChange = (1 - oscillationVariance) * degreeChange + (2 * oscillationVariance) * degreeChange * Math.random(); + + if (timeSinceLastOscillationReset >= oscillationPeriod) { + timeOfLastOscillationReset = System.currentTimeMillis(); + anticlockwise = !anticlockwise; + } + if (anticlockwise) { + newBearing -= degreeChange; + if (newBearing < baselineBearing.degrees() - bearingVariance) { + anticlockwise = !anticlockwise; + timeOfLastOscillationReset = System.currentTimeMillis(); + } else { + wind.setWindDirection(Bearing.fromDegrees(newBearing % 360)); + } + } else { + newBearing += degreeChange; + if (newBearing > baselineBearing.degrees() + bearingVariance) { + anticlockwise = !anticlockwise; + timeOfLastOscillationReset = System.currentTimeMillis(); + } else { + wind.setWindDirection(Bearing.fromDegrees(newBearing % 360)); + } + } + } + + /** + * Slowly shifts the wind up to 180 degrees from where it started + * @param wind the wind to change + */ + private void shiftWind(Wind wind) { + double timeSinceLastShift = System.currentTimeMillis() - timeOfLastShift; + double newBearing = wind.getWindDirection().degrees(); + double degreeChange = 7; + + if (timeSinceLastShift >= shiftTime){ + shiftedSoFar += degreeChange; + if (shiftedSoFar >= 180){ + shiftAnticlockwise = Math.random() > 0.5; + shiftedSoFar = 0; +// System.out.println("Swapping"); + } + + timeOfLastShift = System.currentTimeMillis(); + if (shiftAnticlockwise){ + newBearing -= degreeChange; + wind.setWindDirection(Bearing.fromDegrees(newBearing % 360)); + } else { + newBearing += degreeChange; + wind.setWindDirection(Bearing.fromDegrees(newBearing % 360)); + } + } + } + + /** + * Change the wind speed + * @param wind the wind to change + */ + private void changeWindSpeed(Wind wind) { + double offsetAngle = (wind.getWindDirection().radians() - baselineBearing.radians()); + double offset = Math.sin(offsetAngle) * windSpeedVariance; + double newWindSpeed = baseLineSpeed + offset; + wind.setWindSpeed(newWindSpeed); + } + + /** + * starts the wind oscillation direction + */ + private void initialiseOscillationDirection() { + anticlockwise = new Random().nextBoolean(); + timeOfLastOscillationReset = System.currentTimeMillis(); + } + + public void setBearingVariance(double maxBearingVariance) { + this.bearingVariance = maxBearingVariance; + } + + public void setWindSpeedVariance(double windSpeedVariance) { + this.windSpeedVariance = windSpeedVariance; + } + + public void setOscillationPeriod(double oscillationPeriod) { + this.oscillationPeriod = oscillationPeriod; + } +} diff --git a/racevisionGame/src/main/java/mock/model/WindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/WindGenerator.java similarity index 96% rename from racevisionGame/src/main/java/mock/model/WindGenerator.java rename to racevisionGame/src/main/java/mock/model/wind/WindGenerator.java index 8285d5d3..52ee2cd6 100644 --- a/racevisionGame/src/main/java/mock/model/WindGenerator.java +++ b/racevisionGame/src/main/java/mock/model/wind/WindGenerator.java @@ -1,4 +1,4 @@ -package mock.model; +package mock.model.wind; import shared.model.Wind; diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java index f1b83479..0e538925 100644 --- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java +++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java @@ -74,14 +74,19 @@ public class RaceXMLCreator { * @throws SAXException error in schema file * @throws ParserConfigurationException error in parsing the schema file */ - public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException { + public static String alterRaceToWind(String s, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException { RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath); - XMLRace race = (XMLRace) XMLUtilities.xmlToClass(RaceXMLCreator.class.getClassLoader().getResourceAsStream(s), + XMLRace race = XMLUtilities.xmlToClass( + RaceXMLCreator.class.getClassLoader().getResourceAsStream(s), RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"), XMLRace.class); - setRaceXMLAtCurrentTimeToNow(race); + if(tutorial){ + setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l); + } else { + setRaceXMLAtCurrentTimeToNow(race); + } double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position()); @@ -89,16 +94,10 @@ public class RaceXMLCreator { alterRaceRotation(race, degreesToRotate); - JAXBContext context = JAXBContext.newInstance(XMLRace.class); - Marshaller jaxbMarshaller = context.createMarshaller(); - jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - - StringWriter sw = new StringWriter(); - - jaxbMarshaller.marshal(race, sw); - return sw.toString(); + return XMLUtilities.classToXML(race); } + /** * Rotate the features in a race such as the boundary, and the marks. * @param race the race to alter @@ -186,14 +185,11 @@ public class RaceXMLCreator { } - /** - * 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. - */ - public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { + + public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){ //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 millisecondsToAdd = racePrestartTime + racePreparatoryTime; long secondsToAdd = millisecondsToAdd / 1000; //Scale the time using our time scalar. secondsToAdd = secondsToAdd / Constants.RaceTimeScale; @@ -204,4 +200,13 @@ public class RaceXMLCreator { raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd))); } + /** + * 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. + */ + public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) { + setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime); + + } + } diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java index 6a03b37e..4c75cc52 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RaceTypeEnum.java @@ -71,6 +71,8 @@ public enum RaceTypeEnum { } + + /** * Stores a mapping between Byte values and RaceStatusEnum values. */ @@ -107,4 +109,14 @@ public enum RaceTypeEnum { } + @Override + public String toString() { + if (fromByte(value) == FLEET_RACE) { + return "fleet"; + } else if (fromByte(value) == MATCH_RACE) { + return "match"; + } else { + return super.toString(); + } + } } diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index 738b3ae6..c16d722f 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -35,6 +35,7 @@ public class LatestMessages extends Observable { + /** * Ctor. */ diff --git a/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java index 40f12c75..17bb7c45 100644 --- a/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java @@ -22,4 +22,16 @@ public interface BoatDataSource { * @return Map between source ID and mark. */ Map getMarkerBoats(); + + + /** + * Returns the sequence number associated with this data source. Used to indicate when it has changed. + * @return Sequence number. + */ + int getSequenceNumber(); + + /** + * Increments the sequence number for this data source. Used to indicate that it has changed. + */ + void incrementSequenceNumber(); } diff --git a/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java index 885309c0..25c956d9 100644 --- a/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java @@ -29,6 +29,8 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { private final Map markerMap = new HashMap<>(); + private int sequenceNumber = 0; + /** * Constructor for Boat XML using a file. * @@ -174,4 +176,15 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { public Map getMarkerBoats() { return markerMap; } + + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java index 1de4251e..7a8d2151 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java @@ -22,6 +22,8 @@ public class EmptyBoatDataSource implements BoatDataSource { private final Map markerMap = new HashMap<>(); + private int sequenceNumber = 0; + public EmptyBoatDataSource() { } @@ -44,4 +46,15 @@ public class EmptyBoatDataSource implements BoatDataSource { public Map getMarkerBoats() { return markerMap; } + + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java index 74ccbaf5..676d7e70 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java @@ -2,8 +2,10 @@ package shared.dataInput; import network.Messages.Enums.RaceTypeEnum; import shared.model.CompoundMark; +import shared.model.Corner; import shared.model.GPSCoordinate; import shared.model.Leg; +import shared.xml.Race.XMLCorner; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -48,6 +50,12 @@ public class EmptyRaceDataSource implements RaceDataSource { */ private final List legs = new ArrayList<>(); + /** + * Corners in race. + */ + private final List corners = new ArrayList<>(); + + /** * The time that the race.xml file was created. @@ -76,6 +84,8 @@ public class EmptyRaceDataSource implements RaceDataSource { private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE; + private int sequenceNumber = 0; + public EmptyRaceDataSource() { } @@ -98,6 +108,11 @@ public class EmptyRaceDataSource implements RaceDataSource { return legs; } + @Override + public List getCorners() { + return corners; + } + public List getCompoundMarks() { return new ArrayList<>(compoundMarkMap.values()); } @@ -126,4 +141,14 @@ public class EmptyRaceDataSource implements RaceDataSource { public List getParticipants() { return participants; } + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java index 05a0fcf9..1546972e 100644 --- a/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java @@ -60,6 +60,8 @@ public class EmptyRegattaDataSource implements RegattaDataSource { + private int sequenceNumber = 0; + public EmptyRegattaDataSource() { } @@ -119,4 +121,15 @@ public class EmptyRegattaDataSource implements RegattaDataSource { public GPSCoordinate getGPSCoordinate() { return new GPSCoordinate(centralLatitude, centralLongitude); } + + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java index b89dc45d..765098f2 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java @@ -2,8 +2,10 @@ package shared.dataInput; import network.Messages.Enums.RaceTypeEnum; import shared.model.CompoundMark; +import shared.model.Corner; import shared.model.GPSCoordinate; import shared.model.Leg; +import shared.xml.Race.XMLCorner; import java.time.ZonedDateTime; import java.util.List; @@ -27,6 +29,12 @@ public interface RaceDataSource { */ List getLegs(); + /** + * Returns the list of corners in the race - two adjacent corners form a leg. + * @return List of corners in race. + */ + List getCorners(); + /** * Returns a list of coordinates representing the boundary of the race. * @return The boundary of the race. @@ -83,4 +91,16 @@ public interface RaceDataSource { * @return Bottom right GPS coordinate. */ GPSCoordinate getMapBottomRight(); + + + /** + * Returns the sequence number associated with this data source. Used to indicate when it has changed. + * @return Sequence number. + */ + int getSequenceNumber(); + + /** + * Increments the sequence number for this data source. Used to indicate that it has changed. + */ + void incrementSequenceNumber(); } diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java index c2faea69..41d60e5f 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RaceXMLReader.java @@ -84,6 +84,8 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { private RaceTypeEnum raceType; + private int sequenceNumber = 0; + /** @@ -343,7 +345,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { int cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID")); - cornersList.add(new Corner(cornerID, cornerSeq, "SP", 3)); + cornersList.add(new Corner(cornerID, cornerSeq, cornerRounding, 3)); //Gets the CompoundMark associated with this corner. CompoundMark lastCompoundMark = this.compoundMarkMap.get(cornerID); @@ -366,7 +368,7 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { cornerSeq = Integer.parseInt(getAttribute(cornerElement, "SeqID")); - cornersList.add(new Corner(cornerID, cornerSeq, "Port", 3)); + cornersList.add(new Corner(cornerID, cornerSeq, getCompoundMarkRounding(cornerElement), 3)); //gets the Rounding of this corner element cornerRounding = getCompoundMarkRounding(cornerElement); @@ -488,7 +490,17 @@ public class RaceXMLReader extends XMLReader implements RaceDataSource { return participants; } - public List getCornersList() { + public List getCorners() { return cornersList; } + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java index eb198cb2..9af55217 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaDataSource.java @@ -66,5 +66,15 @@ public interface RegattaDataSource { float getMagneticVariation(); + /** + * Returns the sequence number associated with this data source. Used to indicate when it has changed. + * @return Sequence number. + */ + int getSequenceNumber(); + + /** + * Increments the sequence number for this data source. Used to indicate that it has changed. + */ + void incrementSequenceNumber(); } diff --git a/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java index 0a756b03..36743105 100644 --- a/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java @@ -59,6 +59,8 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource { private float magneticVariation; + private int sequenceNumber = 0; + /** * Constructor for Regatta XML using a file. @@ -209,4 +211,14 @@ public class RegattaXMLReader extends XMLReader implements RegattaDataSource { public GPSCoordinate getGPSCoordinate() { return new GPSCoordinate(centralLatitude, centralLongitude); } + + @Override + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override + public void incrementSequenceNumber() { + sequenceNumber++; + } } diff --git a/racevisionGame/src/main/java/shared/enums/RoundingType.java b/racevisionGame/src/main/java/shared/enums/RoundingType.java index 8f8e719a..e9faef80 100644 --- a/racevisionGame/src/main/java/shared/enums/RoundingType.java +++ b/racevisionGame/src/main/java/shared/enums/RoundingType.java @@ -46,4 +46,19 @@ public enum RoundingType { return null; } } + + public static String getStringOf(RoundingType value) { + switch (value) { + case Port: + return "Port"; + case Starboard: + return "Starboard"; + case SP: + return "SP"; + case PS: + return "PS"; + default: + return null; + } + } } diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java index a6fe3844..7a5e2820 100644 --- a/racevisionGame/src/main/java/shared/model/Constants.java +++ b/racevisionGame/src/main/java/shared/model/Constants.java @@ -33,20 +33,26 @@ public class Constants { * 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; + public static final int RaceTimeScale = 2;//10; /** - * The race pre-start time, in milliseconds. 3 minutes. + * The race pre-start time, in milliseconds. 3 minutes (30 seconds for development). */ - public static final long RacePreStartTime = 1 * 10 * 1000; +// public static final long RacePreStartTime = 30 * 1000; + public static final long RacePreStartTime = 1000; /** - * The race preparatory time, in milliseconds. 1 minutes. + * The race preparatory time, in milliseconds. 1 minute. */ +// public static final long RacePreparatoryTime = 60 * 1000; public static final long RacePreparatoryTime = 1 * 60 * 1000; + + + + /** * The number of milliseconds in one hour. *
diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java deleted file mode 100644 index a15e2edd..00000000 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ /dev/null @@ -1,318 +0,0 @@ -package shared.model; - -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.property.SimpleObjectProperty; -import mock.model.collider.ColliderRegistry; -import network.Messages.Enums.RaceStatusEnum; -import network.Messages.Enums.RaceTypeEnum; -import shared.dataInput.BoatDataSource; -import shared.dataInput.RaceDataSource; -import shared.dataInput.RegattaDataSource; - -import java.util.List; - - -/** - * Represents a yacht race. - * 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 { - /** - * 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; - /** - * 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 race's wind. - */ - protected Property raceWind = new SimpleObjectProperty<>(); - /** - * Registry for all collider object in this race - */ - protected ColliderRegistry colliderRegistry; - /** - * 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, 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...). - */ - public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource) { - - //Keep a reference to data sources. - this.raceDataSource = raceDataSource; - this.boatDataSource = boatDataSource; - this.regattaDataSource = regattaDataSource; - - //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. - this.setWind(Bearing.fromDegrees(0), 0); - // Set up colliders - this.colliderRegistry = new ColliderRegistry(); - - for(CompoundMark mark: compoundMarks) { - colliderRegistry.addCollider(mark.getMark1()); - if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2()); - } - } - - public ColliderRegistry getColliderRegistry() { - return colliderRegistry; - } - - /** - * 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. - */ - public 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; - } - - /** - * Updates the race to have a specified wind bearing and speed. - * @param windBearing New wind bearing. - * @param windSpeedKnots New wind speed, in knots. - */ - protected void setWind(Bearing windBearing, double windSpeedKnots) { - Wind wind = new Wind(windBearing, windSpeedKnots); - setWind(wind); - } - - /** - * Updates the race to have a specified wind (bearing and speed). - * @param wind New wind. - */ - protected void setWind(Wind wind) { - this.raceWind.setValue(wind); - } - - /** - * Returns the wind bearing. - * @return The wind bearing. - */ - public Bearing getWindDirection() { - return raceWind.getValue().getWindDirection(); - } - - /** - * Returns the wind speed. - * Measured in knots. - * @return The wind speed. - */ - public double getWindSpeed() { - return raceWind.getValue().getWindSpeed(); - } - - /** - * 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 marks of the race. - * @return Marks of the race. - */ - public List getCompoundMarks() { - return compoundMarks; - } - - /** - * Returns the legs of the race. - * @return Legs of the race. - */ - public List getLegs() { - return legs; - } - - /** - * 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; - } - } - - public int getRaceId() { - return raceId; - } -} diff --git a/racevisionGame/src/main/java/shared/model/RaceState.java b/racevisionGame/src/main/java/shared/model/RaceState.java index 19a1a52c..48361da2 100644 --- a/racevisionGame/src/main/java/shared/model/RaceState.java +++ b/racevisionGame/src/main/java/shared/model/RaceState.java @@ -188,7 +188,20 @@ public abstract class RaceState { * @return List of mark boats. */ public List getMarks() { - return new ArrayList<>(boatDataSource.getMarkerBoats().values()); + //BoatDataSource contains a collection of Marks, and RaceDataSource contains a collection of Compound marks (which contain marks). RaceDataSource is the "definitive" source of mark data. + List marks = new ArrayList<>(getCompoundMarks().size() * 2); + + for (CompoundMark compoundMark : getCompoundMarks()) { + if (compoundMark.getMark1() != null) { + marks.add(compoundMark.getMark1()); + } + + if (compoundMark.getMark2() != null) { + marks.add(compoundMark.getMark2()); + } + } + + return marks; } /** diff --git a/racevisionGame/src/main/java/shared/model/Wind.java b/racevisionGame/src/main/java/shared/model/Wind.java index 08d391c2..531d8473 100644 --- a/racevisionGame/src/main/java/shared/model/Wind.java +++ b/racevisionGame/src/main/java/shared/model/Wind.java @@ -48,4 +48,11 @@ public class Wind { return windSpeed; } + public void setWindSpeed(double windSpeed) { + this.windSpeed = windSpeed; + } + + public void setWindDirection(Bearing windDirection) { + this.windDirection = windDirection; + } } diff --git a/racevisionGame/src/main/java/shared/xml/Race/RaceDataSourceToXML.java b/racevisionGame/src/main/java/shared/xml/Race/RaceDataSourceToXML.java new file mode 100644 index 00000000..3e3b5f8c --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/Race/RaceDataSourceToXML.java @@ -0,0 +1,131 @@ +package shared.xml.Race; + +import shared.dataInput.RaceDataSource; +import shared.enums.RoundingType; +import shared.model.CompoundMark; +import shared.model.Corner; +import shared.model.GPSCoordinate; +import shared.model.Leg; +import shared.xml.XMLUtilities; + +import javax.xml.bind.JAXBException; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +/** + * Has functions to convert a {@link shared.dataInput.RaceDataSource} to an {@link XMLRace} object. + */ +public class RaceDataSourceToXML { + + + /** + * Converts a race data source to an XMLRace object. + * @param raceDataSource The data source to convert. + * @return The XMLRace file. + */ + public static XMLRace toXML(RaceDataSource raceDataSource) { + + //Kind of ugly. Could be refactored/split up a bit. + + + XMLRace race = new XMLRace(); + + + race.setRaceID(raceDataSource.getRaceId()); + race.setRaceType(raceDataSource.getRaceType().toString()); + + + race.setCreationTimeDate(raceDataSource.getCreationDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"))); + + + XMLRaceStartTime startTime = new XMLRaceStartTime(); + startTime.setPostpone(String.valueOf(raceDataSource.getPostponed())); + startTime.setTime(raceDataSource.getStartDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"))); + race.setRaceStartTime(startTime); + + + XMLParticipants participants = new XMLParticipants(); + participants.yacht = new ArrayList<>(); + for (int i : raceDataSource.getParticipants()) { + XMLYacht yacht = new XMLYacht(); + yacht.setSourceID(i); + participants.yacht.add(yacht); + } + race.setParticipants(participants); + + + XMLCourseLimit courseLimit = new XMLCourseLimit(); + courseLimit.limit = new ArrayList<>(); + for (int i = 0; i < raceDataSource.getBoundary().size(); i++) { + XMLLimit limit = new XMLLimit(); + limit.setLat(raceDataSource.getBoundary().get(i).getLatitude()); + limit.setLon(raceDataSource.getBoundary().get(i).getLongitude()); + limit.setSeqID(i + 1); + courseLimit.limit.add(limit); + } + race.setCourseLimit(courseLimit); + + + XMLCourse course = new XMLCourse(); + course.compoundMark = new ArrayList<>(); + for (CompoundMark compoundMark : raceDataSource.getCompoundMarks()) { + XMLCompoundMark xmlCompoundMark = new XMLCompoundMark(); + xmlCompoundMark.setCompoundMarkID(compoundMark.getCompoundMarkID()); + xmlCompoundMark.setName(compoundMark.getName()); + + if (compoundMark.getMark1() != null) { + XMLMark xmlMark = new XMLMark(); + xmlMark.setName(compoundMark.getMark1().getName()); + xmlMark.setSourceID(compoundMark.getMark1().getSourceID()); + xmlMark.setSeqId(1); + xmlMark.setTargetLat(compoundMark.getMark1().getPosition().getLatitude()); + xmlMark.setTargetLng(compoundMark.getMark1().getPosition().getLongitude()); + + xmlCompoundMark.getMark().add(xmlMark); + } + if (compoundMark.getMark2() != null) { + XMLMark xmlMark = new XMLMark(); + xmlMark.setName(compoundMark.getMark2().getName()); + xmlMark.setSourceID(compoundMark.getMark2().getSourceID()); + xmlMark.setSeqId(2); + xmlMark.setTargetLat(compoundMark.getMark2().getPosition().getLatitude()); + xmlMark.setTargetLng(compoundMark.getMark2().getPosition().getLongitude()); + + xmlCompoundMark.getMark().add(xmlMark); + } + + course.compoundMark.add(xmlCompoundMark); + } + race.setCourse(course); + + XMLCompoundMarkSequence compoundMarkSequence = new XMLCompoundMarkSequence(); + compoundMarkSequence.corner = new ArrayList<>(); + for (Corner corner : raceDataSource.getCorners()) { + XMLCorner xmlCorner = new XMLCorner(); + xmlCorner.setZoneSize(corner.getZoneSize()); + xmlCorner.setSeqID(corner.getSeqID()); + xmlCorner.setCompoundMarkID(corner.getCompoundMarkID()); + xmlCorner.setRounding(corner.getRounding()); + + compoundMarkSequence.corner.add(xmlCorner); + } + race.setCompoundMarkSequence(compoundMarkSequence); + + + return race; + } + + + /** + * Converts a race data source to an xml string. + * @param raceDataSource Data source to convert. + * @return String containing xml file. + * @throws JAXBException Thrown if it cannot be converted. + */ + public static String toString(RaceDataSource raceDataSource) throws JAXBException { + XMLRace race = toXML(raceDataSource); + return XMLUtilities.classToXML(race); + } + + +} diff --git a/racevisionGame/src/main/java/shared/xml/XMLUtilities.java b/racevisionGame/src/main/java/shared/xml/XMLUtilities.java index 8e01cb76..0a50f77d 100644 --- a/racevisionGame/src/main/java/shared/xml/XMLUtilities.java +++ b/racevisionGame/src/main/java/shared/xml/XMLUtilities.java @@ -20,10 +20,17 @@ import java.io.*; import java.net.URL; /** - * Created by fwy13 on 13/08/17. + * Contains utility functions to convert between xml files and xml class objects. */ public class XMLUtilities { + + /** + * Converts an XML class object to an XML string. + * @param o The XML class object to convert. + * @return String containing the serialised XML data. + * @throws JAXBException Thrown if the object is cannot be serialised to XML. + */ public static String classToXML(Object o) throws JAXBException { JAXBContext context = JAXBContext.newInstance(o.getClass()); Marshaller jaxbMarshaller = context.createMarshaller(); @@ -49,14 +56,26 @@ public class XMLUtilities { return xmlToClass(document, schemaURL, c); } - public static Object xmlToClass(InputStream i, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { + /** + * Converts an XML file to an XML class (e.g., {@link shared.xml.Race.XMLRace}). + * @param i The input stream for the XML file. + * @param schemaURL URL for the XML schema. + * @param c The XML class to convert to. + * @param The XML class to convert to. + * @return The XML class object. + * @throws ParserConfigurationException + * @throws IOException + * @throws SAXException + * @throws JAXBException + */ + public static T xmlToClass(InputStream i, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document document = parser.parse(i); return xmlToClass(document, schemaURL, c); } - public static Object xmlToClass(Document document, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { + public static T xmlToClass(Document document, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException { JAXBContext jc = JAXBContext.newInstance(c); Unmarshaller unmarshaller = jc.createUnmarshaller(); @@ -64,7 +83,7 @@ public class XMLUtilities { Schema schema = sf.newSchema(schemaURL); unmarshaller.setSchema(schema); - return unmarshaller.unmarshal(new DOMSource(document)); + return (T) unmarshaller.unmarshal(new DOMSource(document)); } public static boolean validateXML(String file, URL schemaURL){ diff --git a/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java b/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java new file mode 100644 index 00000000..c0361b52 --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java @@ -0,0 +1,533 @@ +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2017.09.01 at 11:12:43 PM NZST +// + + +package shared.xml.boats; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="Boats">
+ *           <complexType>
+ *             <complexContent>
+ *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                 <sequence>
+ *                   <element name="Boat" maxOccurs="unbounded">
+ *                     <complexType>
+ *                       <complexContent>
+ *                         <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                           <sequence>
+ *                             <element name="GPSposition">
+ *                               <complexType>
+ *                                 <complexContent>
+ *                                   <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *                                     <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+ *                                     <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ *                                     <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+ *                                   </restriction>
+ *                                 </complexContent>
+ *                               </complexType>
+ *                             </element>
+ *                           </sequence>
+ *                           <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                           <attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                           <attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                           <attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                           <attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                           <attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *                           <attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *                         </restriction>
+ *                       </complexContent>
+ *                     </complexType>
+ *                   </element>
+ *                 </sequence>
+ *               </restriction>
+ *             </complexContent>
+ *           </complexType>
+ *         </element>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "boats" +}) +@XmlRootElement(name = "BoatConfig") +public class BoatConfig { + + @XmlElement(name = "Boats", required = true) + protected BoatConfig.Boats boats; + + /** + * Gets the value of the boats property. + * + * @return + * possible object is + * {@link BoatConfig.Boats } + * + */ + public BoatConfig.Boats getBoats() { + return boats; + } + + /** + * Sets the value of the boats property. + * + * @param value + * allowed object is + * {@link BoatConfig.Boats } + * + */ + public void setBoats(BoatConfig.Boats value) { + this.boats = value; + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+     * <complexType>
+     *   <complexContent>
+     *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *       <sequence>
+     *         <element name="Boat" maxOccurs="unbounded">
+     *           <complexType>
+     *             <complexContent>
+     *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                 <sequence>
+     *                   <element name="GPSposition">
+     *                     <complexType>
+     *                       <complexContent>
+     *                         <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+     *                           <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+     *                           <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+     *                           <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+     *                         </restriction>
+     *                       </complexContent>
+     *                     </complexType>
+     *                   </element>
+     *                 </sequence>
+     *                 <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 <attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 <attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 <attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 <attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *                 <attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" />
+     *                 <attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" />
+     *               </restriction>
+     *             </complexContent>
+     *           </complexType>
+     *         </element>
+     *       </sequence>
+     *     </restriction>
+     *   </complexContent>
+     * </complexType>
+     * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "boat" + }) + public static class Boats { + + @XmlElement(name = "Boat", required = true) + protected List boat; + + /** + * Gets the value of the boat property. + * + *

+ * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the boat property. + * + *

+ * For example, to add a new item, do as follows: + *

+         *    getBoat().add(newItem);
+         * 
+ * + * + *

+ * Objects of the following type(s) are allowed in the list + * {@link BoatConfig.Boats.Boat } + * + * + */ + public List getBoat() { + if (boat == null) { + boat = new ArrayList(); + } + return this.boat; + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+         * <complexType>
+         *   <complexContent>
+         *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *       <sequence>
+         *         <element name="GPSposition">
+         *           <complexType>
+         *             <complexContent>
+         *               <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+         *                 <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+         *                 <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+         *                 <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+         *               </restriction>
+         *             </complexContent>
+         *           </complexType>
+         *         </element>
+         *       </sequence>
+         *       <attribute name="Type" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       <attribute name="BoatName" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       <attribute name="SourceID" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       <attribute name="HullNum" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       <attribute name="ShortName" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *       <attribute name="ShapeID" type="{http://www.w3.org/2001/XMLSchema}int" />
+         *       <attribute name="StoweName" type="{http://www.w3.org/2001/XMLSchema}string" />
+         *     </restriction>
+         *   </complexContent>
+         * </complexType>
+         * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "gpSposition" + }) + public static class Boat { + + @XmlElement(name = "GPSposition", required = true) + protected BoatConfig.Boats.Boat.GPSposition gpSposition; + @XmlAttribute(name = "Type", required = true) + protected String type; + @XmlAttribute(name = "BoatName", required = true) + protected String boatName; + @XmlAttribute(name = "SourceID", required = true) + protected int sourceID; + @XmlAttribute(name = "HullNum") + protected String hullNum; + @XmlAttribute(name = "ShortName") + protected String shortName; + @XmlAttribute(name = "ShapeID") + protected Integer shapeID; + @XmlAttribute(name = "StoweName") + protected String stoweName; + + /** + * Gets the value of the gpSposition property. + * + * @return + * possible object is + * {@link BoatConfig.Boats.Boat.GPSposition } + * + */ + public BoatConfig.Boats.Boat.GPSposition getGPSposition() { + return gpSposition; + } + + /** + * Sets the value of the gpSposition property. + * + * @param value + * allowed object is + * {@link BoatConfig.Boats.Boat.GPSposition } + * + */ + public void setGPSposition(BoatConfig.Boats.Boat.GPSposition value) { + this.gpSposition = value; + } + + /** + * Gets the value of the type property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getType() { + return type; + } + + /** + * Sets the value of the type property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setType(String value) { + this.type = value; + } + + /** + * Gets the value of the boatName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getBoatName() { + return boatName; + } + + /** + * Sets the value of the boatName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setBoatName(String value) { + this.boatName = value; + } + + /** + * Gets the value of the sourceID property. + * + */ + public int getSourceID() { + return sourceID; + } + + /** + * Sets the value of the sourceID property. + * + */ + public void setSourceID(int value) { + this.sourceID = value; + } + + /** + * Gets the value of the hullNum property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getHullNum() { + return hullNum; + } + + /** + * Sets the value of the hullNum property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setHullNum(String value) { + this.hullNum = value; + } + + /** + * Gets the value of the shortName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getShortName() { + return shortName; + } + + /** + * Sets the value of the shortName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setShortName(String value) { + this.shortName = value; + } + + /** + * Gets the value of the shapeID property. + * + * @return + * possible object is + * {@link Integer } + * + */ + public Integer getShapeID() { + return shapeID; + } + + /** + * Sets the value of the shapeID property. + * + * @param value + * allowed object is + * {@link Integer } + * + */ + public void setShapeID(Integer value) { + this.shapeID = value; + } + + /** + * Gets the value of the stoweName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getStoweName() { + return stoweName; + } + + /** + * Sets the value of the stoweName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setStoweName(String value) { + this.stoweName = value; + } + + + /** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+             * <complexType>
+             *   <complexContent>
+             *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+             *       <attribute name="X" type="{http://www.w3.org/2001/XMLSchema}double" />
+             *       <attribute name="Y" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+             *       <attribute name="Z" use="required" type="{http://www.w3.org/2001/XMLSchema}double" />
+             *     </restriction>
+             *   </complexContent>
+             * </complexType>
+             * 
+ * + * + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class GPSposition { + + @XmlAttribute(name = "X", required = true) + protected Double x; + @XmlAttribute(name = "Y", required = true) + protected double y; + @XmlAttribute(name = "Z", required = true) + protected double z; + + /** + * Gets the value of the x property. + * + * @return + * possible object is + * {@link Double } + * + */ + public Double getX() { + return x; + } + + /** + * Sets the value of the x property. + * + * @param value + * allowed object is + * {@link Double } + * + */ + public void setX(Double value) { + this.x = value; + } + + /** + * Gets the value of the y property. + * + */ + public double getY() { + return y; + } + + /** + * Sets the value of the y property. + * + */ + public void setY(double value) { + this.y = value; + } + + /** + * Gets the value of the z property. + * + */ + public double getZ() { + return z; + } + + /** + * Sets the value of the z property. + * + */ + public void setZ(double value) { + this.z = value; + } + + } + + } + + } + +} diff --git a/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java b/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java new file mode 100644 index 00000000..d24bee56 --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/boats/BoatDataSourceToXML.java @@ -0,0 +1,86 @@ +package shared.xml.boats; + +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; +import shared.enums.RoundingType; +import shared.model.Boat; +import shared.model.CompoundMark; +import shared.model.Leg; +import shared.model.Mark; +import shared.xml.Race.*; +import shared.xml.XMLUtilities; + +import javax.xml.bind.JAXBException; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +/** + * Has functions to convert a {@link shared.dataInput.BoatDataSource} to an {@link BoatConfig} object. + */ +public class BoatDataSourceToXML { + + + /** + * Converts a boat data source to an XMLRace object. + * @param boatDataSource The data source to convert. + * @return The XMLRace file. + */ + public static BoatConfig toXML(BoatDataSource boatDataSource) { + BoatConfig boatConfig = new BoatConfig(); + + boatConfig.boats = new BoatConfig.Boats(); + boatConfig.boats.boat = new ArrayList<>(); + + + for (Boat boat : boatDataSource.getBoats().values()) { + BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat(); + + xmlBoat.setType("Yacht"); + xmlBoat.setBoatName(boat.getName()); + xmlBoat.setSourceID(boat.getSourceID()); + xmlBoat.setStoweName(boat.getCountry()); + xmlBoat.setShortName(boat.getCountry()); + + BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition(); + position.setX(boat.getPosition().getLongitude()); + position.setY(boat.getPosition().getLatitude()); + position.setZ(0); + xmlBoat.setGPSposition(position); + + boatConfig.boats.boat.add(xmlBoat); + } + + + for (Mark mark : boatDataSource.getMarkerBoats().values()) { + BoatConfig.Boats.Boat xmlBoat = new BoatConfig.Boats.Boat(); + + xmlBoat.setType("Mark"); + xmlBoat.setBoatName(mark.getName()); + xmlBoat.setSourceID(mark.getSourceID()); + + BoatConfig.Boats.Boat.GPSposition position = new BoatConfig.Boats.Boat.GPSposition(); + position.setX(mark.getPosition().getLongitude()); + position.setY(mark.getPosition().getLatitude()); + position.setZ(0); + xmlBoat.setGPSposition(position); + + boatConfig.boats.boat.add(xmlBoat); + } + + return boatConfig; + } + + + /** + * Converts a boat data source to an xml string. + * @param boatDataSource Data source to convert. + * @return String containing xml file. + * @throws JAXBException Thrown if it cannot be converted. + */ + public static String toString(BoatDataSource boatDataSource) throws JAXBException { + BoatConfig boats = toXML(boatDataSource); + return XMLUtilities.classToXML(boats); + } + + +} diff --git a/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java new file mode 100644 index 00000000..0319de9a --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java @@ -0,0 +1,71 @@ +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2017.09.01 at 11:12:43 PM NZST +// + + +package shared.xml.boats; + +import javax.xml.bind.annotation.XmlRegistry; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the aaa package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link BoatConfig } + * + */ + public BoatConfig createBoatConfig() { + return new BoatConfig(); + } + + /** + * Create an instance of {@link BoatConfig.Boats } + * + */ + public BoatConfig.Boats createBoatConfigBoats() { + return new BoatConfig.Boats(); + } + + /** + * Create an instance of {@link BoatConfig.Boats.Boat } + * + */ + public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() { + return new BoatConfig.Boats.Boat(); + } + + /** + * Create an instance of {@link BoatConfig.Boats.Boat.GPSposition } + * + */ + public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() { + return new BoatConfig.Boats.Boat.GPSposition(); + } + +} diff --git a/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java new file mode 100644 index 00000000..7fc72202 --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java @@ -0,0 +1,47 @@ +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2017.09.01 at 10:37:23 PM NZST +// + + +package shared.xml.regatta; + +import javax.xml.bind.annotation.XmlRegistry; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the aaa package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: aaa + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link RegattaConfig } + * + */ + public RegattaConfig createRegattaConfig() { + return new RegattaConfig(); + } + +} diff --git a/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java new file mode 100644 index 00000000..d8cc1613 --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java @@ -0,0 +1,219 @@ +// +// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 +// See http://java.sun.com/xml/jaxb +// Any modifications to this file will be lost upon recompilation of the source schema. +// Generated on: 2017.09.01 at 10:37:23 PM NZST +// + + +package shared.xml.regatta; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + + +/** + *

Java class for anonymous complex type. + * + *

The following schema fragment specifies the expected content contained within this class. + * + *

+ * <complexType>
+ *   <complexContent>
+ *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       <sequence>
+ *         <element name="RegattaID" type="{http://www.w3.org/2001/XMLSchema}int"/>
+ *         <element name="RegattaName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *         <element name="CourseName" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *         <element name="CentralLatitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         <element name="CentralLongitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         <element name="CentralAltitude" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         <element name="UtcOffset" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         <element name="MagneticVariation" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *       </sequence>
+ *     </restriction>
+ *   </complexContent>
+ * </complexType>
+ * 
+ * + * + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "regattaID", + "regattaName", + "courseName", + "centralLatitude", + "centralLongitude", + "centralAltitude", + "utcOffset", + "magneticVariation" +}) +@XmlRootElement(name = "RegattaConfig") +public class RegattaConfig { + + @XmlElement(name = "RegattaID") + protected int regattaID; + @XmlElement(name = "RegattaName", required = true) + protected String regattaName; + @XmlElement(name = "CourseName", required = true) + protected String courseName; + @XmlElement(name = "CentralLatitude") + protected double centralLatitude; + @XmlElement(name = "CentralLongitude") + protected double centralLongitude; + @XmlElement(name = "CentralAltitude") + protected double centralAltitude; + @XmlElement(name = "UtcOffset") + protected double utcOffset; + @XmlElement(name = "MagneticVariation") + protected double magneticVariation; + + /** + * Gets the value of the regattaID property. + * + */ + public int getRegattaID() { + return regattaID; + } + + /** + * Sets the value of the regattaID property. + * + */ + public void setRegattaID(int value) { + this.regattaID = value; + } + + /** + * Gets the value of the regattaName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getRegattaName() { + return regattaName; + } + + /** + * Sets the value of the regattaName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setRegattaName(String value) { + this.regattaName = value; + } + + /** + * Gets the value of the courseName property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getCourseName() { + return courseName; + } + + /** + * Sets the value of the courseName property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setCourseName(String value) { + this.courseName = value; + } + + /** + * Gets the value of the centralLatitude property. + * + */ + public double getCentralLatitude() { + return centralLatitude; + } + + /** + * Sets the value of the centralLatitude property. + * + */ + public void setCentralLatitude(double value) { + this.centralLatitude = value; + } + + /** + * Gets the value of the centralLongitude property. + * + */ + public double getCentralLongitude() { + return centralLongitude; + } + + /** + * Sets the value of the centralLongitude property. + * + */ + public void setCentralLongitude(double value) { + this.centralLongitude = value; + } + + /** + * Gets the value of the centralAltitude property. + * + */ + public double getCentralAltitude() { + return centralAltitude; + } + + /** + * Sets the value of the centralAltitude property. + * + */ + public void setCentralAltitude(double value) { + this.centralAltitude = value; + } + + /** + * Gets the value of the utcOffset property. + * + */ + public double getUtcOffset() { + return utcOffset; + } + + /** + * Sets the value of the utcOffset property. + * + */ + public void setUtcOffset(double value) { + this.utcOffset = value; + } + + /** + * Gets the value of the magneticVariation property. + * + */ + public double getMagneticVariation() { + return magneticVariation; + } + + /** + * Sets the value of the magneticVariation property. + * + */ + public void setMagneticVariation(double value) { + this.magneticVariation = value; + } + +} diff --git a/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java b/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java new file mode 100644 index 00000000..bbe1dce6 --- /dev/null +++ b/racevisionGame/src/main/java/shared/xml/regatta/RegattaDataSourceToXML.java @@ -0,0 +1,54 @@ +package shared.xml.regatta; + +import shared.dataInput.RegattaDataSource; +import shared.xml.Race.XMLRace; +import shared.xml.XMLUtilities; + +import javax.xml.bind.JAXBException; + +/** + * Has functions to convert a {@link shared.dataInput.RegattaDataSource} to an {@link RegattaConfig} object. + */ +public class RegattaDataSourceToXML { + + + /** + * Converts a regatta data source to an XMLRace object. + * @param regattaDataSource The data source to convert. + * @return The XMLRace file. + */ + public static RegattaConfig toXML(RegattaDataSource regattaDataSource) { + + RegattaConfig regatta = new RegattaConfig(); + + regatta.setCentralAltitude(regattaDataSource.getCentralAltitude()); + regatta.setCentralLatitude(regattaDataSource.getCentralLatitude()); + regatta.setCentralLongitude(regattaDataSource.getCentralLongitude()); + + regatta.setCourseName(regattaDataSource.getCourseName()); + + regatta.setRegattaName(regattaDataSource.getRegattaName()); + + regatta.setMagneticVariation(regattaDataSource.getMagneticVariation()); + + regatta.setRegattaID(regattaDataSource.getRegattaID()); + + regatta.setUtcOffset(regattaDataSource.getUtcOffset()); + + return regatta; + } + + + /** + * Converts a regatta data source to an xml string. + * @param regattaDataSource Data source to convert. + * @return String containing xml file. + * @throws JAXBException Thrown if it cannot be converted. + */ + public static String toString(RegattaDataSource regattaDataSource) throws JAXBException { + RegattaConfig regatta = toXML(regattaDataSource); + return XMLUtilities.classToXML(regatta); + } + + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java index c5356495..6de6dcdf 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java @@ -71,8 +71,10 @@ public class FinishController extends Controller { //Winner label. - raceWinnerLabel.setText("Winner: "+ boatNameColumn.getCellObservableValue(0).getValue()); - raceWinnerLabel.setWrapText(true); + if (boats.size() > 0) { + raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue()); + raceWinnerLabel.setWrapText(true); + } } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java index 86a13920..4f07647c 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java @@ -1,8 +1,13 @@ package visualiser.Controllers; import javafx.fxml.FXML; +import javafx.scene.control.Button; import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; import javafx.scene.media.AudioClip; import mock.app.Event; import org.xml.sax.SAXException; @@ -17,6 +22,8 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.net.Socket; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,11 +43,30 @@ public class HostController extends Controller { @FXML AnchorPane hostWrapper; + @FXML + Button previousButton; + + @FXML + Button nextButton; + + @FXML + ImageView mapImage; + private Event game; + private ArrayList listOfMaps; + private int currentMapIndex = 0; + @Override - public void initialize(URL location, ResourceBundle resources) { + public void initialize(URL location, ResourceBundle resources){ + Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png")); + Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png")); + Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png")); + Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png")); + + listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap)); + mapImage.setImage(listOfMaps.get(currentMapIndex)); } /** @@ -49,7 +75,7 @@ public class HostController extends Controller { */ public void hostGamePressed() throws IOException{ try { - this.game = new Event(false); + this.game = new Event(false, currentMapIndex); connectSocket("localhost", 4942); } catch (EventConstructionException e) { Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e); @@ -82,9 +108,13 @@ public class HostController extends Controller { * Hosts a game. */ public void hostGame(){ + mapImage.fitWidthProperty().bind(((Stage) mapImage.getScene().getWindow()).widthProperty().multiply(0.6)); hostWrapper.setVisible(true); } + /** + * Menu button pressed. Prompt alert then return to menu + */ public void menuBtnPressed(){ AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); sound.play(); @@ -92,4 +122,27 @@ public class HostController extends Controller { parent.enterTitle(); } + public void nextImage(){ + increaseIndex(); + mapImage.setImage(listOfMaps.get(currentMapIndex)); + } + + public void previousImage(){ + decreaseIndex(); + mapImage.setImage(listOfMaps.get(currentMapIndex)); + } + private void increaseIndex(){ + currentMapIndex = (currentMapIndex + 1)%listOfMaps.size(); + } + + private void decreaseIndex(){ + currentMapIndex = ((((currentMapIndex - 1)%listOfMaps.size())+listOfMaps.size())%listOfMaps.size()); + } + + public void setGameType(int gameType){ + this.currentMapIndex = gameType; + } + + public int getGameType(){ return this.currentMapIndex; } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java new file mode 100644 index 00000000..84d82e91 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/KeyBindingsController.java @@ -0,0 +1,247 @@ +package visualiser.Controllers; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import visualiser.gameController.Keys.ControlKey; +import visualiser.gameController.Keys.KeyFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static visualiser.app.App.keyFactory; + +/** + * Controller for the scene used to display and update current key bindings. + */ +public class KeyBindingsController { + private @FXML Button btnSave; + private @FXML Button btnCancel; + private @FXML Button btnReset; + private @FXML ListView lstControl; + private @FXML ListView lstKey; + private @FXML ListView lstDescription; + private @FXML AnchorPane anchor; + private KeyFactory newKeyFactory; + private Boolean changed = false; // keyBindings have been modified + private Button currentButton = null; // last button clicked + + public void initialize(){ + // create new key factory to modify, keeping the existing one safe + newKeyFactory = copyExistingFactory(); + initializeTable(); + populateTable(); + setKeyListener(); + } + + /** + * Sets up table before populating it. + * Set up includes headings, CSS styling and modifying default properties. + */ + public void initializeTable(){ + // set the headings for each column + lstKey.getItems().add("Key"); + lstControl.getItems().add("Command"); + lstDescription.getItems().add("Description"); + lstKey.getSelectionModel().select(0); + lstControl.getSelectionModel().select(0); + lstDescription.getSelectionModel().select(0); + + // add CSS stylesheet once the scene has been created + lstKey.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) { + newScene.getStylesheets().add("/css/keyBindings.css"); + } + }); + + // stop the columns from being selectable, so only the buttons are + lstKey.getSelectionModel().selectedItemProperty() + .addListener((observable, oldvalue, newValue) -> + Platform.runLater(() -> + lstKey.getSelectionModel().select(0))); + lstDescription.getSelectionModel().selectedItemProperty() + .addListener((observable, oldvalue, newValue) -> + Platform.runLater(() -> + lstDescription.getSelectionModel().select(0))); + lstControl.getSelectionModel().selectedItemProperty() + .addListener((observable, oldvalue, newValue) -> + Platform.runLater(() -> + lstControl.getSelectionModel().select(0))); + } + + /** + * Populates the table with commands and their key binding details. + */ + public void populateTable(){ + // add each command to the table + for (Map.Entry entry : newKeyFactory.getKeyState().entrySet()) { + // create button for command + Button button = new Button(entry.getKey()); + button.setMinWidth(120); + button.setId(entry.getValue().toString()); + button.setOnAction(e -> currentButton = button); + // display details for command in table + lstControl.getItems().add(entry.getValue()); + lstKey.getItems().add(button); + lstDescription.getItems().add(entry.getValue().getProtocolCode()); + } + } + + /** + * Makes a copy of the {@link KeyFactory} that does not modify the original. + * @return new keyfactory to be modified + */ + public KeyFactory copyExistingFactory(){ + newKeyFactory = new KeyFactory(); + Map oldKeyState = keyFactory.getKeyState(); + Map newKeyState = new HashMap<>(); + + // copy over commands and their keys + for (Map.Entry entry : oldKeyState.entrySet()){ + newKeyState.put(entry.getKey(), entry.getValue()); + } + newKeyFactory.setKeyState(newKeyState); + return newKeyFactory; + } + + /** + * Creates a listener for the base anchorpane for key presses. + * It updates the current key bindings of the {@link KeyFactory} if + * required. + */ + public void setKeyListener(){ + anchor.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + // if esc, cancel current button click + if (event.getCode() == KeyCode.ESCAPE){ + btnCancel.requestFocus(); + currentButton = null; + } + // if a button was clicked + else if (currentButton != null) { + // check if a button is already mapped to this key + for (int i = 1; i < lstKey.getItems().size(); i++) { + Button button = (Button)lstKey.getItems().get(i); + // update buttons text and remove key binding from command + if (button.getText().equals(event.getCode().toString())) { + button.setText(""); + newKeyFactory.updateKey(button.getId(), button.getId()); + } + } + // update text on the button + currentButton.setText(event.getCode().toString()); + // update the control key + newKeyFactory.updateKey(event.getCode().toString(), + currentButton.getId()); + // remove current button selection + currentButton = null; + changed = true; + btnCancel.requestFocus(); + } + event.consume(); + }); + } + + /** + * Cancel and exits the key bindings menu. Changes are not forced to be + * saved or fixed if invalid, and instead are defaulted back to the last + * successful saved state. + */ + public void cancel(){ + ((Stage)btnCancel.getScene().getWindow()).close(); + } + + /** + * Resets all key bindings to the built-in defaults. + */ + public void reset(){ + lstKey.getItems().clear(); + lstControl.getItems().clear(); + lstDescription.getItems().clear(); + newKeyFactory = new KeyFactory(); + initializeTable(); + populateTable(); + changed = true; + } + + /** + * Replace existing {@link KeyFactory} with the modified key bindings. + */ + public void save(){ + if (isFactoryValid()) { + keyFactory = newKeyFactory; + newKeyFactory = new KeyFactory(); + changed = false; + keyFactory.save(); // save persistently + loadNotification("Key bindings were successfully saved.", false); + } else { + loadNotification("One or more key bindings are missing. " + + "Failed to save.", true); + } + } + + /** + * Checks the {@link KeyFactory} being modified is valid and that no + * commands are missing a key binding. + * @return True if valid, false if invalid + */ + public Boolean isFactoryValid(){ + for (Map.Entry entry : newKeyFactory.getKeyState().entrySet + ()) { + if (entry.getKey().toString()==entry.getValue().toString()){ + return false; + } + } + return true; + } + + /** + * Method used to stop a user from exiting key bindings without saving + * their changes to the {@link KeyFactory}. + * @param we {@link WindowEvent} close request to be consumed if settings + * have not been successfully saved. + */ + public void onExit(WindowEvent we){ + // if modified KeyFactory hasn't been saved + if (changed){ + loadNotification("Please cancel or save your changes before exiting" + + ".", true); + we.consume(); + } + } + + /** + * Loads a popup window giving confirmation/warning of user activity. + * @param message the message to be displayed to the user + * @param warning true if the message to be displayed is due to user error + */ + public void loadNotification(String message, Boolean warning){ + Parent root = null; + FXMLLoader loader = new FXMLLoader(getClass().getResource + ("/visualiser/scenes/notification.fxml")); + try { + root = loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + NotificationController controller = loader.getController(); + Stage stage = new Stage(); + stage.setScene(new Scene(root)); + stage.centerOnScreen(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.show(); + // displays given message in the window + controller.setMessage(message, warning); + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java index 0524f2ad..8b3f523f 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/LobbyController.java @@ -22,7 +22,7 @@ import java.util.ResourceBundle; public class LobbyController extends Controller { @FXML - AnchorPane lobbyWrapper; + private AnchorPane lobbyWrapper; @FXML private TableView lobbyTable; @FXML diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java index 220a7f19..2f9817d7 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java @@ -43,6 +43,7 @@ public class MainController extends Controller { * Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race). * @param visualiserRace The object modelling the race. * @param controllerClient Socket Client that manipulates the controller. + * @param isHost if the client is the host of a race or not. */ public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) { raceController.startRace(visualiserRace, controllerClient, isHost); @@ -97,6 +98,20 @@ public class MainController extends Controller { */ public void startCss(){titleController.setDayMode();} + /** + * host controller host a game + * @throws IOException throws exception + */ + public void beginGame() throws IOException { + hostController.hostGamePressed(); + } + + public void setGameType(int gameType){ + hostController.setGameType(gameType); + } + + public int getGameType(){ return hostController.getGameType(); } + /** * Main Controller for the applications will house the menu and the displayed pane. * diff --git a/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java new file mode 100644 index 00000000..d122ec80 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Controllers/NotificationController.java @@ -0,0 +1,35 @@ +package visualiser.Controllers; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +/** + * Controller for a popup notification regarding user activity. + */ +public class NotificationController { + private @FXML Label lblDescription; + private @FXML Text txtMessage; + + /** + * Closes the popup window once clicked. + */ + public void ok(){ + ((Stage)lblDescription.getScene().getWindow()).close(); + } + + /** + * Displays the appropriate popup notification. + * @param message message for the user + * @param warning if true warning text shown, if false success text shown + */ + public void setMessage(String message, Boolean warning){ + lblDescription.setText(message); + if (!warning){ + txtMessage.setText("Success!"); + txtMessage.setFill(Color.GREEN); + } + } +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 4fcda581..f4eacb5e 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -1,6 +1,6 @@ package visualiser.Controllers; - +import com.interactivemesh.jfx.importer.stl.StlMeshImporter; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -10,82 +10,81 @@ import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import javafx.scene.control.*; -import javafx.scene.control.Label; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; +import javafx.scene.shape.MeshView; +import javafx.scene.shape.Sphere; +import javafx.scene.transform.Translate; import javafx.util.Callback; import network.Messages.Enums.RaceStatusEnum; +import shared.dataInput.RaceDataSource; import shared.model.Leg; +import shared.model.Mark; import visualiser.app.App; +import visualiser.enums.TutorialState; import visualiser.gameController.ControllerClient; import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; +import visualiser.layout.Subject3D; +import visualiser.layout.View3D; import visualiser.model.*; +import visualiser.utils.GPSConverter; import java.io.IOException; import java.net.URL; -import java.util.Optional; -import java.util.ResourceBundle; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import static visualiser.app.App.keyFactory; /** * Controller used to display a running race. */ public class RaceController extends Controller { - - /** * The race object which describes the currently occurring race. */ private VisualiserRaceEvent visualiserRace; - /** * Service for sending keystrokes to server */ private ControllerClient controllerClient; - private boolean isHost; - /** - * The canvas that draws the race. - */ - private ResizableRaceCanvas raceCanvas; + private TutorialState currentState; + + private ArrayList tutorialStates; + + private boolean isTutorial = false; + + private String keyToPress; - /** - * The sparkline graph. - */ - private Sparkline sparkline; /** * state of the info table */ private boolean infoTableShow; + private View3D view3D; + private ObservableList viewSubjects; + /** * The arrow controller. */ @FXML private ArrowController arrowController; - - @FXML private GridPane canvasBase; + @FXML private SplitPane racePane; - @FXML private SplitPane race; + @FXML private Label tutorialText; - /** - * This is the root node of the arrow control. - */ - @FXML private Pane arrow; /** * This is the pane we place the actual arrow control inside of. @@ -101,9 +100,6 @@ public class RaceController extends Controller { @FXML private TableColumn boatMarkColumn; @FXML private TableColumn boatSpeedColumn; @FXML private LineChart sparklineChart; - @FXML private AnchorPane annotationPane; - - /** * Ctor. @@ -113,11 +109,10 @@ public class RaceController extends Controller { @Override public void initialize(URL location, ResourceBundle resources) { - KeyFactory keyFactory = KeyFactory.getFactory(); infoTableShow = true; // Initialise keyboard handler - race.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + racePane.addEventFilter(KeyEvent.KEY_PRESSED, event -> { String codeString = event.getCode().toString(); if (codeString.equals("TAB")){toggleTable();} @@ -126,13 +121,27 @@ public class RaceController extends Controller { if(controlKey != null) { try { controlKey.onAction(); // Change key state if applicable + + //Check if current race is a tutorial + if (isTutorial){ + //Check if current tutorial state has the same boat protocol code as key press + if (controlKey.getProtocolCode().equals(currentState.getAction())){ + //Update tutorial + checkTutorialState(); + } + } + controllerClient.sendKey(controlKey); event.consume(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Logger.getGlobal().log(Level.WARNING, "RaceController was interrupted on thread: " + Thread.currentThread() + "while sending: " + controlKey, e); + } catch (Exception e) { + e.printStackTrace(); } } + + if(event.getCode() == KeyCode.ESCAPE) { try { @@ -143,7 +152,7 @@ public class RaceController extends Controller { Optional result = alert.showAndWait(); if (result.get() == ButtonType.OK) { parent.endEvent(); - race.setVisible(false); + racePane.setVisible(false); App.app.showMainStage(App.getStage()); } } else { @@ -152,7 +161,7 @@ public class RaceController extends Controller { alert.setContentText("Do you wish to quit the race?"); Optional result = alert.showAndWait(); if (result.get() == ButtonType.OK) { - race.setVisible(false); + racePane.setVisible(false); App.app.showMainStage(App.getStage()); } } @@ -176,14 +185,10 @@ public class RaceController extends Controller { //Information table. initialiseInfoTable(this.visualiserRace); - //Sparkline. - initialiseSparkline(this.visualiserRace); - //Arrow. initialiseArrow(this.visualiserRace); - //Race canvas. - initialiseRaceCanvas(this.visualiserRace); + initialiseView3D(this.visualiserRace); //Race timezone label. initialiseRaceTimezoneLabel(this.visualiserRace); @@ -191,11 +196,106 @@ public class RaceController extends Controller { //Race clock. initialiseRaceClock(this.visualiserRace); - //Start the race animation timer. raceTimer(); } + private void initialiseView3D(VisualiserRaceEvent race) { + viewSubjects = FXCollections.observableArrayList(); + + // Import boat mesh + URL asset = HostController.class.getClassLoader().getResource("assets/V1.2 Complete Boat.stl"); + StlMeshImporter importer = new StlMeshImporter(); + importer.read(asset); + + // Configure camera angles and control + view3D = new View3D(); + view3D.setDistance(1050); + view3D.setYaw(0); + view3D.setPitch(60); + view3D.enableTracking(); + //newPane.getChildren().add(view3D); + canvasBase.add(view3D, 0, 0); + + // Set up projection from GPS to view + RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); + final GPSConverter gpsConverter = new GPSConverter(raceData, 450, 450); + + view3D.setItems(viewSubjects); + // Position and add each mark to view + for(Mark mark: race.getVisualiserRaceState().getMarks()) { + Subject3D subject = new Subject3D(new Sphere(2)); + subject.setX(gpsConverter.convertGPS(mark.getPosition()).getX()); + subject.setZ(gpsConverter.convertGPS(mark.getPosition()).getY()); + + viewSubjects.add(subject); + } + // Position and add each boat to view + for(VisualiserBoat boat: race.getVisualiserRaceState().getBoats()) { + MeshView mesh = new MeshView(importer.getImport()); + Subject3D subject = new Subject3D(mesh); + viewSubjects.add(subject); + + // Track this boat's movement with the new subject + AnimationTimer trackBoat = new AnimationTimer() { + @Override + public void handle(long now) { + subject.setHeading(boat.getBearing().degrees()); + subject.setX(gpsConverter.convertGPS(boat.getPosition()).getX()); + subject.setZ(gpsConverter.convertGPS(boat.getPosition()).getY()); + } + }; + trackBoat.start(); + } + // Fix initial bird's-eye position + view3D.updatePivot(new Translate(250, 0, 210)); + + // Bind zooming to scrolling + view3D.setOnScroll(e -> { + view3D.updateDistance(e.getDeltaY()); + }); + + // Bind zooming to keypress (Z/X default) + racePane.addEventFilter(KeyEvent.KEY_PRESSED, e -> { + ControlKey key = keyFactory.getKey(e.getCode().toString()); + if(key != null) { + switch (key.toString()) { + case "Zoom In": + //Check if race is a tutorial + if (isTutorial) { + //Check if the current tutorial state is zoom-in + if (currentState.equals(TutorialState.ZOOMIN)) { + try { + //Update tutorial + checkTutorialState(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + } + view3D.updateDistance(-10); + break; + case "Zoom Out": + //Check if race is a tutorial + if(isTutorial) { + //Check if current tutorial state is zoom-out + if (currentState.equals(TutorialState.ZOOMOUT)) { + try { + //Update tutorial + checkTutorialState(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + } + view3D.updateDistance(10); + break; + } + } + }); + } + + /** @@ -338,42 +438,8 @@ public class RaceController extends Controller { } - - /** - * Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}. - * @param race The race to listen to. - */ - private void initialiseSparkline(VisualiserRaceEvent race) { - //The race.getBoats() we are passing in is sorted by position in race inside the race class. - this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart); - } - - /** - * Initialises the {@link ResizableRaceCanvas}, provides the race to read data from. - * @param race Race to read data from. - */ - private void initialiseRaceCanvas(VisualiserRaceEvent race) { - - //Create canvas. - raceCanvas = new ResizableRaceCanvas(race); - - //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. + * Initialises the race time zone label with the race's time zone. * @param race The race to get time zone from. */ private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) { @@ -408,14 +474,29 @@ public class RaceController extends Controller { this.controllerClient = controllerClient; this.isHost = isHost; - initialiseRace(); - //Display this controller. - race.setVisible(true); + //Check if the game is a tutorial + if (parent.getGameType()==4){ + isTutorial = true; + tutorialText.setVisible(true); + tutorialStates = new ArrayList<>(Arrays.asList(TutorialState.values())); + + currentState = tutorialStates.get(0); + tutorialStates.remove(0); + + searchMapForKey("Upwind"); + + tutorialText.setText("Welcome to the tutorial! Exit at anytime with ESC. \nWe will first learn how to turn upwind. Press " + keyToPress + " to turn upwind."); + + } else { + isTutorial = false; + tutorialText.setVisible(false); + } + initialiseRace(); - // set up annotation displays - new Annotations(annotationPane, raceCanvas); + //Display this controller. + racePane.setVisible(true); } /** @@ -423,7 +504,7 @@ public class RaceController extends Controller { * @param boats boats there are in the race. */ public void finishRace(ObservableList boats) { - race.setVisible(false); + racePane.setVisible(false); parent.enterFinish(boats); } @@ -458,18 +539,13 @@ public class RaceController extends Controller { finishRace(visualiserRace.getVisualiserRaceState().getBoats()); } else { - //Otherwise, render the canvas. - raceCanvas.drawRace(); - - //Sort the tableview. Doesn't automatically work for all columns. boatInfoTable.sort(); - } //Return to main screen if we lose connection. if (!visualiserRace.getServerConnection().isAlive()) { - race.setVisible(false); + racePane.setVisible(false); //parent.enterTitle(); try { App.app.showMainStage(App.getStage()); @@ -491,10 +567,10 @@ public class RaceController extends Controller { * toggles if the info table is shown */ private void toggleTable() { - double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/race.getWidth(); + double tablePercent = 1 - (boatPlacingColumn.getPrefWidth() + boatTeamColumn.getPrefWidth() + boatMarkColumn.getPrefWidth() + boatSpeedColumn.getPrefWidth())/racePane.getWidth(); if (infoTableShow){ - race.setDividerPositions(tablePercent); + racePane.setDividerPositions(tablePercent); arrowPane.setScaleX(0.5); arrowPane.setScaleY(0.5); @@ -502,7 +578,7 @@ public class RaceController extends Controller { arrowPane.setTranslateY(0 - arrowPane.getScene().getHeight()/4); }else{ - race.setDividerPositions(1); + racePane.setDividerPositions(1); arrowPane.setScaleX(1); arrowPane.setScaleY(1); @@ -514,4 +590,108 @@ public class RaceController extends Controller { infoTableShow = !infoTableShow; } + /** + * Get the next tutorial state + */ + private void updateTutorialState(){ + //Next tutorial state is popped from list + currentState = tutorialStates.get(0); + tutorialStates.remove(0); + } + + /** + * Search key map for key given string of command + * @param command the command of the key + */ + private void searchMapForKey(String command){ + //For loop through keyFactory + for (Map.Entry entry: keyFactory.getKeyState().entrySet()){ + if(entry.getValue().toString().equals(command)){ + + //Found next key required to press + keyToPress = entry.getKey(); + } + } + } + + /** + * Updates tutorial state and gui display for tutorial text + * @throws Exception Exception thrown + */ + private void checkTutorialState() throws Exception { + //Switch statement to check what the current tutorial state is + switch (currentState){ + case UPWIND: + //Set next key to press as the downwind key + searchMapForKey("Downwind"); + //Update tutorial text + tutorialText.setText("Nice! To turn downwind press " + keyToPress + "."); + updateTutorialState(); + break; + case DOWNWIND: + //Set next key to press as the tack/gybe key + searchMapForKey("Tack/Gybe"); + //Update tutorial text + tutorialText.setText("Nice! To tack or gybe press " + keyToPress + "."); + updateTutorialState(); + break; + case TACKGYBE: + //Set next key to press as the VMG key + searchMapForKey("VMG"); + //Update tutorial text + tutorialText.setText("Nice! To use VMG press " + keyToPress + ". This will turn the boat."); + updateTutorialState(); + break; + case VMG: + //Set next key to press as the sails-in key + searchMapForKey("Toggle Sails"); + //Update tutorial text + tutorialText.setText("Nice! To sails in press " + keyToPress + ". This will stop the boat."); + updateTutorialState(); + break; + case SAILSIN: + //Set next key to press as the sails-out key + searchMapForKey("Toggle Sails"); + //Update tutorial text + tutorialText.setText("Nice! To sails out press " + keyToPress + " again. The will start moving again."); + updateTutorialState(); + break; + case SAILSOUT: + //Set next key to press as the zoom-in key + searchMapForKey("Zoom In"); + //Update tutorial text + tutorialText.setText("Nice! To zoom in press " + keyToPress + "."); + updateTutorialState(); + break; + case ZOOMIN: + //Set next key to press as the zoom-out key + searchMapForKey("Zoom Out"); + //Update tutorial text + tutorialText.setText("Nice! You will also be able to zoom into boats and marks by clicking them. To zoom out press " + keyToPress + "."); + updateTutorialState(); + break; + case ZOOMOUT: + //Finished tutorial. Display pop-up for exiting the tutorial + tutorialText.setText("Congratulations! You're done!"); + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Finished Tutorial"); + alert.setHeaderText("You have finished the tutorial."); + alert.setContentText("Now you know the controls you are ready to race!"); + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + parent.endEvent(); + racePane.setVisible(false); + App.app.showMainStage(App.getStage()); + } + break; + default: + //State not found. Exit tutorial to title menu + parent.endEvent(); + racePane.setVisible(false); + App.app.showMainStage(App.getStage()); + break; + } + + } + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java index 890eb816..290837ae 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java @@ -86,9 +86,6 @@ public class StartController extends Controller { - - - /** * Ctor. */ @@ -218,31 +215,30 @@ public class StartController extends Controller { * Countdown timer until race starts. */ private void countdownTimer() { - new AnimationTimer() { - @Override - public void handle(long arg0) { + new AnimationTimer() { + @Override + public void handle(long arg0) { - //Get the current race status. - RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); + //Get the current race status. + RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); - //Display it. - raceStatusLabel.setText("Race Status: " + raceStatus.name()); + //If the race has reached the preparatory phase, or has started... + if (raceStatus == RaceStatusEnum.WARNING + || raceStatus == RaceStatusEnum.PREPARATORY + || raceStatus == RaceStatusEnum.STARTED) { + //Stop this timer. + stop(); + //Hide this, and display the race controller. + startWrapper.setVisible(false); + //start.setVisible(false);//TODO is this needed? - //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);//TODO is this needed? - - parent.beginRace(visualiserRaceEvent, controllerClient, isHost); + parent.beginRace(visualiserRaceEvent, controllerClient, isHost); + } } - } - }.start(); + }.start(); + } diff --git a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java index b8ec3af7..e5c4975b 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/TitleController.java @@ -1,17 +1,25 @@ package visualiser.Controllers; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.RadioButton; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; import javafx.scene.media.AudioClip; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.stage.Modality; import javafx.stage.Stage; +import mock.app.Event; +import mock.exceptions.EventConstructionException; +import javafx.stage.WindowEvent; import visualiser.app.App; import java.io.File; @@ -19,6 +27,8 @@ import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Controller for the opening title window. @@ -36,6 +46,12 @@ public class TitleController extends Controller { RadioButton dayModeRD; @FXML RadioButton nightModeRD; + @FXML + Label tutorialLabel; + @FXML + Pane menuPane; + @FXML + ImageView imgSun; /** * Method called when the 'host a game' button is pressed. @@ -44,8 +60,8 @@ public class TitleController extends Controller { * @throws IOException if main has problems */ public void hostAGame() throws IOException, URISyntaxException { - titleWrapper.setVisible(false); + parent.setGameType(0); parent.hostGame(); App.getStage().setResizable(true); } @@ -72,7 +88,10 @@ public class TitleController extends Controller { */ public void setDayMode(){ dayModeRD.getScene().getStylesheets().clear(); + menuPane.getStylesheets().clear(); + imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sun.png").toExternalForm())); dayModeRD.getScene().getStylesheets().add("/css/dayMode.css"); + menuPane.setStyle("-fx-background-color: #6be6ff;"); nightModeRD.setSelected(false); } @@ -81,12 +100,16 @@ public class TitleController extends Controller { */ public void setNightMode(){ nightModeRD.getScene().getStylesheets().clear(); + menuPane.getStylesheets().clear(); + imgSun.setImage(new Image(getClass().getResource("/visualiser/images/sunsleep.png").toExternalForm())); nightModeRD.getScene().getStylesheets().add("/css/nightMode.css"); + menuPane.setStyle("-fx-background-color: #1f2c60;"); dayModeRD.setSelected(false); } @Override public void initialize(URL location, ResourceBundle resources) { + tutorialLabel.setWrapText(true); } @@ -94,23 +117,39 @@ public class TitleController extends Controller { * Called when control button is pressed. New pop up window displaying controls */ public void controlBtnPressed(){ - AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); - sound.play(); - FXMLLoader loader = new FXMLLoader(); - loader.setLocation(getClass().getResource("/visualiser/scenes/controls.fxml")); - Parent layout; try { - layout = loader.load(); + AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm()); + sound.play(); + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(getClass().getResource("/visualiser/scenes/keyBindings.fxml")); + Parent layout = loader.load(); Scene scene = new Scene(layout); Stage popupStage = new Stage(); popupStage.setResizable(false); popupStage.setTitle("Game Controls"); popupStage.initModality(Modality.WINDOW_MODAL); + popupStage.centerOnScreen(); popupStage.setScene(scene); - popupStage.showAndWait(); + popupStage.show(); + KeyBindingsController controller = loader.getController(); + popupStage.setOnCloseRequest(new EventHandler() { + public void handle(WindowEvent we) { + if (we.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) { + controller.onExit(we); + } + } + }); } catch (Exception e){ e.printStackTrace(); } } + + public void tutorialStartPressed() throws IOException { + titleWrapper.setVisible(false); + parent.setGameType(4); + parent.beginGame(); + + } + } diff --git a/racevisionGame/src/main/java/visualiser/app/App.java b/racevisionGame/src/main/java/visualiser/app/App.java index 6a7d47f2..d33fd2b4 100644 --- a/racevisionGame/src/main/java/visualiser/app/App.java +++ b/racevisionGame/src/main/java/visualiser/app/App.java @@ -29,19 +29,17 @@ import javafx.stage.StageStyle; import javafx.stage.WindowEvent; import javafx.util.Duration; import visualiser.Controllers.MainController; - -import java.io.IOException; +import visualiser.gameController.Keys.KeyFactory; public class App extends Application { - private static Stage stage; private Pane splashLayout; private ProgressBar loadProgress; private Label progressText; private static final int SPLASH_WIDTH = 676; private static final int SPLASH_HEIGHT = 227; - + public static KeyFactory keyFactory = new KeyFactory(); public static App app; /** @@ -55,6 +53,9 @@ public class App extends Application { @Override public void init() { + // load the user's personalised key bindings + keyFactory.load(); + ImageView splash = new ImageView(new Image( getClass().getClassLoader().getResourceAsStream("images/splashScreen.png") )); diff --git a/racevisionGame/src/main/java/visualiser/enums/TutorialState.java b/racevisionGame/src/main/java/visualiser/enums/TutorialState.java new file mode 100644 index 00000000..ab811a43 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/enums/TutorialState.java @@ -0,0 +1,66 @@ +package visualiser.enums; + +import javafx.scene.input.KeyCode; +import network.Messages.BoatAction; +import network.Messages.Enums.BoatActionEnum; +import visualiser.gameController.Keys.ControlKey; +import static visualiser.app.App.keyFactory; + +import java.util.ArrayList; + +/** + * State of which stage the tutorial is currently in + */ +public enum TutorialState { + + /** + * State for upwind in tutorial + */ + UPWIND(BoatActionEnum.UPWIND), + + /** + * State for downwind in tutorial + */ + DOWNWIND(BoatActionEnum.DOWNWIND), + + /** + * State for tacking/gybing in tutorial + */ + TACKGYBE(BoatActionEnum.TACK_GYBE), + + /** + * State for vmg in tutorial + */ + VMG(BoatActionEnum.AUTO_PILOT), + + /** + * State for sails-in in tutorial + */ + SAILSIN(BoatActionEnum.SAILS_IN), + + /** + * State for sails-out in tutorial + */ + SAILSOUT(BoatActionEnum.SAILS_OUT), + + /** + * State for zoom-in in tutorial + */ + ZOOMIN(null), + + /** + * State for zoom-out in tutorial + */ + ZOOMOUT(null); + + private BoatActionEnum action; + + TutorialState(BoatActionEnum action){ + this.action = action; + } + + public BoatActionEnum getAction(){ + return action; + } + +} diff --git a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java index 057b5721..b4b3de9b 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java +++ b/racevisionGame/src/main/java/visualiser/gameController/InputChecker.java @@ -3,10 +3,11 @@ package visualiser.gameController; import javafx.animation.AnimationTimer; import javafx.scene.Scene; import visualiser.gameController.Keys.ControlKey; -import visualiser.gameController.Keys.KeyFactory; import java.util.HashMap; +import static visualiser.app.App.keyFactory; + /** * Class for checking what keys are currently being used */ @@ -18,7 +19,7 @@ public class InputChecker { * @param scene Scene the controller is to run in parallel with. */ public void runWithScene(Scene scene){ - KeyFactory keyFactory = KeyFactory.getFactory(); +// KeyFactory keyFactory = KeyFactory.getFactory(); scene.setOnKeyPressed(event -> { String codeString = event.getCode().toString(); diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java index dd489f73..ce4b341e 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ControlKey.java @@ -1,6 +1,5 @@ package visualiser.gameController.Keys; -import javafx.scene.input.KeyCode; import network.Messages.Enums.BoatActionEnum; /** @@ -45,7 +44,7 @@ public abstract class ControlKey { /** * What this key should do when the command is issued for it to do its job. */ - public abstract void onAction();//may want to make it take in a visualiser and stuff in the future. + public abstract void onAction(); /** * What to do when the key is held diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java index e4b5455a..6c974972 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/DownWindKey.java @@ -9,11 +9,9 @@ public class DownWindKey extends ControlKey { /** * Constructor for Control - * @param name name of the key - * */ - public DownWindKey(String name) { - super(name, BoatActionEnum.DOWNWIND); + public DownWindKey() { + super("Downwind", BoatActionEnum.DOWNWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java index be95abd3..a783f268 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/KeyFactory.java @@ -1,5 +1,8 @@ package visualiser.gameController.Keys; +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.*; import java.util.HashMap; import java.util.Map; @@ -13,39 +16,108 @@ public class KeyFactory { private Map keyState; /** - * Singleton instance to enforce consistent key state + * Constructor for key state, set up initial state of each action. */ - private static KeyFactory theFactory = new KeyFactory(); + public KeyFactory() { + this.keyState = new HashMap<>(); + keyState.put("Z", new ZoomInKey()); + keyState.put("X", new ZoomOutKey()); + keyState.put("SPACE", new VMGKey()); + keyState.put("SHIFT", new SailsToggleKey()); + keyState.put("ENTER", new TackGybeKey()); + keyState.put("UP", new UpWindKey()); + keyState.put("DOWN", new DownWindKey()); + } /** - * Singleton constructor for key state, set up initial state of each action. + * Get the Control Key in charge of a key press + * @param key key pressed (String value of KeyCode) + * @return the Control Key behaviour of the key pressed. */ - private KeyFactory() { - this.keyState = new HashMap<>(); - keyState.put("Z", new ZoomInKey("Zoom In")); - keyState.put("X", new ZoomOutKey("Zoom Out")); - keyState.put("SPACE", new VMGKey("VMG")); - keyState.put("SHIFT", new SailsToggleKey("Toggle Sails")); - keyState.put("ENTER", new TackGybeKey("Tack/Gybe")); - keyState.put("UP", new UpWindKey("Upwind")); - keyState.put("DOWN", new DownWindKey("Downwind")); + public ControlKey getKey(String key){ + return keyState.get(key); + } + + public Map getKeyState() { + return keyState; + } + + public void setKeyState(Map keyState) { + this.keyState = keyState; } /** - * Get singleton instance of KeyFactory to interact with key state - * @return automatically constructed KeyFactory + * Update the key bound to a particular command in the keystate. + * @param newKey the new key value for the command + * @param command the command to be updated */ - public static KeyFactory getFactory() { - return theFactory; + public void updateKey(String newKey, String command){ + ControlKey controlKey = null; + String oldKey = null; + for (Map.Entry entry : keyState.entrySet()) { + // if this is the correct command + if (entry.getValue().toString()==command){ + controlKey = entry.getValue(); + oldKey = entry.getKey(); + } + } + keyState.remove(oldKey, controlKey); + keyState.put(newKey, controlKey); } /** - * Get the Control Key in charge of a key press - * @param key key pressed (String value of KeyCode) - * @return the Control Key behaviour of the key pressed. + * Persistently saves the keybindings the user has set. */ - public ControlKey getKey(String key){ - return keyState.get(key); + public void save(){ + try { + // open the filestream and write to it + FileOutputStream fos = new FileOutputStream( + System.getProperty("user.dir")+ + "/settings/keyBindings.xml"); + XMLEncoder xmlEncoder = new XMLEncoder(fos); + xmlEncoder.writeObject(this.keyState); + xmlEncoder.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * Loads the persistently saved keybindings the user has set. + */ + public void load(){ + try { + // access settings folder, create if it doesn't exist + File settingsFolder = new File( + System.getProperty("user.dir")+"/settings"); + if (!settingsFolder.exists()){ + settingsFolder.mkdir(); + } + + // access keybindings xml file, create if it doesn't exist + File savedFile = new File( + settingsFolder+"/keyBindings.xml"); + if (!savedFile.exists()){ + savedFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(savedFile); + XMLEncoder xmlEncoder = new XMLEncoder(fos); + xmlEncoder.writeObject(this.keyState); + xmlEncoder.close(); + } + + // load the saved settings into the game + InputStream is = new FileInputStream(savedFile); + XMLDecoder xmlDecoder = new XMLDecoder(is); + Map savedKeyState + = (Map)xmlDecoder.readObject(); + xmlDecoder.close(); + this.keyState = savedKeyState; + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } } } diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java index f1e4d65b..a04b7d77 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/SailsToggleKey.java @@ -10,11 +10,9 @@ public class SailsToggleKey extends ControlKey { /** * Constructor for Control - * @param name name of the key - * */ - public SailsToggleKey(String name) { - super(name, BoatActionEnum.NOT_A_STATUS); + public SailsToggleKey() { + super("Toggle Sails", BoatActionEnum.NOT_A_STATUS); } /** diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java index 80252e73..5c164443 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/TackGybeKey.java @@ -9,11 +9,9 @@ public class TackGybeKey extends ControlKey { /** * Constructor for Control - * @param name name of the key - * */ - public TackGybeKey(String name) { - super(name, BoatActionEnum.TACK_GYBE); + public TackGybeKey() { + super("Tack/Gybe", BoatActionEnum.TACK_GYBE); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java index 333e5f1f..b4bbf489 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/UpWindKey.java @@ -9,11 +9,9 @@ public class UpWindKey extends ControlKey { /** * Constructor for Control - * @param name name of the key - * */ - public UpWindKey(String name) { - super(name, BoatActionEnum.UPWIND); + public UpWindKey() { + super("Upwind", BoatActionEnum.UPWIND); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java index c01658bb..2c7237f0 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/VMGKey.java @@ -1,6 +1,5 @@ package visualiser.gameController.Keys; -import javafx.scene.input.KeyCode; import network.Messages.Enums.BoatActionEnum; /** @@ -10,11 +9,9 @@ public class VMGKey extends ControlKey{ /** * Constructor for Control - * - * @param name name of the key */ - public VMGKey(String name) { - super(name, BoatActionEnum.AUTO_PILOT); + public VMGKey() { + super("VMG", BoatActionEnum.AUTO_PILOT); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java index 51f98a58..e9a8ad7b 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomInKey.java @@ -5,8 +5,8 @@ package visualiser.gameController.Keys; */ public class ZoomInKey extends ControlKey { - public ZoomInKey(String name) { - super(name); + public ZoomInKey() { + super("Zoom In"); } @Override diff --git a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java index 6da2210c..cbba97fc 100644 --- a/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java +++ b/racevisionGame/src/main/java/visualiser/gameController/Keys/ZoomOutKey.java @@ -7,11 +7,9 @@ public class ZoomOutKey extends ControlKey{ /** * Constructor for Control - * @param name name of the key - * */ - public ZoomOutKey(String name) { - super(name); + public ZoomOutKey() { + super("Zoom Out"); } @Override diff --git a/racevisionGame/src/main/java/visualiser/layout/Subject3D.java b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java new file mode 100644 index 00000000..af76f4f4 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/Subject3D.java @@ -0,0 +1,65 @@ +package visualiser.layout; + +import javafx.scene.shape.Shape3D; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +/** + * Wrapper for controlling the position and heading of rendered 3D models. + */ +public class Subject3D { + /** + * Rendered mesh + */ + private Shape3D mesh; + + /** + * Position translation updated by state listeners + */ + private Translate position; + + /** + * Heading rotation updated by state listeners + */ + private Rotate heading; + + /** + * Constructor for view subject wrapper + * @param mesh to be rendered + */ + public Subject3D(Shape3D mesh) { + this.mesh = mesh; + this.position = new Translate(); + this.heading = new Rotate(0, Rotate.Y_AXIS); + + this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS)); + } + + public Shape3D getMesh() { + return mesh; + } + + public Translate getPosition() { + return this.position; + } + + public Rotate getHeading() { + return heading; + } + + public void setX(double x) { + position.setX(x); + } + + public void setY(double y) { + position.setY(y); + } + + public void setZ(double z) { + position.setZ(z); + } + + public void setHeading(double angle) { + heading.setAngle(angle); + } +} diff --git a/racevisionGame/src/main/java/visualiser/layout/View3D.java b/racevisionGame/src/main/java/visualiser/layout/View3D.java new file mode 100644 index 00000000..27fe6086 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/View3D.java @@ -0,0 +1,260 @@ +package visualiser.layout; + +import javafx.beans.value.ChangeListener; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.Group; +import javafx.scene.PerspectiveCamera; +import javafx.scene.SubScene; +import javafx.scene.input.PickResult; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Shape3D; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +import java.util.HashMap; +import java.util.Map; + +/** + * Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to + * interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and + * sky box, whose textures are set with special methods. + */ +public class View3D extends Pane { + /** + * Container for group and camera + */ + private SubScene scene; + /** + * Observable list of renderable items + */ + private ObservableList items; + /** + * Map for selecting Subject3D from Shape3D + */ + private Map selectionMap; + /** + * Subject tracked by camera + */ + private Subject3D target; + /** + * Rendering container for shapes + */ + private Group world; + /** + * Near limit of view frustum + */ + private double nearClip; + /** + * Far limit of view frustum + */ + private double farClip; + /** + * Camera origin + */ + private Translate pivot; + /** + * Distance of camera from pivot point + */ + private Translate distance; + /** + * Angle along ground between z-axis and camera + */ + private Rotate yaw; + /** + * Angle between ground plane and camera direction + */ + private Rotate pitch; + /** + * Single listener for subject heading changes + */ + private ChangeListener pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr); + /** + * Single listener for subject position (x) changes + */ + private ChangeListener pivotX = (o, prev, curr) -> pivot.setX((double)curr); + /** + * Single listener for subject position (y) changes + */ + private ChangeListener pivotY = (o, prev, curr) -> pivot.setY((double)curr); + /** + * Single listener for subject position (z) changes + */ + private ChangeListener pivotZ = (o, prev, curr) -> pivot.setZ((double)curr); + /** + * Distance to switch from third person to bird's eye + */ + private double THIRD_PERSON_LIMIT = 100; + + /** + * Default constructor for View3D. Sets up Scene and PerspectiveCamera. + */ + public View3D() { + this.world = new Group(); + this.selectionMap = new HashMap<>(); + this.target = null; + this.scene = new SubScene(world, 300, 300); + + scene.widthProperty().bind(this.widthProperty()); + scene.heightProperty().bind(this.heightProperty()); + scene.setFill(new Color(0.2, 0.6, 1, 1)); + + scene.setCamera(buildCamera()); + + this.getChildren().add(scene); + } + + /** + * Sets up camera view frustum and binds transformations + * @return perspective camera + */ + private PerspectiveCamera buildCamera() { + PerspectiveCamera camera = new PerspectiveCamera(true); + + // Set up view frustum + nearClip = 0.1; + farClip = 3000.0; + camera.setNearClip(nearClip); + camera.setFarClip(farClip); + + // Set up transformations + pivot = new Translate(); + distance = new Translate(); + yaw = new Rotate(0, Rotate.Y_AXIS); + pitch = new Rotate(0, Rotate.X_AXIS); + camera.getTransforms().addAll(pivot, yaw, pitch, distance); + + return camera; + } + + /** + * Provide the list of subjects to be automatically added or removed from the view as the list + * changes. + * @param items list managed by client + */ + public void setItems(ObservableList items) { + this.items = items; + this.items.addListener((ListChangeListener) c -> { + while(c.next()) { + if (c.wasRemoved() || c.wasAdded()) { + for (Subject3D shape : c.getRemoved()) { + world.getChildren().remove(shape.getMesh()); + selectionMap.remove(shape.getMesh()); + } + for (Subject3D shape : c.getAddedSubList()) { + world.getChildren().add(shape.getMesh()); + selectionMap.put(shape.getMesh(), shape); + } + } + } + }); + } + + /** + * Intercept mouse clicks on subjects in view. The applied listener cannot be removed. + */ + public void enableTracking() { + scene.setOnMousePressed(e -> { + PickResult result = e.getPickResult(); + if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) { + trackSubject(selectionMap.get(result.getIntersectedNode())); + } + }); + } + + /** + * Stop camera from following the last selected subject + */ + private void untrackSubject() { + if(target != null) { + target.getPosition().xProperty().removeListener(pivotX); + target.getPosition().yProperty().removeListener(pivotY); + target.getPosition().zProperty().removeListener(pivotZ); + target.getHeading().angleProperty().removeListener(pivotHeading); + } + } + + /** + * Set camera to follow the selected subject + * @param subject to track + */ + private void trackSubject(Subject3D subject) { + untrackSubject(); + target = subject; + + updatePivot(target.getPosition()); + setYaw(target.getHeading().getAngle()); + + target.getPosition().xProperty().addListener(pivotX); + target.getPosition().yProperty().addListener(pivotY); + target.getPosition().zProperty().addListener(pivotZ); + target.getHeading().angleProperty().addListener(pivotHeading); + + this.setDistance(THIRD_PERSON_LIMIT); + this.setPitch(20); + } + + public void setNearClip(double nearClip) { + this.nearClip = nearClip; + } + + public void setFarClip(double farClip) { + this.farClip = farClip; + } + + /** + * Sets the coordinates of the camera pivot once. + * @param pivot source of coordinates + */ + public void updatePivot(Translate pivot) { + this.pivot.setX(pivot.getX()); + this.pivot.setY(pivot.getY()); + this.pivot.setZ(pivot.getZ()); + } + + /** + * Set distance of camera from pivot + * @param distance in units + */ + public void setDistance(double distance) { + this.distance.setZ(-distance); + } + + /** + * Adds delta to current distance and changes camera mode if applicable. + * Third person limit specifies the distance at which a third person camera + * switches to bird's-eye, remaining focused on the same position. + * @param delta amount to change distance by + */ + public void updateDistance(double delta) { + double distance = -this.distance.getZ() + delta; + + if(distance <= 0) { + this.setDistance(0); + } else if(distance > THIRD_PERSON_LIMIT) { + untrackSubject(); + this.setYaw(0); + this.setPitch(60); + this.setDistance(distance); + } else { + this.setDistance(distance); + } + } + + /** + * Set angle of camera from z-axis along ground + * @param yaw in degrees + */ + public void setYaw(double yaw) { + this.yaw.setAngle(yaw); + } + + /** + * Set elevation of camera + * @param pitch in degrees + */ + public void setPitch(double pitch) { + this.pitch.setAngle(-pitch); + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index fc6a0858..03e86e27 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -39,9 +39,9 @@ public class ResizableRaceCanvas extends ResizableCanvas { private Image sailsLuff = new Image("/images/sailsLuff.gif", 25, 10, false, false); /** - * The race we read data from and draw. + * The race state we read data from and draw. */ - private VisualiserRaceEvent visualiserRace; + private VisualiserRaceState raceState; private boolean annoName = true; @@ -56,14 +56,14 @@ public class ResizableRaceCanvas extends ResizableCanvas { /** * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}. - * @param visualiserRace The race that data is read from in order to be drawn. + * @param raceState The race state to be drawn. */ - public ResizableRaceCanvas(VisualiserRaceEvent visualiserRace) { + public ResizableRaceCanvas(VisualiserRaceState raceState) { super(); - this.visualiserRace = visualiserRace; + this.raceState = raceState; - RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); + RaceDataSource raceData = raceState.getRaceDataSource(); double lat1 = raceData.getMapTopLeft().getLatitude(); double long1 = raceData.getMapTopLeft().getLongitude(); @@ -276,8 +276,8 @@ public class ResizableRaceCanvas extends ResizableCanvas { boat.getCountry(), boat.getCurrentSpeed(), this.map.convertGPS(boat.getPosition()), - boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()), - boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()), + boat.getTimeToNextMarkFormatted(raceState.getRaceClock().getCurrentTime()), + boat.getTimeSinceLastMarkFormatted(raceState.getRaceClock().getCurrentTime()), Color.BLACK, 20 ); @@ -291,7 +291,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ private void drawBoats() { - List boats = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoats()); + List boats = new ArrayList<>(raceState.getBoats()); //Sort to ensure we draw boats in consistent order. boats.sort(Comparator.comparingInt(Boat::getSourceID)); @@ -510,7 +510,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ private void drawMarks() { - for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) { + for (Mark mark : new ArrayList<>(raceState.getMarks())) { drawMark(mark); } } @@ -574,7 +574,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Calculate the screen coordinates of the boundary. - List boundary = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoundary()); + List boundary = new ArrayList<>(raceState.getBoundary()); double[] xpoints = new double[boundary.size()]; double[] ypoints = new double[boundary.size()]; @@ -601,8 +601,8 @@ public class ResizableRaceCanvas extends ResizableCanvas { public void drawRace() { //Update RaceMap with new GPS values of race. - this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft()); - this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight()); + this.map.setGPSTopLeft(raceState.getRaceDataSource().getMapTopLeft()); + this.map.setGPSBotRight(raceState.getRaceDataSource().getMapBottomRight()); clear(); @@ -627,7 +627,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { * draws a transparent line around the course that shows the paths boats must travel */ public void drawRaceLine(){ - List legs = this.visualiserRace.getVisualiserRaceState().getLegs(); + List legs = raceState.getLegs(); GPSCoordinate legStartPoint = legs.get(0).getStartCompoundMark().getAverageGPSCoordinate(); GPSCoordinate nextStartPoint; for (int i = 0; i < legs.size() -1; i++) { diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java index 6cbfdaa3..555def15 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java @@ -1,11 +1,10 @@ package visualiser.model; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; 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 shared.model.*; import java.time.Duration; import java.time.ZonedDateTime; @@ -61,7 +60,8 @@ public class VisualiserBoat extends Boat { private boolean isClientBoat = false; - + private ObjectProperty positionProperty; + private ObjectProperty bearingProperty; /** @@ -239,4 +239,38 @@ public class VisualiserBoat extends Boat { public void setClientBoat(boolean clientBoat) { isClientBoat = clientBoat; } + + @Override + public GPSCoordinate getPosition() { + return positionProperty.get(); + } + + @Override + public void setPosition(GPSCoordinate position) { + if(this.positionProperty == null) { + this.positionProperty = new SimpleObjectProperty<>(); + } + this.positionProperty.set(position); + } + + public ObjectProperty positionProperty() { + return positionProperty; + } + + @Override + public Bearing getBearing() { + return bearingProperty.get(); + } + + @Override + public void setBearing(Bearing bearing) { + if(this.bearingProperty == null) { + this.bearingProperty = new SimpleObjectProperty<>(); + } + this.bearingProperty.set(bearing); + } + + public ObjectProperty bearingProperty() { + return bearingProperty; + } } diff --git a/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java new file mode 100644 index 00000000..22dd937f --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/utils/GPSConverter.java @@ -0,0 +1,104 @@ +package visualiser.utils; + +import shared.dataInput.RaceDataSource; +import shared.model.GPSCoordinate; +import visualiser.model.GraphCoordinate; + +/** + * Converts GPS coordinates to view volume coordinates. Longitudes are equally spaced at all latitudes, + * which leads to inaccurate distance measurements close to the poles. This is acceptable as races are + * not likely to be set there. + */ +public class GPSConverter { + private double longRight; + private double longLeft; + private double latBottom; + private double latTop; + /** + * Conversion factor from longitude to view units + */ + private double longitudeFactor; + /** + * Conversion factor from latitude to view units + */ + private double latitudeFactor; + + /** + * Set up projection with default view boundaries from RaceDataSource + * @param source for view boundaries + * @param longitudeFactor separation of a degree of longitude in view units + * @param latitudeFactor separation of a degree of latitude in view units + */ + public GPSConverter(RaceDataSource source, double longitudeFactor, double latitudeFactor) { + this.latTop = source.getMapTopLeft().getLatitude(); + this.longLeft = source.getMapTopLeft().getLongitude(); + this.latBottom = source.getMapBottomRight().getLatitude(); + this.longRight = source.getMapBottomRight().getLongitude(); + this.longitudeFactor = longitudeFactor; + this.latitudeFactor = latitudeFactor; + } + + /** + * 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 metric 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. + double smallerDimension = Math.min(longitudeFactor, latitudeFactor); + + //Calculate the x and y pixel coordinates. + //We take the complement of latProportion to flip it. + int x = (int) (longProportion * smallerDimension); + int y = (int) (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). + double extraDistance = Math.abs(longitudeFactor - latitudeFactor); + //We therefore "center" the coordinates along this larger dimension, by adding half of the extra pixels. + if (longitudeFactor > latitudeFactor) { + x += extraDistance / 2; + } else { + y += extraDistance / 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()); + } + +} diff --git a/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl b/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl new file mode 100644 index 00000000..a952ca77 Binary files /dev/null and b/racevisionGame/src/main/resources/assets/V1.2 Complete Boat.stl differ diff --git a/racevisionGame/src/main/resources/css/dayMode.css b/racevisionGame/src/main/resources/css/dayMode.css index aa14c68b..b62f8391 100644 --- a/racevisionGame/src/main/resources/css/dayMode.css +++ b/racevisionGame/src/main/resources/css/dayMode.css @@ -53,5 +53,39 @@ } #arrowImage { - -fx-image: url("/visualiser/images/arrow.png"); + -fx-graphic: url("/visualiser/images/arrow.png"); +} + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; } diff --git a/racevisionGame/src/main/resources/css/keyBindings.css b/racevisionGame/src/main/resources/css/keyBindings.css new file mode 100644 index 00000000..a572116e --- /dev/null +++ b/racevisionGame/src/main/resources/css/keyBindings.css @@ -0,0 +1,45 @@ +.list-view .list-cell { + -fx-cell-size: 40; + -fx-font-family: "Tahoma"; + -fx-background-color: #fffff0; + -fx-alignment: center; +} + +.list-view .list-cell:even { + -fx-background-color: #ffffdc; +} + +.list-view .list-cell:selected { + -fx-background-color: #f9e5c3; + -fx-font-family: "Comic Sans MS"; + -fx-font-weight: bold; + -fx-font-size: 18; +} + +.button { + -fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%); + -fx-background-radius: 4px; + -fx-border-radius: 4px; + -fx-text-fill: #242d35; + -fx-font-size: 12px; + -fx-font-family: "Courier New"; + -fx-border-color: #a9c8ff; +} + +.button:focused { + -fx-background-color: white; + -fx-border-color: #0056bd; +} + +#anchor { + -fx-background-color: #fdfac3; +} +#menu{ + -fx-background-color: linear-gradient(#acdeff 50%, #a9c8ff 100%); + -fx-background-radius: 4px; + -fx-border-radius: 4px; + -fx-text-fill: #242d35; + -fx-font-size: 12px; + -fx-font-family: "Verdana"; + -fx-border-color: #a9c8ff; +} \ No newline at end of file diff --git a/racevisionGame/src/main/resources/css/nightMode.css b/racevisionGame/src/main/resources/css/nightMode.css index 7fe6a67b..deefa51a 100644 --- a/racevisionGame/src/main/resources/css/nightMode.css +++ b/racevisionGame/src/main/resources/css/nightMode.css @@ -57,3 +57,37 @@ #arrowImage { -fx-image: url("/visualiser/images/arrowLight.png"); } + +#nextButton { + -fx-background-image: url("/visualiser/images/ArrowRoundRight.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#nextButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundRightClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} + +#previousButton { + -fx-background-image: url("/visualiser/images/ArrowRoundLeft.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} +#previousButton:pressed { + -fx-background-image: url("/visualiser/images/ArrowRoundLeftClicked.png"); + -fx-background-size: 60px; + -fx-background-repeat: no-repeat; + -fx-background-position: center center; + -fx-focus-color: transparent; + -fx-background-color: transparent; +} diff --git a/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png new file mode 100644 index 00000000..1b5e39ca Binary files /dev/null and b/racevisionGame/src/main/resources/images/AC35_Racecourse_MAP.png differ diff --git a/racevisionGame/src/main/resources/images/iMapLayout.png b/racevisionGame/src/main/resources/images/iMapLayout.png new file mode 100644 index 00000000..5932a60d Binary files /dev/null and b/racevisionGame/src/main/resources/images/iMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/mMapLayout.png b/racevisionGame/src/main/resources/images/mMapLayout.png new file mode 100644 index 00000000..fa0af51f Binary files /dev/null and b/racevisionGame/src/main/resources/images/mMapLayout.png differ diff --git a/racevisionGame/src/main/resources/images/oMapLayout.png b/racevisionGame/src/main/resources/images/oMapLayout.png new file mode 100644 index 00000000..ca520959 Binary files /dev/null and b/racevisionGame/src/main/resources/images/oMapLayout.png differ diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml index 9295dc07..59e2f79d 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml @@ -32,7 +32,6 @@ - diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml new file mode 100644 index 00000000..023c5090 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/boatTutorial.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml new file mode 100644 index 00000000..ca914b12 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/iMapLayout.xml @@ -0,0 +1,53 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml new file mode 100644 index 00000000..ce715ba6 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/mMapLayout.xml @@ -0,0 +1,43 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml new file mode 100644 index 00000000..5021bbba --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/oMapLayout.xml @@ -0,0 +1,49 @@ + + + 5326 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml index e9e9378a..c4efcd17 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/raceSinglePlayer.xml @@ -5,7 +5,6 @@ RACE_CREATION_TIME - diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml similarity index 96% rename from racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml rename to racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml index e0b81837..6ae04ef8 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/raceThreePlayers.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/raceSixPlayers.xml @@ -5,9 +5,7 @@ RACE_CREATION_TIME - - - + diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml deleted file mode 100644 index ad14d931..00000000 --- a/racevisionGame/src/main/resources/mock/mockXML/raceTest.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - 5326 - FLEET - RACE_CREATION_TIME - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml new file mode 100644 index 00000000..47519d00 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/raceTutorial.xml @@ -0,0 +1,43 @@ + + + 9999 + FLEET + RACE_CREATION_TIME + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml b/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml new file mode 100644 index 00000000..d7f6b9e7 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/regattaTutorial.xml @@ -0,0 +1,10 @@ + + 0 + Race Tutorial + Tutorial + -36.82791529 + 174.81218919 + 0.00 + 12 + 14.1 + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd b/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd new file mode 100644 index 00000000..6d5af617 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/schema/boatsSchema.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd b/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd new file mode 100644 index 00000000..5c9fb774 --- /dev/null +++ b/racevisionGame/src/main/resources/mock/mockXML/schema/regattaSchema.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png new file mode 100644 index 00000000..ea4f4d64 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeft.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png new file mode 100644 index 00000000..794ba57c Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundLeftClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png new file mode 100644 index 00000000..751698cb Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRight.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png new file mode 100644 index 00000000..10c1b53b Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/ArrowRoundRightClicked.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/arrow.png b/racevisionGame/src/main/resources/visualiser/images/arrow.png index fab6e21d..cd7bab10 100644 Binary files a/racevisionGame/src/main/resources/visualiser/images/arrow.png and b/racevisionGame/src/main/resources/visualiser/images/arrow.png differ diff --git a/racevisionGame/src/main/resources/visualiser/images/lobby.gif b/racevisionGame/src/main/resources/visualiser/images/lobby.gif new file mode 100644 index 00000000..c70d8df6 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/lobby.gif differ diff --git a/racevisionGame/src/main/resources/visualiser/images/sunsleep.png b/racevisionGame/src/main/resources/visualiser/images/sunsleep.png new file mode 100644 index 00000000..e52b2674 Binary files /dev/null and b/racevisionGame/src/main/resources/visualiser/images/sunsleep.png differ diff --git a/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml b/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml new file mode 100644 index 00000000..db7a6978 --- /dev/null +++ b/racevisionGame/src/main/resources/visualiser/mock/mockXML/raceXML/raceTutorial.xml @@ -0,0 +1,91 @@ + + + + + 99999999 + + Match + + 2011-08-06T13:25:00-0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml b/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml index ced36627..0767dd44 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/hostgame.fxml @@ -1,39 +1,60 @@ + + + + + + + - + + + + + + - + - - + + - - - + + + - - - - - diff --git a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml b/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml index 007ef599..da0b495b 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/titleScreen.fxml @@ -1,10 +1,13 @@ + + + @@ -40,7 +43,7 @@ + diff --git a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java index 6f67dc30..f7c69acf 100644 --- a/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java +++ b/racevisionGame/src/test/java/mock/model/ConstantWindGeneratorTest.java @@ -1,5 +1,7 @@ package mock.model; +import mock.model.wind.ConstantWindGenerator; +import mock.model.wind.WindGenerator; import org.junit.Before; import org.junit.Test; import shared.model.Bearing; diff --git a/racevisionGame/src/test/java/mock/model/MockBoatTest.java b/racevisionGame/src/test/java/mock/model/MockBoatTest.java index 8d1f45ee..98b4ca27 100644 --- a/racevisionGame/src/test/java/mock/model/MockBoatTest.java +++ b/racevisionGame/src/test/java/mock/model/MockBoatTest.java @@ -3,6 +3,7 @@ package mock.model; import org.junit.Before; import org.junit.Test; import shared.model.Bearing; +import shared.model.Boat; import shared.model.GPSCoordinate; import shared.model.Mark; @@ -13,6 +14,13 @@ public class MockBoatTest { private Mark near; private Mark far; + + public static MockBoat createMockBoat() { + Boat boat = new Boat(121, "Test boat", "TS"); + MockBoat mockBoat = new MockBoat(boat, null); + return mockBoat; + } + @Before public void setUp() { boat = new MockBoat(0, "Bob", "NZ", null); diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java index 4f3f7705..f53b2970 100644 --- a/racevisionGame/src/test/java/mock/model/MockRaceTest.java +++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java @@ -1,7 +1,8 @@ package mock.model; import mock.dataInput.PolarParserTest; -import network.Messages.LatestMessages; +import mock.model.wind.ConstantWindGenerator; +import mock.model.wind.WindGenerator; import shared.dataInput.*; import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidRaceDataException; @@ -9,8 +10,6 @@ import shared.exceptions.InvalidRegattaDataException; import shared.model.Bearing; import shared.model.Constants; -import static org.junit.Assert.*; - public class MockRaceTest { //TODO diff --git a/racevisionGame/src/test/java/mock/model/NewPolarsTest.java b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java new file mode 100644 index 00000000..dcbcc6cc --- /dev/null +++ b/racevisionGame/src/test/java/mock/model/NewPolarsTest.java @@ -0,0 +1,141 @@ +package mock.model; + +import mock.dataInput.PolarParser; +import org.junit.Before; +import org.junit.Test; +import shared.model.Bearing; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +/** + * Created by fwy13 on 5/09/17. + */ +public class NewPolarsTest { + + @Before + public void setUp(){ + PolarParser.parseNewPolars("mock/polars/acc_polars.csv"); + NewPolars.linearInterpolatePolars(); + + +// Uncomment if you want to read the linear interpolation in text +// Method getPolars = null; +// try { +// getPolars = NewPolars.class.getDeclaredMethod("printOutLinearInterpolated"); +// } catch (NoSuchMethodException e) { +// e.printStackTrace(); +// } +// getPolars.setAccessible(true); +// try { +// getPolars.invoke(NewPolars.newPolars); +// } catch (IllegalAccessException e) { +// e.printStackTrace(); +// } catch (InvocationTargetException e) { +// e.printStackTrace(); +// } + } + + @Test + public void testQuads() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + //reflection for private class + Class[] parameterTypes = new Class[1]; + parameterTypes[0] = Double.TYPE; + Method getQuads = NewPolars.class.getDeclaredMethod("getQuadrant", parameterTypes); + getQuads.setAccessible(true); + + //start invoking + Object[] paras1 = new Object[1]; + paras1[0] = (new Double(0)).doubleValue(); + int q1 = (int) getQuads.invoke(NewPolars.newPolars, paras1); + assertEquals(q1, 1); + + //start invoking + Object[] paras2 = new Object[1]; + paras2[0] = (new Double(90)).doubleValue(); + int q2 = (int) getQuads.invoke(NewPolars.newPolars, paras2); + assertEquals(q2, 2); + + //start invoking + Object[] paras3 = new Object[1]; + paras3[0] = (new Double(180)).doubleValue(); + int q3 = (int) getQuads.invoke(NewPolars.newPolars, paras3); + assertEquals(q3, 3); + + //start invoking + Object[] paras4 = new Object[1]; + paras4[0] = (new Double(270)).doubleValue(); + int q4 = (int) getQuads.invoke(NewPolars.newPolars, paras4); + assertEquals(q4, 4); + + //start invoking + Object[] paras5 = new Object[1]; + paras5[0] = (new Double(360)).doubleValue(); + int q5 = (int) getQuads.invoke(NewPolars.newPolars, paras5); + assertEquals(q5, 1); + + } + + @Test + public void testEdgeSpeeds(){ + //just make sure that speeds at certain angles do not throw a null exception and are not negative + double maxTWS = 30; + + for (double tws = 0; tws < maxTWS; tws += 1){ + for (double j = 0; j < 360; j++){ + Bearing twa = Bearing.fromDegrees(j); + for (double i = 0; i < 360; i++){ + Bearing boatBearing = Bearing.fromDegrees(i); + double speed = NewPolars.calculateSpeed(twa, tws, boatBearing); + assertTrue(speed >= 0); + } + } + } + + } + + @Test + public void testClosestSpeeds() throws NoSuchMethodException, NoSuchFieldException, InvocationTargetException, IllegalAccessException { + //reflection for private class + Method getClosest = NewPolars.class.getDeclaredMethod("getClosest", double.class, Set.class); + getClosest.setAccessible(true); + + Method getPolars = NewPolars.class.getDeclaredMethod("getPolars"); + getPolars.setAccessible(true); + + double maxTWS = 30; + + //only catches for nulls + for (double tws = 0; tws < maxTWS; tws += 1){ + Map> polars = (Map>) getPolars.invoke(NewPolars.newPolars); + double speed = (double) getClosest.invoke(NewPolars.newPolars, tws, polars.keySet()); + assertTrue(speed >= 0); + } + } + + @Test + public void testAutoVSCalculated(){ + //test that the auto chosen speed is the same speed that is calculated + double maxTWS = 30; + for (double tws = 0; tws < maxTWS; tws ++){ + for (double twa = 0; twa < 360; twa ++){ + Bearing TW = Bearing.fromDegrees(twa); + for (double ba = 0; ba < 360; ba ++){ + Bearing boatBearing = Bearing.fromDegrees(ba); + VMG autoVMG = NewPolars.setBestVMG(TW, tws, boatBearing); + double speed = NewPolars.calculateSpeed(TW, tws, autoVMG.getBearing()); + assertTrue(autoVMG.getSpeed() == speed); + } + } + } + } + +} diff --git a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java index 76eed977..0f60bcea 100644 --- a/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java +++ b/racevisionGame/src/test/java/mock/model/RandomWindGeneratorTest.java @@ -1,5 +1,6 @@ package mock.model; +import mock.model.wind.RandomWindGenerator; import org.junit.Before; import org.junit.Test; import shared.model.Bearing; diff --git a/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java index 7240e01b..ad310540 100644 --- a/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java +++ b/racevisionGame/src/test/java/mock/model/SourceIdAllocatorTest.java @@ -1,6 +1,7 @@ package mock.model; import mock.exceptions.SourceIDAllocationException; +import network.Messages.Enums.RaceStatusEnum; import org.junit.Before; import org.junit.Test; @@ -15,30 +16,16 @@ import static org.junit.Assert.*; */ public class SourceIdAllocatorTest { - /** - * This is the list of source IDs that we start with. - */ - private List originalSourceIDs; - - /** - * Used to allocate source IDs. - */ + private MockRace mockRace; private SourceIdAllocator sourceIdAllocator; @Before public void setUp() throws Exception { - originalSourceIDs = new ArrayList<>(); - originalSourceIDs.add(120); - originalSourceIDs.add(121); - originalSourceIDs.add(122); - originalSourceIDs.add(123); - originalSourceIDs.add(124); - originalSourceIDs.add(125); - + mockRace = MockRaceTest.createMockRace(); - sourceIdAllocator = new SourceIdAllocator(originalSourceIDs); + sourceIdAllocator = new SourceIdAllocator(mockRace); } @@ -49,11 +36,12 @@ public class SourceIdAllocatorTest { @Test public void emptyAllocationTest() { - SourceIdAllocator allocator = new SourceIdAllocator(new ArrayList<>()); + mockRace.getRaceDataSource().getParticipants().removeAll(mockRace.getBoatDataSource().getBoats().keySet()); + mockRace.getRaceDataSource().getParticipants().addAll(mockRace.getBoatDataSource().getBoats().keySet()); try { - int sourceID = allocator.allocateSourceID(); + int sourceID = sourceIdAllocator.allocateSourceID(); fail("Exception should have been thrown, but wasn't."); @@ -73,6 +61,7 @@ public class SourceIdAllocatorTest { @Test public void allocationTest() throws Exception { + mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART); int sourceID = sourceIdAllocator.allocateSourceID(); @@ -108,10 +97,7 @@ public class SourceIdAllocatorTest { @Test public void reallocationTest() throws Exception { - List sourceIDList = new ArrayList<>(); - sourceIDList.add(123); - - SourceIdAllocator sourceIdAllocator = new SourceIdAllocator(sourceIDList); + mockRace.setRaceStatusEnum(RaceStatusEnum.PRESTART); //Allocate. int sourceID = sourceIdAllocator.allocateSourceID(); diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index 2193eb7a..158c43d6 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -1,11 +1,11 @@ package mock.model.commandFactory; import mock.exceptions.CommandConstructionException; -import mock.model.MockBoat; -import mock.model.MockRace; -import mock.model.MockRaceTest; +import mock.exceptions.SourceIDAllocationException; +import mock.model.*; import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; +import network.Messages.Enums.RaceStatusEnum; import org.junit.Before; import org.junit.Test; import shared.exceptions.InvalidBoatDataException; @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; */ public class WindCommandTest { private MockRace race; + private SourceIdAllocator allocator; private MockBoat boat; private Command upwind; private Command downwind; @@ -29,12 +30,15 @@ public class WindCommandTest { private double offset = 3.0; @Before - public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException { + public void setUp() throws CommandConstructionException, InvalidBoatDataException, InvalidRegattaDataException, InvalidRaceDataException, SourceIDAllocationException { race = MockRaceTest.createMockRace(); - + allocator = new SourceIdAllocator(race); + race.setRaceStatusEnum(RaceStatusEnum.PRESTART); + allocator.allocateSourceID(); boat = race.getBoats().get(0); + //when(race.getWindDirection()).thenReturn(Bearing.fromDegrees(0.0)); boat.setBearing(Bearing.fromDegrees(45.0)); diff --git a/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java deleted file mode 100644 index 35e21a0e..00000000 --- a/racevisionGame/src/test/java/shared/dataInput/BoatsXMLTest.java +++ /dev/null @@ -1,47 +0,0 @@ -//package seng302.Mock; -// -//import org.junit.Before; -//import org.junit.Test; -// -//import java.util.HashMap; -//import java.util.Map; -// -//import static org.junit.Assert.assertEquals; -// -///** -// * Created by jjg64 on 21/04/17. -// */ -//public class BoatsXMLTest { -// private BoatXMLReader boatXMLReader; -// -// @Before -// public void setup() { -// try { -// boatXMLReader = new BoatXMLReader("mockXML/boatXML/boatTest.xml", false); -// } catch (Exception e) { -// e.printStackTrace(); -// //fail("Cannot find mockXML/raceXML/raceTest.xml in the resources folder"); -// } -// } -// -// @Test -// public void testInvalidParticipant() { -// Map inputParticipants = new HashMap<>(); -// inputParticipants.put(420, new StreamedBoat(420)); -// boatXMLReader.setParticipants(inputParticipants); -// boatXMLReader.read(); -// assertEquals(boatXMLReader.getBoats().size(), 0); -// } -// -// @Test -// public void testValidParticipant() { -// Map inputParticipants = new HashMap<>(); -// inputParticipants.put(101, new StreamedBoat(101)); -// boatXMLReader.setParticipants(inputParticipants); -// boatXMLReader.read(); -// assertEquals(boatXMLReader.getBoats().size(), 1); -// StreamedBoat boat = (StreamedBoat) boatXMLReader.getBoats().get(0); -// assertEquals(boat.getSourceID(), 101); -// } -// -//} diff --git a/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java b/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java deleted file mode 100644 index a299c2bf..00000000 --- a/racevisionGame/src/test/java/shared/dataInput/FailBoatXMLTest.java +++ /dev/null @@ -1,26 +0,0 @@ -//package seng302.Mock; -// -//import org.junit.Test; -//import org.xml.sax.SAXException; -// -//import javax.xml.parsers.ParserConfigurationException; -//import java.io.IOException; -//import java.text.ParseException; -// -///** -// * Created by jjg64 on 1/05/17. -// */ -//public class FailBoatXMLTest { -// private final String path = "mockXML/boatXML/"; -// -// @Test(expected = NumberFormatException.class) -// public void invalidSourceID() throws SAXException, ParserConfigurationException, ParseException, IOException { -// new BoatXMLReader(path + "invalidSourceID.xml"); -// } -// -// @Test(expected = NullPointerException.class) -// public void insufficientInformation() throws SAXException, ParserConfigurationException, ParseException, IOException { -// new BoatXMLReader(path + "insufficientInformation.xml"); -// } -// -//} diff --git a/racevisionGame/src/test/java/shared/model/RaceTest.java b/racevisionGame/src/test/java/shared/model/RaceTest.java deleted file mode 100644 index 3334aefd..00000000 --- a/racevisionGame/src/test/java/shared/model/RaceTest.java +++ /dev/null @@ -1,345 +0,0 @@ -//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/VisualiserRaceTest.java b/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java deleted file mode 100644 index ef5e691a..00000000 --- a/racevisionGame/src/test/java/visualiser/model/VisualiserRaceTest.java +++ /dev/null @@ -1,100 +0,0 @@ -//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/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java index 64fdfcb5..4b6ed5e3 100644 --- a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java +++ b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java @@ -94,7 +94,7 @@ public class ConnectionToServerParticipantTest { incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(250); + Thread.sleep(500); assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null); diff --git a/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml b/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml index 4ad5f88f..b10a0158 100644 --- a/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml +++ b/racevisionGame/src/test/resources/mock/mockXML/raceTest.xml @@ -5,12 +5,6 @@ 2017-04-19T15:30:00+1200 - - - - - - @@ -54,4 +48,4 @@ - \ No newline at end of file + diff --git a/settings/keyBindings.xml b/settings/keyBindings.xml new file mode 100644 index 00000000..8aa46d6c --- /dev/null +++ b/settings/keyBindings.xml @@ -0,0 +1,33 @@ + + + + + SPACE + + + + SHIFT + + + + LEFT + + + + X + + + + ENTER + + + + RIGHT + + + + Z + + + +