diff --git a/racevisionGame/src/main/java/mock/dataInput/BoatDataSource.java b/racevisionGame/src/main/java/mock/dataInput/BoatDataSource.java deleted file mode 100644 index 92ac15b1..00000000 --- a/racevisionGame/src/main/java/mock/dataInput/BoatDataSource.java +++ /dev/null @@ -1,14 +0,0 @@ -package mock.dataInput; - -import seng302.Model.Boat; -import seng302.Model.Mark; - -import java.util.Map; - -/** - * Boats Data - */ -public interface BoatDataSource { - Map getBoats(); - Map getMarkerBoats(); -} diff --git a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java index a45d77ed..d33c0ac5 100644 --- a/racevisionGame/src/main/java/mock/dataInput/PolarParser.java +++ b/racevisionGame/src/main/java/mock/dataInput/PolarParser.java @@ -4,6 +4,7 @@ package mock.dataInput; import mock.exceptions.InvalidPolarFileException; import mock.model.Polars; +import shared.model.Bearing; import java.io.*; import java.util.ArrayList; diff --git a/racevisionGame/src/main/java/mock/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/mock/dataInput/RaceDataSource.java deleted file mode 100644 index 54c97a41..00000000 --- a/racevisionGame/src/main/java/mock/dataInput/RaceDataSource.java +++ /dev/null @@ -1,32 +0,0 @@ -package mock.dataInput; - -import seng302.Model.Boat; -import seng302.Model.CompoundMark; -import seng302.Model.GPSCoordinate; -import seng302.Model.Leg; - -import java.time.ZonedDateTime; -import java.util.List; - -/** - * Data Class for a Race - */ -public interface RaceDataSource { - List getBoats(); - - List getLegs(); - - List getBoundary(); - - List getCompoundMarks(); - - int getRaceId(); - - String getRaceType(); - - ZonedDateTime getZonedDateTime(); - - GPSCoordinate getMapTopLeft(); - - GPSCoordinate getMapBottomRight(); -} diff --git a/racevisionGame/src/main/java/mock/dataInput/RaceXMLReader.java b/racevisionGame/src/main/java/mock/dataInput/RaceXMLReader.java index 4eb35f9f..b5e410b6 100644 --- a/racevisionGame/src/main/java/mock/dataInput/RaceXMLReader.java +++ b/racevisionGame/src/main/java/mock/dataInput/RaceXMLReader.java @@ -6,6 +6,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import seng302.Exceptions.StreamedCourseXMLException; +import shared.dataInput.BoatDataSource; +import shared.dataInput.RaceDataSource; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; diff --git a/racevisionGame/src/main/java/mock/model/Polars.java b/racevisionGame/src/main/java/mock/model/Polars.java index 08df0325..32ee8842 100644 --- a/racevisionGame/src/main/java/mock/model/Polars.java +++ b/racevisionGame/src/main/java/mock/model/Polars.java @@ -1,6 +1,7 @@ package mock.model; import javafx.util.Pair; +import shared.model.Bearing; import java.util.ArrayList; import java.util.HashMap; diff --git a/racevisionGame/src/main/java/mock/model/Race.java b/racevisionGame/src/main/java/mock/model/Race.java new file mode 100644 index 00000000..3e90408a --- /dev/null +++ b/racevisionGame/src/main/java/mock/model/Race.java @@ -0,0 +1,927 @@ +package mock.model; + +import javafx.animation.AnimationTimer; +import javafx.collections.FXCollections; +import mock.app.MockOutput; +import shared.dataInput.RaceDataSource; +import network.Messages.Enums.RaceStatusEnum; +import network.Messages.Enums.RaceTypeEnum; +import shared.model.Bearing; +import shared.model.Constants; +import shared.model.GPSCoordinate; +import shared.model.Leg; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +import static java.lang.Math.cos; + + +/** + * Represents a yacht race. + * Has a course, boats, boundaries, etc... + * Is responsible for simulating the race, and sending messages to a MockOutput instance. + */ +public class Race implements Runnable { + + /** + * An observable list of boats in the race. + */ + private ObservableList boats; + + /** + * An observable list of compound marks in the race. + */ + private ObservableList compoundMarks; + + /** + * A list of legs in the race. + */ + private List legs; + + /** + * A list of coordinates describing the boundary of the course. + */ + private List boundary; + + /** + * A copy of the boundary list, except "shrunk" inwards by 50m. + */ + private List shrinkBoundary; + + /** + * The elapsed time, in milliseconds, of the race. + */ + private long totalTimeElapsed; + + /** + * The starting timestamp, in milliseconds, of the race. + */ + private long startTime; + + /** + * The scale factor of the race. + * Frame periods are multiplied by this to get the amount of time a single frame represents. + * E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred. + */ + private int scaleFactor = 5; + + /** + * The race ID of the course. + */ + private int raceId; + + /** + * The current status of the race. + */ + private RaceStatusEnum raceStatusEnum; + + /** + * The type of race this is. + */ + private RaceTypeEnum raceType; + + /** + * The percent chance that a boat fails the race, and enters a DNF state, at each checkpoint. + * 0 = 0%, 100 = 100%. + */ + private int dnfChance = 0; + + + /** + * The mockOutput to send messages to. + */ + private MockOutput mockOutput; + + + /** + * Wind direction bearing. + */ + private Bearing windDirection; + + /** + * Wind speed (knots). + * Convert this to millimeters per second before passing to RaceStatus. + */ + private double windSpeed; + + private double windDirDegrees; + private double windDir; + private int changeWind = 4; + private static final int windUpperBound = 235; + private static final int windLowerBound = 215; + + + + /** + * Constructs a race object with a given RaceDataSource and sends events to the given mockOutput. + * @param raceData Data source for race related data (boats, legs, etc...). + * @param mockOutput The mockOutput to send events to. + */ + public Race(RaceDataSource raceData, MockOutput mockOutput) { + + this.mockOutput = mockOutput; + + this.boats = FXCollections.observableArrayList(raceData.getBoats()); + this.compoundMarks = FXCollections.observableArrayList(raceData.getCompoundMarks()); + this.boundary = raceData.getBoundary(); + this.shrinkBoundary = GPSCoordinate.getShrinkBoundary(this.boundary); + + + this.legs = raceData.getLegs(); + this.legs.add(new Leg("Finish", this.legs.size())); + + this.raceId = raceData.getRaceId(); + + //The start time is current time + 4 minutes, scaled. prestart is 3 minutes, and we add another. + this.startTime = System.currentTimeMillis() + ((Constants.RacePreStartTime + (1 * 60 * 1000)) / this.scaleFactor); + + this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); + this.raceType = raceData.getRaceType(); + + this.windSpeed = 12; + this.windDirection = Bearing.fromDegrees(180); + + + } + + /** + * Runnable for the thread. + */ + public void run() { + initialiseBoats(); + initialiseWindDir(); + countdownTimer.start(); + } + + /** + * Parse the compound marker boats through mock output. + */ + private void parseMarks() { + for (CompoundMark compoundMark : this.compoundMarks) { + + //Get the individual marks from the compound mark. + Mark mark1 = compoundMark.getMark1(); + Mark mark2 = compoundMark.getMark2(); + + //If they aren't null, parse them (some compound marks only have one mark). + if (mark1 != null) { + this.parseIndividualMark(mark1); + } + + if (mark2 != null) { + this.parseIndividualMark(mark2); + } + + } + } + + /** + * Parses an individual marker boat, and sends it to mockOutput. + * @param mark The marker boat to parse. + */ + private void parseIndividualMark(Mark mark) { + + this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime); + + } + + /** + * Parse the boats in the race, and send it to mockOutput. + */ + private void parseBoatLocations() { + + //Parse each boat. + for (Boat boat : this.boats) { + + this.parseIndividualBoatLocation(boat); + + } + + } + + /** + * Parses an individual boat, and sends it to mockOutput. + * @param boat The boat to parse. + */ + private void parseIndividualBoatLocation(Boat boat) { + + this.mockOutput.parseBoatLocation( + boat.getSourceID(), + boat.getCurrentPosition().getLatitude(), + boat.getCurrentPosition().getLongitude(), + boat.getBearing().degrees(), + boat.getCurrentSpeed(), + startTime + totalTimeElapsed + ); + + } + + + /** + * Updates the race status enumeration based on the current time, in milliseconds. + * @param currentTime The current time, in milliseconds. + */ + private void updateRaceStatusEnum(long currentTime) { + + //The amount of milliseconds until the race starts. + long timeToStart = this.startTime - currentTime; + + //Scale the time to start based on the scale factor. + long timeToStartScaled = timeToStart / this.scaleFactor; + + + if (timeToStartScaled > Constants.RacePreStartTime) { + //Time > 3 minutes is the prestart period. + this.setRaceStatusEnum(RaceStatusEnum.PRESTART); + + } else if ((timeToStartScaled <= Constants.RacePreStartTime) && (timeToStartScaled >= Constants.RacePreparatoryTime)) { + //Time between [1, 3] minutes is the warning period. + this.setRaceStatusEnum(RaceStatusEnum.WARNING); + + } else if ((timeToStartScaled <= Constants.RacePreparatoryTime) && (timeToStartScaled > 0)) { + //Time between (0, 1] minutes is the preparatory period. + this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY); + + } else { + //Otherwise, the race has started! + this.setRaceStatusEnum(RaceStatusEnum.STARTED); + + } + + + } + + /** + * Parses the race status, and sends it to mockOutput. + */ + private void parseRaceStatus() { + + //A race status message contains a list of boat statuses. + List boatStatuses = new ArrayList<>(); + + //Add each boat status to the status list. + for (Boat boat : boats) { + + BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime()); + + boatStatuses.add(boatStatus); + } + + //TODO REFACTOR for consistency, could send parameters to mockOutput instead of the whole racestatus. This will also fix the sequence number issue. + + //Convert wind direction and speed to ints. //TODO this conversion should be done inside the racestatus class. + int windDirectionInt = AC35UnitConverter.encodeHeading(this.windDirection.degrees()); + int windSpeedInt = (int) (windSpeed * Constants.KnotsToMMPerSecond); + + //Create race status object, and send it. + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), this.raceId, this.getRaceStatusEnum().getValue(), this.startTime, windDirectionInt, windSpeedInt, this.getRaceType().getValue(), boatStatuses); + + mockOutput.parseRaceStatus(raceStatus); + + + } + + + /** + * Sets the status of all boats in the race to RACING. + */ + private void setBoatsStatusToRacing() { + + for (Boat boat : this.boats) { + boat.setStatus(BoatStatusEnum.RACING); + } + } + + + /** + * Countdown timer until race starts. + */ + protected AnimationTimer countdownTimer = new AnimationTimer() { + + + long currentTime = System.currentTimeMillis(); + + @Override + public void handle(long arg0) { + + //Update the race status based on the current time. + updateRaceStatusEnum(this.currentTime); + + //Parse the boat locations. + parseBoatLocations(); + + //Parse the marks. + parseMarks(); + + // Change wind direction + changeWindDir(); + + //Parse the race status. + parseRaceStatus(); + + + if (getRaceStatusEnum() == RaceStatusEnum.STARTED) { + System.setProperty("javafx.animation.fullspeed", "true"); + setBoatsStatusToRacing(); + raceTimer.start(); + this.stop(); + } + + //Update the animations timer's time. + currentTime = System.currentTimeMillis(); + } + }; + + + /** + * Timer that runs for the duration of the race, until all boats finish. + */ + private AnimationTimer raceTimer = new AnimationTimer() { + + /** + * Start time of loop, in milliseconds. + */ + long timeRaceStarted = System.currentTimeMillis(); + + /** + * The time of the previous frame, in milliseconds. + */ + long lastFrameTime = timeRaceStarted; + + @Override + public void handle(long arg0) { + + //Get the current time. + long currentTime = System.currentTimeMillis(); + + //Update the total elapsed time. + totalTimeElapsed = currentTime - this.timeRaceStarted; + + //As long as there is at least one boat racing, we still simulate the race. + if (getNumberOfActiveBoats() != 0) { + + //Get the time period of this frame. + long framePeriod = currentTime - lastFrameTime; + //We actually simulate 20ms istead of the amount of time that has occurred, as that ensure that we don't end up with large frame periods on slow computers, causing position issues. + framePeriod = 20; + + + //For each boat, we update its position, and generate a BoatLocationMessage. + for (Boat boat : boats) { + + //If it is still racing, update its position. + if (boat.getStatus() == BoatStatusEnum.RACING) { + + updatePosition(boat, framePeriod, totalTimeElapsed); + + } + + } + + } else { + //Otherwise, the race is over! + raceFinished.start(); + setRaceStatusEnum(RaceStatusEnum.FINISHED); + this.stop(); + } + + if (getNumberOfActiveBoats() != 0) { + // Change wind direction + changeWindDir(); + + //Parse the boat locations. + parseBoatLocations(); + + //Parse the marks. + parseMarks(); + + //Parse the race status. + parseRaceStatus(); + + + //Update the last frame time. + this.lastFrameTime = currentTime; + } + } + }; + + /** + * Broadcast that the race has finished. + */ + protected AnimationTimer raceFinished = new AnimationTimer(){ + int iters = 0; + @Override + public void handle(long now) { + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>()); + mockOutput.parseRaceStatus(raceStatus); + if (iters > 500){ + mockOutput.stop(); + stop(); + } + iters++; + } + }; + + /** + * Initialise the boats in the race. + * This sets their starting positions and current legs. + */ + public void initialiseBoats() { + + //Gets the starting positions of the boats. + List startingPositions = getSpreadStartingPositions(); + + //Get iterators for our boat and position lists. + Iterator boatIt = this.boats.iterator(); + Iterator startPositionIt = startingPositions.iterator(); + + //Iterate over the pair of lists. + while (boatIt.hasNext() && startPositionIt.hasNext()) { + + //Get the next boat and position. + Boat boat = boatIt.next(); + GPSCoordinate startPosition = startPositionIt.next(); + + + //The boat starts on the first leg of the race. + boat.setCurrentLeg(this.legs.get(0)); + + //Boats start with 0 knots speed. + boat.setCurrentSpeed(0d); + + //Place the boat at its starting position. + boat.setCurrentPosition(startPosition); + + //Boats start facing their next marker. + boat.setBearing(boat.calculateBearingToNextMarker()); + + //Sets the boats status to prestart - it changes to racing when the race starts. + 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(999999); + + } + + } + + + /** + * Creates a list of starting positions for the different boats, so they do not appear cramped at the start line. + * + * @return A list of starting positions. + */ + public List getSpreadStartingPositions() { + + //The first compound marker of the race - the starting gate. + CompoundMark compoundMark = this.legs.get(0).getStartCompoundMark(); + + //The position of the two markers from the compound marker. + GPSCoordinate mark1Position = compoundMark.getMark1Position(); + GPSCoordinate mark2Position = compoundMark.getMark2Position(); + + + //Calculates the azimuth between the two points. + Azimuth azimuth = GPSCoordinate.calculateAzimuth(mark1Position, mark2Position); + + //Calculates the distance between the two points. + double distanceMeters = GPSCoordinate.calculateDistanceMeters(mark1Position, mark2Position); + + //The number of boats in the race. + int numberOfBoats = this.boats.size(); + + //Calculates the distance between each boat. We divide by numberOfBoats + 1 to ensure that no boat is placed on one of the starting gate's marks. + double distanceBetweenBoatsMeters = distanceMeters / (numberOfBoats + 1); + + + //List to store coordinates in. + List positions = new ArrayList<>(); + + //We start spacing boats out from mark 1. + GPSCoordinate position = mark1Position; + + //For each boat, displace position, and store it. + for (int i = 0; i < numberOfBoats; i++) { + + position = GPSCoordinate.calculateNewPosition(position, distanceBetweenBoatsMeters, azimuth); + + positions.add(position); + + } + + return positions; + } + + + /** + * Calculates a boat's VMG. + * @param boat The boat to calculate VMG for. + * @param bearingBounds An array containing the lower and upper acceptable bearing bounds to keep the boat in the course. + * @return VMG for the specified boat. + */ + private VMG calculateVMG(Boat boat, Bearing[] bearingBounds) { + + //Get the lower and upper acceptable bounds. + Bearing lowerAcceptableBound = bearingBounds[0]; + Bearing upperAcceptableBound = bearingBounds[1]; + + + //Find the VMG inside these bounds. + VMG bestVMG = boat.getPolars().calculateVMG(this.windDirection, this.windSpeed, boat.calculateBearingToNextMarker(), lowerAcceptableBound, upperAcceptableBound); + + + + return bestVMG; + + } + + + /** + * Determines whether or not a given VMG improves the velocity of a boat, if it were currently using currentVMG. + * @param currentVMG The current VMG of the boat. + * @param potentialVMG The new VMG to test. + * @param bearingToDestination The bearing between the boat and its destination. + * @return True if the new VMG is improves velocity, false otherwise. + */ + private boolean improvesVelocity(VMG currentVMG, VMG potentialVMG, Bearing bearingToDestination) { + + //Calculates the angle between the boat and its destination. + Angle angleBetweenDestAndHeading = Angle.fromDegrees(currentVMG.getBearing().degrees() - bearingToDestination.degrees()); + + //Calculates the angle between the new VMG and the boat's destination. + Angle angleBetweenDestAndNewVMG = Angle.fromDegrees(potentialVMG.getBearing().degrees() - bearingToDestination.degrees()); + + + //Calculate the boat's current velocity. + double currentVelocity = Math.cos(angleBetweenDestAndHeading.radians()) * currentVMG.getSpeed(); + + //Calculate the potential velocity with the new VMG. + double vmgVelocity = Math.cos(angleBetweenDestAndNewVMG.radians()) * potentialVMG.getSpeed(); + + //Return whether or not the new VMG gives better velocity. + return vmgVelocity > currentVelocity; + + } + + /** + * Determines whether or not a given VMG improves the velocity of a boat. + * @param boat The boat to test. + * @param vmg The new VMG to test. + * @return True if the new VMG is improves velocity, false otherwise. + */ + private boolean improvesVelocity(Boat boat, VMG vmg) { + + //Get the boats "current" VMG. + VMG boatVMG = new VMG(boat.getCurrentSpeed(), boat.getBearing()); + + //Check if the new VMG is better than the boat's current VMG. + return this.improvesVelocity(boatVMG, vmg, boat.calculateBearingToNextMarker()); + + } + + + /** + * Calculates the distance a boat has travelled and updates its current position according to this value. + * + * @param boat The boat to be updated. + * @param updatePeriodMilliseconds The time, in milliseconds, since the last update. + * @param totalElapsedMilliseconds The total number of milliseconds that have elapsed since the start of the race. + */ + protected void updatePosition(Boat boat, long updatePeriodMilliseconds, long totalElapsedMilliseconds) { + + //Checks if the current boat has finished the race or not. + boolean finish = this.isLastLeg(boat.getCurrentLeg()); + + if (!finish) { + + + //Calculates the distance travelled, in meters, in the current timeslice. + double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds); + + //Scale it. + distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor; + + + //Move the boat forwards that many meters, and advances its time counters by enough milliseconds. + boat.moveForwards(distanceTravelledMeters, updatePeriodMilliseconds * this.scaleFactor); + + + //Only get a new VMG if the boat will go outside the course, or X seconds have elapsed. + boolean willStayInsideCourse = this.checkBearingInsideCourse(boat.getBearing(), boat.getCurrentPosition()); + long tackPeriod = 15000; + if (!willStayInsideCourse || (boat.getTimeSinceTackChange() > tackPeriod)) { + + //Calculate the boat's bearing bounds, to ensure that it doesn't go out of the course. + Bearing[] bearingBounds = this.calculateBearingBounds(boat); + + + //Calculate the new VMG. + VMG newVMG = this.calculateVMG(boat, bearingBounds); + + + //If the new vmg improves velocity, use it. + if (improvesVelocity(boat, newVMG)) { + boat.setVMG(newVMG); + + } else { + //We also need to use the new VMG if our current bearing will take us out of the course. + if (!willStayInsideCourse) { + boat.setVMG(newVMG); + } + } + } + + this.updateEstimatedTime(boat); + + + //Check the boats position (update leg and stuff). + this.checkPosition(boat, totalTimeElapsed); + + } + + } + + /** + * Calculates the upper and lower bounds that the boat may have in order to not go outside of the course. + * @param boat The boat to check. + * @return An array of bearings. The first is the lower bound, the second is the upper bound. + */ + private Bearing[] calculateBearingBounds(Boat boat) { + + Bearing[] bearings = new Bearing[2]; + + Bearing lowerBearing = Bearing.fromDegrees(0.001); + Bearing upperBearing = Bearing.fromDegrees(359.999); + + + + double lastAngle = -1; + boolean lastAngleWasGood = false; + + //Check all bearings between [0, 360). + for (double angle = 0; angle < 360; angle += 1) { + + //Create bearing from angle. + Bearing bearing = Bearing.fromDegrees(angle); + + //Check that if it is acceptable. + boolean bearingIsGood = this.checkBearingInsideCourse(bearing, boat.getCurrentPosition()); + + + if (lastAngle != -1) { + + if (lastAngleWasGood && !bearingIsGood) { + //We have flipped over from good bearings to bad bearings. So the last good bearing is the upper bearing. + upperBearing = Bearing.fromDegrees(lastAngle); + } + + if (!lastAngleWasGood && bearingIsGood) { + //We have flipped over from bad bearings to good bearings. So the current bearing is the lower bearing. + lowerBearing = Bearing.fromDegrees(angle); + } + + } + + lastAngle = angle; + lastAngleWasGood = bearingIsGood; + + } + + + + //TODO BUG if it can't find either upper or lower, it returns (0, 359.999). Should return (boatbearing, boatbearing+0.0001) + bearings[0] = lowerBearing; + bearings[1] = upperBearing; + + return bearings; + } + + + + /** + * Checks if a given bearing, starting at a given position, would put a boat out of the course boundaries. + * @param bearing The bearing to check. + * @param position The position to start from. + * @return True if the bearing would keep the boat in the course, false if it would take it out of the course. + */ + private boolean checkBearingInsideCourse(Bearing bearing, GPSCoordinate position) { + + //Get azimuth from bearing. + Azimuth azimuth = Azimuth.fromBearing(bearing); + + + //Tests to see if a point in front of the boat is out of bounds. + double epsilonMeters = 50d; + GPSCoordinate testCoord = GPSCoordinate.calculateNewPosition(position, epsilonMeters, azimuth); + + //If it isn't inside the boundary, calculate new bearing. + if (GPSCoordinate.isInsideBoundary(testCoord, this.shrinkBoundary)) { + return true; + } else { + return false; + } + + } + + + /** + * Checks if a boat has finished any legs, or has pulled out of race (DNF). + * @param boat The boat to check. + * @param timeElapsed The total time, in milliseconds, that has elapsed since the race started. + */ + protected void checkPosition(Boat boat, long timeElapsed) { + + //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker. + double epsilonNauticalMiles = 100.0 / Constants.NMToMetersConversion; //100 meters. TODO should be more like 5-10. + + if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) { + //Boat has reached its target marker, and has moved on to a new leg. + + + + //Calculate how much the boat overshot the marker by. + double overshootMeters = boat.calculateDistanceToNextMarker(); + + + //Move boat on to next leg. + Leg nextLeg = this.legs.get(boat.getCurrentLeg().getLegNumber() + 1); + boat.setCurrentLeg(nextLeg); + + //Add overshoot distance into the distance travelled for the next leg. + boat.setDistanceTravelledInLeg(overshootMeters); + + //Setting a high value for this allows the boat to immediately do a large turn, as it needs to in order to get to the next mark. + boat.setTimeSinceTackChange(999999); + + + //Check if the boat has finished or stopped racing. + + if (this.isLastLeg(boat.getCurrentLeg())) { + //Boat has finished. + boat.setTimeFinished(timeElapsed); + boat.setCurrentSpeed(0); + boat.setStatus(BoatStatusEnum.FINISHED); + } else if (doNotFinish()) { + //Boat has pulled out of race. + boat.setTimeFinished(timeElapsed); + boat.setCurrentLeg(new Leg("DNF", -1)); + boat.setCurrentSpeed(0); + boat.setStatus(BoatStatusEnum.DNF); + + } + + } + + } + + + /** + * Determines whether or not a specific leg is the last leg in the race. + * @param leg The leg to check. + * @return Returns true if it is the last, false otherwse. + */ + private boolean isLastLeg(Leg leg) { + + //Get the last leg. + Leg lastLeg = this.legs.get(this.legs.size() - 1); + + //Check its ID. + int lastLegID = lastLeg.getLegNumber(); + + //Get the specified leg's ID. + int legID = leg.getLegNumber(); + + + //Check if they are the same. + return legID == lastLegID; + } + + + /** + * Sets the chance each boat has of failing at a gate or marker + * + * @param chance percentage chance a boat has of failing per checkpoint. + */ + protected void setDnfChance(int chance) { + if (chance >= 0 && chance <= 100) { + dnfChance = chance; + } + } + + /** + * Decides if a boat should received a DNF status. + * @return True means it should DNF, false means it shouldn't. + */ + protected boolean doNotFinish() { + Random rand = new Random(); + return rand.nextInt(100) < dnfChance; + } + + + /** + * Returns the current race status. + * @return The current race status. + */ + public RaceStatusEnum getRaceStatusEnum() { + return raceStatusEnum; + } + + /** + * Sets the current race status. + * @param raceStatusEnum The new status of the race. + */ + private void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { + this.raceStatusEnum = raceStatusEnum; + } + + + /** + * Returns the type of race this is. + * @return The type of race this is. + */ + public RaceTypeEnum getRaceType() { + return raceType; + } + + + /** + * Returns the number of boats that are still active in the race. + * They become inactive by either finishing or withdrawing. + * @return The number of boats still active in the race. + */ + protected int getNumberOfActiveBoats() { + + int numberofActiveBoats = 0; + + for (Boat boat : this.boats) { + + //If the boat is currently racing, count it. + if (boat.getStatus() == BoatStatusEnum.RACING) { + numberofActiveBoats++; + } + + } + + return numberofActiveBoats; + } + + + /** + * Returns an observable list of boats in the race. + * @return List of boats in the race. + */ + public ObservableList getBoats() { + return boats; + } + + protected void initialiseWindDir(){ + windDirDegrees = 225; + windDir = AC35UnitConverter.convertHeading(windDirDegrees); + /*windDir = new Random().nextInt(65535+1); + windDir = BoatLocation.convertHeadingIntToDouble(255);*/ + this.windDirection = new Bearing((int)windDir); + } + + protected void changeWindDir(){ + int r = new Random().nextInt(changeWind)+1; + if(r==1){ + windDirDegrees = (0.5 + windDirDegrees) % 360; + } else if (r==2){ + windDirDegrees = ((windDirDegrees - 0.5) + 360) % 360;///keep the degrees positive when below 0 + } + if (windDirDegrees > windUpperBound){ + windDirDegrees = windUpperBound; + } + if (windDirDegrees < windLowerBound){ + windDirDegrees = windLowerBound; + } + + windDir = AC35UnitConverter.convertHeading(windDirDegrees); + this.windDirection = new Bearing(windDirDegrees); + } + + protected void setChangeWind(int changeVal){ + if (changeVal>=0){ + changeWind = changeVal; + } + } + + protected int getWind(){ + return (int)windDir; + } + + /** + * Updates the boat's estimated time to next mark if positive + * @param boat to estimate time given its velocity + */ + private void updateEstimatedTime(Boat boat) { + double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond; + if (velocityToMark > 0) { + long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark); + boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow); + } + } +} diff --git a/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java new file mode 100644 index 00000000..40f12c75 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/BoatDataSource.java @@ -0,0 +1,25 @@ +package shared.dataInput; + + +import shared.model.Boat; +import shared.model.Mark; + +import java.util.Map; + +/** + * Provides information about the boats and marker boats in a race. + */ +public interface BoatDataSource { + + /** + * Returns a map between source ID and boat for all boats in the race. + * @return Map between source ID and boat. + */ + Map getBoats(); + + /** + * Returns a map between source ID and mark for all marks in the race. + * @return Map between source ID and mark. + */ + Map getMarkerBoats(); +} diff --git a/racevisionGame/src/main/java/mock/dataInput/BoatXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java similarity index 82% rename from racevisionGame/src/main/java/mock/dataInput/BoatXMLReader.java rename to racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java index 949c6b23..5e155090 100644 --- a/racevisionGame/src/main/java/mock/dataInput/BoatXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/BoatXMLReader.java @@ -1,44 +1,39 @@ -package mock.dataInput; +package shared.dataInput; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.xml.sax.SAXException; -import seng302.Model.Boat; -import seng302.Model.GPSCoordinate; -import seng302.Model.Mark; -import seng302.Model.Polars; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; +import shared.exceptions.XMLReaderException; +import shared.model.Boat; +import shared.model.GPSCoordinate; +import shared.model.Mark; + import java.util.HashMap; import java.util.Map; /** - * Xml Reader class for Boat XML used for the race + * Xml Reader class for Boat XML used for the race. */ public class BoatXMLReader extends XMLReader implements BoatDataSource { + /** + * A map of source ID to boat for all boats in the race. + */ private final Map boatMap = new HashMap<>(); - private final Map markerMap = new HashMap<>(); /** - * Polars table to assign to each boat. + * A map of source ID to mark for all marks in the race. */ - Polars boatPolars; + private final Map markerMap = new HashMap<>(); /** * Constructor for Boat XML * * @param filePath Name/path of file to read. Read as a resource. - * @param boatPolars polars used by the boats - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * @throws XMLReaderException Thrown if the file cannot be parsed. */ - public BoatXMLReader(String filePath, Polars boatPolars) throws IOException, SAXException, ParserConfigurationException { + public BoatXMLReader(String filePath) throws XMLReaderException { super(filePath); - this.boatPolars = boatPolars; read(); } @@ -109,9 +104,9 @@ public class BoatXMLReader extends XMLReader implements BoatDataSource { String shortName = boatNode.getAttributes().getNamedItem("ShortName").getTextContent(); if (exists(boatNode, "Country")) { String country = boatNode.getAttributes().getNamedItem("Country").getTextContent(); - boatMap.put(sourceID, new Boat(sourceID, name, country, this.boatPolars)); + boatMap.put(sourceID, new Boat(sourceID, name, country)); } else { - boatMap.put(sourceID, new Boat(sourceID, name, shortName, this.boatPolars)); + boatMap.put(sourceID, new Boat(sourceID, name, shortName)); } } diff --git a/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java new file mode 100644 index 00000000..d9238bcc --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/RaceDataSource.java @@ -0,0 +1,72 @@ +package shared.dataInput; + +import network.Messages.Enums.RaceTypeEnum; +import shared.model.Boat; +import shared.model.CompoundMark; +import shared.model.GPSCoordinate; +import shared.model.Leg; + +import java.time.ZonedDateTime; +import java.util.List; + +/** + * An object that holds relevant data for a race.
+ * Information includes: {@link shared.model.Boat Boat}s, + * {@link shared.model.Leg Leg}s, {@link shared.model.CompoundMark CompoundMark}s and + * the {@link shared.model.GPSCoordinate GPSCoordinate}s. + */ +public interface RaceDataSource { + /** + * Returns the list of boats competing in the race. + * @return Boats competing in the race. + */ + List getBoats(); + + /** + * Returns the list of legs in the race. + * @return The list of legs in the race. + */ + List getLegs(); + + /** + * Returns a list of coordinates representing the boundary of the race. + * @return The boundary of the race. + */ + List getBoundary(); + + /** + * Returns a list of CompoundMarks in the race. + * @return + */ + List getCompoundMarks(); + + /** + * Returns the ID of the race. + * @return The ID of the race. + */ + int getRaceId(); + + /** + * Returns the type of race. + * @return The type of race. + */ + RaceTypeEnum getRaceType(); + + /** + * Returns the start time/date of the race. + * @return The race's start time. + */ + ZonedDateTime getZonedDateTime(); + + /** + * Returns the GPS coordinate of the top left of the race map area. + * @return Top left GPS coordinate. + */ + GPSCoordinate getMapTopLeft(); + + /** + * Returns the GPS coordinate of the bottom right of the race map area. + * @return Bottom right GPS coordinate. + */ + GPSCoordinate getMapBottomRight(); +} diff --git a/racevisionGame/src/main/java/visualiser/dataInput/RegattaXMLReader.java b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java similarity index 75% rename from racevisionGame/src/main/java/visualiser/dataInput/RegattaXMLReader.java rename to racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java index a1c3670f..812ba79e 100644 --- a/racevisionGame/src/main/java/visualiser/dataInput/RegattaXMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/RegattaXMLReader.java @@ -1,68 +1,91 @@ -package visualiser.dataInput; +package shared.dataInput; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import seng302.GPSCoordinate; +import shared.dataInput.XMLReader; +import shared.exceptions.XMLReaderException; +import shared.model.GPSCoordinate; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; /** - * Created by jjg64 on 19/04/17. + * XML reader class for regatta xml file. */ public class RegattaXMLReader extends XMLReader { + /** + * The regatta ID. + */ private int regattaID; + + /** + * The regatta name. + */ private String regattaName; + + /** + * The race ID. + */ private int raceID = 0; + + /** + * The course name. + */ private String courseName; + + /** + * The central latitude of the course. + */ private double centralLatitude; + + /** + * The central longitude of the course. + */ private double centralLongitude; + + /** + * The central altitude of the course. + */ private double centralAltitude; + + /** + * The UTC offset of the course. + */ private float utcOffset; - private float magneticVariation; /** - * Constructor for Regatta XML - * - * @param filePath path of the file - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * The magnetic variation of the course. */ - public RegattaXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - } + private float magneticVariation; + + /** * Constructor for Regatta XML * - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error + * @param filePath path of the file to read. Read as a resource. + * @throws XMLReaderException Thrown if the file cannot be parsed. */ - private RegattaXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException { + public RegattaXMLReader(String filePath) throws XMLReaderException { super(filePath); - if (read) { - read(); - } + read(); } + + /** * Alternate Constructor that takes in an inputstream instead * @param xmlString Input stream of the XML - * @throws IOException Error with input - * @throws SAXException Error with XML Format - * @throws ParserConfigurationException Error with XMl contents + * @throws XMLReaderException Thrown if the input stream cannot be parsed. */ - public RegattaXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException { + public RegattaXMLReader(InputStream xmlString) throws XMLReaderException { super(xmlString); read(); } + /** * Read the XML */ @@ -77,14 +100,19 @@ public class RegattaXMLReader extends XMLReader { * @param attributes attributes to extract information form. */ private void makeRegatta(Element attributes) { + this.regattaID = Integer.parseInt(getTextValueOfNode(attributes, "RegattaID")); this.regattaName = getTextValueOfNode(attributes, "RegattaName"); this.courseName = getTextValueOfNode(attributes, "CourseName"); + this.centralLatitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLatitude")); this.centralLongitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralLongitude")); this.centralAltitude = Double.parseDouble(getTextValueOfNode(attributes, "CentralAltitude")); + this.utcOffset = Float.parseFloat(getTextValueOfNode(attributes, "UtcOffset")); + this.magneticVariation = Float.parseFloat(getTextValueOfNode(attributes, "MagneticVariation")); + } public int getRegattaID() { diff --git a/racevisionGame/src/main/java/mock/dataInput/XMLReader.java b/racevisionGame/src/main/java/shared/dataInput/XMLReader.java similarity index 58% rename from racevisionGame/src/main/java/mock/dataInput/XMLReader.java rename to racevisionGame/src/main/java/shared/dataInput/XMLReader.java index 3ac56f54..189c8626 100644 --- a/racevisionGame/src/main/java/mock/dataInput/XMLReader.java +++ b/racevisionGame/src/main/java/shared/dataInput/XMLReader.java @@ -1,10 +1,10 @@ -package mock.dataInput; +package shared.dataInput; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import shared.exceptions.XMLReaderException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -16,7 +16,7 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.IOException; -import java.io.StringReader; +import java.io.InputStream; import java.io.StringWriter; /** @@ -27,38 +27,61 @@ public abstract class XMLReader { protected Document doc; /** - * Read in XML file - * @param filePath filepath for XML file - * @throws ParserConfigurationException If a document builder cannot be created. - * @throws IOException If any IO errors occur while parsing the XML file. - * @throws SAXException If any parse error occurs while parsing. + * Read an XML file by name as a resource. + * @param filePath filepath for XML file. Loaded as a resource. + * @throws XMLReaderException Thrown if the file cannot be parsed. */ - public XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException { + public XMLReader(String filePath) throws XMLReaderException { - InputSource fXmlFile; - if (filePath.contains("<")) { - fXmlFile = new InputSource(); - fXmlFile.setCharacterStream(new StringReader(filePath)); + //Read file as resource. + InputStream xmlInputStream = getClass().getClassLoader().getResourceAsStream(filePath); - } else { - fXmlFile = new InputSource(getClass().getClassLoader().getResourceAsStream(filePath)); - } + this.doc = parseInputStream(xmlInputStream); - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(fXmlFile); - doc.getDocumentElement().normalize(); } /** - * Alternate constructor - * @param xmlFile File to be read - * @param isWholeFile boolean value whether entire file is being passed + * Reads an XML file from an input stream. + * @param xmlInputStream The input stream to parse. + * @throws XMLReaderException Thrown if the input stream cannot be parsed. */ - public XMLReader(String xmlFile, Boolean isWholeFile) { + public XMLReader(InputStream xmlInputStream) throws XMLReaderException { + this.doc = parseInputStream(xmlInputStream); } + + /** + * Parses an input stream into a document. + * @param inputStream The xml input stream to parse. + * @return The parsed document. + * @throws XMLReaderException Thrown when a document builder cannot be constructed, or the stream cannot be parsed. + */ + private Document parseInputStream(InputStream inputStream) throws XMLReaderException { + + //Create document builder. + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + + DocumentBuilder dBuilder = null; + try { + dBuilder = dbFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new XMLReaderException("Could not create a DocumentBuilder.", e); + } + + //Parse document. + Document document = null; + try { + document = dBuilder.parse(inputStream); + } catch (SAXException | IOException e) { + throw new XMLReaderException("Could not parse the xml input stream.", e); + } + document.getDocumentElement().normalize(); + + return document; + } + + /** * Return Document data of the read-in XML * @return XML document diff --git a/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java b/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java new file mode 100644 index 00000000..7948be58 --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/XMLReaderException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when an XMLReader cannot be constructed for some reason. + */ +public class XMLReaderException extends Exception { + + public XMLReaderException(String message) { + super(message); + } + + public XMLReaderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/visualiser/dataInput/BoatXMLReader.java b/racevisionGame/src/main/java/visualiser/dataInput/BoatXMLReader.java deleted file mode 100644 index c0e562f4..00000000 --- a/racevisionGame/src/main/java/visualiser/dataInput/BoatXMLReader.java +++ /dev/null @@ -1,156 +0,0 @@ -package visualiser.dataInput; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; -import seng302.Model.Boat; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * XML Reader that reads in a file and initializes - * {@link seng302.Mock.StreamedBoat StreamedBoat}s that will be participating - * in a race. - */ -public class BoatXMLReader extends XMLReader { - private final Map streamedBoatMap = new HashMap<>(); - private Map participants = new HashMap<>(); - - /** - * Constructor for Boat XML Reader - * @param filePath path of the file - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { - this(filePath, true); - } - - /** - * Constructor for Boat XML Reader - * @param filePath file path to read - * @param read whether or not to read and store the files straight away. - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(String filePath, boolean read) throws IOException, SAXException, ParserConfigurationException { - super(filePath); - if (read) { - read(); - } - } - - /** - * Constructor for Boat XML Reader - * @param xmlString sting to read - * @throws IOException error - * @throws SAXException error - * @throws ParserConfigurationException error - */ - public BoatXMLReader(InputStream xmlString) throws IOException, SAXException, ParserConfigurationException { - super(xmlString); - read(); - } - - public void read() { - readSettings(); - readShapes(); - readBoats(); - } - - /** - * Reads boats settings. - * INFORMATION FROM HERE IS IGNORED FOR NOW - */ - private void readSettings() { - - } - - /** - * Reads different kinds of boat. - * INFORMATION FROM HERE IS IGNORED FOR NOW - */ - private void readShapes() { - - } - - /** - * Reads the boats in the race - */ - private void readBoats() { - Element nBoats = (Element) doc.getElementsByTagName("Boats").item(0); - for (int i = 0; i < nBoats.getChildNodes().getLength(); i++) { - Node boat = nBoats.getChildNodes().item(i); - if (boat.getNodeName().equals("Boat") && boat.getAttributes().getNamedItem("Type").getTextContent().equals("Yacht")) { - readSingleBoat(boat); - } - } - } - - /** - * Reads the information about one boat - * Ignored values: ShapeID, StoweName, HullNum, Skipper, Type - * @param boat The node to read boat data from. - */ - private void readSingleBoat(Node boat) { - StreamedBoat streamedBoat; - String country = null; - int sourceID = Integer.parseInt(boat.getAttributes().getNamedItem("SourceID").getTextContent()); - String boatName = boat.getAttributes().getNamedItem("BoatName").getTextContent(); - String shortName = boat.getAttributes().getNamedItem("ShortName").getTextContent(); - if (exists(boat, "Country")) country = boat.getAttributes().getNamedItem("Country").getTextContent(); - - // Ignore all non participating boats - if (participants.containsKey(sourceID)) { - - if (!streamedBoatMap.containsKey(sourceID)) { - if (country != null) { - streamedBoat = new StreamedBoat(sourceID, boatName, country); - } else { - streamedBoat = new StreamedBoat(sourceID, boatName, shortName); - } - streamedBoatMap.put(sourceID, streamedBoat); - // Override boat with new boat - participants.put(sourceID, streamedBoat); - } - - for (int i = 0; i < boat.getChildNodes().getLength(); i++) { - Node GPSposition = boat.getChildNodes().item(i); - if (GPSposition.getNodeName().equals("GPSposition")) - readBoatPositionInformation(sourceID, GPSposition); - } - } - } - - - /** - * Reads the positional information about a boat - * Ignored values: FlagPosition, MastTop, Z value of GPSPosition - * @param sourceID The source ID of the boat. - * @param GPSposition The relative GPS position of the boat. - */ - private void readBoatPositionInformation(int sourceID, Node GPSposition) { - // TODO Get relative point before implementing. (GPSposition is based - // off a relative point). - } - - /** - * Sets the participants - * @param participants boats participating the race mapped by their source ID's - */ - public void setParticipants(Map participants) { - this.participants = participants; - } - - public List getBoats() { - return new ArrayList<>(streamedBoatMap.values()); - } -} diff --git a/racevisionGame/src/main/java/visualiser/dataInput/RaceDataSource.java b/racevisionGame/src/main/java/visualiser/dataInput/RaceDataSource.java deleted file mode 100644 index fb6ebcba..00000000 --- a/racevisionGame/src/main/java/visualiser/dataInput/RaceDataSource.java +++ /dev/null @@ -1,26 +0,0 @@ -package visualiser.dataInput; - -import seng302.Model.Boat; -import seng302.Model.Leg; -import seng302.Model.Marker; - -import java.time.ZonedDateTime; -import java.util.List; - -/** - * An object that holds relevant data for a race.
- * Information includes: {@link seng302.Model.Boat Boat}s, - * {@link seng302.Model.Leg Leg}s, {@link seng302.Model.Marker Marker}s and - * the {@link seng302.GPSCoordinate GPSCoordinate}s to create a - * {@link seng302.Model.ResizableRaceMap ResizableRaceMap}. - */ -public interface RaceDataSource { - List getBoats(); - List getLegs(); - List getMarkers(); - List getBoundary(); - - ZonedDateTime getZonedDateTime(); - GPSCoordinate getMapTopLeft(); - GPSCoordinate getMapBottomRight(); -} diff --git a/racevisionGame/src/main/java/visualiser/dataInput/XMLReader.java b/racevisionGame/src/main/java/visualiser/dataInput/XMLReader.java deleted file mode 100644 index 146433cd..00000000 --- a/racevisionGame/src/main/java/visualiser/dataInput/XMLReader.java +++ /dev/null @@ -1,52 +0,0 @@ -package visualiser.dataInput; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; - -/** - * The abstract class for reading in XML race data. - */ -public abstract class XMLReader { - - protected Document doc; - - protected XMLReader(String filePath) throws ParserConfigurationException, IOException, SAXException { - InputStream fXmlFile = getClass().getClassLoader().getResourceAsStream(filePath); - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(fXmlFile); - doc.getDocumentElement().normalize(); - } - - protected XMLReader(InputStream xmlInput) throws ParserConfigurationException, IOException, SAXException { - - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(xmlInput); - } - - public Document getDocument() { - return doc; - } - - protected String getTextValueOfNode(Element n, String tagName) { - return n.getElementsByTagName(tagName).item(0).getTextContent(); - } - - protected boolean exists(Node node, String attribute) { - return node.getAttributes().getNamedItem(attribute) != null; - } - - public String getAttribute(Element n, String attr) { - return n.getAttribute(attr); - } - -}