package shared.model; import javafx.beans.property.IntegerProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import mock.model.collider.ColliderRegistry; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import shared.dataInput.RegattaDataSource; import java.util.List; /** * Represents a yacht race. * 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 Race { /** * The source of race related data. */ protected RaceDataSource raceDataSource; /** * The source of boat related data. */ protected BoatDataSource boatDataSource; /** * The source of regatta related data. */ protected RegattaDataSource regattaDataSource; /** * A list of compound marks in the race. */ protected List compoundMarks; /** * A list of legs in the race. */ protected List legs; /** * A list of coordinates describing the boundary of the course. */ protected List boundary; /** * The clock which tracks the race's start time, current time, and elapsed duration. */ protected RaceClock raceClock; /** * The race ID of the course. */ protected int raceId; /** * The name of the regatta. */ protected String regattaName; /** * The current status of the race. */ protected RaceStatusEnum raceStatusEnum; /** * The type of race this is. */ protected RaceTypeEnum raceType; /** * The race's wind. */ protected Property raceWind = new SimpleObjectProperty<>(); /** * Registry for all collider object in this race */ protected ColliderRegistry colliderRegistry; /** * The number of frames per second. * We essentially track the number of frames generated per second, over a one second period. When {@link #lastFpsResetTime} reaches 1 second, currentFps is reset. */ private int currentFps = 0; /** * The number of frames per second we generated over the last 1 second period. */ private IntegerProperty lastFps = new SimpleIntegerProperty(0); /** * The time, in milliseconds, since we last reset our {@link #currentFps} counter. */ private long lastFpsResetTime; /** * Constructs a race object with a given BoatDataSource, RaceDataSource, and RegattaDataSource. * @param boatDataSource Data source for boat related data (yachts and marker boats). * @param raceDataSource Data source for race related data (participating boats, legs, etc...). * @param regattaDataSource Data source for race related data (course name, location, timezone, etc...). */ public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource) { //Keep a reference to data sources. this.raceDataSource = raceDataSource; this.boatDataSource = boatDataSource; this.regattaDataSource = regattaDataSource; //Marks. this.compoundMarks = raceDataSource.getCompoundMarks(); //Boundaries. this.boundary = raceDataSource.getBoundary(); //Legs. this.useLegsList(raceDataSource.getLegs()); //Race ID. this.raceId = raceDataSource.getRaceId(); //Regatta name. this.regattaName = regattaDataSource.getRegattaName(); //Race clock. this.raceClock = new RaceClock(this.raceDataSource.getStartDateTime()); //Race status. this.setRaceStatusEnum(RaceStatusEnum.NOT_ACTIVE); //Race type. this.raceType = raceDataSource.getRaceType(); //Wind. this.setWind(Bearing.fromDegrees(0), 0); // Set up colliders this.colliderRegistry = new ColliderRegistry(); for(CompoundMark mark: compoundMarks) { colliderRegistry.addCollider(mark.getMark1()); if(mark.getMark2() != null) colliderRegistry.addCollider(mark.getMark2()); } } public ColliderRegistry getColliderRegistry() { return colliderRegistry; } /** * 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) { //We add a "dummy" leg at the end of the race. this.legs = legs; this.legs.add(new Leg("Finish", this.legs.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 = 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; } /** * 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 raceType; } /** * Returns the name of the regatta. * @return The name of the regatta. */ public String getRegattaName() { return regattaName; } /** * Updates the race to have a specified wind bearing and speed. * @param windBearing New wind bearing. * @param windSpeedKnots New wind speed, in knots. */ protected 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. */ protected 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 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 RaceDataSource used for the race. * @return The RaceDataSource used for the race. */ public RaceDataSource getRaceDataSource() { return raceDataSource; } /** * 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 legs.size() - 1; } /** * Returns the race boundary. * @return The race boundary. */ public List getBoundary() { return boundary; } /** * Returns the marks of the race. * @return Marks of the race. */ public List getCompoundMarks() { return compoundMarks; } /** * Returns the legs of the race. * @return Legs of the race. */ public List getLegs() { return legs; } /** * Returns the fps property. * @return The fps property. */ public IntegerProperty fpsProperty() { return lastFps; } /** * Increments the FPS counter, and adds timePeriod milliseconds to our FPS reset timer. * @param timePeriod Time, in milliseconds, to add to {@link #lastFpsResetTime}. */ protected void incrementFps(long timePeriod) { //Increment. this.currentFps++; //Add period to timer. this.lastFpsResetTime += timePeriod; //If we have reached 1 second period, snapshot the framerate and reset. if (this.lastFpsResetTime > 1000) { this.lastFps.set(this.currentFps); this.currentFps = 0; this.lastFpsResetTime = 0; } } public int getRaceId() { return raceId; } }