Merge remote-tracking branch 'remotes/origin/master' into storyB-)
This commit is contained in:
+22
-2
@@ -25,10 +25,30 @@
|
|||||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
|
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-all</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>1.9.5</version>
|
<version>2.9.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Maven Dependencies block START-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.bytebuddy</groupId>
|
||||||
|
<artifactId>byte-buddy</artifactId>
|
||||||
|
<version>1.7.0</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.bytebuddy</groupId>
|
||||||
|
<artifactId>byte-buddy-agent</artifactId>
|
||||||
|
<version>1.7.0</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.objenesis</groupId>
|
||||||
|
<artifactId>objenesis</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Maven Dependencies block END-->
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import mock.dataInput.PolarParser;
|
|||||||
import mock.exceptions.EventConstructionException;
|
import mock.exceptions.EventConstructionException;
|
||||||
import mock.model.*;
|
import mock.model.*;
|
||||||
import mock.model.commandFactory.CompositeCommand;
|
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 mock.xml.RaceXMLCreator;
|
||||||
import network.Messages.LatestMessages;
|
import network.Messages.LatestMessages;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
@@ -18,11 +21,8 @@ import shared.model.Constants;
|
|||||||
|
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.transform.TransformerException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -106,6 +106,7 @@ public class Event {
|
|||||||
this.xmlFileType = XMLFileType.Contents;
|
this.xmlFileType = XMLFileType.Contents;
|
||||||
|
|
||||||
this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
|
this.boatPolars = PolarParser.parse("mock/polars/acc_polars.csv");
|
||||||
|
PolarParser.parseNewPolars("mock/polars/acc_polars.csv");
|
||||||
|
|
||||||
|
|
||||||
//Parse the XML files into data sources.
|
//Parse the XML files into data sources.
|
||||||
@@ -124,14 +125,10 @@ public class Event {
|
|||||||
this.compositeCommand = new CompositeCommand();
|
this.compositeCommand = new CompositeCommand();
|
||||||
this.latestMessages = new LatestMessages();
|
this.latestMessages = new LatestMessages();
|
||||||
|
|
||||||
//Create and start race.
|
WindGenerator windGenerator = new ShiftingWindGenerator(
|
||||||
WindGenerator windGenerator = new RandomWindGenerator(
|
|
||||||
Bearing.fromDegrees(225),
|
Bearing.fromDegrees(225),
|
||||||
Bearing.fromDegrees(215),
|
12
|
||||||
Bearing.fromDegrees(235),
|
);
|
||||||
12d,
|
|
||||||
8d,
|
|
||||||
16d );
|
|
||||||
RaceLogic newRace = new RaceLogic(
|
RaceLogic newRace = new RaceLogic(
|
||||||
new MockRace(
|
new MockRace(
|
||||||
boatDataSource,
|
boatDataSource,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package mock.dataInput;
|
|||||||
|
|
||||||
|
|
||||||
import mock.exceptions.InvalidPolarFileException;
|
import mock.exceptions.InvalidPolarFileException;
|
||||||
|
import mock.model.NewPolars;
|
||||||
import mock.model.Polars;
|
import mock.model.Polars;
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
|
|
||||||
@@ -104,4 +105,90 @@ public class PolarParser {
|
|||||||
return polarTable;
|
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<String[]> 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package mock.model;
|
package mock.model;
|
||||||
|
|
||||||
|
import mock.model.wind.WindGenerator;
|
||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import mock.model.collider.ColliderRegistry;
|
import mock.model.collider.ColliderRegistry;
|
||||||
import mock.xml.*;
|
import mock.xml.*;
|
||||||
@@ -244,7 +245,9 @@ public class MockRace extends RaceState {
|
|||||||
boat.setStatus(BoatStatusEnum.PRESTART);
|
boat.setStatus(BoatStatusEnum.PRESTART);
|
||||||
|
|
||||||
//We set a large time since tack change so that it calculates a new VMG when the simulation starts.
|
//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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -371,6 +374,7 @@ public class MockRace extends RaceState {
|
|||||||
|
|
||||||
if (boat.getAutoVMG()) {
|
if (boat.getAutoVMG()) {
|
||||||
newOptimalVMG(boat);
|
newOptimalVMG(boat);
|
||||||
|
boat.setAutoVMG(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -383,31 +387,38 @@ public class MockRace extends RaceState {
|
|||||||
|
|
||||||
private void newOptimalVMG(MockBoat boat) {
|
private void newOptimalVMG(MockBoat boat) {
|
||||||
long tackPeriod = 1000;
|
long tackPeriod = 1000;
|
||||||
|
|
||||||
if (boat.getTimeSinceTackChange() > tackPeriod) {
|
if (boat.getTimeSinceTackChange() > tackPeriod) {
|
||||||
|
//System.out.println("optim called");
|
||||||
//Calculate the new VMG.
|
//Calculate the new VMG.
|
||||||
VMG newVMG = boat.getPolars().calculateVMG(
|
// VMG newVMG = boat.getPolars().calculateVMG(
|
||||||
this.getWindDirection(),
|
// this.getWindDirection(),
|
||||||
this.getWindSpeed(),
|
// this.getWindSpeed(),
|
||||||
boat.calculateBearingToNextMarker(),
|
// boat.calculateBearingToNextMarker(),
|
||||||
Bearing.fromDegrees(0d),
|
// Bearing.fromDegrees(0d),
|
||||||
Bearing.fromDegrees(359.99999d));
|
// 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 the new vmg improves velocity, use it.
|
||||||
if (improvesVelocity(boat, newVMG)) {
|
/*if (improvesVelocity(boat, newVMG)) {
|
||||||
boat.setVMG(newVMG);
|
}*/
|
||||||
}
|
boat.setVMG(newVMG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBoatSpeed(MockBoat boat) {
|
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.getWindDirection(),
|
||||||
this.getWindSpeed(),
|
this.getWindSpeed(),
|
||||||
boat.getBearing(),
|
boat.getBearing()
|
||||||
Bearing.fromDegrees(boat.getBearing().degrees() - 1),
|
), boat.getBearing()) ;
|
||||||
Bearing.fromDegrees(boat.getBearing().degrees() + 1));
|
|
||||||
if (vmg.getSpeed() > 0) {
|
if (vmg.getSpeed() > 0) {
|
||||||
boat.setCurrentSpeed(vmg.getSpeed());
|
boat.setCurrentSpeed(vmg.getSpeed());
|
||||||
}
|
}
|
||||||
@@ -557,7 +568,7 @@ public class MockRace extends RaceState {
|
|||||||
if (boat.isStarboardSide(roundingMark) &&
|
if (boat.isStarboardSide(roundingMark) &&
|
||||||
GPSCoordinate.passesLine(roundingMark.getPosition(),
|
GPSCoordinate.passesLine(roundingMark.getPosition(),
|
||||||
roundingChecks.get(0), boat.getPosition(), legBearing) &&
|
roundingChecks.get(0), boat.getPosition(), legBearing) &&
|
||||||
gateCheck &&
|
gateCheck &&
|
||||||
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
|
boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
|
||||||
boat.increaseRoundingStatus();
|
boat.increaseRoundingStatus();
|
||||||
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
|
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
|
||||||
|
|||||||
@@ -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, <true wind angle, best boat angle>
|
||||||
|
private static Map<Double, TreeMap<Double, Double>> 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<Double, Double> prevTWS = null;
|
||||||
|
TreeMap<Double, TreeMap<Double, Double>> 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<Double, Double> tws = iterablePolars.get(windSpeed);
|
||||||
|
|
||||||
|
if (prevTWS == null){
|
||||||
|
prevTWS = tws;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double previousTWA = -1;
|
||||||
|
TreeMap<Double, Double> 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<Double> 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<Double, Double> 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<Double, TreeMap<Double, Double>> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,10 +3,7 @@ package mock.model;
|
|||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates an entire polar table. Has a function to calculate VMG.
|
* 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.
|
* 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.
|
||||||
* <br>
|
* <br>
|
||||||
|
|||||||
@@ -45,4 +45,8 @@ public class VMG {
|
|||||||
return bearing;
|
return bearing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString(){
|
||||||
|
return String.format("VMG Object: Speed %f, Bearing %f.", speed, bearing.degrees());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -1,11 +1,9 @@
|
|||||||
package mock.model;
|
package mock.model.wind;
|
||||||
|
|
||||||
|
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
import shared.model.Wind;
|
import shared.model.Wind;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class generates Wind objects for use in a MockRace.
|
* This class generates Wind objects for use in a MockRace.
|
||||||
* Initialised with a baseline wind speed and direction, and keeps it constant.
|
* Initialised with a baseline wind speed and direction, and keeps it constant.
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package mock.model;
|
package mock.model.wind;
|
||||||
|
|
||||||
|
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package mock.model;
|
package mock.model.wind;
|
||||||
|
|
||||||
import shared.model.Wind;
|
import shared.model.Wind;
|
||||||
|
|
||||||
@@ -48,4 +48,11 @@ public class Wind {
|
|||||||
return windSpeed;
|
return windSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setWindSpeed(double windSpeed) {
|
||||||
|
this.windSpeed = windSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindDirection(Bearing windDirection) {
|
||||||
|
this.windDirection = windDirection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package visualiser.Controllers;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import visualiser.app.App;
|
|
||||||
import visualiser.gameController.ControllerClient;
|
import visualiser.gameController.ControllerClient;
|
||||||
import visualiser.model.VisualiserBoat;
|
import visualiser.model.VisualiserBoat;
|
||||||
import visualiser.model.VisualiserRaceEvent;
|
import visualiser.model.VisualiserRaceEvent;
|
||||||
@@ -40,6 +39,7 @@ public class MainController extends Controller {
|
|||||||
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
|
* Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race).
|
||||||
* @param visualiserRace The object modelling the race.
|
* @param visualiserRace The object modelling the race.
|
||||||
* @param controllerClient Socket Client that manipulates the controller.
|
* @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) {
|
public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient, Boolean isHost) {
|
||||||
raceController.startRace(visualiserRace, controllerClient, isHost);
|
raceController.startRace(visualiserRace, controllerClient, isHost);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -1,5 +1,7 @@
|
|||||||
package mock.model;
|
package mock.model;
|
||||||
|
|
||||||
|
import mock.model.wind.ConstantWindGenerator;
|
||||||
|
import mock.model.wind.WindGenerator;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package mock.model;
|
package mock.model;
|
||||||
|
|
||||||
import mock.dataInput.PolarParserTest;
|
import mock.dataInput.PolarParserTest;
|
||||||
import network.Messages.LatestMessages;
|
import mock.model.wind.ConstantWindGenerator;
|
||||||
|
import mock.model.wind.WindGenerator;
|
||||||
import shared.dataInput.*;
|
import shared.dataInput.*;
|
||||||
import shared.exceptions.InvalidBoatDataException;
|
import shared.exceptions.InvalidBoatDataException;
|
||||||
import shared.exceptions.InvalidRaceDataException;
|
import shared.exceptions.InvalidRaceDataException;
|
||||||
@@ -9,8 +10,6 @@ import shared.exceptions.InvalidRegattaDataException;
|
|||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
import shared.model.Constants;
|
import shared.model.Constants;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class MockRaceTest {
|
public class MockRaceTest {
|
||||||
//TODO
|
//TODO
|
||||||
|
|
||||||
|
|||||||
@@ -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<Double, TreeMap<Double, Double>> polars = (Map<Double, TreeMap<Double, Double>>) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package mock.model;
|
package mock.model;
|
||||||
|
|
||||||
|
import mock.model.wind.RandomWindGenerator;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import shared.model.Bearing;
|
import shared.model.Bearing;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<java version="1.8.0_111" class="java.beans.XMLDecoder">
|
||||||
|
<object class="java.util.HashMap">
|
||||||
|
<void method="put">
|
||||||
|
<string>SPACE</string>
|
||||||
|
<object class="visualiser.gameController.Keys.VMGKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>SHIFT</string>
|
||||||
|
<object class="visualiser.gameController.Keys.SailsToggleKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>DOWN</string>
|
||||||
|
<object class="visualiser.gameController.Keys.DownWindKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>X</string>
|
||||||
|
<object class="visualiser.gameController.Keys.ZoomOutKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>ENTER</string>
|
||||||
|
<object class="visualiser.gameController.Keys.TackGybeKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>Z</string>
|
||||||
|
<object class="visualiser.gameController.Keys.ZoomInKey"/>
|
||||||
|
</void>
|
||||||
|
<void method="put">
|
||||||
|
<string>UP</string>
|
||||||
|
<object class="visualiser.gameController.Keys.UpWindKey"/>
|
||||||
|
</void>
|
||||||
|
</object>
|
||||||
|
</java>
|
||||||
Reference in New Issue
Block a user