package shared.model; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import shared.dataInput.RegattaDataSource; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import java.util.Observable; /** * Represents a yacht race. * This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRaceState}. * Has a course, state, wind, boundaries, etc.... Boats are added by inheriting classes (see {@link Boat}, {@link mock.model.MockBoat}, {@link visualiser.model.VisualiserBoat}. */ public abstract class RaceState extends Observable{ /** * Data source for race information. */ private RaceDataSource raceDataSource; /** * Data source for boat information. */ private BoatDataSource boatDataSource; /** * Data source for regatta information. */ private RegattaDataSource regattaDataSource; /** * Legs in the race. * We have this in a separate list so that it can be observed. */ private ObservableList legs; /** * The sequence of rounding points for each leg/mark. */ private MarkRoundingSequence markRoundingSequence; /** * The clock which tracks the race's start time, current time, and elapsed duration. */ private RaceClock raceClock; /** * The current status of the race. */ private RaceStatusEnum raceStatusEnum; /** * The race's wind. */ private Property raceWind = new SimpleObjectProperty<>(); /** * Constructs an empty race object. * This is initialised into a "default" state, with no data. */ public RaceState() { //Legs. this.legs = FXCollections.observableArrayList(); //Race clock. this.raceClock = new RaceClock(ZonedDateTime.now()); //Race status. this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); //Wind. this.setWind(Bearing.fromDegrees(0), 0); } /** * Initialise the boats in the race. * This sets their starting positions and current legs. */ protected abstract void initialiseBoats(); /** * Updates the race to use a new list of legs, and adds a dummy "Finish" leg at the end. * @param legs The new list of legs to use. */ protected void useLegsList(List legs) { this.legs.setAll(legs); //We create this before adding the extra finish leg, as it doesn't contain compound marks. this.markRoundingSequence = new MarkRoundingSequence(getLegs()); //We add a "dummy" leg at the end of the race. if (getLegs().size() > 0) { getLegs().add(new Leg("Finish", getLegs().size())); } } /** * Determines whether or not a specific leg is the last leg in the race. * @param leg The leg to check. * @return Returns true if it is the last, false otherwise. */ protected boolean isLastLeg(Leg leg) { //Get the last leg. Leg lastLeg = getLegs().get(getLegs().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 race data source for the race. * @param raceDataSource New race data source. */ public void setRaceDataSource(RaceDataSource raceDataSource) { this.raceDataSource = raceDataSource; this.getRaceClock().setStartingTime(raceDataSource.getStartDateTime()); useLegsList(raceDataSource.getLegs()); } /** * Sets the boat data source for the race. * @param boatDataSource New boat data source. */ public void setBoatDataSource(BoatDataSource boatDataSource) { this.boatDataSource = boatDataSource; } /** * Sets the regatta data source for the race. * @param regattaDataSource New regatta data source. */ public void setRegattaDataSource(RegattaDataSource regattaDataSource) { this.regattaDataSource = regattaDataSource; } /** * Returns the race data source for the race. * @return Race data source. */ public RaceDataSource getRaceDataSource() { return raceDataSource; } /** * Returns the race data source for the race. * @return Race data source. */ public BoatDataSource getBoatDataSource() { return boatDataSource; } /** * Returns the race data source for the race. * @return Race data source. */ public RegattaDataSource getRegattaDataSource() { return regattaDataSource; } /** * Returns a list of {@link Mark} boats. * @return List of mark boats. */ public List getMarks() { //BoatDataSource contains a collection of Marks, and RaceDataSource contains a collection of Compound marks (which contain marks). RaceDataSource is the "definitive" source of mark data. List marks = new ArrayList<>(getCompoundMarks().size() * 2); for (CompoundMark compoundMark : getCompoundMarks()) { if (compoundMark.getMark1() != null) { marks.add(compoundMark.getMark1()); } if (compoundMark.getMark2() != null) { marks.add(compoundMark.getMark2()); } } return marks; } /** * Returns a list of sourceIDs participating in the race. * @return List of sourceIDs participating in the race. */ public List getParticipants() { return raceDataSource.getParticipants(); } /** * Returns the current race status. * @return The current race status. */ public RaceStatusEnum getRaceStatusEnum() { return raceStatusEnum; } /** * Sets the current race status. * @param raceStatusEnum The new status of the race. */ public void setRaceStatusEnum(RaceStatusEnum raceStatusEnum) { this.raceStatusEnum = raceStatusEnum; } /** * Returns the type of race this is. * @return The type of race this is. */ public RaceTypeEnum getRaceType() { return raceDataSource.getRaceType(); } /** * Returns the name of the regatta. * @return The name of the regatta. */ public String getRegattaName() { return regattaDataSource.getRegattaName(); } /** * Updates the race to have a specified wind bearing and speed. * @param windBearing New wind bearing. * @param windSpeedKnots New wind speed, in knots. */ public void setWind(Bearing windBearing, double windSpeedKnots) { Wind wind = new Wind(windBearing, windSpeedKnots); setWind(wind); } /** * Updates the race to have a specified wind (bearing and speed). * @param wind New wind. */ public void setWind(Wind wind) { this.raceWind.setValue(wind); } /** * Returns the wind bearing. * @return The wind bearing. */ public Bearing getWindDirection() { return raceWind.getValue().getWindDirection(); } /** * Returns the wind speed. * Measured in knots. * @return The wind speed. */ public double getWindSpeed() { return raceWind.getValue().getWindSpeed(); } /** * Returns the race's wind. * @return The race's wind. */ public Property windProperty() { return raceWind; } /** * Returns the RaceClock for this race. * This is used to track the start time, current time, and elapsed duration of the race. * @return The RaceClock for the race. */ public RaceClock getRaceClock() { return raceClock; } /** * Returns the number of legs in the race. * @return The number of legs in the race. */ public int getLegCount() { //We minus one, as we have added an extra "dummy" leg. return getLegs().size() - 1; } /** * Returns the race boundary. * @return The race boundary. */ public List getBoundary() { return raceDataSource.getBoundary(); } /** * Returns the marks of the race. * @return Marks of the race. */ public List getCompoundMarks() { return raceDataSource.getCompoundMarks(); } /** * Returns the legs of the race. * @return Legs of the race. */ public ObservableList getLegs() { return legs; } /** * Returns the ID of the race. * @return ID of the race. */ public int getRaceId() { return raceDataSource.getRaceId(); } /** * Returns the ID of the regatta. * @return The ID of the regatta. */ public int getRegattaID() { return regattaDataSource.getRegattaID(); } /** * Returns the name of the course. * @return Name of the course. */ public String getCourseName() { return regattaDataSource.getCourseName(); } /** * Returns the rounding sequences for each leg. * @return Rounding sequence for each leg. */ public MarkRoundingSequence getMarkRoundingSequence() { return markRoundingSequence; } }