diff --git a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java index ba22fef0..d399210d 100644 --- a/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java +++ b/racevisionGame/src/main/java/mock/app/ConnectionAcceptor.java @@ -13,7 +13,12 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,7 +41,7 @@ public class ConnectionAcceptor implements Runnable { /** * List of client connections. */ - private ArrayBlockingQueue clientConnections = new ArrayBlockingQueue<>(16, true); + private BlockingQueue clientConnections = new ArrayBlockingQueue<>(16, true); /** * Snapshot of the race. @@ -137,13 +142,13 @@ public class ConnectionAcceptor implements Runnable { */ class CheckClientConnection implements Runnable{ - private ArrayBlockingQueue connections; + private BlockingQueue connections; /** * Constructor * @param connections Clients "connected" */ - public CheckClientConnection(ArrayBlockingQueue connections){ + public CheckClientConnection(BlockingQueue connections){ this.connections = connections; } @@ -153,13 +158,29 @@ public class ConnectionAcceptor implements Runnable { @Override public void run() { - while(true) { - //System.out.println(connections.size());//used to see current amount of visualisers connected. - ArrayBlockingQueue clientConnections = new ArrayBlockingQueue<>(16, true, connections); + //We track the number of times each connection fails the !isAlive() test. + //This is to give a bit of lee-way in case the connection checker checks a connection before its thread has actually started. + Map connectionDeadCount = new HashMap<>(); + + while(!Thread.interrupted()) { + + //Make copy of connections. + List clientConnections = new ArrayList<>(connections); + for (ClientConnection client : clientConnections) { + + connectionDeadCount.put(client, connectionDeadCount.getOrDefault(client, 0)); + if (!client.isAlive()) { + //Add one to fail count. + connectionDeadCount.put(client, connectionDeadCount.get(client) + 1); + } + + //We only remove them if they fail 5 times. + if (connectionDeadCount.get(client) > 5) { connections.remove(client); + connectionDeadCount.remove(client); client.terminate(); Logger.getGlobal().log(Level.WARNING, "CheckClientConnection is removing the dead connection: " + client); diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java index b5b9865f..f0783fc0 100644 --- a/racevisionGame/src/main/java/mock/app/Event.java +++ b/racevisionGame/src/main/java/mock/app/Event.java @@ -127,7 +127,6 @@ public class Event { boatDataSource, raceDataSource, regattaDataSource, - this.latestMessages, this.boatPolars, Constants.RaceTimeScale, windGenerator ), diff --git a/racevisionGame/src/main/java/mock/model/ClientConnection.java b/racevisionGame/src/main/java/mock/model/ClientConnection.java index 23e00819..d2ca7609 100644 --- a/racevisionGame/src/main/java/mock/model/ClientConnection.java +++ b/racevisionGame/src/main/java/mock/model/ClientConnection.java @@ -245,7 +245,7 @@ public class ClientConnection implements Runnable { private void sendJoinAcceptanceMessage(int sourceID) throws HandshakeException { //Send them the source ID. - JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID); + JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, sourceID); try { outputQueue.put(joinAcceptance); diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java index 2f23712f..3d895478 100644 --- a/racevisionGame/src/main/java/mock/model/MockRace.java +++ b/racevisionGame/src/main/java/mock/model/MockRace.java @@ -53,14 +53,13 @@ public class MockRace extends Race { * @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...). - * @param latestMessages The LatestMessages to send events to. * @param polars The polars table to be used for boat simulation. * @param timeScale The timeScale for the race. See {@link Constants#RaceTimeScale}. * @param windGenerator The wind generator used for the race. */ - public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, Polars polars, int timeScale, WindGenerator windGenerator) { + public MockRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, Polars polars, int timeScale, WindGenerator windGenerator) { - super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); + super(boatDataSource, raceDataSource, regattaDataSource); this.scaleFactor = timeScale; @@ -456,7 +455,5 @@ public class MockRace extends Race { } - public List getCompoundMarks() { - return compoundMarks; - } + } diff --git a/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java b/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java index b767b880..5b5bcbf5 100644 --- a/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java +++ b/racevisionGame/src/main/java/network/MessageRouters/MessageRouter.java @@ -103,15 +103,19 @@ public class MessageRouter implements RunnableWithFramePeriod { AC35Data message = incomingMessages.take(); - if (routeMap.containsKey(message.getType())) { - //We have a route. - routeMap.get(message.getType()).put(message); + BlockingQueue queue = routeMap.get(message.getType()); + + if (queue != null) { + queue.put(message); } else { //No route. Use default. - if (defaultRoute.isPresent()) { - defaultRoute.get().put(message); + BlockingQueue defaultQueue = defaultRoute.orElse(null); + + if (defaultQueue != null) { + defaultQueue.put(message); } + } diff --git a/racevisionGame/src/main/java/network/Messages/AssignPlayerBoat.java b/racevisionGame/src/main/java/network/Messages/AssignPlayerBoat.java new file mode 100644 index 00000000..c4197fe9 --- /dev/null +++ b/racevisionGame/src/main/java/network/Messages/AssignPlayerBoat.java @@ -0,0 +1,39 @@ +package network.Messages; + +import network.Messages.Enums.JoinAcceptanceEnum; +import network.Messages.Enums.MessageType; + + +/** + * This is the message the client generates and sends to itself once the server has assigned a boat source ID with {@link JoinAcceptance}. + */ +public class AssignPlayerBoat extends AC35Data { + + + /** + * The source ID of the boat assigned to the client. + * 0 indicates they haven't been assigned a boat. + */ + private int sourceID = 0; + + + + + /** + * Constructs a AssignPlayerBoat message. + * @param sourceID The sourceID to assign to the client. 0 indicates no sourceID. + */ + public AssignPlayerBoat(int sourceID){ + super(MessageType.ASSIGN_PLAYER_BOAT); + this.sourceID = sourceID; + } + + + /** + * Returns the source ID of the boat assigned to the client. + * @return The source ID of the boat assigned to the client. + */ + public int getSourceID() { + return sourceID; + } +} diff --git a/racevisionGame/src/main/java/network/Messages/BoatStatus.java b/racevisionGame/src/main/java/network/Messages/BoatStatus.java index b62c4469..7fa9228f 100644 --- a/racevisionGame/src/main/java/network/Messages/BoatStatus.java +++ b/racevisionGame/src/main/java/network/Messages/BoatStatus.java @@ -128,7 +128,7 @@ public class BoatStatus { } /** - * Returns he time at which it is estimated the boat will reach the next mark. Milliseconds since unix epoch. + * Returns the time at which it is estimated the boat will reach the next mark. Milliseconds since unix epoch. * @return Time at which boat will reach next mark. */ public long getEstTimeAtNextMark() { @@ -136,7 +136,7 @@ public class BoatStatus { } /** - * Returns he time at which it is estimated the boat will finish the race. Milliseconds since unix epoch. + * Returns the time at which it is estimated the boat will finish the race. Milliseconds since unix epoch. * @return Time at which boat will finish the race. */ public long getEstTimeAtFinish() { diff --git a/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java index 6939d8f3..908efa27 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/JoinAcceptanceEnum.java @@ -11,24 +11,35 @@ public enum JoinAcceptanceEnum { /** - * Client is allowed to join. + * Client is allowed to join and spectate. */ - JOIN_SUCCESSFUL(1), + JOIN_SUCCESSFUL_SPECTATOR(0), /** - * The race is full - no more participants allowed. + * Client is allowed to join and participate. */ - RACE_PARTICIPANTS_FULL(2), + JOIN_SUCCESSFUL_PARTICIPANT(1), /** - * The race cannot allow any more ghost participants to join. + * Client is allowed to join and play the tutorial. */ - GHOST_PARTICIPANTS_FULL(3), + JOIN_SUCCESSFUL_TUTORIAL(2), + + /** + * Client is allowed to join and participate as a ghost player. + */ + JOIN_SUCCESSFUL_GHOST(3), + + + /** + * Join Request was denied. + */ + JOIN_FAILURE(0x10), /** * The server is completely full, cannot participate or spectate. */ - SERVER_FULL(4), + SERVER_FULL(0x11), /** diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java index 15f70f40..aed5d70a 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java @@ -20,17 +20,25 @@ public enum MessageType { COURSEWIND(44), AVGWIND(47), + + BOATACTION(100), + /** * This is used for {@link network.Messages.RequestToJoin} messages. */ - REQUEST_TO_JOIN(55), + REQUEST_TO_JOIN(101), /** * This is used for {@link network.Messages.JoinAcceptance} messages. */ - JOIN_ACCEPTANCE(56), + JOIN_ACCEPTANCE(102), + + + /** + * This is used for {@link network.Messages.AssignPlayerBoat} messages. + */ + ASSIGN_PLAYER_BOAT(121), - BOATACTION(100), NOTAMESSAGE(0); diff --git a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java index 36bb5955..d868a25d 100644 --- a/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java +++ b/racevisionGame/src/main/java/network/Messages/Enums/RequestToJoinEnum.java @@ -20,10 +20,15 @@ public enum RequestToJoinEnum { */ PARTICIPANT(1), + /** + * Client wants to play the tutorial. + */ + CONTROL_TUTORIAL(2), + /** * Client wants to particpate as a ghost. */ - GHOST(5), + GHOST(3), /** diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java index 147f58e7..738b3ae6 100644 --- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java +++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java @@ -1,7 +1,6 @@ package network.Messages; import network.Messages.Enums.XMLMessageType; -import shared.dataInput.RaceDataSource; import java.util.*; @@ -11,36 +10,6 @@ import java.util.*; */ public class LatestMessages extends Observable { - /** - * The latest RaceStatus message. - */ - private RaceStatus raceStatus; - - /** - * A map of the last BoatStatus message received, for each boat. - */ - private final Map boatStatusMap = new HashMap<>(); - - /** - * A map of the last BoatLocation message received, for each boat. - */ - private final Map boatLocationMap = new HashMap<>(); - - /** - * A map of the last MarkRounding message received, for each boat. - */ - private final Map markRoundingMap = new HashMap<>(); - - /** - * The last AverageWind message received. - */ - private AverageWind averageWind; - - /** - * The last CourseWinds message received. - */ - private CourseWinds courseWinds; - /** * A list of messages containing a snapshot of the race. @@ -91,138 +60,7 @@ public class LatestMessages extends Observable { } - /** - * Gets the latest RaceStatus message received. - * @return The latest RaceStatus message received. - */ - public RaceStatus getRaceStatus() { - return raceStatus; - } - - /** - * Sets the latest RaceStatus message received. - * @param raceStatus The new RaceStatus message to store. - */ - public void setRaceStatus(RaceStatus raceStatus) { - this.raceStatus = raceStatus; - } - - - - /** - * Returns the latest BoatStatus message received for a given boat. - * @param sourceID Source ID of the boat. - * @return The latest BoatStatus message for the specified boat. - */ - public BoatStatus getBoatStatus(int sourceID) { - return boatStatusMap.get(sourceID); - } - - /** - * Inserts a BoatStatus message for a given boat. - * @param boatStatus The BoatStatus message to set. - */ - public void setBoatStatus(BoatStatus boatStatus) { - boatStatusMap.put(boatStatus.getSourceID(), boatStatus); - } - - - - /** - * Returns the latest BoatLocation message received for a given boat. - * @param sourceID Source ID of the boat. - * @return The latest BoatLocation message for the specified boat. - */ - public BoatLocation getBoatLocation(int sourceID) { - return boatLocationMap.get(sourceID); - } - - /** - * Inserts a BoatLocation message for a given boat. - * @param boatLocation The BoatLocation message to set. - */ - public void setBoatLocation(BoatLocation boatLocation) { - //TODO should compare the sequence number of the new boatLocation with the existing boatLocation for this boat (if it exists), and use the newer one. - boatLocationMap.put(boatLocation.getSourceID(), boatLocation); - } - - /** - * Returns the latest MarkRounding message received for a given boat. - * @param sourceID Source ID of the boat. - * @return The latest MarkRounding message for the specified boat. - */ - public MarkRounding getMarkRounding(int sourceID) { - return markRoundingMap.get(sourceID); - } - - /** - * Inserts a MarkRounding message for a given boat. - * @param markRounding The MarkRounding message to set. - */ - public void setMarkRounding(MarkRounding markRounding) { - //TODO should compare the sequence number of the new markRounding with the existing boatLocation for this boat (if it exists), and use the newer one. - markRoundingMap.put(markRounding.getSourceID(), markRounding); - } - - - - /** - * Gets the latest AverageWind message received. - * @return The latest AverageWind message received. - */ - public AverageWind getAverageWind() { - return averageWind; - } - - /** - * Sets the latest AverageWind message received. - * @param averageWind The new AverageWind message to store. - */ - public void setAverageWind(AverageWind averageWind) { - this.averageWind = averageWind; - } - - - /** - * Gets the latest CourseWinds message received. - * @return The latest CourseWinds message received. - */ - public CourseWinds getCourseWinds() { - return courseWinds; - } - /** - * Sets the latest CourseWinds message received. - * @param courseWinds The new CourseWinds message to store. - */ - public void setCourseWinds(CourseWinds courseWinds) { - this.courseWinds = courseWinds; - } - - - /** - * Returns the map of boat sourceIDs to BoatLocation messages. - * @return Map between boat sourceID and BoatLocation. - */ - public Map getBoatLocationMap() { - return boatLocationMap; - } - - /** - * Returns the map of boat sourceIDs to BoatStatus messages. - * @return Map between boat sourceID and BoatStatus. - */ - public Map getBoatStatusMap() { - return boatStatusMap; - } - - /** - * Returns the map of boat sourceIDs to MarkRounding messages. - * @return Map between boat sourceID and MarkRounding. - */ - public Map getMarkRoundingMap() { - return markRoundingMap; - } diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java new file mode 100644 index 00000000..1de4251e --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyBoatDataSource.java @@ -0,0 +1,47 @@ +package shared.dataInput; + +import shared.model.Boat; +import shared.model.Mark; + +import java.util.HashMap; +import java.util.Map; + +/** + * An empty {@link BoatDataSource}. Can be used to initialise a race with no data. + */ +public class EmptyBoatDataSource implements BoatDataSource { + + /** + * A map of source ID to boat for all boats in the race. + */ + private final Map boatMap = new HashMap<>(); + + /** + * A map of source ID to mark for all marks in the race. + */ + private final Map markerMap = new HashMap<>(); + + + + public EmptyBoatDataSource() { + } + + + /** + * Get the boats that are going to participate in this race + * @return Dictionary of boats that are to participate in this race indexed by SourceID + */ + @Override + public Map getBoats() { + return boatMap; + } + + /** + * Get the marker Boats that are participating in this race + * @return Dictionary of the Markers Boats that are in this race indexed by their Source ID. + */ + @Override + public Map getMarkerBoats() { + return markerMap; + } +} diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java new file mode 100644 index 00000000..74ccbaf5 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRaceDataSource.java @@ -0,0 +1,129 @@ +package shared.dataInput; + +import network.Messages.Enums.RaceTypeEnum; +import shared.model.CompoundMark; +import shared.model.GPSCoordinate; +import shared.model.Leg; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An empty {@link RaceDataSource}. Can be used to initialise a race with no data. + */ +public class EmptyRaceDataSource implements RaceDataSource { + + + /** + * The GPS coordinate of the top left of the race boundary. + */ + private GPSCoordinate mapTopLeft = new GPSCoordinate(0, 0); + + /** + * The GPS coordinate of the bottom right of the race boundary. + */ + private GPSCoordinate mapBottomRight = new GPSCoordinate(0, 0); + + + /** + * A list of GPS coordinates that make up the boundary of the race. + */ + private final List boundary = new ArrayList<>(); + + /** + * A map between compoundMarkID and a CompoundMark for all CompoundMarks in a race. + */ + private final Map compoundMarkMap = new HashMap<>(); + + /** + * A list of boat sourceIDs participating in the race. + */ + private final List participants = new ArrayList<>(); + + /** + * A list of legs in the race. + */ + private final List legs = new ArrayList<>(); + + + /** + * The time that the race.xml file was created. + */ + private ZonedDateTime creationTimeDate = ZonedDateTime.now(); + + /** + * The time that the race should start at, if it hasn't been postponed. + */ + private ZonedDateTime raceStartTime = ZonedDateTime.now().plusMinutes(5); + + /** + * Whether or not the race has been postponed. + */ + private boolean postpone = false; + + + /** + * The ID number of the race. + */ + private int raceID = 0; + + /** + * The type of the race. + */ + private RaceTypeEnum raceType = RaceTypeEnum.NOT_A_RACE_TYPE; + + + + public EmptyRaceDataSource() { + } + + + + public List getBoundary() { + return boundary; + } + + public GPSCoordinate getMapTopLeft() { + return mapTopLeft; + } + + public GPSCoordinate getMapBottomRight() { + return mapBottomRight; + } + + public List getLegs() { + return legs; + } + + public List getCompoundMarks() { + return new ArrayList<>(compoundMarkMap.values()); + } + + + public ZonedDateTime getCreationDateTime() { + return creationTimeDate; + } + + public ZonedDateTime getStartDateTime() { + return raceStartTime; + } + + public int getRaceId() { + return raceID; + } + + public RaceTypeEnum getRaceType() { + return raceType; + } + + public boolean getPostponed() { + return postpone; + } + + public List getParticipants() { + return participants; + } +} diff --git a/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java new file mode 100644 index 00000000..05a0fcf9 --- /dev/null +++ b/racevisionGame/src/main/java/shared/dataInput/EmptyRegattaDataSource.java @@ -0,0 +1,122 @@ +package shared.dataInput; + +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; +import shared.model.GPSCoordinate; + +import java.io.InputStream; + +/** + * An empty {@link RegattaDataSource}. Can be used to initialise a race with no data. + */ +public class EmptyRegattaDataSource implements RegattaDataSource { + /** + * The regatta ID. + */ + private int regattaID = 0; + + /** + * 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 = 0; + + /** + * The central longitude of the course. + */ + private double centralLongitude = 0; + + /** + * The central altitude of the course. + */ + private double centralAltitude = 0; + + /** + * The UTC offset of the course. + */ + private float utcOffset = 0; + + /** + * The magnetic variation of the course. + */ + private float magneticVariation = 0; + + + + + public EmptyRegattaDataSource() { + } + + + + + public int getRegattaID() { + return regattaID; + } + + + public String getRegattaName() { + return regattaName; + } + + + public int getRaceID() { + return raceID; + } + + + public String getCourseName() { + return courseName; + } + + + public double getCentralLatitude() { + return centralLatitude; + } + + + public double getCentralLongitude() { + return centralLongitude; + } + + + public double getCentralAltitude() { + return centralAltitude; + } + + + public float getUtcOffset() { + return utcOffset; + } + + + public float getMagneticVariation() { + return magneticVariation; + } + + + /** + * Returns the GPS coorindates of the centre of the regatta. + * @return The gps coordinate for the centre of the regatta. + */ + public GPSCoordinate getGPSCoordinate() { + return new GPSCoordinate(centralLatitude, centralLongitude); + } +} diff --git a/racevisionGame/src/main/java/shared/exceptions/MarkNotFoundException.java b/racevisionGame/src/main/java/shared/exceptions/MarkNotFoundException.java new file mode 100644 index 00000000..49cf4f5c --- /dev/null +++ b/racevisionGame/src/main/java/shared/exceptions/MarkNotFoundException.java @@ -0,0 +1,15 @@ +package shared.exceptions; + +/** + * An exception thrown when a specific mark cannot be found. + */ +public class MarkNotFoundException extends Exception { + + public MarkNotFoundException(String message) { + super(message); + } + + public MarkNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/racevisionGame/src/main/java/shared/model/FrameRateTracker.java b/racevisionGame/src/main/java/shared/model/FrameRateTracker.java new file mode 100644 index 00000000..ba683f01 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/FrameRateTracker.java @@ -0,0 +1,109 @@ +package shared.model; + + +import javafx.animation.AnimationTimer; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + + +/** + * This class is used to track the framerate of something. + * Use {@link #incrementFps(long)} to update it, and {@link #fpsProperty()} to observe it. + */ +public class FrameRateTracker { + + + /** + * 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, {@link #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; + + + /** + * Creates a {@link FrameRateTracker}. Use {@link #incrementFps(long)} to update it, and {@link #fpsProperty()} to observe it. + */ + public FrameRateTracker() { + timer.start(); + } + + + /** + * Returns the number of frames generated per second. + * @return Frames per second. + */ + public int getFps() { + return lastFps.getValue(); + } + + /** + * 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}. + */ + private 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; + } + } + + + /** + * Timer used to update the framerate. + * This is used because we care about frames in the javaFX thread. + */ + private AnimationTimer timer = new AnimationTimer() { + + long previousFrameTime = System.currentTimeMillis(); + + @Override + public void handle(long now) { + + long currentFrameTime = System.currentTimeMillis(); + long framePeriod = currentFrameTime - previousFrameTime; + + //Increment fps. + incrementFps(framePeriod); + + previousFrameTime = currentFrameTime; + } + }; + + + /** + * Stops the {@link FrameRateTracker}'s timer. + */ + public void stop() { + timer.stop(); + } + + +} diff --git a/racevisionGame/src/main/java/shared/model/Race.java b/racevisionGame/src/main/java/shared/model/Race.java index f9fc984e..75e2cf2c 100644 --- a/racevisionGame/src/main/java/shared/model/Race.java +++ b/racevisionGame/src/main/java/shared/model/Race.java @@ -6,17 +6,17 @@ import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RaceTypeEnum; -import network.Messages.LatestMessages; import shared.dataInput.BoatDataSource; import shared.dataInput.RaceDataSource; import shared.dataInput.RegattaDataSource; +import visualiser.model.VisualiserRaceEvent; import java.util.List; /** * Represents a yacht race. - * This is a base class inherited by {@link mock.model.MockRace} and {@link visualiser.model.VisualiserRace}. + * This is a base class inherited by {@link mock.model.MockRace} and {@link VisualiserRaceEvent}. * 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 { @@ -37,11 +37,6 @@ public abstract class Race { */ protected RegattaDataSource regattaDataSource; - /** - * The collection of latest race messages. - * Can be either read from or written to. - */ - protected LatestMessages latestMessages; /** * A list of compound marks in the race. @@ -116,16 +111,14 @@ public abstract class Race { * @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...). - * @param latestMessages The collection of latest messages, which can be written to, or read from. */ - public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages) { + public Race(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource) { //Keep a reference to data sources. this.raceDataSource = raceDataSource; this.boatDataSource = boatDataSource; this.regattaDataSource = regattaDataSource; - this.latestMessages = latestMessages; //Marks. @@ -241,7 +234,7 @@ public abstract class Race { * @param windBearing New wind bearing. * @param windSpeedKnots New wind speed, in knots. */ - protected void setWind(Bearing windBearing, double windSpeedKnots) { + public void setWind(Bearing windBearing, double windSpeedKnots) { Wind wind = new Wind(windBearing, windSpeedKnots); setWind(wind); } @@ -250,7 +243,7 @@ public abstract class Race { * Updates the race to have a specified wind (bearing and speed). * @param wind New wind. */ - protected void setWind(Wind wind) { + public void setWind(Wind wind) { this.raceWind.setValue(wind); } @@ -316,6 +309,23 @@ public abstract class Race { 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 number of frames generated per second. * @return Frames per second. diff --git a/racevisionGame/src/main/java/shared/model/RaceState.java b/racevisionGame/src/main/java/shared/model/RaceState.java new file mode 100644 index 00000000..d77a4129 --- /dev/null +++ b/racevisionGame/src/main/java/shared/model/RaceState.java @@ -0,0 +1,346 @@ +package shared.model; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +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; + + +/** + * 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 { + + + + /** + * Data source for race information. + */ + private RaceDataSource raceDataSource; + + /** + * Data source for boat information. + */ + private BoatDataSource boatDataSource; + + /** + * Data source for regatta information. + */ + private RegattaDataSource regattaDataSource; + + + + /** + * 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() { + + //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) { + //We add a "dummy" leg at the end of the race. + if (legs.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()); + } + + /** + * 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() { + return new ArrayList<>(boatDataSource.getMarkerBoats().values()); + } + + /** + * 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 List getLegs() { + return raceDataSource.getLegs(); + } + + + /** + * 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(); + } + + + + +} diff --git a/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java index 7ab12532..fd5827f0 100644 --- a/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java +++ b/racevisionGame/src/main/java/shared/model/RunnableWithFramePeriod.java @@ -50,4 +50,5 @@ public interface RunnableWithFramePeriod extends Runnable { } + } diff --git a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/ConnectionToServerCommandFactory.java b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/ConnectionToServerCommandFactory.java index bac06db3..27c87a08 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/ConnectionToServerCommandFactory.java +++ b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/ConnectionToServerCommandFactory.java @@ -30,8 +30,12 @@ public class ConnectionToServerCommandFactory { switch(joinAcceptance.getAcceptanceType()) { - case JOIN_SUCCESSFUL: return new JoinSuccessfulCommand(joinAcceptance, connectionToServer); - case RACE_PARTICIPANTS_FULL: return new RaceParticipantsFullCommand(joinAcceptance, connectionToServer); + case JOIN_SUCCESSFUL_PARTICIPANT: return new JoinSuccessParticipantCommand(joinAcceptance, connectionToServer); + + case JOIN_SUCCESSFUL_SPECTATOR: return new JoinSuccessSpectatorCommand(joinAcceptance, connectionToServer); + + case JOIN_FAILURE: return new JoinFailureCommand(joinAcceptance, connectionToServer); + case SERVER_FULL: return new ServerFullCommand(joinAcceptance, connectionToServer); default: throw new CommandConstructionException("Could not create command for JoinAcceptance: " + joinAcceptance + ". Unknown JoinAcceptanceEnum."); diff --git a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/RaceParticipantsFullCommand.java b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinFailureCommand.java similarity index 76% rename from racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/RaceParticipantsFullCommand.java rename to racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinFailureCommand.java index ae631c6e..283d6fc2 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/RaceParticipantsFullCommand.java +++ b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinFailureCommand.java @@ -5,13 +5,11 @@ import network.Messages.JoinAcceptance; import visualiser.enums.ConnectionToServerState; import visualiser.network.ConnectionToServer; -import java.util.Optional; - /** * Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#RACE_PARTICIPANTS_FULL} {@link JoinAcceptance} message is received. */ -public class RaceParticipantsFullCommand implements Command { +public class JoinFailureCommand implements Command { /** * The message to operate on. @@ -25,11 +23,11 @@ public class RaceParticipantsFullCommand implements Command { /** - * Creates a new {@link RaceParticipantsFullCommand}, which operates on a given {@link ConnectionToServer}. + * Creates a new {@link JoinFailureCommand}, which operates on a given {@link ConnectionToServer}. * @param joinAcceptance The message to operate on. * @param connectionToServer The context to operate on. */ - public RaceParticipantsFullCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) { + public JoinFailureCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) { this.joinAcceptance = joinAcceptance; this.connectionToServer = connectionToServer; } diff --git a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessParticipantCommand.java b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessParticipantCommand.java new file mode 100644 index 00000000..da29664c --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessParticipantCommand.java @@ -0,0 +1,57 @@ +package visualiser.Commands.ConnectionToServerCommands; + +import mock.model.commandFactory.Command; +import network.Messages.AssignPlayerBoat; +import network.Messages.JoinAcceptance; +import visualiser.enums.ConnectionToServerState; +import visualiser.network.ConnectionToServer; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#JOIN_SUCCESSFUL_PARTICIPANT} {@link network.Messages.JoinAcceptance} message is received. + */ +public class JoinSuccessParticipantCommand implements Command { + + /** + * The message to operate on. + */ + private JoinAcceptance joinAcceptance; + + /** + * The context to operate on. + */ + private ConnectionToServer connectionToServer; + + + /** + * Creates a new {@link JoinSuccessParticipantCommand}, which operates on a given {@link ConnectionToServer}. + * @param joinAcceptance The message to operate on. + * @param connectionToServer The context to operate on. + */ + public JoinSuccessParticipantCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) { + this.joinAcceptance = joinAcceptance; + this.connectionToServer = connectionToServer; + } + + + + @Override + public void execute() { + + connectionToServer.setJoinAcceptance(joinAcceptance); + + connectionToServer.setConnectionState(ConnectionToServerState.CONNECTED); + + + AssignPlayerBoat assignPlayerBoat = new AssignPlayerBoat(joinAcceptance.getSourceID()); + try { + connectionToServer.send(assignPlayerBoat); + } catch (InterruptedException e) { + Logger.getGlobal().log(Level.WARNING, "JoinSuccessParticipantCommand: " + this + " was interrupted on thread: " + Thread.currentThread() + " while sending AssignPlayerBoat message.", e); + } + + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessfulCommand.java b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessSpectatorCommand.java similarity index 71% rename from racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessfulCommand.java rename to racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessSpectatorCommand.java index 8cdfea5b..6e7032fa 100644 --- a/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessfulCommand.java +++ b/racevisionGame/src/main/java/visualiser/Commands/ConnectionToServerCommands/JoinSuccessSpectatorCommand.java @@ -5,13 +5,11 @@ import network.Messages.JoinAcceptance; import visualiser.enums.ConnectionToServerState; import visualiser.network.ConnectionToServer; -import java.util.Optional; - /** - * Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#JOIN_SUCCESSFUL} {@link network.Messages.JoinAcceptance} message is received. + * Command created when a {@link network.Messages.Enums.JoinAcceptanceEnum#JOIN_SUCCESSFUL_PARTICIPANT} {@link JoinAcceptance} message is received. */ -public class JoinSuccessfulCommand implements Command { +public class JoinSuccessSpectatorCommand implements Command { /** * The message to operate on. @@ -25,11 +23,11 @@ public class JoinSuccessfulCommand implements Command { /** - * Creates a new {@link JoinSuccessfulCommand}, which operates on a given {@link ConnectionToServer}. + * Creates a new {@link JoinSuccessSpectatorCommand}, which operates on a given {@link ConnectionToServer}. * @param joinAcceptance The message to operate on. * @param connectionToServer The context to operate on. */ - public JoinSuccessfulCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) { + public JoinSuccessSpectatorCommand(JoinAcceptance joinAcceptance, ConnectionToServer connectionToServer) { this.joinAcceptance = joinAcceptance; this.connectionToServer = connectionToServer; } diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/AssignPlayerBoatCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/AssignPlayerBoatCommand.java new file mode 100644 index 00000000..fd265325 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/AssignPlayerBoatCommand.java @@ -0,0 +1,53 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.AssignPlayerBoat; +import network.Messages.BoatLocation; +import shared.exceptions.BoatNotFoundException; +import shared.exceptions.MarkNotFoundException; +import shared.model.GPSCoordinate; +import shared.model.Mark; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceState; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Command created when a {@link AssignPlayerBoat} message is received. + */ +public class AssignPlayerBoatCommand implements Command { + + /** + * The message to operate on. + */ + private AssignPlayerBoat assignPlayerBoat; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link AssignPlayerBoatCommand}, which operates on a given {@link VisualiserRaceState}. + * @param assignPlayerBoat The message to operate on. + * @param visualiserRace The context to operate on. + */ + public AssignPlayerBoatCommand(AssignPlayerBoat assignPlayerBoat, VisualiserRaceState visualiserRace) { + this.assignPlayerBoat = assignPlayerBoat; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + visualiserRace.setPlayerBoatID(assignPlayerBoat.getSourceID()); + + } + + +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatLocationCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatLocationCommand.java new file mode 100644 index 00000000..d2c60bd7 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatLocationCommand.java @@ -0,0 +1,127 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.BoatLocation; +import network.Messages.Enums.BoatStatusEnum; +import shared.exceptions.BoatNotFoundException; +import shared.exceptions.MarkNotFoundException; +import shared.model.GPSCoordinate; +import shared.model.Mark; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Command created when a {@link BoatLocation} message is received. + */ +public class BoatLocationCommand implements Command { + + /** + * The message to operate on. + */ + private BoatLocation boatLocation; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link BoatLocationCommand}, which operates on a given {@link VisualiserRaceState}. + * @param boatLocation The message to operate on. + * @param visualiserRace The context to operate on. + */ + public BoatLocationCommand(BoatLocation boatLocation, VisualiserRaceState visualiserRace) { + this.boatLocation = boatLocation; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + if (visualiserRace.isVisualiserBoat(boatLocation.getSourceID())) { + updateBoatLocation(); + } else if (visualiserRace.isMark(boatLocation.getSourceID())) { + updateMarkLocation(); + } + + } + + + /** + * Updates the boat specified in the message. + */ + private void updateBoatLocation() { + + try { + VisualiserBoat boat = visualiserRace.getBoat(boatLocation.getSourceID()); + + //Get the new position. + GPSCoordinate gpsCoordinate = new GPSCoordinate( + boatLocation.getLatitude(), + boatLocation.getLongitude()); + + boat.setCurrentPosition(gpsCoordinate); + + //Bearing. + boat.setBearing(boatLocation.getHeading()); + + + //Speed. + boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots()); + + + //Attempt to add a track point. + attemptAddTrackPoint(boat); + + + } catch (BoatNotFoundException e) { + Logger.getGlobal().log(Level.WARNING, "BoatLocationCommand: " + this + " could not execute. Boat with sourceID: " + boatLocation.getSourceID() + " not found.", e); + return; + } + + } + + + /** + * Attempts to add a track point to the boat. Only works if the boat is currently racing. + * @param boat The boat to add a track point to. + */ + private void attemptAddTrackPoint(VisualiserBoat boat) { + if (boat.getStatus() == BoatStatusEnum.RACING) { + boat.addTrackPoint(boat.getCurrentPosition(), visualiserRace.getRaceClock().getCurrentTime()); + } + } + + + /** + * Updates the marker boat specified in message. + */ + private void updateMarkLocation() { + + try { + Mark mark = visualiserRace.getMark(boatLocation.getSourceID()); + + GPSCoordinate gpsCoordinate = new GPSCoordinate( + boatLocation.getLatitude(), + boatLocation.getLongitude()); + + mark.setPosition(gpsCoordinate); + } catch (MarkNotFoundException e) { + Logger.getGlobal().log(Level.WARNING, "BoatLocationCommand: " + this + " could not execute. Mark with sourceID: " + boatLocation.getSourceID() + " not found.", e); + return; + + } + + + + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java new file mode 100644 index 00000000..7f56d745 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatsXMLMessageCommand.java @@ -0,0 +1,48 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.XMLMessage; +import shared.dataInput.BoatDataSource; +import shared.dataInput.BoatXMLReader; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.XMLReaderException; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + + +/** + * Command created when a {@link network.Messages.Enums.XMLMessageType#BOAT} {@link XMLMessage} message is received. + */ +public class BoatsXMLMessageCommand implements Command { + + /** + * The data source to operate on. + */ + private BoatDataSource boatDataSource; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link BoatsXMLMessageCommand}, which operates on a given {@link VisualiserRaceEvent}. + * @param boatDataSource The data source to operate on. + * @param visualiserRace The context to operate on. + */ + public BoatsXMLMessageCommand(BoatDataSource boatDataSource, VisualiserRaceState visualiserRace) { + this.boatDataSource = boatDataSource; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + visualiserRace.setBoatDataSource(boatDataSource); + + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceStatusCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceStatusCommand.java new file mode 100644 index 00000000..11fd8c3b --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceStatusCommand.java @@ -0,0 +1,185 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.BoatStatus; +import network.Messages.Enums.BoatStatusEnum; +import network.Messages.RaceStatus; +import shared.exceptions.BoatNotFoundException; +import shared.model.Leg; +import visualiser.model.VisualiserBoat; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Command created when a {@link RaceStatus} message is received. + */ +public class RaceStatusCommand implements Command { + + /** + * The message to operate on. + */ + private RaceStatus raceStatus; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link RaceStatusCommand}, which operates on a given {@link VisualiserRaceState}. + * @param raceStatus The message to operate on. + * @param visualiserRace The context to operate on. + */ + public RaceStatusCommand(RaceStatus raceStatus, VisualiserRaceState visualiserRace) { + this.raceStatus = raceStatus; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + //Race status enum. + visualiserRace.setRaceStatusEnum(raceStatus.getRaceStatus()); + + //Wind. + visualiserRace.setWind( + raceStatus.getWindDirection(), + raceStatus.getWindSpeed() ); + + //Current race time. + visualiserRace.getRaceClock().setUTCTime(raceStatus.getCurrentTime()); + + + + for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) { + updateBoatStatus(boatStatus); + } + + + visualiserRace.updateBoatPositions(visualiserRace.getBoats()); + + + + } + + + /** + * Updates a single boat's status using the boatStatus message. + * @param boatStatus BoatStatus message to get data from. + */ + private void updateBoatStatus(BoatStatus boatStatus) { + try { + VisualiserBoat boat = visualiserRace.getBoat(boatStatus.getSourceID()); + + //Time at next mark. + updateEstimatedTimeAtNextMark(boatStatus, boat); + + + BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus(); + + //Time at last mark. + initialiseTimeAtLastMark(boat, boat.getStatus(), newBoatStatusEnum); + + //Status. + boat.setStatus(newBoatStatusEnum); + + + List legs = visualiserRace.getLegs(); + + //Leg. + updateLeg(boatStatus.getLegNumber(), boat, legs); + + + //Set finish time if boat finished. + attemptUpdateFinishTime(boatStatus, boat, legs); + + + } catch (BoatNotFoundException e) { + Logger.getGlobal().log(Level.WARNING, "RaceStatusCommand.updateBoatStatus: " + this + " could not execute. Boat with sourceID: " + boatStatus.getSourceID() + " not found.", e); + return; + } + } + + + /** + * Attempts to update the finish time of the boat. Only works if the boat has actually finished the race. + * @param boatStatus BoatStatus to read data from. + * @param boat Boat to update. + * @param legs Legs of the race. + */ + private void attemptUpdateFinishTime(BoatStatus boatStatus, VisualiserBoat boat, List legs) { + + if (boat.getStatus() == BoatStatusEnum.FINISHED || boatStatus.getLegNumber() == legs.size()) { + boat.setTimeFinished(visualiserRace.getRaceClock().getCurrentTimeMilli()); + boat.setStatus(BoatStatusEnum.FINISHED); + } + + } + + + + /** + * Updates a boat's leg. + * @param legNumber The new leg number. + * @param boat The boat to update. + * @param legs The legs in the race. + */ + private void updateLeg(int legNumber, VisualiserBoat boat, List legs) { + + if (legNumber >= 1 && legNumber < legs.size()) { + if (boat.getCurrentLeg() != legs.get(legNumber)) { + boatFinishedLeg(boat, legs.get(legNumber)); + } + } + } + + /** + * Initialises the time at last mark for a boat. Only changes if the boat's status is changing from non-racing to racing. + * @param boat The boat to update. + * @param currentBoatStatus The current status of the boat. + * @param newBoatStatusEnum The new status of the boat, from the BoatStatus message. + */ + private void initialiseTimeAtLastMark(VisualiserBoat boat, BoatStatusEnum currentBoatStatus, BoatStatusEnum newBoatStatusEnum) { + //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. + if ((currentBoatStatus != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { + boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime()); + } + } + + + /** + * Updates the estimated time at next mark for a given boat. + * @param boatStatus BoatStatus to read data from. + * @param boat Boat to update. + */ + private void updateEstimatedTimeAtNextMark(BoatStatus boatStatus, VisualiserBoat boat) { + boat.setEstimatedTimeAtNextMark(visualiserRace.getRaceClock().getLocalTime(boatStatus.getEstTimeAtNextMark())); + } + + + + /** + * Updates a boat's leg to a specified leg. Also records the order in which the boat passed the leg. + * @param boat The boat to update. + * @param leg The leg to use. + */ + private void boatFinishedLeg(VisualiserBoat boat, Leg leg) { + + //Record order in which boat finished leg. + visualiserRace.getLegCompletionOrder().get(boat.getCurrentLeg()).add(boat); + + //Update boat. + boat.setCurrentLeg(leg); + boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime()); + + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java new file mode 100644 index 00000000..0b194674 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RaceXMLMessageCommand.java @@ -0,0 +1,44 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.XMLMessage; +import shared.dataInput.RaceDataSource; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + + +/** + * Command created when a {@link network.Messages.Enums.XMLMessageType#BOAT} {@link XMLMessage} message is received. + */ +public class RaceXMLMessageCommand implements Command { + + /** + * The data source to operate on. + */ + private RaceDataSource raceDataSource; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link RaceXMLMessageCommand}, which operates on a given {@link VisualiserRaceEvent}. + * @param raceDataSource The data source to operate on. + * @param visualiserRace The context to operate on. + */ + public RaceXMLMessageCommand(RaceDataSource raceDataSource, VisualiserRaceState visualiserRace) { + this.raceDataSource = raceDataSource; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + visualiserRace.setRaceDataSource(raceDataSource); + + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java new file mode 100644 index 00000000..6597e557 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/RegattaXMLMessageCommand.java @@ -0,0 +1,44 @@ +package visualiser.Commands.VisualiserRaceCommands; + +import mock.model.commandFactory.Command; +import network.Messages.XMLMessage; +import shared.dataInput.RegattaDataSource; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + + +/** + * Command created when a {@link network.Messages.Enums.XMLMessageType#BOAT} {@link XMLMessage} message is received. + */ +public class RegattaXMLMessageCommand implements Command { + + /** + * The data source to operate on. + */ + private RegattaDataSource regattaDataSource; + + /** + * The context to operate on. + */ + private VisualiserRaceState visualiserRace; + + + /** + * Creates a new {@link RegattaXMLMessageCommand}, which operates on a given {@link VisualiserRaceEvent}. + * @param regattaDataSource The data source to operate on. + * @param visualiserRace The context to operate on. + */ + public RegattaXMLMessageCommand(RegattaDataSource regattaDataSource, VisualiserRaceState visualiserRace) { + this.regattaDataSource = regattaDataSource; + this.visualiserRace = visualiserRace; + } + + + + @Override + public void execute() { + + visualiserRace.setRegattaDataSource(regattaDataSource); + + } +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java new file mode 100644 index 00000000..37755e91 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/VisualiserRaceCommandFactory.java @@ -0,0 +1,40 @@ +package visualiser.Commands.VisualiserRaceCommands; + + +import mock.exceptions.CommandConstructionException; +import mock.model.commandFactory.Command; +import network.Messages.*; +import visualiser.model.VisualiserRaceEvent; +import visualiser.model.VisualiserRaceState; + +/** + * Factory to create VisualiserRace commands. + */ +public class VisualiserRaceCommandFactory { + + /** + * Generates a command on an VisualiserRace. + * @param message The message to turn into a command. + * @param visualiserRace The context for the command to operate on. + * @return The command to execute the given action. + * @throws CommandConstructionException Thrown if the command cannot be constructed. + */ + public static Command create(AC35Data message, VisualiserRaceState visualiserRace) throws CommandConstructionException { + + switch (message.getType()) { + + case BOATLOCATION: return new BoatLocationCommand((BoatLocation) message, visualiserRace); + + case RACESTATUS: return new RaceStatusCommand((RaceStatus) message, visualiserRace); + + case XMLMESSAGE: return XMLMessageCommandFactory.create((XMLMessage) message, visualiserRace); + + case ASSIGN_PLAYER_BOAT: return new AssignPlayerBoatCommand((AssignPlayerBoat) message, visualiserRace); + + default: throw new CommandConstructionException("Could not create VisualiserRaceCommand. Unrecognised or unsupported MessageType: " + message.getType()); + + } + + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java new file mode 100644 index 00000000..b9b58f1c --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/XMLMessageCommandFactory.java @@ -0,0 +1,63 @@ +package visualiser.Commands.VisualiserRaceCommands; + + +import mock.exceptions.CommandConstructionException; +import mock.model.commandFactory.Command; +import network.Messages.AC35Data; +import network.Messages.BoatLocation; +import network.Messages.RaceStatus; +import network.Messages.XMLMessage; +import shared.dataInput.*; +import shared.enums.XMLFileType; +import shared.exceptions.InvalidBoatDataException; +import shared.exceptions.InvalidRaceDataException; +import shared.exceptions.InvalidRegattaDataException; +import shared.exceptions.XMLReaderException; +import visualiser.model.VisualiserRaceState; + +/** + * Factory to create VisualiserRace commands, from XMLMessages. + */ +public class XMLMessageCommandFactory { + + /** + * Generates a command on an VisualiserRace. + * @param message The message to turn into a command. + * @param visualiserRace The context for the command to operate on. + * @return The command to execute the given action. + * @throws CommandConstructionException Thrown if the command cannot be constructed. + */ + public static Command create(XMLMessage message, VisualiserRaceState visualiserRace) throws CommandConstructionException { + + try { + + switch (message.getXmlMsgSubType()) { + + case BOAT: + BoatDataSource boatDataSource = new BoatXMLReader(message.getXmlMessage(), XMLFileType.Contents); + return new BoatsXMLMessageCommand(boatDataSource, visualiserRace); + + + case RACE: + RaceDataSource raceDataSource = new RaceXMLReader(message.getXmlMessage(), XMLFileType.Contents); + return new RaceXMLMessageCommand(raceDataSource, visualiserRace); + + + case REGATTA: + RegattaDataSource regattaDataSource = new RegattaXMLReader(message.getXmlMessage(), XMLFileType.Contents); + return new RegattaXMLMessageCommand(regattaDataSource, visualiserRace); + + + default: + throw new CommandConstructionException("Could not create VisualiserRaceCommand/XMLCommand. Unrecognised or unsupported MessageType: " + message.getType()); + + } + + } catch (XMLReaderException | InvalidBoatDataException | InvalidRegattaDataException | InvalidRaceDataException e) { + throw new CommandConstructionException("Could not create VisualiserRaceCommand/XMLCommand. Could not parse XML message payload.", e); + + } + + } + +} diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java index 3e81cd16..ab9bd421 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java @@ -4,7 +4,6 @@ package visualiser.Controllers; import javafx.application.Platform; import javafx.beans.property.Property; import javafx.fxml.FXML; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.Pane; @@ -12,7 +11,6 @@ import javafx.scene.layout.StackPane; import javafx.scene.shape.Circle; import shared.model.Bearing; import shared.model.Wind; -import visualiser.model.VisualiserRace; /** * Controller for the arrow.fxml view. diff --git a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java index c801f16a..7cdd0e73 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/MainController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/MainController.java @@ -4,14 +4,14 @@ import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.layout.AnchorPane; import visualiser.gameController.ControllerClient; -import visualiser.network.ServerConnection; import visualiser.model.VisualiserBoat; -import visualiser.model.VisualiserRace; +import visualiser.model.VisualiserRaceEvent; import java.net.Socket; import java.net.URL; import java.util.ResourceBundle; + /** * Controller that everything is overlayed onto. This makes it so that changing scenes does not resize your stage. */ @@ -38,10 +38,9 @@ public class MainController extends Controller { * Transitions from the StartController screen (displays pre-race information) to the RaceController (displays the actual race). * @param visualiserRace The object modelling the race. * @param controllerClient Socket Client that manipulates the controller. - * @param serverConnection The connection to the server. */ - public void beginRace(VisualiserRace visualiserRace, ControllerClient controllerClient, ServerConnection serverConnection) { - raceController.startRace(visualiserRace, controllerClient, serverConnection); + public void beginRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient) { + raceController.startRace(visualiserRace, controllerClient); } /** diff --git a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java index 2e1a811e..be5b5cd1 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/RaceController.java @@ -4,7 +4,9 @@ package visualiser.Controllers; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.chart.LineChart; import javafx.scene.control.*; @@ -28,6 +30,8 @@ import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; + + /** * Controller used to display a running race. */ @@ -37,12 +41,16 @@ public class RaceController extends Controller { /** * The race object which describes the currently occurring race. */ - private VisualiserRace visualiserRace; + private VisualiserRaceEvent visualiserRace; + /** - * An additional observable list of boats. This is used by the table view, to allow it to sort boats without effecting the race's own list of boats. + * Service for sending keystrokes to server */ - private ObservableList tableBoatList; + private ControllerClient controllerClient; + + + /** * The canvas that draws the race. @@ -59,15 +67,6 @@ public class RaceController extends Controller { */ @FXML private ArrowController arrowController; - /** - * Service for sending keystrokes to server - */ - private ControllerClient controllerClient; - - /** - * The connection to the server. - */ - private ServerConnection serverConnection; @FXML private GridPane canvasBase; @@ -162,7 +161,7 @@ public class RaceController extends Controller { * Initialises the frame rate functionality. This allows for toggling the frame rate, and connect the fps label to the race's fps property. * @param visualiserRace The race to connect the fps label to. */ - private void initialiseFps(VisualiserRace visualiserRace) { + private void initialiseFps(VisualiserRaceEvent visualiserRace) { //On/off toggle. initialiseFpsToggle(); @@ -192,9 +191,9 @@ public class RaceController extends Controller { * Initialises the fps label to update when the race fps changes. * @param visualiserRace The race to monitor the frame rate of. */ - private void initialiseFpsLabel(VisualiserRace visualiserRace) { + private void initialiseFpsLabel(VisualiserRaceEvent visualiserRace) { - visualiserRace.fpsProperty().addListener((observable, oldValue, newValue) -> { + visualiserRace.getFrameRateProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> this.FPS.setText("FPS: " + newValue.toString())); }); @@ -206,14 +205,15 @@ public class RaceController extends Controller { * Initialises the information table view to listen to a given race. * @param race Race to listen to. */ - public void initialiseInfoTable(VisualiserRace race) { + public void initialiseInfoTable(VisualiserRaceEvent race) { //Copy list of boats. - this.tableBoatList = FXCollections.observableArrayList(race.getBoats()); + SortedList sortedBoats = new SortedList<>(race.getVisualiserRaceState().getBoats()); + sortedBoats.comparatorProperty().bind(boatInfoTable.comparatorProperty()); //Set up table. - boatInfoTable.setItems(this.tableBoatList); + boatInfoTable.setItems(sortedBoats); //Set up each column. @@ -293,12 +293,12 @@ public class RaceController extends Controller { /** - * Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRace}. + * Initialises the {@link Sparkline}, and listens to a specified {@link VisualiserRaceEvent}. * @param race The race to listen to. */ - private void initialiseSparkline(VisualiserRace race) { + private void initialiseSparkline(VisualiserRaceEvent race) { //The race.getBoats() we are passing in is sorted by position in race inside the race class. - this.sparkline = new Sparkline(this.visualiserRace, this.sparklineChart); + this.sparkline = new Sparkline(this.visualiserRace.getVisualiserRaceState(), this.sparklineChart); } @@ -306,7 +306,7 @@ public class RaceController extends Controller { * Initialises the {@link ResizableRaceCanvas}, provides the race to read data from. * @param race Race to read data from. */ - private void initialiseRaceCanvas(VisualiserRace race) { + private void initialiseRaceCanvas(VisualiserRaceEvent race) { //Create canvas. raceCanvas = new ResizableRaceCanvas(race); @@ -329,18 +329,18 @@ public class RaceController extends Controller { * Intialises the race time zone label with the race's time zone. * @param race The race to get time zone from. */ - private void initialiseRaceTimezoneLabel(VisualiserRace race) { - timeZone.setText(race.getRaceClock().getTimeZone()); + private void initialiseRaceTimezoneLabel(VisualiserRaceEvent race) { + timeZone.setText(race.getVisualiserRaceState().getRaceClock().getTimeZone()); } /** * Initialises the race clock to listen to the specified race. * @param race The race to listen to. */ - private void initialiseRaceClock(VisualiserRace race) { + private void initialiseRaceClock(VisualiserRaceEvent race) { //RaceClock.duration isn't necessarily being changed in the javaFX thread, so we need to runlater the update. - race.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { + race.getVisualiserRaceState().getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { timer.setText(newValue); }); @@ -353,13 +353,11 @@ public class RaceController extends Controller { * Displays a specified race. * @param visualiserRace Object modelling the race. * @param controllerClient Socket Client that manipulates the controller. - * @param serverConnection The connection to the server. */ - public void startRace(VisualiserRace visualiserRace, ControllerClient controllerClient, ServerConnection serverConnection) { + public void startRace(VisualiserRaceEvent visualiserRace, ControllerClient controllerClient) { this.visualiserRace = visualiserRace; this.controllerClient = controllerClient; - this.serverConnection = serverConnection; initialiseRace(); @@ -375,7 +373,7 @@ public class RaceController extends Controller { * Transition from the race view to the finish view. * @param boats boats there are in the race. */ - public void finishRace(ObservableList boats){ + public void finishRace(ObservableList boats) { race.setVisible(false); parent.enterFinish(boats); } @@ -385,8 +383,8 @@ public class RaceController extends Controller { * Initialises the arrow controller with data from the race to observe. * @param race The race to observe. */ - private void initialiseArrow(VisualiserRace race) { - arrowController.setWindProperty(race.windProperty()); + private void initialiseArrow(VisualiserRaceEvent race) { + arrowController.setWindProperty(race.getVisualiserRaceState().windProperty()); } @@ -399,7 +397,7 @@ public class RaceController extends Controller { public void handle(long arg0) { //Get the current race status. - RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum(); + RaceStatusEnum raceStatus = visualiserRace.getVisualiserRaceState().getRaceStatusEnum(); //If the race has finished, go to finish view. @@ -408,7 +406,7 @@ public class RaceController extends Controller { stop(); //Hide this, and display the finish controller. - finishRace(visualiserRace.getBoats()); + finishRace(visualiserRace.getVisualiserRaceState().getBoats()); } else { //Otherwise, render the canvas. @@ -421,7 +419,7 @@ public class RaceController extends Controller { } //Return to main screen if we lose connection. - if (!serverConnection.isAlive()) { + if (!visualiserRace.getServerConnection().isAlive()) { race.setVisible(false); parent.enterTitle(); //TODO currently this doesn't work correctly - the title screen remains visible after clicking join game diff --git a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java index b1c17bb1..b2d6b2b7 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/StartController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/StartController.java @@ -9,7 +9,7 @@ import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; -import javafx.scene.paint.Color; +import mock.model.commandFactory.CompositeCommand; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RequestToJoinEnum; import network.Messages.LatestMessages; @@ -20,9 +20,10 @@ import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRegattaDataException; import shared.exceptions.XMLReaderException; import visualiser.gameController.ControllerClient; +import visualiser.model.VisualiserRaceState; import visualiser.network.ServerConnection; import visualiser.model.VisualiserBoat; -import visualiser.model.VisualiserRace; +import visualiser.model.VisualiserRaceEvent; import java.io.IOException; import java.net.Socket; @@ -31,10 +32,11 @@ import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; + /** * Controller to for waiting for the race to start. */ -public class StartController extends Controller implements Observer { +public class StartController extends Controller { @FXML private GridPane start; @FXML private AnchorPane startWrapper; @@ -70,9 +72,10 @@ public class StartController extends Controller implements Observer { /** - * Our connection to the server. + * The race + connection to server. */ - private ServerConnection serverConnection; + private VisualiserRaceEvent visualiserRaceEvent; + /** * Writes BoatActions to outgoing message queue. @@ -80,27 +83,9 @@ public class StartController extends Controller implements Observer { private ControllerClient controllerClient; - /** - * The race object which describes the currently occurring race. - */ - private VisualiserRace visualiserRace; - /** - * An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor. - */ - private List colors = new ArrayList<>(Arrays.asList( - Color.BLUEVIOLET, - Color.BLACK, - Color.RED, - Color.ORANGE, - Color.DARKOLIVEGREEN, - Color.LIMEGREEN, - Color.PURPLE, - Color.DARKGRAY, - Color.YELLOW - )); @@ -117,41 +102,17 @@ public class StartController extends Controller implements Observer { /** * Starts the race. - * Called once we have received all XML files from the server. - * @param latestMessages The set of latest race messages to use for race. - * @throws XMLReaderException Thrown if XML file cannot be parsed. - * @throws InvalidRaceDataException Thrown if XML file cannot be parsed. - * @throws InvalidBoatDataException Thrown if XML file cannot be parsed. - * @throws InvalidRegattaDataException Thrown if XML file cannot be parsed. */ - private void startRace(LatestMessages latestMessages) throws XMLReaderException, InvalidRaceDataException, InvalidBoatDataException, InvalidRegattaDataException { - - //Create data sources from latest messages for the race. - RaceDataSource raceDataSource = new RaceXMLReader(latestMessages.getRaceXMLMessage().getXmlMessage(), XMLFileType.Contents); - BoatDataSource boatDataSource = new BoatXMLReader(latestMessages.getBoatXMLMessage().getXmlMessage(), XMLFileType.Contents); - RegattaDataSource regattaDataSource = new RegattaXMLReader(latestMessages.getRegattaXMLMessage().getXmlMessage(), XMLFileType.Contents); - - //Create race. - this.visualiserRace = new VisualiserRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, this.colors); - new Thread(this.visualiserRace).start(); - - //Ugly. TODO refactor - ObservableList boats = visualiserRace.getBoats(); - for (VisualiserBoat boat : boats) { - if (boat.getSourceID() == serverConnection.getAllocatedSourceID()) { - boat.setClientBoat(true); - } - } - + private void startRace() { //Initialise the boat table. - initialiseBoatTable(this.visualiserRace); + initialiseBoatTable(this.visualiserRaceEvent.getVisualiserRaceState()); //Initialise the race name. - initialiseRaceName(this.visualiserRace); + initialiseRaceName(this.visualiserRaceEvent.getVisualiserRaceState()); //Initialises the race clock. - initialiseRaceClock(this.visualiserRace); + initialiseRaceClock(this.visualiserRaceEvent.getVisualiserRaceState()); //Starts the race countdown timer. countdownTimer(); @@ -169,7 +130,7 @@ public class StartController extends Controller implements Observer { * Initialises the boat table that is to be shown on the pane. * @param visualiserRace The race to get data from. */ - private void initialiseBoatTable(VisualiserRace visualiserRace) { + private void initialiseBoatTable(VisualiserRaceState visualiserRace) { //Get the boats. ObservableList boats = visualiserRace.getBoats(); @@ -184,7 +145,7 @@ public class StartController extends Controller implements Observer { * Initialises the race name which is shown on the pane. * @param visualiserRace The race to get data from. */ - private void initialiseRaceName(VisualiserRace visualiserRace) { + private void initialiseRaceName(VisualiserRaceState visualiserRace) { raceTitleLabel.setText(visualiserRace.getRegattaName()); @@ -194,7 +155,7 @@ public class StartController extends Controller implements Observer { * Initialises the race clock/timer labels for the start time, current time, and remaining time. * @param visualiserRace The race to get data from. */ - private void initialiseRaceClock(VisualiserRace visualiserRace) { + private void initialiseRaceClock(VisualiserRaceState visualiserRace) { //Start time. initialiseRaceClockStartTime(visualiserRace); @@ -212,7 +173,7 @@ public class StartController extends Controller implements Observer { * Initialises the race current time label. * @param visualiserRace The race to get data from. */ - private void initialiseRaceClockStartTime(VisualiserRace visualiserRace) { + private void initialiseRaceClockStartTime(VisualiserRaceState visualiserRace) { raceStartLabel.setText(visualiserRace.getRaceClock().getStartingTimeString()); @@ -229,7 +190,7 @@ public class StartController extends Controller implements Observer { * Initialises the race current time label. * @param visualiserRace The race to get data from. */ - private void initialiseRaceClockCurrentTime(VisualiserRace visualiserRace) { + private void initialiseRaceClockCurrentTime(VisualiserRaceState visualiserRace) { visualiserRace.getRaceClock().currentTimeProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { @@ -243,7 +204,7 @@ public class StartController extends Controller implements Observer { * Initialises the race duration label. * @param visualiserRace The race to get data from. */ - private void initialiseRaceClockDuration(VisualiserRace visualiserRace) { + private void initialiseRaceClockDuration(VisualiserRaceState visualiserRace) { visualiserRace.getRaceClock().durationProperty().addListener((observable, oldValue, newValue) -> { Platform.runLater(() -> { @@ -261,13 +222,13 @@ public class StartController extends Controller implements Observer { @Override public void handle(long arg0) { - //TODO instead of having an AnimationTimer checking the race status, we could provide a Property, and connect a listener to that. //Get the current race status. - RaceStatusEnum raceStatus = visualiserRace.getRaceStatusEnum(); + RaceStatusEnum raceStatus = visualiserRaceEvent.getVisualiserRaceState().getRaceStatusEnum(); //Display it. raceStatusLabel.setText("Race Status: " + raceStatus.name()); + //If the race has reached the preparatory phase, or has started... if (raceStatus == RaceStatusEnum.PREPARATORY || raceStatus == RaceStatusEnum.STARTED) { //Stop this timer. @@ -275,9 +236,10 @@ public class StartController extends Controller implements Observer { //Hide this, and display the race controller. startWrapper.setVisible(false); - start.setVisible(false); + //start.setVisible(false);//TODO is this needed? + + parent.beginRace(visualiserRaceEvent, controllerClient); - parent.beginRace(visualiserRace, controllerClient, serverConnection); } } }.start(); @@ -285,58 +247,25 @@ public class StartController extends Controller implements Observer { - /** - * Function to handle changes in objects we observe. - * We observe LatestMessages. - * @param o The observed object. - * @param arg The {@link Observable#notifyObservers(Object)} parameter. - */ - @Override - public void update(Observable o, Object arg) { - - //Check that we actually have LatestMessages. - if (o instanceof LatestMessages) { - LatestMessages latestMessages = (LatestMessages) o; - - //If we've received all of the xml files, start the race. Only start it if it hasn't already been created. - if (latestMessages.hasAllXMLMessages() && this.visualiserRace == null) { - - //Need to handle it in the javafx thread. - Platform.runLater(() -> { - try { - this.startRace(latestMessages); - - } catch (XMLReaderException | InvalidBoatDataException | InvalidRaceDataException | InvalidRegattaDataException e) { - //We currently don't handle this in meaningful way, as it should never occur. - //If we reach this point it means that malformed XML files were sent. - e.printStackTrace(); - - } - }); - } - } - - } - /** * Show starting information for a race given a socket. * @param socket network source of information */ public void enterLobby(Socket socket) { - startWrapper.setVisible(true); try { - LatestMessages latestMessages = new LatestMessages(); - this.serverConnection = new ServerConnection(socket, latestMessages, RequestToJoinEnum.PARTICIPANT); - this.controllerClient = serverConnection.getControllerClient(); + this.visualiserRaceEvent = new VisualiserRaceEvent(socket, RequestToJoinEnum.PARTICIPANT); + + this.controllerClient = visualiserRaceEvent.getControllerClient(); + + startWrapper.setVisible(true); - //Store a reference to latestMessages so that we can observe it. - latestMessages.addObserver(this); - new Thread(this.serverConnection).start(); + startRace(); } catch (IOException e) { - Logger.getGlobal().log(Level.WARNING, "Could not connection to server.", e); + //TODO should probably let this propagate, so that we only enter this scene if everything works + Logger.getGlobal().log(Level.WARNING, "Could not connect to server.", e); } } diff --git a/racevisionGame/src/main/java/visualiser/model/RaceMap.java b/racevisionGame/src/main/java/visualiser/model/RaceMap.java index 1583e0a5..96e60347 100644 --- a/racevisionGame/src/main/java/visualiser/model/RaceMap.java +++ b/racevisionGame/src/main/java/visualiser/model/RaceMap.java @@ -11,22 +11,22 @@ public class RaceMap { /** * The longitude of the left side of the map. */ - private final double longLeft; + private double longLeft; /** * The longitude of the right side of the map. */ - private final double longRight; + private double longRight; /** * The latitude of the top side of the map. */ - private final double latTop; + private double latTop; /** * The latitude of the bottom side of the map. */ - private final double latBottom; + private double latBottom; /** @@ -143,4 +143,27 @@ public class RaceMap { public void setHeight(int height) { this.height = height; } + + + /** + * Updates the bottom right GPS coordinates of the RaceMap. + * @param bottomRight New bottom right GPS coordinates. + */ + public void setGPSBotRight(GPSCoordinate bottomRight) { + this.latBottom = bottomRight.getLatitude(); + this.longRight = bottomRight.getLongitude(); + } + + + /** + * Updates the top left GPS coordinates of the RaceMap. + * @param topLeft New top left GPS coordinates. + */ + public void setGPSTopLeft(GPSCoordinate topLeft) { + this.latTop = topLeft.getLatitude(); + this.longLeft = topLeft.getLongitude(); + } + + + } diff --git a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java index fd2ce085..5a0243f6 100644 --- a/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java +++ b/racevisionGame/src/main/java/visualiser/model/ResizableRaceCanvas.java @@ -1,8 +1,6 @@ package visualiser.model; -import javafx.scene.Node; -import javafx.scene.image.Image; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.transform.Rotate; @@ -12,6 +10,7 @@ import shared.model.GPSCoordinate; import shared.model.Mark; import shared.model.RaceClock; +import java.util.ArrayList; import java.util.List; /** @@ -36,7 +35,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { /** * The race we read data from and draw. */ - private VisualiserRace visualiserRace; + private VisualiserRaceEvent visualiserRace; private boolean annoName = true; @@ -49,15 +48,15 @@ public class ResizableRaceCanvas extends ResizableCanvas { /** - * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRace}. + * Constructs a {@link ResizableRaceCanvas} using a given {@link VisualiserRaceEvent}. * @param visualiserRace The race that data is read from in order to be drawn. */ - public ResizableRaceCanvas(VisualiserRace visualiserRace) { + public ResizableRaceCanvas(VisualiserRaceEvent visualiserRace) { super(); this.visualiserRace = visualiserRace; - RaceDataSource raceData = visualiserRace.getRaceDataSource(); + RaceDataSource raceData = visualiserRace.getVisualiserRaceState().getRaceDataSource(); double lat1 = raceData.getMapTopLeft().getLatitude(); double long1 = raceData.getMapTopLeft().getLongitude(); @@ -264,8 +263,8 @@ public class ResizableRaceCanvas extends ResizableCanvas { boat.getCountry(), boat.getCurrentSpeed(), this.map.convertGPS(boat.getCurrentPosition()), - boat.getTimeToNextMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime()), - boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getRaceClock().getCurrentTime()) ); + boat.getTimeToNextMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()), + boat.getTimeSinceLastMarkFormatted(this.visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()) ); } @@ -277,7 +276,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ private void drawBoats() { - for (VisualiserBoat boat : visualiserRace.getBoats()) { + for (VisualiserBoat boat : new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoats())) { //Draw the boat. drawBoat(boat); @@ -290,7 +289,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { //If the race hasn't started, we set the time since last mark to the current time, to ensure we don't start counting until the race actually starts. if ((boat.getStatus() != BoatStatusEnum.RACING) && (boat.getStatus() == BoatStatusEnum.FINISHED)) { - boat.setTimeAtLastMark(visualiserRace.getRaceClock().getCurrentTime()); + boat.setTimeAtLastMark(visualiserRace.getVisualiserRaceState().getRaceClock().getCurrentTime()); } //Draw boat label. @@ -402,7 +401,8 @@ public class ResizableRaceCanvas extends ResizableCanvas { * Draws all of the {@link Mark}s on the canvas. */ private void drawMarks() { - for (Mark mark : this.visualiserRace.getMarks()) { + + for (Mark mark : new ArrayList<>(visualiserRace.getVisualiserRaceState().getMarks())) { drawMark(mark); } } @@ -462,7 +462,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { //Calculate the screen coordinates of the boundary. - List boundary = this.visualiserRace.getBoundary(); + List boundary = new ArrayList<>(visualiserRace.getVisualiserRaceState().getBoundary()); double[] xpoints = new double[boundary.size()]; double[] ypoints = new double[boundary.size()]; @@ -486,6 +486,10 @@ public class ResizableRaceCanvas extends ResizableCanvas { */ public void drawRace() { + //Update RaceMap with new GPS values of race. + this.map.setGPSTopLeft(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapTopLeft()); + this.map.setGPSBotRight(visualiserRace.getVisualiserRaceState().getRaceDataSource().getMapBottomRight()); + gc.setLineWidth(2); clear(); @@ -520,7 +524,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { gc.setFill(boat.getColor()); //Draw each TrackPoint. - for (TrackPoint point : boat.getTrack()) { + for (TrackPoint point : new ArrayList<>(boat.getTrack())) { //Convert the GPSCoordinate to a screen coordinate. GraphCoordinate scaledCoordinate = this.map.convertGPS(point.getCoordinate()); diff --git a/racevisionGame/src/main/java/visualiser/model/Sparkline.java b/racevisionGame/src/main/java/visualiser/model/Sparkline.java index 5e802ff9..d8f21f77 100644 --- a/racevisionGame/src/main/java/visualiser/model/Sparkline.java +++ b/racevisionGame/src/main/java/visualiser/model/Sparkline.java @@ -2,6 +2,7 @@ package visualiser.model; import javafx.application.Platform; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; @@ -23,7 +24,7 @@ public class Sparkline { /** * The race to observe. */ - private VisualiserRace race; + private VisualiserRaceState race; /** * The boats to observe. @@ -58,9 +59,9 @@ public class Sparkline { * @param race The race to listen to. * @param sparklineChart JavaFX LineChart for the sparkline. */ - public Sparkline(VisualiserRace race, LineChart sparklineChart) { + public Sparkline(VisualiserRaceState race, LineChart sparklineChart) { this.race = race; - this.boats = race.getBoats(); + this.boats = new SortedList<>(race.getBoats()); this.legNum = race.getLegCount(); this.sparklineChart = sparklineChart; diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java index c99c85cb..41d0e484 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserBoat.java @@ -174,28 +174,33 @@ public class VisualiserBoat extends Boat { */ public String getTimeToNextMarkFormatted(ZonedDateTime currentTime) { - //Calculate time delta. - Duration timeUntil = Duration.between(currentTime, getEstimatedTimeAtNextMark()); + if (getTimeAtLastMark() != null) { + //Calculate time delta. + Duration timeUntil = Duration.between(currentTime, getEstimatedTimeAtNextMark()); - //Convert to seconds. - long secondsUntil = timeUntil.getSeconds(); + //Convert to seconds. + long secondsUntil = timeUntil.getSeconds(); - //This means the estimated time is in the past, or not racing. - if ((secondsUntil < 0) || (getStatus() != BoatStatusEnum.RACING)) { - return " -"; - } + //This means the estimated time is in the past, or not racing. + if ((secondsUntil < 0) || (getStatus() != BoatStatusEnum.RACING)) { + return " -"; + } - if (secondsUntil <= 60) { - //If less than 1 minute, display seconds only. - return " " + secondsUntil + "s"; + if (secondsUntil <= 60) { + //If less than 1 minute, display seconds only. + return " " + secondsUntil + "s"; - } else { - //Otherwise display minutes and seconds. - long seconds = secondsUntil % 60; - long minutes = (secondsUntil - seconds) / 60; - return String.format(" %dm %ds", minutes, seconds); + } else { + //Otherwise display minutes and seconds. + long seconds = secondsUntil % 60; + long minutes = (secondsUntil - seconds) / 60; + return String.format(" %dm %ds", minutes, seconds); + + } + } else { + return " -"; } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java deleted file mode 100644 index 0dc172cd..00000000 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRace.java +++ /dev/null @@ -1,472 +0,0 @@ -package visualiser.model; - -import javafx.animation.AnimationTimer; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.paint.Color; -import network.Messages.BoatLocation; -import network.Messages.BoatStatus; -import network.Messages.Enums.BoatStatusEnum; -import network.Messages.Enums.RaceStatusEnum; -import network.Messages.LatestMessages; -import network.Messages.RaceStatus; -import shared.dataInput.BoatDataSource; -import shared.dataInput.RaceDataSource; -import shared.dataInput.RegattaDataSource; -import shared.model.*; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The Class used to view the race streamed. - * Has a course, boats, boundaries, etc... - * Observes LatestMessages and updates its state based on new messages. - */ -public class VisualiserRace extends Race implements Runnable { - - - /** - * An observable list of boats in the race. - */ - private final ObservableList boats; - - /** - * An observable list of marker boats in the race. - */ - private ObservableList boatMarkers; - - - /** - * 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. - */ - private Map> legCompletionOrder = new HashMap<>(); - - - - /** - * Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and receives events from LatestMessages. - * @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...). - * @param latestMessages The LatestMessages to send events to. - * @param colors A collection of colors used to assign a color to each boat. - */ - public VisualiserRace(BoatDataSource boatDataSource, RaceDataSource raceDataSource, RegattaDataSource regattaDataSource, LatestMessages latestMessages, List colors) { - - super(boatDataSource, raceDataSource, regattaDataSource, latestMessages); - - - this.boats = FXCollections.observableArrayList(this.generateVisualiserBoats(boatDataSource.getBoats(), raceDataSource.getParticipants(), colors)); - - this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values()); - - - //Initialise the leg completion order map. - for (Leg leg : this.legs) { - this.legCompletionOrder.put(leg, new ArrayList<>(this.boats.size())); - } - - } - - - /** - * 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) { - this.raceDataSource = raceDataSource; - - this.boundary = raceDataSource.getBoundary(); - - this.useLegsList(raceDataSource.getLegs()); - } - - /** - * 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) { - this.boatDataSource = boatDataSource; - - this.boatMarkers = FXCollections.observableArrayList(boatDataSource.getMarkerBoats().values()); - } - - /** - * Sets the regatta data source for this race to a new RegattaDataSource. - * @param regattaDataSource The new RegattaDataSource to use. - */ - public void setRegattaDataSource(RegattaDataSource regattaDataSource) { - this.regattaDataSource = regattaDataSource; - } - - - /** - * Returns a list of {@link Mark} boats. - * @return List of mark boats. - */ - public ObservableList getMarks() { - return boatMarkers; - } - - /** - * Generates a list of VisualiserBoats given a list of Boats, and a list of participating boats. - * @param boats The map of Boats 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 colors to be used for the boats. - * @return A list of MockBoats that are participating in the race. - */ - private List generateVisualiserBoats(Map boats, List sourceIDs, List colors) { - - List visualiserBoats = new ArrayList<>(sourceIDs.size()); - - //For each sourceID participating... - int colorIndex = 0; - for (int sourceID : sourceIDs) { - - //Get the boat associated with the sourceID. - Boat boat = boats.get(sourceID); - - //Get a color for the boat. - Color color = colors.get(colorIndex); - - //Construct a VisualiserBoat using the Boat and Polars. - VisualiserBoat visualiserBoat = new VisualiserBoat(boat, color); - - visualiserBoats.add(visualiserBoat); - - //Next color. - colorIndex++; - - } - - return visualiserBoats; - - } - - - /** - * Initialise the boats in the race. - * This sets their current leg. - */ - @Override - protected void initialiseBoats() { - - Leg startingLeg = legs.get(0); - - for (VisualiserBoat boat : boats) { - - boat.setCurrentLeg(startingLeg); - boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); - - } - - } - - - /** - * Updates all of the racing boats based on messages received. - * @param boats The list of racing boats. - * @param boatLocationMap A map between boat sourceIDs and BoatLocation messages. - * @param boatStatusMap A map between boat sourceIDs and BoatStatus messages. - */ - private void updateBoats(ObservableList boats, Map boatLocationMap, Map boatStatusMap) { - - for (VisualiserBoat boat : boats) { - BoatLocation boatLocation = boatLocationMap.get(boat.getSourceID()); - BoatStatus boatStatus = boatStatusMap.get(boat.getSourceID()); - updateBoat(boat, boatLocation, boatStatus); - } - } - - - /** - * Updates an individual racing boat based on messages received. - * @param boat The boat to update. - * @param boatLocation The BoatLocation message to use. - * @param boatStatus The BoatStatus message to use. - */ - private void updateBoat(VisualiserBoat boat, BoatLocation boatLocation, BoatStatus boatStatus) { - - if (boatLocation != null && boatStatus != null) { - - //Get the new position. - double latitude = boatLocation.getLatitude(); - double longitude = boatLocation.getLongitude(); - GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); - - boat.setCurrentPosition(gpsCoordinate); - - //Bearing. - boat.setBearing(boatLocation.getHeading()); - - //Time until next mark. - boat.setEstimatedTimeAtNextMark(raceClock.getLocalTime(boatStatus.getEstTimeAtNextMark())); - - //Speed. - boat.setCurrentSpeed(boatLocation.getBoatSpeedKnots()); - - - //Boat status. - BoatStatusEnum newBoatStatusEnum = boatStatus.getBoatStatus(); - - //If we are changing from non-racing to racing, we need to initialise boat with their time at last mark. - if ((boat.getStatus() != BoatStatusEnum.RACING) && (newBoatStatusEnum == BoatStatusEnum.RACING)) { - boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); - } - - boat.setStatus(newBoatStatusEnum); - - - //Leg. - int legNumber = boatStatus.getLegNumber(); - - if (legNumber >= 1 && legNumber < legs.size()) { - if (boat.getCurrentLeg() != legs.get(legNumber)) { - boatFinishedLeg(boat, legs.get(legNumber)); - } - } - - - //Attempt to add a track point. - if (newBoatStatusEnum == BoatStatusEnum.RACING) { - boat.addTrackPoint(boat.getCurrentPosition(), raceClock.getCurrentTime()); - } - - //Set finish time if boat finished. - if (newBoatStatusEnum == BoatStatusEnum.FINISHED || legNumber == this.legs.size()) { - boat.setTimeFinished(boatLocation.getTime()); - boat.setStatus(BoatStatusEnum.FINISHED); - - } - - } - } - - - /** - * Updates a boat's leg to a specified leg. Also records the order in which the boat passed the leg. - * @param boat The boat to update. - * @param leg The leg to use. - */ - private void boatFinishedLeg(VisualiserBoat boat, Leg leg) { - - //Record order in which boat finished leg. - this.legCompletionOrder.get(boat.getCurrentLeg()).add(boat); - - //Update boat. - boat.setCurrentLeg(leg); - boat.setTimeAtLastMark(this.raceClock.getCurrentTime()); - - } - - - /** - * Updates all of the marker boats based on messages received. - * @param boatMarkers The list of marker boats. - * @param boatLocationMap A map between boat sourceIDs and BoatLocation messages. - * @param boatStatusMap A map between boat sourceIDs and BoatStatus messages. - */ - private void updateMarkers(ObservableList boatMarkers, Map boatLocationMap, Map boatStatusMap) { - - for (Mark mark : boatMarkers) { - BoatLocation boatLocation = boatLocationMap.get(mark.getSourceID()); - updateMark(mark, boatLocation); - } - } - - /** - * Updates an individual marker boat based on messages received. - * @param mark The marker boat to be updated. - * @param boatLocation The message describing the boat's new location. - */ - private void updateMark(Mark mark, BoatLocation boatLocation) { - - if (boatLocation != null) { - - //We only update the boat's position. - double latitude = boatLocation.getLatitude(); - double longitude = boatLocation.getLongitude(); - GPSCoordinate gpsCoordinate = new GPSCoordinate(latitude, longitude); - - mark.setPosition(gpsCoordinate); - - } - } - - - /** - * Updates the race status (RaceStatusEnum, wind bearing, wind speed) based on received messages. - * @param raceStatus The RaceStatus message received. - */ - private void updateRaceStatus(RaceStatus raceStatus) { - - //Race status enum. - this.raceStatusEnum = raceStatus.getRaceStatus(); - - //Wind. - this.setWind( - raceStatus.getWindDirection(), - raceStatus.getWindSpeed() ); - - //Current race time. - this.raceClock.setUTCTime(raceStatus.getCurrentTime()); - - } - - - - - /** - * Runnable for the thread. - */ - public void run() { - initialiseBoats(); - startRaceStream(); - } - - - - - /** - * Starts the race. - * This updates the race based on {@link #latestMessages}. - */ - private void startRaceStream() { - - new AnimationTimer() { - - long lastFrameTime = System.currentTimeMillis(); - - @Override - public void handle(long arg0) { - - - //Calculate the frame period. - long currentFrameTime = System.currentTimeMillis(); - long framePeriod = currentFrameTime - lastFrameTime; - - - //Update race status. - if (latestMessages.getRaceStatus() != null) { - updateRaceStatus(latestMessages.getRaceStatus()); - } - - - //Update racing boats. - updateBoats(boats, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap()); - //And their positions (e.g., 5th). - updateBoatPositions(boats); - - - //Update marker boats. - updateMarkers(boatMarkers, latestMessages.getBoatLocationMap(), latestMessages.getBoatStatusMap()); - - - - - if (getRaceStatusEnum() == RaceStatusEnum.FINISHED) { - stop(); - } - - lastFrameTime = currentFrameTime; - - //Increment fps. - incrementFps(framePeriod); - - } - }.start(); - } - - /** - * 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. - */ - private void updateBoatPositions(ObservableList 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.setPosition("-"); - - } else { - boat.setPosition(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(ObservableList boats) { - - FXCollections.sort(boats, (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) { - return (int) Duration.between(b.getEstimatedTimeAtNextMark(), a.getEstimatedTimeAtNextMark()).toMillis(); - } else { - return legNumberDelta; - } - - }); - - } - - - - - /** - * Returns the boats participating in the race. - * @return ObservableList of boats participating in the race. - */ - public ObservableList getBoats() { - return boats; - } - - /** - * 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> getLegCompletionOrder() { - return legCompletionOrder; - } - - /** - * Takes an estimated time an event will occur, and converts it to the - * number of seconds before the event will occur. - * - * @param estTimeMillis The estimated time, in milliseconds. - * @param currentTime The current time, in milliseconds. - * @return int difference between time the race started and the estimated time - */ - private int convertEstTime(long estTimeMillis, long currentTime) { - - //Calculate millisecond delta. - long estElapsedMillis = estTimeMillis - currentTime; - - //Convert milliseconds to seconds. - int estElapsedSecs = Math.round(estElapsedMillis / 1000); - - return estElapsedSecs; - - } - -} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceController.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceController.java index 15adc13b..748808b3 100644 --- a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceController.java +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceController.java @@ -1,6 +1,11 @@ package visualiser.model; + +import mock.exceptions.CommandConstructionException; +import mock.model.commandFactory.Command; +import mock.model.commandFactory.CompositeCommand; import network.Messages.*; import shared.model.RunnableWithFramePeriod; +import visualiser.Commands.VisualiserRaceCommands.VisualiserRaceCommandFactory; import java.util.concurrent.BlockingQueue; import java.util.logging.Level; @@ -8,8 +13,7 @@ import java.util.logging.Logger; /** - * TCP client which receives packets/messages from a race data source - * (e.g., mock source, official source), and exposes them to any observers. + * The controller for race related messages, coming from the server to the client. */ public class VisualiserRaceController implements RunnableWithFramePeriod { @@ -21,31 +25,29 @@ public class VisualiserRaceController implements RunnableWithFramePeriod { /** - * An object containing the set of latest messages to write to. - * Every server frame, VisualiserInput reads messages from its incomingMessages, and write them to this. + * Commands are placed in here, and executed by visualiserRace. */ - private LatestMessages latestMessages; + private CompositeCommand compositeRaceCommand; /** - * Constructs a visualiserInput to convert an incoming stream of messages into LatestMessages. - * - * @param latestMessages Object to place messages in. - * @param incomingMessages The incoming queue of messages. + * The context that created commands operate on. */ - public VisualiserRaceController(LatestMessages latestMessages, BlockingQueue incomingMessages) { - this.latestMessages = latestMessages; - this.incomingMessages = incomingMessages; - } + private VisualiserRaceState visualiserRace; + + /** - * Returns the LatestMessages object, which can be queried for any received race related messages. - * - * @return The LatestMessages object. + * Constructs a visualiserInput to convert an incoming stream of messages into commands. + * @param incomingMessages The incoming queue of messages. + * @param visualiserRace The context to for commands to operate on. + * @param compositeRaceCommand The composite command to place command in. */ - public LatestMessages getLatestMessages() { - return latestMessages; + public VisualiserRaceController(BlockingQueue incomingMessages, VisualiserRaceState visualiserRace, CompositeCommand compositeRaceCommand) { + this.incomingMessages = incomingMessages; + this.compositeRaceCommand = compositeRaceCommand; + this.visualiserRace = visualiserRace; } @@ -56,157 +58,21 @@ public class VisualiserRaceController implements RunnableWithFramePeriod { while (!Thread.interrupted()) { - AC35Data message = null; try { - message = incomingMessages.take(); - } catch (InterruptedException e) { - Logger.getGlobal().log(Level.SEVERE, "VisualiserInput was interrupted on thread: " + Thread.currentThread() + " while waiting for messages."); - Thread.currentThread().interrupt(); - return; - } - - - //TODO refactor below - - //Checks which message is being received and does what is needed for that message. - switch (message.getType()) { - - - //RaceStatus. - case RACESTATUS: { - RaceStatus raceStatus = (RaceStatus) message; - - //System.out.println("Race Status Message"); - this.latestMessages.setRaceStatus(raceStatus); - - for (BoatStatus boatStatus : raceStatus.getBoatStatuses()) { - this.latestMessages.setBoatStatus(boatStatus); - } - - break; - } - - //DisplayTextMessage. - case DISPLAYTEXTMESSAGE: { - //System.out.println("Display Text Message"); - //No decoder for this. - - break; - } - - //XMLMessage. - case XMLMESSAGE: { - XMLMessage xmlMessage = (XMLMessage) message; - - //System.out.println("XML Message!"); - - this.latestMessages.setXMLMessage(xmlMessage); - - break; - } - - //RaceStartStatus. - case RACESTARTSTATUS: { - - //System.out.println("Race Start Status Message"); + AC35Data message = incomingMessages.take(); - break; - } - - //YachtEventCode. - case YACHTEVENTCODE: { - //YachtEventCode yachtEventCode = (YachtEventCode) message; - - //System.out.println("Yacht Event Code!"); - //No decoder for this. - - break; - } - - //YachtActionCode. - case YACHTACTIONCODE: { - //YachtActionCode yachtActionCode = (YachtActionCode) message; - - //System.out.println("Yacht Action Code!"); - // No decoder for this. - - break; - } - - //ChatterText. - case CHATTERTEXT: { - //ChatterText chatterText = (ChatterText) message; - - //System.out.println("Chatter Text Message!"); - //No decoder for this. - - break; - } - - //BoatLocation. - case BOATLOCATION: { - BoatLocation boatLocation = (BoatLocation) message; - - //System.out.println("Boat Location!"); - - BoatLocation existingBoatLocation = this.latestMessages.getBoatLocationMap().get(boatLocation.getSourceID()); - if (existingBoatLocation != null) { - //If our boatlocation map already contains a boat location message for this boat, check that the new message is actually for a later timestamp (i.e., newer). - if (boatLocation.getTime() > existingBoatLocation.getTime()) { - //If it is, replace the old message. - this.latestMessages.setBoatLocation(boatLocation); - } - } else { - //If the map _doesn't_ already contain a message for this boat, insert the message. - this.latestMessages.setBoatLocation(boatLocation); - } - - break; - } - - //MarkRounding. - case MARKROUNDING: { - MarkRounding markRounding = (MarkRounding) message; - - //System.out.println("Mark Rounding Message!"); - - MarkRounding existingMarkRounding = this.latestMessages.getMarkRoundingMap().get(markRounding.getSourceID()); - if (existingMarkRounding != null) { - - //If our markRoundingMap already contains a mark rounding message for this boat, check that the new message is actually for a later timestamp (i.e., newer). - if (markRounding.getTime() > existingMarkRounding.getTime()) { - //If it is, replace the old message. - this.latestMessages.setMarkRounding(markRounding); - } - - } else { - //If the map _doesn't_ already contain a message for this boat, insert the message. - this.latestMessages.setMarkRounding(markRounding); - } - - break; - } - - //CourseWinds. - case COURSEWIND: { - - //System.out.println("Course Wind Message!"); - CourseWinds courseWinds = (CourseWinds) message; - - this.latestMessages.setCourseWinds(courseWinds); - - break; - } + Command command = VisualiserRaceCommandFactory.create(message, visualiserRace); + compositeRaceCommand.addCommand(command); + } catch (CommandConstructionException e) { + Logger.getGlobal().log(Level.WARNING, "VisualiserRaceController could not create a command for incoming message."); + } catch (InterruptedException e) { + Logger.getGlobal().log(Level.SEVERE, "VisualiserRaceController was interrupted on thread: " + Thread.currentThread() + " while waiting for messages."); + Thread.currentThread().interrupt(); + return; } - //Main loop. - // take message - // create command - // place in command queue - - } } diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java new file mode 100644 index 00000000..d5ab9b66 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceEvent.java @@ -0,0 +1,119 @@ +package visualiser.model; + + +import javafx.beans.property.IntegerProperty; +import mock.model.commandFactory.CompositeCommand; +import network.Messages.Enums.RequestToJoinEnum; +import shared.dataInput.EmptyBoatDataSource; +import shared.dataInput.EmptyRaceDataSource; +import shared.dataInput.EmptyRegattaDataSource; +import visualiser.gameController.ControllerClient; +import visualiser.network.ServerConnection; + +import java.io.IOException; +import java.net.Socket; + + +/** + * This class holds a race, and a client's connection to it + */ +public class VisualiserRaceEvent { + + /** + * Our connection to the server. + */ + private ServerConnection serverConnection; + /** + * The thread serverConnection is running on. + */ + private Thread serverConnectionThread; + + + + /** + * The race object which describes the currently occurring race. + */ + private VisualiserRaceState visualiserRaceState; + + + /** + * The service for updating the {@link #visualiserRaceState}. + */ + private VisualiserRaceService visualiserRaceService; + /** + * The thread {@link #visualiserRaceService} is running on. + */ + private Thread visualiserRaceServiceThread; + + + + /** + * Creates a visualiser race event, with a given socket and request type. + * @param socket The socket to connect to. + * @param requestType The type of {@link network.Messages.RequestToJoin} to make. + * @throws IOException Thrown if there is a problem with the socket. + */ + public VisualiserRaceEvent(Socket socket, RequestToJoinEnum requestType) throws IOException { + + this.visualiserRaceState = new VisualiserRaceState(new EmptyRaceDataSource(), new EmptyRegattaDataSource(), new EmptyBoatDataSource()); + + + CompositeCommand raceCommands = new CompositeCommand(); + this.visualiserRaceService = new VisualiserRaceService(raceCommands, visualiserRaceState); + + this.visualiserRaceServiceThread = new Thread(visualiserRaceService, "VisualiserRaceEvent()->VisualiserRaceService thread " + visualiserRaceService); + this.visualiserRaceServiceThread.start(); + + + this.serverConnection = new ServerConnection(socket, visualiserRaceState, raceCommands, requestType); + this.serverConnectionThread = new Thread(serverConnection, "StartController.enterLobby()->serverConnection thread " + serverConnection); + this.serverConnectionThread.start(); + + + } + + + /** + * Returns the state of the race. + * @return The state of the race. + */ + public VisualiserRaceState getVisualiserRaceState() { + return visualiserRaceState; + } + + + /** + * Returns the controller client, which writes BoatAction messages to the outgoing queue. + * @return The ControllerClient. + */ + public ControllerClient getControllerClient() { + return serverConnection.getControllerClient(); + } + + /** + * Returns the connection to server. + * @return Connection to server. + */ + public ServerConnection getServerConnection() { + return serverConnection; + } + + /** + * Returns the framerate property of the race. + * @return Framerate property of race. + */ + public IntegerProperty getFrameRateProperty() { + return visualiserRaceService.getFrameRateProperty(); + } + + + + /** + * Terminates the server connection and race service. + */ + public void terminate() { + this.serverConnectionThread.interrupt(); + + this.visualiserRaceServiceThread.interrupt(); + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java new file mode 100644 index 00000000..21be26d9 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceService.java @@ -0,0 +1,93 @@ +package visualiser.model; + +import javafx.beans.property.IntegerProperty; +import mock.model.commandFactory.CompositeCommand; +import shared.model.FrameRateTracker; +import shared.model.RunnableWithFramePeriod; + + +/** + * Handles updating a {@link VisualiserRaceState} with incoming commands. + */ +public class VisualiserRaceService implements RunnableWithFramePeriod { + + + /** + * The race state to update. + */ + private VisualiserRaceState visualiserRaceState; + + + /** + * A composite commands to execute to update the race. + */ + private CompositeCommand raceCommands; + + + /** + * Used to track the framerate of the "simulation". + */ + private FrameRateTracker frameRateTracker; + + + + + + + /** + * Constructs a visualiser race which models a yacht race, and is modified by CompositeCommand. + * @param raceCommands A composite commands to execute to update the race. + * @param visualiserRaceState The race state to update. + */ + public VisualiserRaceService(CompositeCommand raceCommands, VisualiserRaceState visualiserRaceState) { + this.raceCommands = raceCommands; + this.visualiserRaceState = visualiserRaceState; + + this.frameRateTracker = new FrameRateTracker(); + } + + + + /** + * Returns the CompositeCommand executed by the race. + * @return CompositeCommand executed by race. + */ + public CompositeCommand getRaceCommands() { + return raceCommands; + } + + + + + + @Override + public void run() { + + long previousFrameTime = System.currentTimeMillis(); + + while (!Thread.interrupted()) { + + long currentFrameTime = System.currentTimeMillis(); + + waitForFramePeriod(previousFrameTime, currentFrameTime, 16); + + previousFrameTime = currentFrameTime; + + + raceCommands.execute(); + + } + + frameRateTracker.stop(); + + } + + + /** + * Returns the framerate property of the race. + * @return Framerate property of race. + */ + public IntegerProperty getFrameRateProperty() { + return frameRateTracker.fpsProperty(); + } +} diff --git a/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java new file mode 100644 index 00000000..4dc6519d --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/model/VisualiserRaceState.java @@ -0,0 +1,411 @@ +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 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> legCompletionOrder; + + + + + /** + * An array of colors used to assign colors to each boat - passed in to the VisualiserRace constructor. + */ + private List 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. + */ + 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); + } + + useLegsList(raceDataSource.getLegs()); + } + + /** + * 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); + + } + + + /** + * See {@link RaceState#useLegsList(List)}. + * Also initialises the {@link #legCompletionOrder} map. + * @param legs The new list of legs to use. + */ + @Override + public void useLegsList(List legs) { + super.useLegsList(legs); + + //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 existingBoats, Map boats, List sourceIDs, List 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 existingBoatIDs = new ArrayList<>(); + for (VisualiserBoat boat : existingBoats) { + existingBoatIDs.add(boat.getSourceID()); + } + + //Get source IDs of only newly participating boats. + List 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 : 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); + boat.setTimeAtLastMark(getRaceClock().getCurrentTime()); + boat.setCurrentPosition(new GPSCoordinate(0, 0)); + + } + + } + + + + + /** + * 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 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.setPosition("-"); + + } else { + boat.setPosition(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 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 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> 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(); + } + + +} diff --git a/racevisionGame/src/main/java/visualiser/network/ConnectionToServer.java b/racevisionGame/src/main/java/visualiser/network/ConnectionToServer.java index 4d22fc23..72d49c4a 100644 --- a/racevisionGame/src/main/java/visualiser/network/ConnectionToServer.java +++ b/racevisionGame/src/main/java/visualiser/network/ConnectionToServer.java @@ -147,11 +147,27 @@ public class ConnectionToServer implements RunnableWithFramePeriod { //Send them the source ID. RequestToJoin requestToJoin = new RequestToJoin(requestType); - outgoingMessages.put(requestToJoin); + send(requestToJoin); connectionState = ConnectionToServerState.REQUEST_SENT; } + /** + * Sends a given message to the server, via the {@link #outgoingMessages} queue. + * @param message Message to send. + * @throws InterruptedException Thrown if thread is interrupted while sending message. + */ + public void send(AC35Data message) throws InterruptedException { + outgoingMessages.put(message); + } + + /** + * Returns the type of join request that was made. + * @return Type of join request made. + */ + public RequestToJoinEnum getRequestType() { + return requestType; + } } diff --git a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java index 35b25727..ed540566 100644 --- a/racevisionGame/src/main/java/visualiser/network/ServerConnection.java +++ b/racevisionGame/src/main/java/visualiser/network/ServerConnection.java @@ -2,18 +2,20 @@ package visualiser.network; import mock.model.commandFactory.Command; +import mock.model.commandFactory.CompositeCommand; import network.MessageRouters.MessageRouter; import network.Messages.AC35Data; import network.Messages.Enums.MessageType; import network.Messages.Enums.RequestToJoinEnum; -import network.Messages.JoinAcceptance; import network.Messages.LatestMessages; import network.StreamRelated.MessageDeserialiser; import network.StreamRelated.MessageSerialiser; import shared.model.RunnableWithFramePeriod; +import visualiser.model.VisualiserRaceEvent; import visualiser.model.VisualiserRaceController; import visualiser.enums.ConnectionToServerState; import visualiser.gameController.ControllerClient; +import visualiser.model.VisualiserRaceState; import java.io.IOException; import java.net.Socket; @@ -23,7 +25,7 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * This class handles the client-server connection handshake, and creation of VisualiserInput and ControllerClient. + * This class handles the client-server connection handshake. */ public class ServerConnection implements RunnableWithFramePeriod { @@ -32,16 +34,8 @@ public class ServerConnection implements RunnableWithFramePeriod { */ private Socket socket; - /** - * The source ID that has been allocated to the client. - */ - private int allocatedSourceID = 0; - /** - * Latest snapshot of the race, received from the server. - */ - private LatestMessages latestMessages; /** @@ -122,6 +116,16 @@ public class ServerConnection implements RunnableWithFramePeriod { private Thread heartBeatControllerThread; + /** + * This is the race we are modelling. + */ + private VisualiserRaceState visualiserRaceState; + + /** + * The CompositeCommand to place race commands in. + */ + private CompositeCommand raceCommands; + /** * Used to convert incoming messages into a race snapshot. @@ -130,7 +134,7 @@ public class ServerConnection implements RunnableWithFramePeriod { /** * The thread {@link #visualiserRaceController} runs on. */ - private Thread visualiserInputThread; + private Thread visualiserRaceControllerThread; @@ -138,13 +142,15 @@ public class ServerConnection implements RunnableWithFramePeriod { /** * Creates a server connection, using a given socket. * @param socket The socket which connects to the client. - * @param latestMessages Latest race snapshot to send to client. + * @param visualiserRaceState The race for the {@link VisualiserRaceController} to send commands to. + * @param raceCommands The CompositeCommand to place race commands in. * @param requestType The type of join request to make. * @throws IOException Thrown if there is a problem with the client socket. */ - public ServerConnection(Socket socket, LatestMessages latestMessages, RequestToJoinEnum requestType) throws IOException { + public ServerConnection(Socket socket, VisualiserRaceState visualiserRaceState, CompositeCommand raceCommands, RequestToJoinEnum requestType) throws IOException { this.socket = socket; - this.latestMessages = latestMessages; + this.visualiserRaceState = visualiserRaceState; + this.raceCommands = raceCommands; createMessageSerialiser(socket); createMessageDeserialiser(socket); @@ -205,6 +211,15 @@ public class ServerConnection implements RunnableWithFramePeriod { this.connectionToServerControllerThread.start(); } + /** + * Removes connection message related routes from the router. + * This is called after the client-server connection is properly established, so that any future (erroneous) connection messages get ignored. + */ + private void removeConnectionRoutes() { + this.messageRouter.removeRoute(MessageType.JOIN_ACCEPTANCE); + this.messageRouter.removeRoute(MessageType.REQUEST_TO_JOIN); + } + /** * Creates the {@link #messageSerialiser} and starts its thread. @@ -234,7 +249,7 @@ public class ServerConnection implements RunnableWithFramePeriod { /** - * Creates the {@link #heartBeatService} and {@link #heartBeatController} and starts its thread. + * Creates the {@link #heartBeatService} and {@link #heartBeatController} and starts their threads. */ private void createHeartBeatService() { @@ -260,14 +275,16 @@ public class ServerConnection implements RunnableWithFramePeriod { } + /** + * Creates the {@link #visualiserRaceController} and starts its thread. + */ + private void createVisualiserRaceController() { - private void createVisualiserRace() { + //VisualiserRaceController receives messages, and places commands on the race's command queue. BlockingQueue incomingMessages = new LinkedBlockingQueue<>(); + this.visualiserRaceController = new VisualiserRaceController(incomingMessages, visualiserRaceState, raceCommands); - this.visualiserRaceController = new VisualiserRaceController(latestMessages, incomingMessages); - this.visualiserInputThread = new Thread(visualiserRaceController, "ServerConnection()->VisualiserInput thread " + visualiserRaceController); - this.visualiserInputThread.start(); //Routes. this.messageRouter.addRoute(MessageType.BOATLOCATION, incomingMessages); @@ -281,15 +298,19 @@ public class ServerConnection implements RunnableWithFramePeriod { this.messageRouter.addRoute(MessageType.YACHTEVENTCODE, incomingMessages); this.messageRouter.addRoute(MessageType.MARKROUNDING, incomingMessages); this.messageRouter.addRoute(MessageType.XMLMESSAGE, incomingMessages); - this.messageRouter.removeDefaultRoute(); + this.messageRouter.addRoute(MessageType.ASSIGN_PLAYER_BOAT, incomingMessages); + this.messageRouter.removeDefaultRoute(); //We no longer want to keep un-routed messages. - //TODO create VisualiserRace here or somewhere else? + //Start the above on a new thread. - } + this.visualiserRaceControllerThread = new Thread(visualiserRaceController, "ServerConnection()->VisualiserRaceController thread " + visualiserRaceController); + this.visualiserRaceControllerThread.start(); + } + //TODO create input controller here. RaceController should query for it, if it exists. private void createPlayerInputController() { this.messageRouter.addRoute(MessageType.BOATACTION, messageSerialiser.getMessagesToSend()); @@ -342,16 +363,17 @@ public class ServerConnection implements RunnableWithFramePeriod { */ private void connected() { - JoinAcceptance joinAcceptance = connectionToServer.getJoinAcceptance(); - - allocatedSourceID = joinAcceptance.getSourceID(); + createHeartBeatService(); + createVisualiserRaceController(); - createHeartBeatService(); + if (connectionToServer.getRequestType() == RequestToJoinEnum.PARTICIPANT) { + createPlayerInputController(); + } - createVisualiserRace(); - createPlayerInputController(); + //We no longer want connection messages to be accepted. + removeConnectionRoutes(); //We interrupt as this thread's run() isn't needed anymore. @@ -403,13 +425,7 @@ public class ServerConnection implements RunnableWithFramePeriod { return controllerClient; } - /** - * Returns the source ID that has been allocated to the client. - * @return Source ID allocated to the client. 0 if it hasn't been allocated. - */ - public int getAllocatedSourceID() { - return allocatedSourceID; - } + /** @@ -447,10 +463,12 @@ public class ServerConnection implements RunnableWithFramePeriod { } - if (this.visualiserInputThread != null) { - this.visualiserInputThread.interrupt(); + if (this.visualiserRaceControllerThread != null) { + this.visualiserRaceControllerThread.interrupt(); } - //TODO visualiser race? + + + //TODO input controller? } diff --git a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml index 0b2b6a00..9295dc07 100644 --- a/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml +++ b/racevisionGame/src/main/resources/mock/mockXML/boatTest.xml @@ -49,7 +49,7 @@ - + diff --git a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml index 159d725c..e6544cad 100644 --- a/racevisionGame/src/main/resources/visualiser/scenes/race.fxml +++ b/racevisionGame/src/main/resources/visualiser/scenes/race.fxml @@ -3,9 +3,25 @@ - - + + + + + + + + + + + + + + + + + + @@ -87,8 +103,8 @@ - - + + diff --git a/racevisionGame/src/test/java/mock/model/MockRaceTest.java b/racevisionGame/src/test/java/mock/model/MockRaceTest.java index 402e37f0..4f3f7705 100644 --- a/racevisionGame/src/test/java/mock/model/MockRaceTest.java +++ b/racevisionGame/src/test/java/mock/model/MockRaceTest.java @@ -29,13 +29,12 @@ public class MockRaceTest { RaceDataSource raceDataSource = RaceXMLReaderTest.createRaceDataSource(); RegattaDataSource regattaDataSource = RegattaXMLReaderTest.createRegattaDataSource(); - LatestMessages latestMessages = new LatestMessages(); Polars polars = PolarParserTest.createPolars(); WindGenerator windGenerator = new ConstantWindGenerator(Bearing.fromDegrees(230), 10); - MockRace mockRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, latestMessages, polars, Constants.RaceTimeScale, windGenerator); + MockRace mockRace = new MockRace(boatDataSource, raceDataSource, regattaDataSource, polars, Constants.RaceTimeScale, windGenerator); return mockRace; diff --git a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java index 3a48c096..2193eb7a 100644 --- a/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java +++ b/racevisionGame/src/test/java/mock/model/commandFactory/WindCommandTest.java @@ -8,16 +8,11 @@ import network.Messages.BoatAction; import network.Messages.Enums.BoatActionEnum; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import shared.exceptions.InvalidBoatDataException; import shared.exceptions.InvalidRaceDataException; import shared.exceptions.InvalidRegattaDataException; import shared.model.Bearing; -import shared.model.Boat; -import shared.model.Race; -import visualiser.model.VisualiserRace; -import static org.mockito.Mockito.when; import static org.testng.Assert.*; import static org.mockito.Mockito.mock; diff --git a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java index 1e3a5c3d..6c287dd6 100644 --- a/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java +++ b/racevisionGame/src/test/java/network/MessageDecoders/JoinAcceptanceDecoderTest.java @@ -64,7 +64,7 @@ public class JoinAcceptanceDecoderTest { */ @Test public void joinSuccessSourceIDTest() throws Exception { - responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 12345); + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, 12345); } /** @@ -73,26 +73,9 @@ public class JoinAcceptanceDecoderTest { */ @Test public void joinSuccessNoSourceIDTest() throws Exception { - responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL, 0); + responseTypeTest(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, 0); } - /** - * Tests if a participants full message can be encoded and decoded correctly. - * @throws Exception if test fails. - */ - @Test - public void participantFullTest() throws Exception { - responseTypeTest(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, 0); - } - - /** - * Tests if a ghosts full message can be encoded and decoded correctly. - * @throws Exception if test fails. - */ - @Test - public void ghostFullTest() throws Exception { - responseTypeTest(JoinAcceptanceEnum.GHOST_PARTICIPANTS_FULL, 0); - } /** * Tests if a server full message can be encoded and decoded correctly. diff --git a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java index fae94d1b..64fdfcb5 100644 --- a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java +++ b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerParticipantTest.java @@ -7,13 +7,15 @@ import network.Messages.Enums.RequestToJoinEnum; import network.Messages.JoinAcceptance; import org.junit.Before; import org.junit.Test; -import visualiser.Commands.ConnectionToServerCommands.JoinSuccessfulCommand; -import visualiser.Commands.ConnectionToServerCommands.RaceParticipantsFullCommand; +import visualiser.Commands.ConnectionToServerCommands.JoinSuccessParticipantCommand; +import visualiser.Commands.ConnectionToServerCommands.JoinFailureCommand; import visualiser.Commands.ConnectionToServerCommands.ServerFullCommand; import visualiser.enums.ConnectionToServerState; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; import static org.junit.Assert.*; @@ -50,7 +52,7 @@ public class ConnectionToServerParticipantTest { public void expectRequestSent() throws Exception { //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.REQUEST_SENT, connectionToServer.getConnectionState()); } @@ -64,9 +66,14 @@ public class ConnectionToServerParticipantTest { public void interruptTimedOut() throws Exception { //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); + + //Disable logging as we know this will log but we don't care. + Logger.getGlobal().setLevel(Level.OFF); connectionToServerThread.interrupt(); + Logger.getGlobal().setLevel(null); + connectionToServerThread.join(); assertEquals(ConnectionToServerState.TIMED_OUT, connectionToServer.getConnectionState()); @@ -80,20 +87,20 @@ public class ConnectionToServerParticipantTest { @Test public void sendJoinSuccessCommand() throws Exception { int sourceID = 123; - JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID); + JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, sourceID); - Command command = new JoinSuccessfulCommand(joinAcceptance, connectionToServer); + Command command = new JoinSuccessParticipantCommand(joinAcceptance, connectionToServer); incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null); assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID()); assertNotEquals(0, connectionToServer.getJoinAcceptance().getSourceID()); - assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL, connectionToServer.getJoinAcceptance().getAcceptanceType()); + assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, connectionToServer.getJoinAcceptance().getAcceptanceType()); } @@ -104,21 +111,21 @@ public class ConnectionToServerParticipantTest { * @throws Exception On error. */ @Test - public void sendRaceParticipantsFullCommand() throws Exception { + public void sendJoinFailureCommand() throws Exception { int sourceID = 0; - JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, sourceID); + JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_FAILURE, sourceID); - Command command = new RaceParticipantsFullCommand(joinAcceptance, connectionToServer); + Command command = new JoinFailureCommand(joinAcceptance, connectionToServer); incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null); assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID()); - assertEquals(JoinAcceptanceEnum.RACE_PARTICIPANTS_FULL, connectionToServer.getJoinAcceptance().getAcceptanceType()); + assertEquals(JoinAcceptanceEnum.JOIN_FAILURE, connectionToServer.getJoinAcceptance().getAcceptanceType()); } @@ -136,7 +143,7 @@ public class ConnectionToServerParticipantTest { incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null); diff --git a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerSpectatorTest.java b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerSpectatorTest.java index 8b69c861..c381dc88 100644 --- a/racevisionGame/src/test/java/visualiser/network/ConnectionToServerSpectatorTest.java +++ b/racevisionGame/src/test/java/visualiser/network/ConnectionToServerSpectatorTest.java @@ -7,8 +7,7 @@ import network.Messages.Enums.RequestToJoinEnum; import network.Messages.JoinAcceptance; import org.junit.Before; import org.junit.Test; -import visualiser.Commands.ConnectionToServerCommands.JoinSuccessfulCommand; -import visualiser.Commands.ConnectionToServerCommands.RaceParticipantsFullCommand; +import visualiser.Commands.ConnectionToServerCommands.JoinSuccessParticipantCommand; import visualiser.Commands.ConnectionToServerCommands.ServerFullCommand; import visualiser.enums.ConnectionToServerState; @@ -50,7 +49,7 @@ public class ConnectionToServerSpectatorTest { public void expectRequestSent() throws Exception { //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.REQUEST_SENT, connectionToServer.getConnectionState()); } @@ -63,19 +62,19 @@ public class ConnectionToServerSpectatorTest { @Test public void sendJoinSuccessCommand() throws Exception { int sourceID = 0; - JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL, sourceID); + JoinAcceptance joinAcceptance = new JoinAcceptance(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, sourceID); - Command command = new JoinSuccessfulCommand(joinAcceptance, connectionToServer); + Command command = new JoinSuccessParticipantCommand(joinAcceptance, connectionToServer); incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.CONNECTED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null); assertEquals(sourceID, connectionToServer.getJoinAcceptance().getSourceID()); - assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL, connectionToServer.getJoinAcceptance().getAcceptanceType()); + assertEquals(JoinAcceptanceEnum.JOIN_SUCCESSFUL_PARTICIPANT, connectionToServer.getJoinAcceptance().getAcceptanceType()); } @@ -95,7 +94,7 @@ public class ConnectionToServerSpectatorTest { incomingCommands.put(command); //Need to wait for connection thread to execute commands. - Thread.sleep(20); + Thread.sleep(250); assertEquals(ConnectionToServerState.DECLINED, connectionToServer.getConnectionState()); assertTrue(connectionToServer.getJoinAcceptance() != null);