You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
404 lines
12 KiB
404 lines
12 KiB
package visualiser.model;
|
|
|
|
|
|
import javafx.collections.FXCollections;
|
|
import javafx.collections.ObservableList;
|
|
import javafx.scene.paint.Color;
|
|
import network.Messages.Enums.BoatStatusEnum;
|
|
import shared.dataInput.BoatDataSource;
|
|
import shared.dataInput.RaceDataSource;
|
|
import shared.dataInput.RegattaDataSource;
|
|
import shared.exceptions.BoatNotFoundException;
|
|
import shared.exceptions.MarkNotFoundException;
|
|
import shared.model.*;
|
|
|
|
import java.time.Duration;
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
* This class contains all of the state of a race on the client (visualiser) side.
|
|
*/
|
|
public class VisualiserRaceState extends RaceState {
|
|
|
|
|
|
/**
|
|
* A list of boats in the race.
|
|
*/
|
|
private ObservableList<VisualiserBoat> boats;
|
|
|
|
/**
|
|
* The source ID of the boat assigned to the player.
|
|
* 0 if no boat has been assigned.
|
|
*/
|
|
private int playerBoatID;
|
|
|
|
|
|
/**
|
|
* Maps between a Leg to a list of boats, in the order that they finished the leg.
|
|
* Used by the Sparkline to ensure it has correct information.
|
|
* TODO BUG: if we receive a race.xml file during the race, then we need to add/remove legs to this, without losing information.
|
|
*/
|
|
private Map<Leg, List<VisualiserBoat>> legCompletionOrder;
|
|
|
|
|
|
|
|
|
|
/**
|
|
* An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor.
|
|
*/
|
|
private List<Color> unassignedColors = new ArrayList<>(Arrays.asList(
|
|
Color.BLUEVIOLET,
|
|
Color.BLACK,
|
|
Color.RED,
|
|
Color.ORANGE,
|
|
Color.DARKOLIVEGREEN,
|
|
Color.LIMEGREEN,
|
|
Color.PURPLE,
|
|
Color.DARKGRAY,
|
|
Color.YELLOW
|
|
//TODO may need to add more colors.
|
|
));
|
|
|
|
|
|
/**
|
|
* Constructs a visualiser race which models a yacht race.
|
|
* @param raceDataSource The raceDataSource to initialise with.
|
|
* @param regattaDataSource The regattaDataSource to initialise with.
|
|
* @param boatDataSource The boatDataSource to initialise with.
|
|
*/
|
|
public VisualiserRaceState(RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, BoatDataSource boatDataSource) {
|
|
|
|
this.boats = FXCollections.observableArrayList();
|
|
|
|
this.playerBoatID = 0;
|
|
|
|
this.legCompletionOrder = new HashMap<>();
|
|
|
|
|
|
setRaceDataSource(raceDataSource);
|
|
setRegattaDataSource(regattaDataSource);
|
|
setBoatDataSource(boatDataSource);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Sets the race data source for this race to a new RaceDataSource.
|
|
* Uses the boundary and legs specified by the new RaceDataSource.
|
|
* @param raceDataSource The new RaceDataSource to use.
|
|
*/
|
|
public void setRaceDataSource(RaceDataSource raceDataSource) {
|
|
super.setRaceDataSource(raceDataSource);
|
|
|
|
if (getBoatDataSource() != null) {
|
|
this.generateVisualiserBoats(this.boats, getBoatDataSource().getBoats(), raceDataSource.getParticipants(), unassignedColors);
|
|
}
|
|
|
|
initialiseLegCompletionOrder();
|
|
}
|
|
|
|
/**
|
|
* Sets the boat data source for this race to a new BoatDataSource.
|
|
* Uses the marker boats specified by the new BoatDataSource.
|
|
* @param boatDataSource The new BoatDataSource to use.
|
|
*/
|
|
public void setBoatDataSource(BoatDataSource boatDataSource) {
|
|
super.setBoatDataSource(boatDataSource);
|
|
|
|
if (getRaceDataSource() != null) {
|
|
this.generateVisualiserBoats(this.boats, boatDataSource.getBoats(), getRaceDataSource().getParticipants(), unassignedColors);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the regatta data source for this race to a new RegattaDataSource.
|
|
* @param regattaDataSource The new RegattaDataSource to use.
|
|
*/
|
|
public void setRegattaDataSource(RegattaDataSource regattaDataSource) {
|
|
super.setRegattaDataSource(regattaDataSource);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialises the {@link #legCompletionOrder} map.
|
|
*/
|
|
public void initialiseLegCompletionOrder() {
|
|
//Initialise the leg completion order map.
|
|
for (Leg leg : getLegs()) {
|
|
this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size()));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Generates a list of VisualiserBoats given a list of Boats, and a list of participating boats.
|
|
* This will add VisualiserBoats for newly participating sourceID, and remove VisualiserBoats for any participating sourceIDs that have been removed.
|
|
*
|
|
* @param existingBoats The visualiser boats that already exist in the race. This will be populated when we receive a new race.xml or boats.xml.
|
|
* @param boats The map of {@link Boat}s describing boats that are potentially in the race. Maps boat sourceID to boat.
|
|
* @param sourceIDs The list of boat sourceIDs describing which specific boats are actually participating.
|
|
* @param colors The list of unassignedColors to be used for the boats.
|
|
*/
|
|
private void generateVisualiserBoats(ObservableList<VisualiserBoat> existingBoats, Map<Integer, Boat> boats, List<Integer> sourceIDs, List<Color> colors) {
|
|
|
|
//Remove any VisualiserBoats that are no longer participating.
|
|
for (VisualiserBoat boat : new ArrayList<>(existingBoats)) {
|
|
|
|
//Boat no longer is participating.
|
|
if (!sourceIDs.contains(boat.getSourceID())) {
|
|
//Return their colors to the color list.
|
|
colors.add(boat.getColor());
|
|
|
|
//Remove boat.
|
|
existingBoats.remove(boat);
|
|
}
|
|
|
|
}
|
|
|
|
//Get source IDs of already existing boats.
|
|
List<Integer> existingBoatIDs = new ArrayList<>();
|
|
for (VisualiserBoat boat : existingBoats) {
|
|
existingBoatIDs.add(boat.getSourceID());
|
|
}
|
|
|
|
//Get source IDs of only newly participating boats.
|
|
List<Integer> newBoatIDs = new ArrayList<>(sourceIDs);
|
|
newBoatIDs.removeAll(existingBoatIDs);
|
|
|
|
//Create VisualiserBoat for newly participating boats.
|
|
for (Integer sourceID : newBoatIDs) {
|
|
|
|
if (boats.containsKey(sourceID)) {
|
|
|
|
VisualiserBoat boat = new VisualiserBoat(
|
|
boats.get(sourceID),
|
|
colors.remove(colors.size() - 1));//TODO potential bug: not enough colors for boats.
|
|
|
|
boat.setCurrentLeg(getLegs().get(0));
|
|
|
|
existingBoats.add(boat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setPlayerBoat();
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the boat the player has been assigned to as belonging to them.
|
|
*/
|
|
private void setPlayerBoat() {
|
|
|
|
if (getPlayerBoatID() != 0) {
|
|
|
|
for (VisualiserBoat boat : new ArrayList<>(getBoats())) {
|
|
|
|
if (boat.getSourceID() == getPlayerBoatID()) {
|
|
boat.setClientBoat(true);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise the boats in the race.
|
|
* This sets their current leg.
|
|
*/
|
|
@Override
|
|
protected void initialiseBoats() {
|
|
|
|
Leg startingLeg = getLegs().get(0);
|
|
|
|
for (VisualiserBoat boat : boats) {
|
|
boat.setCurrentLeg(startingLeg);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Update position of boats in race (e.g, 5th), no position if on starting leg or DNF.
|
|
* @param boats The list of boats to update.
|
|
*/
|
|
public void updateBoatPositions(List<VisualiserBoat> boats) {
|
|
|
|
//Sort boats.
|
|
sortBoatsByPosition(boats);
|
|
|
|
//Assign new positions.
|
|
for (int i = 0; i < boats.size(); i++) {
|
|
VisualiserBoat boat = boats.get(i);
|
|
|
|
|
|
if ((boat.getStatus() == BoatStatusEnum.DNF) || (boat.getStatus() == BoatStatusEnum.PRESTART) || (boat.getCurrentLeg().getLegNumber() < 0)) {
|
|
|
|
boat.setPlacing("-");
|
|
|
|
} else {
|
|
boat.setPlacing(Integer.toString(i + 1));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Sorts the list of boats by their position within the race.
|
|
* @param boats The list of boats in the race.
|
|
*/
|
|
private void sortBoatsByPosition(List<VisualiserBoat> boats) {
|
|
|
|
boats.sort((a, b) -> {
|
|
//Get the difference in leg numbers.
|
|
int legNumberDelta = b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber();
|
|
|
|
//If they're on the same leg, we need to compare time to finish leg.
|
|
if (legNumberDelta == 0) {
|
|
|
|
//These are potentially null until we receive our first RaceStatus containing BoatStatuses.
|
|
if ((a.getEstimatedTimeAtNextMark() != null) && (b.getEstimatedTimeAtNextMark() != null)) {
|
|
|
|
return (int) Duration.between(
|
|
b.getEstimatedTimeAtNextMark(),
|
|
a.getEstimatedTimeAtNextMark() ).toMillis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return legNumberDelta;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Returns the boats participating in the race.
|
|
* @return List of boats participating in the race.
|
|
*/
|
|
public ObservableList<VisualiserBoat> getBoats() {
|
|
return boats;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a boat by sourceID.
|
|
* @param sourceID The source ID the boat.
|
|
* @return The boat.
|
|
* @throws BoatNotFoundException Thrown if there is no boat with the specified sourceID.
|
|
*/
|
|
public VisualiserBoat getBoat(int sourceID) throws BoatNotFoundException {
|
|
|
|
for (VisualiserBoat boat : boats) {
|
|
|
|
if (boat.getSourceID() == sourceID) {
|
|
return boat;
|
|
}
|
|
|
|
}
|
|
|
|
throw new BoatNotFoundException("Boat with sourceID: " + sourceID + " was not found.");
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not there exists a {@link VisualiserBoat} with the given source ID.
|
|
* @param sourceID SourceID of VisualiserBoat.
|
|
* @return True if VisualiserBoat exists, false otherwise.
|
|
*/
|
|
public boolean isVisualiserBoat(int sourceID) {
|
|
|
|
try {
|
|
getBoat(sourceID);
|
|
return true;
|
|
} catch (BoatNotFoundException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a mark by sourceID.
|
|
* @param sourceID The source ID the mark.
|
|
* @return The mark.
|
|
* @throws MarkNotFoundException Thrown if there is no mark with the specified sourceID.
|
|
*/
|
|
public Mark getMark(int sourceID) throws MarkNotFoundException {
|
|
|
|
for (Mark mark : getMarks()) {
|
|
|
|
if (mark.getSourceID() == sourceID) {
|
|
return mark;
|
|
}
|
|
|
|
}
|
|
|
|
throw new MarkNotFoundException("Mark with sourceID: " + sourceID + " was not found.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns whether or not there exists a {@link Mark} with the given source ID.
|
|
* @param sourceID SourceID of mark.
|
|
* @return True if mark exists, false otherwise.
|
|
*/
|
|
public boolean isMark(int sourceID) {
|
|
|
|
try {
|
|
getMark(sourceID);
|
|
return true;
|
|
} catch (MarkNotFoundException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Returns the order in which boats completed each leg. Maps the leg to a list of boats, ordered by the order in which they finished the leg.
|
|
* @return Leg completion order for each leg.
|
|
*/
|
|
public Map<Leg, List<VisualiserBoat>> getLegCompletionOrder() {
|
|
return legCompletionOrder;
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the source ID of the player's boat. 0 if not assigned.
|
|
* @return Players boat source ID.
|
|
*/
|
|
public int getPlayerBoatID() {
|
|
return playerBoatID;
|
|
}
|
|
|
|
/**
|
|
* sets the source ID of the player's boat. 0 if not assigned.
|
|
* @param playerBoatID Players boat source ID.
|
|
*/
|
|
public void setPlayerBoatID(int playerBoatID) {
|
|
this.playerBoatID = playerBoatID;
|
|
setPlayerBoat();
|
|
}
|
|
|
|
|
|
}
|