mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Merge branch '1124_Mark_Sequence_From_RaceXML' into 'develop'
Loading mark sequence from RaceXML
# Loading mark sequence from RaceXML
## Change Log
1. Added MarkOrder class
* Mark order is read from the generated RaceXML and stored
* Added .getNextMark() to get the next mark in the race
* Added .equals() and .hashCode() for Marks
* NEW: Added RacePosition class to hold players position in the race
* NEW: Fixed issue where the duplicates weren't stored in the mark order
## Testing
* Unit tests in models/MarkOrderTest.java
## Acceptance Criteria
* Use the mark sequence in the raceXML
* Met by change log item (1)
* Store relevant mark details with each participating boat (Last mark, next mark)
* Method in MarkOrder to get next mark, however the last mark and next mark will need to be stored by whoever implements the second task.
See merge request !52
This commit is contained in:
@@ -4,12 +4,9 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import seng302.gameServer.server.messages.BoatActionType;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.Yacht;
|
||||
import seng302.gameServer.server.messages.BoatActionType;
|
||||
|
||||
/**
|
||||
* A Static class to hold information about the current state of the game (model)
|
||||
@@ -30,8 +27,6 @@ public class GameState implements Runnable {
|
||||
private static GameStages currentStage;
|
||||
private static long startTime;
|
||||
|
||||
// TODO: 26/07/17 cir27 - Super hackish fix until something more permanent can be made.
|
||||
private static ObservableList<String> observablePlayers = FXCollections.observableArrayList();
|
||||
private static Map<Player, String> playerStringMap = new HashMap<>();
|
||||
/*
|
||||
Ideally I would like to make this class an object instantiated by the server and given to
|
||||
@@ -60,7 +55,6 @@ public class GameState implements Runnable {
|
||||
yachts = new HashMap<>();
|
||||
|
||||
new Thread(this).start();
|
||||
|
||||
}
|
||||
|
||||
public static String getHostIpAddress() {
|
||||
@@ -71,20 +65,14 @@ public class GameState implements Runnable {
|
||||
return players;
|
||||
}
|
||||
|
||||
public static ObservableList<String> getObservablePlayers () {
|
||||
return observablePlayers;
|
||||
}
|
||||
|
||||
public static void addPlayer(Player player) {
|
||||
players.add(player);
|
||||
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + " " + player.getYacht().getCountry();
|
||||
Platform.runLater(() -> observablePlayers.add(playerText)); //Had to add this to handle javaFX window using array
|
||||
playerStringMap.put(player, playerText);
|
||||
}
|
||||
|
||||
public static void removePlayer(Player player) {
|
||||
players.remove(player);
|
||||
observablePlayers.remove(playerStringMap.get(player));
|
||||
playerStringMap.remove(player);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package seng302.gameServer.server.messages;
|
||||
|
||||
public class BoatLocationMessage extends Message {
|
||||
|
||||
private final int MESSAGE_SIZE = 56;
|
||||
|
||||
private long messageVersionNumber;
|
||||
@@ -28,6 +29,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Describes the location, altitude and sensor data from the boat.
|
||||
*
|
||||
* @param sourceId ID of the boat
|
||||
* @param sequenceNum Sequence number of the message
|
||||
* @param latitude The boats latitude
|
||||
@@ -35,7 +37,8 @@ public class BoatLocationMessage extends Message {
|
||||
* @param heading The boats heading
|
||||
* @param boatSpeed The boats speed
|
||||
*/
|
||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude,
|
||||
double heading, long boatSpeed) {
|
||||
messageVersionNumber = 1;
|
||||
time = System.currentTimeMillis();
|
||||
this.sourceId = sourceId;
|
||||
@@ -94,6 +97,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert binary latitude or longitude to floating point number
|
||||
*
|
||||
* @param binaryPackedLatLon Binary packed lat OR lon
|
||||
* @return Floating point lat/lon
|
||||
*/
|
||||
@@ -103,6 +107,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert binary packed heading to floating point number
|
||||
*
|
||||
* @param binaryPackedHeading Binary packed heading
|
||||
* @return heading as a decimal
|
||||
*/
|
||||
@@ -112,6 +117,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert binary packed wind angle to floating point number
|
||||
*
|
||||
* @param binaryPackedWindAngle Binary packed wind angle
|
||||
* @return wind angle as a decimal
|
||||
*/
|
||||
@@ -121,6 +127,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert a latitude or longitude to a binary packed long
|
||||
*
|
||||
* @param latLon A floating point latitude/longitude
|
||||
* @return A binary packed lat/lon
|
||||
*/
|
||||
@@ -130,6 +137,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert a heading to a binary packed long
|
||||
*
|
||||
* @param heading A floating point heading
|
||||
* @return A binary packed heading
|
||||
*/
|
||||
@@ -139,6 +147,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert a wind angle to a binary packed long
|
||||
*
|
||||
* @param windAngle Floating point wind angle
|
||||
* @return A binary packed wind angle
|
||||
*/
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package seng302.gameServer.server.simulator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.gameServer.server.simulator.parsers.RaceParser;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
public class Simulator extends Observable implements Runnable {
|
||||
|
||||
private List<Corner> course;
|
||||
private List<Boat> boats;
|
||||
private long lapse;
|
||||
private boolean isRaceStarted;
|
||||
|
||||
/**
|
||||
* Creates a simulator instance with given time lapse.
|
||||
* @param lapse time duration in millisecond.
|
||||
*/
|
||||
public Simulator(long lapse) {
|
||||
RaceParser rp = new RaceParser("/server_config/race.xml");
|
||||
course = rp.getCourse();
|
||||
boats = rp.getBoats();
|
||||
this.lapse = lapse;
|
||||
isRaceStarted = false;
|
||||
|
||||
setLegs();
|
||||
|
||||
// set start line's coordinate to boats
|
||||
Double startLat = course.get(0).getCompoundMark().getSubMark(1).getLat();
|
||||
Double startLng = course.get(0).getCompoundMark().getSubMark(1).getLng();
|
||||
for (Boat boat : boats) {
|
||||
boat.setLat(startLat);
|
||||
boat.setLng(startLng);
|
||||
boat.setLastPassedCorner(course.get(0));
|
||||
boat.setHeadingCorner(course.get(1));
|
||||
boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
int numOfFinishedBoats = 0;
|
||||
|
||||
while (numOfFinishedBoats < boats.size()) {
|
||||
|
||||
// if race has started, then boat should start to move.
|
||||
if (isRaceStarted) {
|
||||
for (Boat boat : boats) {
|
||||
numOfFinishedBoats += moveBoat(boat, lapse);
|
||||
}
|
||||
}
|
||||
|
||||
setChanged();
|
||||
notifyObservers(boats);
|
||||
|
||||
try {
|
||||
Thread.sleep(lapse);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("[Simulator] interrupted exception ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a boat with given time duration.
|
||||
* @param boat the boat to be moved
|
||||
* @param duration the moving duration in milliseconds
|
||||
* @return 1 if the boat has reached the final line, otherwise return 0
|
||||
*/
|
||||
private int moveBoat(Boat boat, double duration) {
|
||||
if (boat.getHeadingCorner() != null) {
|
||||
|
||||
boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration);
|
||||
|
||||
GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng());
|
||||
GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getSubMark(1);
|
||||
|
||||
double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos);
|
||||
// if a boat passes its heading mark
|
||||
while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) {
|
||||
double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner();
|
||||
boat.setLastPassedCorner(boat.getHeadingCorner());
|
||||
boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner());
|
||||
|
||||
// heading corner == null means boat has reached the final mark
|
||||
if (boat.getHeadingCorner() == null) {
|
||||
boat.setFinished(true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// move compensate distance for the mark just passed
|
||||
GeoPoint pos = GeoUtility.getGeoCoordinate(
|
||||
boat.getLastPassedCorner().getCompoundMark().getSubMark(1),
|
||||
boat.getLastPassedCorner().getBearingToNextCorner(),
|
||||
compensateDistance);
|
||||
boat.setLat(pos.getLat());
|
||||
boat.setLng(pos.getLng());
|
||||
distanceFromLastMark = GeoUtility.getDistance(new GeoPoint(boat.getLat(), boat.getLng()),
|
||||
boat.getLastPassedCorner().getCompoundMark().getSubMark(1));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Link all the corners in the course list so that every corner knows its next
|
||||
* corner, as well as the distance and bearing to its next corner. However,
|
||||
* the last corner's heading is null, which means it is the final line.
|
||||
*/
|
||||
private void setLegs() {
|
||||
// get the bearing from one mark to the next heading mark
|
||||
for (int i = 0; i < course.size() - 1; i++) {
|
||||
|
||||
Mark mark1 = course.get(i).getCompoundMark().getSubMark(1);
|
||||
Mark mark2 = course.get(i + 1).getCompoundMark().getSubMark(1);
|
||||
course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2));
|
||||
|
||||
course.get(i).setNextCorner(course.get(i + 1));
|
||||
|
||||
course.get(i).setBearingToNextCorner(
|
||||
GeoUtility.getBearing(course.get(i).getCompoundMark().getSubMark(1),
|
||||
course.get(i + 1).getCompoundMark().getSubMark(1)));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Boat> getBoats(){
|
||||
return boats;
|
||||
}
|
||||
|
||||
public void setRaceStarted(boolean raceStarted) {
|
||||
isRaceStarted = raceStarted;
|
||||
}
|
||||
}
|
||||
@@ -86,4 +86,46 @@ public class CompoundMark {
|
||||
public List<Mark> getMarks () {
|
||||
return marks;
|
||||
}
|
||||
|
||||
|
||||
// @Override
|
||||
// public boolean equals(Object other) {
|
||||
// if (other == null) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!(other instanceof Mark)){
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// Mark otherMark = (Mark) other;
|
||||
//
|
||||
// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (otherMark.getId() != getId()){
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!otherMark.getName().equals(name)){
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 0;
|
||||
for (Mark mark : marks) {
|
||||
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
|
||||
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
|
||||
}
|
||||
return hash + getName().hashCode() + Integer.hashCode(getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.model.stream.xml.generator.Race;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
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.
|
||||
*/
|
||||
public class MarkOrder {
|
||||
private List<Mark> raceMarkOrder;
|
||||
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
||||
|
||||
public MarkOrder(){
|
||||
loadRaceProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An ordered list of marks in the race
|
||||
* OR null if the mark order could not be loaded
|
||||
*/
|
||||
public List<Mark> getMarkOrder(){
|
||||
if (raceMarkOrder == null){
|
||||
logger.warn("Race order accessed but not instantiated");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(raceMarkOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mark in the race after the previous mark
|
||||
* @param position The current race position
|
||||
* @return the next race position
|
||||
* OR null if there is no position
|
||||
*/
|
||||
public RacePosition getNextPosition(RacePosition position){
|
||||
Mark previousMark = position.getNextMark();
|
||||
Mark nextMark;
|
||||
|
||||
if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){
|
||||
RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark);
|
||||
nextRacePosition.setFinishingLeg();
|
||||
|
||||
return nextRacePosition;
|
||||
}
|
||||
|
||||
Integer nextPositionIndex = position.getPositionIndex() + 1;
|
||||
RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark);
|
||||
|
||||
return nextRacePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the race order from an XML string
|
||||
* @param xml An AC35 RaceXML
|
||||
* @return An ordered list of marks in the race
|
||||
*/
|
||||
private List<Mark> loadRaceOrderFromXML(String xml){
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db;
|
||||
Document doc;
|
||||
|
||||
try {
|
||||
db = dbf.newDocumentBuilder();
|
||||
doc = db.parse(new InputSource(new StringReader(xml)));
|
||||
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||
logger.error("Failed to read generated race XML");
|
||||
return null;
|
||||
}
|
||||
|
||||
RaceXMLData data = XMLParser.parseRace(doc);
|
||||
|
||||
if (data != null){
|
||||
logger.debug("Loaded RaceXML for mark order");
|
||||
List<Corner> corners = data.getMarkSequence();
|
||||
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
|
||||
List<Mark> course = new ArrayList<>();
|
||||
|
||||
for (Corner corner : corners){
|
||||
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
|
||||
course.add(compoundMark.getMarks().get(0));
|
||||
}
|
||||
|
||||
return course;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The first position in the race
|
||||
*/
|
||||
public RacePosition getFirstPosition(){
|
||||
if (raceMarkOrder.size() > 0){
|
||||
return new RacePosition(-1, raceMarkOrder.get(0), null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the raceXML and mark order
|
||||
*/
|
||||
private void loadRaceProperties(){
|
||||
XMLGenerator generator = new XMLGenerator();
|
||||
|
||||
generator.setRace(new Race());
|
||||
|
||||
String raceXML = generator.getRaceAsXml();
|
||||
|
||||
if (raceXML == null){
|
||||
logger.error("Failed to generate raceXML (for race properties)");
|
||||
return;
|
||||
}
|
||||
raceMarkOrder = loadRaceOrderFromXML(raceXML);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
/**
|
||||
* Represents a boats position between two marks
|
||||
*/
|
||||
public class RacePosition {
|
||||
private Integer positionIndex;
|
||||
private Mark nextMark;
|
||||
private Mark previousMark;
|
||||
private Boolean isFinishingLeg;
|
||||
|
||||
public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){
|
||||
this.positionIndex = positionIndex;
|
||||
this.nextMark = nextMark;
|
||||
this.previousMark = previousMark;
|
||||
isFinishingLeg = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The position of the boat (0...number of marks in race - 1)
|
||||
*/
|
||||
public Integer getPositionIndex(){
|
||||
return positionIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mark the boat is heading to
|
||||
* will return NULL if this is the finishing legg
|
||||
*/
|
||||
public Mark getNextMark(){
|
||||
return nextMark;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mark the boat is heading away from,
|
||||
* Will return NULL if this is the starting leg
|
||||
*/
|
||||
public Mark getPreviousMark(){
|
||||
return previousMark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag that this is the last leg in the race
|
||||
*/
|
||||
public void setFinishingLeg(){
|
||||
isFinishingLeg = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this is the last leg in the race
|
||||
*/
|
||||
public boolean getIsFinishingLeg() {
|
||||
return isFinishingLeg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package seng302.models;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.mark.MarkOrder;
|
||||
import seng302.model.mark.RacePosition;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
public class MarkOrderTest {
|
||||
private static MarkOrder markOrder;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup(){
|
||||
markOrder = new MarkOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure marks are loaded from XML
|
||||
*/
|
||||
@Test
|
||||
public void testMarkOrderLoadedFromXML(){
|
||||
assertTrue(markOrder != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if .getNextMark() returns null if it is called with the final mark in the race
|
||||
*/
|
||||
@Test
|
||||
public void testNextMarkAtEnd(){
|
||||
// There are no marks in the XML, therefore this can't be tested
|
||||
if (markOrder.getMarkOrder().size() == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1);
|
||||
Integer lastIndex = markOrder.getMarkOrder().size() - 1;
|
||||
|
||||
RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null);
|
||||
|
||||
assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if .getNextMark() method returns the next mark in the race
|
||||
*/
|
||||
@Test
|
||||
public void testNextMark(){
|
||||
// There are not enough marks for this to be tested
|
||||
if (markOrder.getMarkOrder().size() < 2){
|
||||
return;
|
||||
}
|
||||
|
||||
RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null);
|
||||
|
||||
assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a whole race can be completed
|
||||
*/
|
||||
@Test
|
||||
public void testMarkSequence(){
|
||||
RacePosition current = markOrder.getFirstPosition();
|
||||
|
||||
while (!current.getIsFinishingLeg()){
|
||||
|
||||
current = markOrder.getNextPosition(current);
|
||||
|
||||
if (current.getIsFinishingLeg()){
|
||||
assertEquals(null, current.getNextMark());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown(){
|
||||
markOrder = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user