Merge branch 'develop' into Story64_SailsAnimations

# Conflicts:
#	src/main/java/seng302/model/Yacht.java
This commit is contained in:
Kusal Ekanayake
2017-08-13 15:27:25 +12:00
9 changed files with 224 additions and 61 deletions
+1 -1
View File
@@ -23,5 +23,5 @@ Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz> Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz> Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com> Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com> Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz> Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
@@ -8,10 +8,10 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import seng302.model.mark.CompoundMark;
import seng302.gameServer.server.simulator.Corner; import seng302.gameServer.server.simulator.Corner;
import seng302.model.mark.Mark;
import seng302.gameServer.server.simulator.RoundingType; import seng302.gameServer.server.simulator.RoundingType;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
/** /**
* Parses the race xml file to get course details * Parses the race xml file to get course details
@@ -84,17 +84,17 @@ public class CourseParser extends FileParser {
NodeList marks = e.getElementsByTagName("Mark"); NodeList marks = e.getElementsByTagName("Mark");
List<Mark> subMarks = new ArrayList<>(); List<Mark> subMarks = new ArrayList<>();
for (int i = 0; i < marks.getLength(); i++) { for (int i = 0; i < marks.getLength(); i++) {
Mark mark = getMark(marks.item(i)); Mark mark = getMark(marks.item(i));
if (mark != null) { if (mark != null) {
subMarks.add(mark); subMarks.add(mark);
} }
} }
return new CompoundMark(markID, name, subMarks); return new CompoundMark(markID, name, subMarks);
} }
System.out.println("Failed to create compound mark."); System.out.println("Failed to create compound mark.");
return null; return null;
} }
private Mark getMark(Node node) { private Mark getMark(Node node) {
+100 -26
View File
@@ -59,6 +59,8 @@ public class Yacht {
private GeoPoint location; private GeoPoint location;
private Integer boatStatus; private Integer boatStatus;
private Double velocity; private Double velocity;
private Boolean isAuto;
private Double autoHeading;
//MARK ROUNDING INFO //MARK ROUNDING INFO
private GeoPoint lastLocation; //For purposes of mark rounding calculations private GeoPoint lastLocation; //For purposes of mark rounding calculations
@@ -85,6 +87,8 @@ public class Yacht {
this.shortName = shortName; this.shortName = shortName;
this.boatName = boatName; this.boatName = boatName;
this.country = country; this.country = country;
this.sailIn = false;
this.isAuto = false;
this.location = new GeoPoint(57.670341, 11.826856); this.location = new GeoPoint(57.670341, 11.826856);
this.lastLocation = location; this.lastLocation = location;
this.heading = 120.0; //In degrees this.heading = 120.0; //In degrees
@@ -129,6 +133,8 @@ public class Yacht {
} }
} }
runAutoPilot();
//UPDATE BOAT LOCATION //UPDATE BOAT LOCATION
lastLocation = location; lastLocation = location;
location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed);
@@ -301,15 +307,60 @@ public class Yacht {
} }
/**
* Adjusts the heading of the boat by a given amount, while recording the boats
* last heading.
*
* @param amount the amount by which to adjust the boat heading.
*/
public void adjustHeading(Double amount) { public void adjustHeading(Double amount) {
Double newVal = heading + amount; Double newVal = heading + amount;
lastHeading = heading; lastHeading = heading;
heading = (double) Math.floorMod(newVal.longValue(), 360L); heading = (double) Math.floorMod(newVal.longValue(), 360L);
} }
/**
* Swaps the boats direction from one side of the wind to the other.
*/
public void tackGybe(Double windDirection) { public void tackGybe(Double windDirection) {
Double normalizedHeading = normalizeHeading(); if (isAuto) {
adjustHeading(-2 * normalizedHeading); disableAutoPilot();
} else {
Double normalizedHeading = normalizeHeading();
Double newVal = (-2 * normalizedHeading) + heading;
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
setAutoPilot(newHeading);
}
}
/**
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
* @param thisHeading The heading to move the boat towards.
*/
private void setAutoPilot(Double thisHeading) {
isAuto = true;
autoHeading = thisHeading;
}
/**
* Disables the auto pilot function.
*/
public void disableAutoPilot() {
isAuto = false;
}
/**
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
* in the event that the boat is within the range of 1 turn step of its goal.
*/
public void runAutoPilot() {
if (isAuto) {
turnTowardsHeading(autoHeading);
if (Math.abs(heading - autoHeading)
<= TURN_STEP) { //Cancel when within 1 turn step of target.
isAuto = false;
}
}
} }
public void toggleSailIn() { public void toggleSailIn() {
@@ -317,6 +368,7 @@ public class Yacht {
} }
public void turnUpwind() { public void turnUpwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading(); Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) { if (normalizedHeading == 0) {
if (lastHeading < 180) { if (lastHeading < 180) {
@@ -338,6 +390,7 @@ public class Yacht {
} }
public void turnDownwind() { public void turnDownwind() {
disableAutoPilot();
Double normalizedHeading = normalizeHeading(); Double normalizedHeading = normalizeHeading();
if (normalizedHeading == 0) { if (normalizedHeading == 0) {
if (lastHeading < 180) { if (lastHeading < 180) {
@@ -358,38 +411,59 @@ public class Yacht {
} }
} }
/**
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
* and uses this to calculate a heading to move the yacht towards.
*/
public void turnToVMG() { public void turnToVMG() {
Double normalizedHeading = normalizeHeading(); if (isAuto) {
Double optimalHeading; disableAutoPilot();
HashMap<Double, Double> optimalPolarMap;
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
optimalHeading = optimalPolarMap.keySet().iterator().next();
} else { } else {
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots()); Double normalizedHeading = normalizeHeading();
optimalHeading = optimalPolarMap.keySet().iterator().next(); Double optimalHeading;
} HashMap<Double, Double> optimalPolarMap;
// Take optimal heading and turn into correct
optimalHeading =
optimalHeading + (double) Math.floorMod(GameState.getWindDirection().longValue(), 360L);
turnTowardsHeading(optimalHeading); if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
}
private void turnTowardsHeading(Double newHeading) {
if (heading < 90 && newHeading > 270) {
adjustHeading(-TURN_STEP);
} else {
if (heading < newHeading) {
adjustHeading(TURN_STEP);
} else { } else {
adjustHeading(-TURN_STEP); optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
} }
optimalHeading = optimalPolarMap.keySet().iterator().next();
if (normalizedHeading > 180) {
optimalHeading = 360 - optimalHeading;
}
// Take optimal heading and turn into a boat heading rather than a wind heading.
optimalHeading =
optimalHeading + GameState.getWindDirection();
setAutoPilot(optimalHeading);
} }
} }
/**
* Takes a given heading and rotates the boat towards that heading.
* This does not care about being upwind or downwind, just which direction will reach a given
* heading faster.
*
* @param newHeading The heading to turn the yacht towards.
*/
private void turnTowardsHeading(Double newHeading) {
Double newVal = heading - newHeading;
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
adjustHeading(TURN_STEP);
} else {
adjustHeading(-TURN_STEP);
}
}
/**
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
* directly away is 180.
*
* @return The normalized heading accounting for wind direction.
*/
private Double normalizeHeading() { private Double normalizeHeading() {
Double normalizedHeading = heading - GameState.windDirection; Double normalizedHeading = heading - GameState.windDirection;
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L); normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
@@ -15,7 +15,7 @@ public class CompoundMark {
public CompoundMark(int markID, String name, List<Mark> marks) { public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID; this.compoundMarkId = markID;
this.name = name; this.name = name;
this.marks.addAll(marks); this.marks.addAll(marks);
if (marks.size() > 1) { if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1)); this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
+17 -18
View File
@@ -1,5 +1,14 @@
package seng302.model.mark; package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@@ -10,20 +19,11 @@ import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.XMLGenerator; import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser; import seng302.utilities.XMLParser;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/** /**
* Class to hold the order of the marks in the race. * Class to hold the order of the marks in the race.
*/ */
public class MarkOrder { public class MarkOrder {
private List<CompoundMark> raceMarkOrder; private List<CompoundMark> raceMarkOrder;
private Logger logger = LoggerFactory.getLogger(MarkOrder.class); private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
@@ -35,7 +35,7 @@ public class MarkOrder {
* @return An ordered list of marks in the race * @return An ordered list of marks in the race
* OR null if the mark order could not be loaded * OR null if the mark order could not be loaded
*/ */
public List<CompoundMark> getMarkOrder(){ public List<CompoundMark> getMarkOrder() {
if (raceMarkOrder == null){ if (raceMarkOrder == null){
logger.warn("Race order accessed but not instantiated"); logger.warn("Race order accessed but not instantiated");
return null; return null;
@@ -55,10 +55,9 @@ public class MarkOrder {
/** /**
* @param currentSeqID The seqID of the current mark the boat is heading to * @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark last passed * @return The mark last passed
* @throws IndexOutOfBoundsException if there is no next mark. * @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
* Check seqID != 0 first
*/ */
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException{ public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID - 1); return raceMarkOrder.get(currentSeqID - 1);
} }
@@ -69,10 +68,10 @@ public class MarkOrder {
/** /**
* @param currentSeqID The seqID of the current mark the boat is heading to * @param currentSeqID The seqID of the current mark the boat is heading to
* @return The mark following the mark that the boat is heading to * @return The mark following the mark that the boat is heading to
* @throws IndexOutOfBoundsException if there is no next mark. * @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
* Check using {@link #isLastMark(Integer)} * #isLastMark(Integer)}
*/ */
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException{ public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1); return raceMarkOrder.get(currentSeqID + 1);
} }
@@ -81,7 +80,7 @@ public class MarkOrder {
* @param xml An AC35 RaceXML * @param xml An AC35 RaceXML
* @return An ordered list of marks in the race * @return An ordered list of marks in the race
*/ */
private List<CompoundMark> loadRaceOrderFromXML(String xml){ private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db; DocumentBuilder db;
@@ -13,6 +13,8 @@ import javafx.scene.Node;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import seng302.gameServer.MainServerThread; import seng302.gameServer.MainServerThread;
import seng302.gameServer.server.messages.BoatActionMessage;
import seng302.gameServer.server.messages.BoatActionType;
import seng302.model.RaceState; import seng302.model.RaceState;
import seng302.model.Yacht; import seng302.model.Yacht;
import seng302.model.stream.packets.StreamPacket; import seng302.model.stream.packets.StreamPacket;
@@ -22,8 +24,6 @@ import seng302.model.stream.parser.PositionUpdateData.DeviceType;
import seng302.model.stream.parser.RaceStatusData; import seng302.model.stream.parser.RaceStatusData;
import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData; import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.gameServer.server.messages.BoatActionMessage;
import seng302.gameServer.server.messages.BoatActionType;
import seng302.utilities.StreamParser; import seng302.utilities.StreamParser;
import seng302.utilities.XMLParser; import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.LobbyController; import seng302.visualiser.controllers.LobbyController;
@@ -1,6 +1,8 @@
package seng302.model.mark; package seng302.model.mark;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -1,16 +1,15 @@
package seng302.models; package seng302.models;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import seng302.model.mark.CompoundMark; import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder; import seng302.model.mark.MarkOrder;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
public class MarkOrderTest { public class MarkOrderTest {
private static MarkOrder markOrder; private static MarkOrder markOrder;
private static Integer currentSeqID; private static Integer currentSeqID;
@@ -0,0 +1,89 @@
package seng302.models;
import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import seng302.gameServer.GameState;
import seng302.model.PolarTable;
import seng302.model.Yacht;
public class YachtTest {
private static Yacht y1;
//Yacht y2;
private static Double windDirection = 180d;
private static Double windSpeed = 20d;
private static GameState gs;
@BeforeClass
public static void setUp() {
y1 = new Yacht("Yacht", 101, "Y1", "Y1", "Yacht 1", "C1");
gs = new GameState("localhost");
}
@Test
public void tackGybeTest() {
HashMap<Double, Double> values = new HashMap<>();
values.put(280.0, 80.0);
values.put(270.0, 90.0);
values.put(359.0, 1.0);
values.put(180.0, 180.0);
values.put(75.0, 285.0);
for (Double begin : values.keySet()) {
y1.setHeading(begin);
y1.tackGybe(windDirection);
for (int i = 0; i < 50; i++) {
y1.runAutoPilot();
}
assertEquals(values.get(begin), y1.getHeading(), 5.0);
}
}
@Test
public void vmgTest() {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
Double upwind = PolarTable.getOptimalUpwindVMG(windSpeed).keySet().iterator().next();
Double downwind = PolarTable.getOptimalDownwindVMG(windSpeed).keySet().iterator().next();
HashMap<Double, Double> values = new HashMap<>();
upwind = (double) Math.floorMod(upwind.longValue() + windDirection.longValue(), 360L);
Double upwindRight = upwind;
Double upwindLeft = 360 - upwindRight;
downwind = (double) Math.floorMod(downwind.longValue() + windDirection.longValue(), 360L);
Double downwindRight = downwind;
Double downwindLeft = 360 - downwindRight;
values.put(190d, upwindRight);
values.put(170d, upwindLeft);
values.put(10d, downwindLeft);
values.put(350d, downwindRight);
for (Double begin : values.keySet()) {
System.out.println(begin);
y1.setHeading(begin);
y1.turnToVMG();
for (int i = 0; i < 50; i++) {
y1.runAutoPilot();
System.out.println(y1.getHeading());
}
y1.disableAutoPilot();
assertEquals(values.get(begin), y1.getHeading(), 5.0);
}
}
@AfterClass
public static void tearDown() {
y1 = null;
}
}