diff --git a/.gitignore b/.gitignore
index c56ab43c..b7e0c073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -183,5 +183,4 @@ local.properties
# IntelliJDEA ignore
*.iml
dedicatedServer/.idea/
-.idea/copyright/
settings/keyBindings.xml
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 00000000..e7bedf33
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.mailmap b/.mailmap
index c5401576..7498647c 100644
--- a/.mailmap
+++ b/.mailmap
@@ -18,3 +18,4 @@
Erika Savell
Connor Taylor-Brown
Fraser Cope
+Jessica Syder Jessica Syder
\ No newline at end of file
diff --git a/matchBrowser/pom.xml b/matchBrowser/pom.xml
new file mode 100644
index 00000000..91581a28
--- /dev/null
+++ b/matchBrowser/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+ team-7
+ seng302
+ 2.0
+
+ 4.0.0
+
+
+ jar
+ matchBrowser
+ matchBrowser
+ 2.0
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+ org.mockito
+ mockito-all
+ 1.9.5
+
+
+
+ org.testng
+ testng
+ 6.11
+ test
+
+
+
+ seng302
+ racevisionGame
+ 2.0
+
+
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.3
+
+
+
+
+ app.Main
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/matchBrowser/src/main/java/app/Main.java b/matchBrowser/src/main/java/app/Main.java
new file mode 100644
index 00000000..0d858d6f
--- /dev/null
+++ b/matchBrowser/src/main/java/app/Main.java
@@ -0,0 +1,12 @@
+package app;
+
+import networkInterface.NetworkInterface;
+
+/**
+ * Used when starting the matchmaking browser
+ */
+public class Main {
+ public static void main(String[] args) {
+ NetworkInterface networkInterface = new NetworkInterface();
+ }
+}
diff --git a/matchBrowser/src/main/java/model/ClientAddress.java b/matchBrowser/src/main/java/model/ClientAddress.java
new file mode 100644
index 00000000..51c7249d
--- /dev/null
+++ b/matchBrowser/src/main/java/model/ClientAddress.java
@@ -0,0 +1,37 @@
+package model;
+
+public class ClientAddress {
+ private String ip;
+ private int port;
+
+ public ClientAddress(String ip, int port) {
+ this.ip = ip;
+ this.port = port;
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o != null && o instanceof ClientAddress && hashCode() == o.hashCode();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = ip != null ? ip.hashCode() : 0;
+ result *= 31;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "{ip='" + ip + '\'' +
+ ", port=" + port+"}";
+ }
+}
diff --git a/matchBrowser/src/main/java/model/MatchTable.java b/matchBrowser/src/main/java/model/MatchTable.java
new file mode 100644
index 00000000..0e177cc7
--- /dev/null
+++ b/matchBrowser/src/main/java/model/MatchTable.java
@@ -0,0 +1,32 @@
+package model;
+
+
+import network.Messages.HostGame;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Holds a table object that stores current games
+ */
+public class MatchTable {
+ private HashMap matchTable;
+
+ public MatchTable() {
+ this.matchTable = new HashMap<>();
+ }
+
+ public void addEntry(ClientAddress address, HostGame newEntry) {
+ this.matchTable.put(address, newEntry);
+ }
+
+ public HashMap getMatchTable() {
+ return matchTable;
+ }
+
+ @Override
+ public String toString() {
+ return "MatchTable=" + matchTable;
+ }
+}
diff --git a/matchBrowser/src/main/java/model/TableKey.java b/matchBrowser/src/main/java/model/TableKey.java
new file mode 100644
index 00000000..3a8a85e9
--- /dev/null
+++ b/matchBrowser/src/main/java/model/TableKey.java
@@ -0,0 +1,37 @@
+package model;
+
+/**
+ * Used to create a key made of an ip and port.
+ */
+public class TableKey {
+
+ private final String ip;
+ private final int port;
+
+ public TableKey(String ip, int port) {
+ this.ip = ip;
+ this.port = port;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TableKey)) return false;
+ TableKey key = (TableKey) o;
+ return ip.equals(key.ip) && port == key.port;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = port;
+ result = 31 * result + ip.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "[ip='" + ip + '\'' +
+ ", port=" + port +
+ ']';
+ }
+}
diff --git a/matchBrowser/src/main/java/networkInterface/NetworkInterface.java b/matchBrowser/src/main/java/networkInterface/NetworkInterface.java
new file mode 100644
index 00000000..ce211f68
--- /dev/null
+++ b/matchBrowser/src/main/java/networkInterface/NetworkInterface.java
@@ -0,0 +1,132 @@
+package networkInterface;
+
+import model.ClientAddress;
+import model.MatchTable;
+import network.BinaryMessageDecoder;
+import network.Exceptions.InvalidMessageException;
+import network.MessageDecoders.HostGameMessageDecoder;
+import network.MessageDecoders.HostedGamesRequestDecoder;
+import network.MessageEncoders.HostedGamesRequestEncoder;
+import network.Messages.Enums.MessageType;
+import network.Messages.HostGame;
+import network.Messages.HostGamesRequest;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * Holds the output for the network for
+ */
+public class NetworkInterface {
+ private Timer scheduler;
+ private DatagramSocket serverSocket;
+ private byte[] receiveData = new byte[1024];
+
+ private Set clientsAddresses;
+ private MatchTable matchTable;
+
+
+ public NetworkInterface(){
+ this.clientsAddresses = new HashSet<>();
+ this.matchTable = new MatchTable();
+ this.scheduler = new Timer(true);
+ try {
+ this.serverSocket = new DatagramSocket(3779);
+
+ startBroadcast(5000);
+ scheduleFlush(70000);
+ this.run();
+ } catch (IOException e) {
+ System.err.println("Error listening on port: " + this.serverSocket.getLocalPort() + ".");
+ System.exit(-1);
+ }
+
+ }
+
+ /**
+ * Broadcasts match table to clients at a requested interval
+ * @param period interval to broadcast table
+ */
+ private void startBroadcast(int period) {
+ scheduler.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ List games = new ArrayList<>();
+
+ for(Map.Entry tableEntry: matchTable.getMatchTable().entrySet()) {
+ HostGame game = tableEntry.getValue();
+ if(game != null) {
+ games.add(game);
+ }
+ }
+
+ HostedGamesRequestEncoder encoder = new HostedGamesRequestEncoder();
+ try {
+ byte[] message = encoder.encode(new HostGamesRequest(games));
+ for(ClientAddress address: clientsAddresses) {
+ serverSocket.send(new DatagramPacket(message, message.length, InetAddress.getByName(address.getIp()), 4941));
+ }
+ } catch (InvalidMessageException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }, period, period);
+ }
+
+ /**
+ * Flushes the match table at a requested interval
+ * @param period interval to flush table
+ */
+ private void scheduleFlush(int period) {
+ scheduler.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ matchTable.getMatchTable().clear();
+ }
+ }, period, period);
+ }
+
+ private void run() throws IOException{
+ while(true) {
+ DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
+ serverSocket.receive(receivePacket);
+
+ BinaryMessageDecoder messageDecoder = new BinaryMessageDecoder(receivePacket.getData());
+ switch (MessageType.fromByte(messageDecoder.getHeaderMessageType())){
+ case HOST_GAME:
+ //decode and update table
+ HostGameMessageDecoder decoder = new HostGameMessageDecoder();
+ HostGame newKnownGame;
+ try{
+ newKnownGame = (HostGame) decoder.decode(messageDecoder.getMessageBody());
+ newKnownGame.setIp(receivePacket.getAddress().getHostAddress());
+ this.matchTable.addEntry(new ClientAddress(receivePacket.getAddress().getHostAddress(), receivePacket.getPort()), newKnownGame);
+
+ }catch (InvalidMessageException e){
+ System.out.println(e);
+ System.err.println("Message received that is not a hostedGame packet");
+ }
+ break;
+ case HOSTED_GAMES_REQUEST:
+ //update known clients
+ HostedGamesRequestDecoder decoder2 = new HostedGamesRequestDecoder();
+ HostGamesRequest newKnownGames;
+ try{
+ newKnownGames = (HostGamesRequest) decoder2.decode(messageDecoder.getMessageBody());
+ if (newKnownGames.getKnownGames().size() == 0){
+ //this is just an alert message with no content
+ clientsAddresses.add(new ClientAddress(receivePacket.getAddress().getHostAddress(), receivePacket.getPort()));
+ }
+ }catch (InvalidMessageException e){
+ System.out.println(e);
+ System.err.println("Message received that is not a hostedGamesRequest packet");
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/matchBrowser/src/test/java/model/MatchTableTest.java b/matchBrowser/src/test/java/model/MatchTableTest.java
new file mode 100644
index 00000000..530412f9
--- /dev/null
+++ b/matchBrowser/src/test/java/model/MatchTableTest.java
@@ -0,0 +1,29 @@
+package model;
+
+import network.Messages.Enums.RaceStatusEnum;
+import network.Messages.HostGame;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+public class MatchTableTest {
+ private MatchTable testTable;
+
+ @Before
+ public void setUp() {
+ testTable = new MatchTable();
+ }
+
+ @Test
+ public void testTable() {
+ HostGame entry = new HostGame("127.0.0.1", 4942, (byte)1, (byte)1, RaceStatusEnum.PRESTART, (byte)6, (byte)1);
+
+ testTable.addEntry(new ClientAddress("127.0.0.1", 3779), entry);
+
+ assertEquals(testTable.getMatchTable().get(new ClientAddress("127.0.0.1", 4942)), entry);
+ }
+}
diff --git a/pom.xml b/pom.xml
index ee4cba6f..5f38965a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,6 +10,7 @@
racevisionGame
dedicatedServer
+ matchBrowser
https://eng-git.canterbury.ac.nz/SENG302-2016/team-7
diff --git a/racevisionGame/src/main/java/mock/app/Event.java b/racevisionGame/src/main/java/mock/app/Event.java
index 2c9785f1..a9f55a7f 100644
--- a/racevisionGame/src/main/java/mock/app/Event.java
+++ b/racevisionGame/src/main/java/mock/app/Event.java
@@ -2,14 +2,17 @@ package mock.app;
import mock.dataInput.PolarParser;
import mock.exceptions.EventConstructionException;
-import mock.model.*;
+import mock.model.MockRace;
+import mock.model.Polars;
+import mock.model.RaceLogic;
+import mock.model.SourceIdAllocator;
import mock.model.commandFactory.CompositeCommand;
-import mock.model.wind.RandomWindGenerator;
import mock.model.wind.ShiftingWindGenerator;
import mock.model.wind.WindGenerator;
import mock.xml.RaceXMLCreator;
+import network.Messages.Enums.RaceStatusEnum;
+import network.Messages.HostGame;
import network.Messages.LatestMessages;
-import org.xml.sax.SAXException;
import shared.dataInput.*;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidBoatDataException;
@@ -18,19 +21,12 @@ import shared.exceptions.InvalidRegattaDataException;
import shared.exceptions.XMLReaderException;
import shared.model.Bearing;
import shared.model.Constants;
-import shared.xml.XMLUtilities;
-import javax.xml.bind.JAXBException;
-import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
+import java.net.Inet4Address;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
/**
@@ -80,6 +76,7 @@ public class Event {
/**
* Constructs an event, using various XML files.
* @param singlePlayer Whether or not to create a single player event.
+ * @param mapIndex Specifies which map to use.
* @throws EventConstructionException Thrown if we cannot create an Event for any reason.
*/
public Event(boolean singlePlayer, int mapIndex) throws EventConstructionException {
@@ -111,18 +108,23 @@ public class Event {
boatsXMLFile = "mock/mockXML/boatsSinglePlayer.xml";
}
+ double windAngle = 300;
+
//Read XML files.
try {
//this.raceXML = RaceXMLCreator.alterRaceToWind(raceXMLFile, 90);
- this.raceXML = XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8);
this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8));
if(mapIndex==4){
- this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
+ //this.raceXML = Event.setRaceXMLAtCurrentTimeToNow(XMLReader.readXMLFileToString(raceXMLFile, StandardCharsets.UTF_8), 1000, 5000);
+ this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, -1, true);
+ } else {
+ this.raceXML = RaceXMLCreator.alterRaceToWind(this.raceXML, XMLFileType.Contents, windAngle, false);
}
+
this.boatXML = XMLReader.readXMLFileToString(boatsXMLFile, StandardCharsets.UTF_8);
this.regattaXML = XMLReader.readXMLFileToString(regattaXMLFile, StandardCharsets.UTF_8);
- } catch (XMLReaderException e) {
+ } catch (XMLReaderException | InvalidRaceDataException e) {
throw new EventConstructionException("Could not read XML files.", e);
}
@@ -149,17 +151,26 @@ public class Event {
this.latestMessages = new LatestMessages();
WindGenerator windGenerator = new ShiftingWindGenerator(
- Bearing.fromDegrees(225),
+ Bearing.fromDegrees(windAngle),
12
);
+
+ MockRace mockRace = new MockRace(
+ boatDataSource,
+ raceDataSource,
+ regattaDataSource,
+ this.boatPolars,
+ Constants.RaceTimeScale,
+ windGenerator );
+
+ if(mapIndex==4){
+ mockRace.setRacePreStartTime(1000);
+ mockRace.setRacePreparatoryTime(5000);
+ }
+
+
RaceLogic newRace = new RaceLogic(
- new MockRace(
- boatDataSource,
- raceDataSource,
- regattaDataSource,
- this.boatPolars,
- Constants.RaceTimeScale,
- windGenerator ),
+ mockRace,
this.latestMessages,
this.compositeCommand);
@@ -194,7 +205,6 @@ public class Event {
- //TODO remove this after demo on 18th august!
/**
* Sets the xml description of the race to show the race was created now, and starts in 4 minutes
* @param raceXML The race.xml contents.
@@ -210,8 +220,6 @@ public class Event {
long millisecondsToAdd = racePreStartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
- //Scale the time using our time scalar.
- secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
@@ -221,4 +229,14 @@ public class Event {
return raceXML;
}
+ /**
+ * Creates the needed data type for a network packet
+ * @return hostGame Ac35DataType
+ * @throws IOException Inet4Address issue
+ */
+ public HostGame getHostedGameData() throws IOException{
+ String ip = Inet4Address.getLocalHost().getHostAddress();
+ return new HostGame(ip, 3779,(byte) 1,(byte) 1, RaceStatusEnum.PRESTART, (byte) 1, (byte) 1);
+ }
+
}
diff --git a/racevisionGame/src/main/java/mock/app/MockOutput.java b/racevisionGame/src/main/java/mock/app/MockOutput.java
index 023235cc..198dcc3f 100644
--- a/racevisionGame/src/main/java/mock/app/MockOutput.java
+++ b/racevisionGame/src/main/java/mock/app/MockOutput.java
@@ -60,7 +60,6 @@ public class MockOutput implements RunnableWithFramePeriod {
try {
Thread.sleep(500);
-
} catch (InterruptedException e) {
//If we get interrupted, exit the function.
Logger.getGlobal().log(Level.WARNING, "MockOutput.run().sleep(waitForXMLs) was interrupted on thread: " + Thread.currentThread(), e);
@@ -72,7 +71,6 @@ public class MockOutput implements RunnableWithFramePeriod {
}
}
-
long previousFrameTime = System.currentTimeMillis();
diff --git a/racevisionGame/src/main/java/mock/model/MockBoat.java b/racevisionGame/src/main/java/mock/model/MockBoat.java
index b6cd7a24..a5dc98fe 100644
--- a/racevisionGame/src/main/java/mock/model/MockBoat.java
+++ b/racevisionGame/src/main/java/mock/model/MockBoat.java
@@ -34,7 +34,10 @@ public class MockBoat extends Boat {
*/
private boolean autoVMG = false;
-
+ /**
+ * Indicates whether boat velocity is determined by wind
+ */
+ private boolean velocityDefault = true;
/**
* Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table.
@@ -300,4 +303,12 @@ public class MockBoat extends Boat {
public void setAutoVMG(boolean autoVMG) {
this.autoVMG = autoVMG;
}
+
+ public boolean isVelocityDefault() {
+ return velocityDefault;
+ }
+
+ public void setVelocityDefault(boolean velocityDefault) {
+ this.velocityDefault = velocityDefault;
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/MockRace.java b/racevisionGame/src/main/java/mock/model/MockRace.java
index a9f11770..41fbe4d8 100644
--- a/racevisionGame/src/main/java/mock/model/MockRace.java
+++ b/racevisionGame/src/main/java/mock/model/MockRace.java
@@ -1,18 +1,15 @@
package mock.model;
+import mock.model.commandFactory.ActiveObserverCommand;
+import mock.model.commandFactory.ObserverCommand;
import mock.model.wind.WindGenerator;
-import javafx.animation.AnimationTimer;
import mock.model.collider.ColliderRegistry;
-import mock.xml.*;
-import network.Messages.BoatLocation;
-import network.Messages.BoatStatus;
import network.Messages.Enums.BoatStatusEnum;
import network.Messages.Enums.RaceStatusEnum;
import shared.dataInput.BoatDataSource;
import shared.dataInput.RaceDataSource;
import shared.dataInput.RegattaDataSource;
import shared.exceptions.BoatNotFoundException;
-import shared.enums.RoundingType;
import shared.model.*;
import shared.model.Bearing;
@@ -64,7 +61,11 @@ public class MockRace extends RaceState {
*/
private Polars polars;
+ private ActiveObserverCommand activeObserverCommand;
+ private long racePreStartTime = Constants.RacePreStartTime;
+
+ private long racePreparatoryTime = Constants.RacePreparatoryTime;
/**
* Constructs a race object with a given RaceDataSource, BoatDataSource, and RegattaDataSource and sends events to the given mockOutput.
@@ -81,6 +82,7 @@ public class MockRace extends RaceState {
this.setRaceDataSource(raceDataSource);
this.setRegattaDataSource(regattaDataSource);
+ this.activeObserverCommand = new ActiveObserverCommand();
this.polars = polars;
this.scaleFactor = timeScale;
@@ -119,6 +121,7 @@ public class MockRace extends RaceState {
//Construct a MockBoat using the Boat and Polars.
MockBoat mockBoat = new MockBoat(boat, polars);
mockBoat.setCurrentLeg(this.getLegs().get(0));
+ mockBoat.setEstimatedTimeAtNextMark(this.getRaceClock().getCurrentTime());
//Update participant list.
getRaceDataSource().getParticipants().add(sourceID);
@@ -126,6 +129,7 @@ public class MockRace extends RaceState {
this.boats.add(mockBoat);
getRaceDataSource().incrementSequenceNumber();
+
}
/**
@@ -162,15 +166,15 @@ public class MockRace extends RaceState {
long timeToStart = - this.getRaceClock().getDurationMilli();
- if (timeToStart > Constants.RacePreStartTime) {
+ if (timeToStart > racePreStartTime) {
//Time > 3 minutes is the prestart period.
this.setRaceStatusEnum(RaceStatusEnum.PRESTART);
- } else if ((timeToStart <= Constants.RacePreStartTime) && (timeToStart >= Constants.RacePreparatoryTime)) {
+ } else if ((timeToStart <= racePreStartTime) && (timeToStart >= racePreparatoryTime)) {
//Time between [1, 3] minutes is the warning period.
this.setRaceStatusEnum(RaceStatusEnum.WARNING);
- } else if ((timeToStart <= Constants.RacePreparatoryTime) && (timeToStart > 0)) {
+ } else if ((timeToStart <= racePreparatoryTime) && (timeToStart > 0)) {
//Time between (0, 1] minutes is the preparatory period.
this.setRaceStatusEnum(RaceStatusEnum.PREPARATORY);
@@ -183,6 +187,13 @@ public class MockRace extends RaceState {
}
+ public void setRacePreStartTime(long racePreStartTime) {
+ this.racePreStartTime = racePreStartTime;
+ }
+
+ public void setRacePreparatoryTime(long racePreparatoryTime) {
+ this.racePreparatoryTime = racePreparatoryTime;
+ }
/**
* Sets the status of all boats in the race to RACING.
@@ -355,70 +366,24 @@ public class MockRace extends RaceState {
//Checks if the current boat has finished the race or not.
boolean finish = this.isLastLeg(boat.getCurrentLeg());
- if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds && boat.isSailsOut()) {
-
- checkPosition(boat, totalElapsedMilliseconds);
-
- if (boat.getCurrentSpeed() == 0) {
- newOptimalVMG(boat);
- boat.setBearing(boat.calculateBearingToNextMarker());
- }
+ if (!finish && totalElapsedMilliseconds >= updatePeriodMilliseconds) {
- setBoatSpeed(boat);
+ if(boat.isVelocityDefault()) setBoatSpeed(boat);
//Calculates the distance travelled, in meters, in the current timeslice.
- double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds);
-
- //Scale it.
- distanceTravelledMeters = distanceTravelledMeters * this.scaleFactor;
-
+ double distanceTravelledMeters = boat.calculateMetersTravelled(updatePeriodMilliseconds) * this.scaleFactor;
+ checkPosition(boat, totalElapsedMilliseconds);
//Move the boat forwards that many meters, and advances its time counters by enough milliseconds.
boat.moveForwards(distanceTravelledMeters);
boat.setTimeSinceTackChange(boat.getTimeSinceTackChange() + updatePeriodMilliseconds);
-
- if (boat.getAutoVMG()) {
- newOptimalVMG(boat);
- boat.setAutoVMG(false);
- }
-
- } else {
- boat.setCurrentSpeed(0);
}
this.updateEstimatedTime(boat);
}
- private void newOptimalVMG(MockBoat boat) {
- long tackPeriod = 1000;
- if (boat.getTimeSinceTackChange() > tackPeriod) {
- //System.out.println("optim called");
- //Calculate the new VMG.
-// VMG newVMG = boat.getPolars().calculateVMG(
-// this.getWindDirection(),
-// this.getWindSpeed(),
-// boat.calculateBearingToNextMarker(),
-// Bearing.fromDegrees(0d),
-// Bearing.fromDegrees(359.99999d));
-
- VMG newVMG = NewPolars.setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
- //System.out.println(newVMG);
- //If the new vmg improves velocity, use it.
- /*if (improvesVelocity(boat, newVMG)) {
- }*/
- boat.setVMG(newVMG);
- }
- }
-
private void setBoatSpeed(MockBoat boat) {
-// VMG vmg = boat.getPolars().calculateVMG(
-// this.getWindDirection(),
-// this.getWindSpeed(),
-// boat.getBearing(),
-// Bearing.fromDegrees(boat.getBearing().degrees() - 1),
-// Bearing.fromDegrees(boat.getBearing().degrees() + 1));
- //VMG vmg = boat.getPolars().setBestVMG(this.getWindDirection(), this.getWindSpeed(), boat.getBearing());
VMG vmg = new VMG(NewPolars.calculateSpeed(
this.getWindDirection(),
this.getWindSpeed(),
@@ -514,22 +479,28 @@ public class MockRace extends RaceState {
/**
* Checks to be run on boats rounding marks on the port side
* @param boat the boat that is rounding a mark
- * @param roundingChecks the checks to run
- * @param legBearing the direction of the leg
+ * @param roundingData The data for the current leg's rounding.
*/
- private void boatRoundingCheckPort(MockBoat boat, List roundingChecks, Bearing legBearing) {
+ private void boatRoundingCheckPort(MockBoat boat, MarkRoundingData roundingData) {
//boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
- Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
+
+
switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding
- if (boat.isPortSide(roundingMark) &&
- GPSCoordinate.passesLine(roundingMark.getPosition(),
- roundingChecks.get(0), boat.getPosition(), legBearing) &&
- gateCheck && boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
+ if (boat.isPortSide(roundingData.getMarkToRound()) &&
+ GPSCoordinate.passesLine(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck1(),
+ boat.getPosition(),
+ roundingData.getLegBearing()) &&
+ gateCheck &&
+ boat.isBetweenGate(
+ roundingData.getMarkToRound(),
+ Mark.tempMark(roundingData.getRoundCheck1())) ) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
@@ -538,11 +509,18 @@ public class MockRace extends RaceState {
}
break;
case 1://has been parallel to the mark;
- if (boat.isPortSide(roundingMark) &&
- GPSCoordinate.passesLine(roundingMark.getPosition(),
- roundingChecks.get(1), boat.getPosition(),
- Bearing.fromDegrees(legBearing.degrees() - 90)) &&//negative 90 from bearing because of port rounding
- boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) {
+ if (boat.isPortSide(roundingData.getMarkToRound()) &&
+ GPSCoordinate.passesLine(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck2(),
+ boat.getPosition(),
+ Bearing.fromDegrees(
+ GPSCoordinate.calculateBearing(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck2()).degrees() - 90)) &&//negative 90 from bearing because of port rounding
+ boat.isBetweenGate(
+ roundingData.getMarkToRound(),
+ Mark.tempMark(roundingData.getRoundCheck2()))) {
boat.increaseRoundingStatus();
}
break;
@@ -558,23 +536,27 @@ public class MockRace extends RaceState {
/**
* Checks to be run on boats rounding marks on the starboard side
* @param boat the boat that is rounding a mark
- * @param roundingChecks the checks to run
- * @param legBearing the direction of the leg
+ * @param roundingData The data for the current leg's rounding.
*/
- private void boatRoundingCheckStarboard(MockBoat boat, List roundingChecks, Bearing legBearing){
+ private void boatRoundingCheckStarboard(MockBoat boat, MarkRoundingData roundingData){
//boats must pass all checks in order to round a mark
//boolean for if boat has to/needs to pass through a gate
boolean gateCheck = boat.getCurrentLeg().getEndCompoundMark().getMark2() == null || boat.isBetweenGate(boat.getCurrentLeg().getEndCompoundMark());
- Mark roundingMark = boat.getCurrentLeg().getEndCompoundMark().getMarkForRounding(legBearing);
+
switch (boat.getRoundingStatus()) {
case 0://hasn't started rounding
- if (boat.isStarboardSide(roundingMark) &&
- GPSCoordinate.passesLine(roundingMark.getPosition(),
- roundingChecks.get(0), boat.getPosition(), legBearing) &&
+ if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
+ GPSCoordinate.passesLine(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck1(),
+ boat.getPosition(),
+ roundingData.getLegBearing()) &&
gateCheck &&
- boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(0)))) {
+ boat.isBetweenGate(
+ roundingData.getMarkToRound(),
+ Mark.tempMark(roundingData.getRoundCheck1()))) {
boat.increaseRoundingStatus();
if (boat.getCurrentLeg().getLegNumber() + 2 >= getLegs().size()){
//boat has finished race
@@ -583,10 +565,18 @@ public class MockRace extends RaceState {
}
break;
case 1://has been parallel to the mark
- if (boat.isStarboardSide(roundingMark) &&
- GPSCoordinate.passesLine(roundingMark.getPosition(),
- roundingChecks.get(1), boat.getPosition(), Bearing.fromDegrees(legBearing.degrees() + 90)) && //positive 90 from bearing because of starboard rounding
- boat.isBetweenGate(roundingMark, Mark.tempMark(roundingChecks.get(1)))) {
+ if (boat.isStarboardSide(roundingData.getMarkToRound()) &&
+ GPSCoordinate.passesLine(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck2(),
+ boat.getPosition(),
+ Bearing.fromDegrees(
+ GPSCoordinate.calculateBearing(
+ roundingData.getMarkToRound().getPosition(),
+ roundingData.getRoundCheck2() ).degrees() + 90)) && //positive 90 from bearing because of starboard rounding
+ boat.isBetweenGate(
+ roundingData.getMarkToRound(),
+ Mark.tempMark(roundingData.getRoundCheck2())) ) {
boat.increaseRoundingStatus();
}
break;
@@ -605,67 +595,29 @@ public class MockRace extends RaceState {
* @param timeElapsed The total time, in milliseconds, that has elapsed since the race started.
*/
protected void checkPosition(MockBoat boat, long timeElapsed) {
- //The distance, in nautical miles, within which the boat needs to get in order to consider that it has reached the marker.
- double epsilonNauticalMiles = boat.getCurrentLeg().getEndCompoundMark().getRoundingDistance(); //250 meters.
-
- if (boat.calculateDistanceToNextMarker() < epsilonNauticalMiles) {
- //Boat is within an acceptable distance from the mark.
-
- GPSCoordinate startDirectionLinePoint = boat.getCurrentLeg().getStartCompoundMark().getMark1Position();
- GPSCoordinate endDirectionLinePoint = boat.getCurrentLeg().getEndCompoundMark().getMark1Position();
- Bearing bearingOfDirectionLine = GPSCoordinate.calculateBearing(startDirectionLinePoint, endDirectionLinePoint);
-
- //use the direction line to create three invisible points that act as crossover lines a boat must cross
- //to round a mark
-
- double bearingToAdd;
- if (boat.getCurrentLeg().getEndCompoundMark().getRoundingType() == RoundingType.Port ||
- boat.getCurrentLeg().getEndCompoundMark().getRoundingType() == RoundingType.SP){
- bearingToAdd = 90;
- }else{
- bearingToAdd = -90;
- }
-
- GPSCoordinate roundCheck1 = GPSCoordinate.calculateNewPosition(endDirectionLinePoint,
- epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfDirectionLine.degrees() + bearingToAdd));
-
- GPSCoordinate roundCheck2;
- try{
- Leg nextLeg = getLegs().get(getLegs().indexOf(boat.getCurrentLeg()) + 1);
-
- GPSCoordinate startNextDirectionLinePoint = nextLeg.getStartCompoundMark().getMark1Position();
- GPSCoordinate endNextDirectionLinePoint = nextLeg.getEndCompoundMark().getMark1Position();
- Bearing bearingOfNextDirectionLine = GPSCoordinate.calculateBearing(startNextDirectionLinePoint, endNextDirectionLinePoint);
-
- roundCheck2 = GPSCoordinate.calculateNewPosition(endDirectionLinePoint,
- epsilonNauticalMiles, Azimuth.fromDegrees(bearingOfNextDirectionLine.degrees() + bearingToAdd));
- }catch(NullPointerException e){
- //this is caused by the last leg not being having a leg after it
- roundCheck2 = roundCheck1;
- }
-
- List roundingChecks = new ArrayList(Arrays.asList(roundCheck1, roundCheck2));
-
- switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {
- case SP://Not yet implemented so these gates will be rounded port side
- case Port:
- boatRoundingCheckPort(boat, roundingChecks, bearingOfDirectionLine);
- break;
- case PS://not yet implemented so these gates will be rounded starboard side
- case Starboard:
- boatRoundingCheckStarboard(boat, roundingChecks, bearingOfDirectionLine);
- break;
- }
+ switch (boat.getCurrentLeg().getEndCompoundMark().getRoundingType()) {
+ case SP://Not yet implemented so these gates will be rounded port side
+ case Port:
+ boatRoundingCheckPort(
+ boat,
+ getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
+ break;
+ case PS://not yet implemented so these gates will be rounded starboard side
+ case Starboard:
+ boatRoundingCheckStarboard(
+ boat,
+ getMarkRoundingSequence().getRoundingData(boat.getCurrentLeg()) );
+ break;
+ }
- //Check if the boat has finished or stopped racing.
- if (this.isLastLeg(boat.getCurrentLeg())) {
- //Boat has finished.
- boat.setTimeFinished(timeElapsed);
- boat.setCurrentSpeed(0);
- boat.setStatus(BoatStatusEnum.FINISHED);
- }
+ //Check if the boat has finished or stopped racing.
+ if (this.isLastLeg(boat.getCurrentLeg())) {
+ //Boat has finished.
+ boat.setTimeFinished(timeElapsed);
+ boat.setCurrentSpeed(0);
+ boat.setStatus(BoatStatusEnum.FINISHED);
}
@@ -754,4 +706,18 @@ public class MockRace extends RaceState {
}
+ /**
+ * Made public, so race logic can control it
+ */
+ public void setChanged() {
+ super.setChanged();
+ }
+
+ public void addVelocityCommand(ObserverCommand c) {
+ this.activeObserverCommand.changeVelocityCommand(this, c);
+ }
+
+ public void addAngularCommand(ObserverCommand c) {
+ this.activeObserverCommand.changeAngularCommand(this, c);
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/NewPolars.java b/racevisionGame/src/main/java/mock/model/NewPolars.java
index c525316c..3a40e0e2 100644
--- a/racevisionGame/src/main/java/mock/model/NewPolars.java
+++ b/racevisionGame/src/main/java/mock/model/NewPolars.java
@@ -145,8 +145,6 @@ public class NewPolars {
* @return the best vmg that the boat can change to
*/
public static VMG setBestVMG(Bearing trueWindAngle, double trueWindSpeed, Bearing boatAngle){
- //System.out.println("VMG AUTO CALLED");
- //speed
double closestSpeed = getClosest(trueWindSpeed, polars.keySet());
double angle = modulateAngle(boatAngle.degrees() - trueWindAngle.degrees());
@@ -181,15 +179,29 @@ public class NewPolars {
}
+ /**
+ * gets the angle bound between 0 and 360 following modular arithmetic
+ * @param angle angle to modulate
+ * @return resultant angle after modulation.
+ */
public static double modulateAngle(double angle){
return (angle % 360 + 360) % 360;
}
+ /**
+ * DO NOT DELETE THIS FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
+ * @return current polars map
+ */
+ @SuppressWarnings("unused")
private Map> getPolars(){
//this function is just for testing so therefore it is private
return polars;
}
+ /**
+ * DO NOT DELETE THESE FUNCTIONS THEY ARE USED FOR TESTING PURPOSES
+ */
+ @SuppressWarnings("unused")
private void printOutLinearInterpolated(){
for (double tws: polars.keySet()){
System.out.println("==================================================");
diff --git a/racevisionGame/src/main/java/mock/model/RaceLogic.java b/racevisionGame/src/main/java/mock/model/RaceLogic.java
index 0b3dffb2..dcfa1458 100644
--- a/racevisionGame/src/main/java/mock/model/RaceLogic.java
+++ b/racevisionGame/src/main/java/mock/model/RaceLogic.java
@@ -2,6 +2,7 @@ package mock.model;
import javafx.animation.AnimationTimer;
import mock.model.collider.Collision;
+import mock.model.commandFactory.CollisionCommand;
import mock.model.commandFactory.Command;
import mock.model.commandFactory.CompositeCommand;
import mock.model.commandFactory.CommandFactory;
@@ -148,9 +149,13 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
//Get the current time.
long currentTime = System.currentTimeMillis();
- //Execute commands from clients.
+ // Execute commands from clients.
commands.execute();
+ // Notify Observers
+ race.setChanged();
+ race.notifyObservers();
+
//Update race time.
race.updateRaceTime(currentTime);
@@ -234,11 +239,9 @@ public class RaceLogic implements RunnableWithFramePeriod, Observer {
@Override
public void update(Observable o, Object arg) {
- Collision e = (Collision)arg;
-
-// if(e.getBearing().degrees() == 0) System.out.println("Ahead");
-// else if(e.getBearing().degrees() < 90) System.out.println("Starboard");
-// else if(e.getBearing().degrees() > 270) System.out.println("Port");
-// else System.out.println("Behind");
+ if(arg instanceof Collision) {
+ Collision collision = (Collision)arg;
+ commands.addCommand(new CollisionCommand(race, (MockBoat)collision.getBoat()));
+ }
}
}
diff --git a/racevisionGame/src/main/java/mock/model/RaceServer.java b/racevisionGame/src/main/java/mock/model/RaceServer.java
index 80ec5e8b..7f213a72 100644
--- a/racevisionGame/src/main/java/mock/model/RaceServer.java
+++ b/racevisionGame/src/main/java/mock/model/RaceServer.java
@@ -24,6 +24,7 @@ import java.util.logging.Logger;
public class RaceServer {
private MockRace race;
private LatestMessages latestMessages;
+ private static RaceServer server;
private List collisionEvents = new ArrayList<>();
@@ -49,10 +50,17 @@ public class RaceServer {
public RaceServer(MockRace race, LatestMessages latestMessages) {
+ server = this;
this.race = race;
this.latestMessages = latestMessages;
}
+ public static void staticUpdateXML() {
+ if (server != null) {
+ server.updateXMLFiles();
+ }
+ }
+
/**
* Parses the race to create a snapshot, and places it in latestMessages.
*/
@@ -84,7 +92,6 @@ public class RaceServer {
}
-
/**
* Checks if the race/boat/regatta data sources have changed, and if they have, update their xml representations.
*/
diff --git a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java
index f590d9b7..f6370d6e 100644
--- a/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java
+++ b/racevisionGame/src/main/java/mock/model/SourceIdAllocator.java
@@ -38,7 +38,7 @@ public class SourceIdAllocator {
if (!((mockRace.getRaceStatusEnum() == RaceStatusEnum.PRESTART)
|| (mockRace.getRaceStatusEnum() == RaceStatusEnum.WARNING))) {
- throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start period. It is currently: " + mockRace.getRaceStatusEnum());
+ throw new SourceIDAllocationException("Could not allocate a source ID. Can only allocate during pre-start or warning period. It is currently: " + mockRace.getRaceStatusEnum());
}
List allocatedIDs = mockRace.getRaceDataSource().getParticipants();
diff --git a/racevisionGame/src/main/java/mock/model/collider/Collider.java b/racevisionGame/src/main/java/mock/model/collider/Collider.java
index 81b2ed62..28c01f9f 100644
--- a/racevisionGame/src/main/java/mock/model/collider/Collider.java
+++ b/racevisionGame/src/main/java/mock/model/collider/Collider.java
@@ -25,15 +25,9 @@ public abstract class Collider extends Observable implements Locatable {
Bearing relative = Bearing.fromDegrees(absolute.degrees() - boat.getBearing().degrees());
if(actualDistance <= distance) {
- Collision collision = new Collision(relative, distance);
+ Collision collision = new Collision(boat, relative, distance);
// Notify object of collision
- onCollisionEnter(boat, collision);
- // Notify observers of collision
- notifyObservers(collision);
- this.setChanged();
-
- //Send out packet to all boats
-
+ onCollisionEnter(collision);
return true;
} else return false;
}
@@ -47,8 +41,7 @@ public abstract class Collider extends Observable implements Locatable {
/**
* Handle a collision event
- * @param collider Boat that is colliding
* @param e details of collision
*/
- public abstract void onCollisionEnter(Boat collider, Collision e);
+ public abstract void onCollisionEnter(Collision e);
}
diff --git a/racevisionGame/src/main/java/mock/model/collider/ColliderRegistry.java b/racevisionGame/src/main/java/mock/model/collider/ColliderRegistry.java
index 69eac91a..6f82daeb 100644
--- a/racevisionGame/src/main/java/mock/model/collider/ColliderRegistry.java
+++ b/racevisionGame/src/main/java/mock/model/collider/ColliderRegistry.java
@@ -1,5 +1,6 @@
package mock.model.collider;
+import mock.model.MockBoat;
import shared.model.Boat;
import shared.model.GPSCoordinate;
@@ -39,7 +40,7 @@ public class ColliderRegistry extends Collider implements Observer {
}
@Override
- public void onCollisionEnter(Boat collider, Collision e) {}
+ public void onCollisionEnter(Collision e) {}
@Override
public GPSCoordinate getPosition() {
@@ -60,7 +61,7 @@ public class ColliderRegistry extends Collider implements Observer {
public void update(Observable o, Object arg) {
Collision collision = (Collision)arg;
- notifyObservers(collision);
this.setChanged();
+ notifyObservers(collision);
}
}
diff --git a/racevisionGame/src/main/java/mock/model/collider/Collision.java b/racevisionGame/src/main/java/mock/model/collider/Collision.java
index 5a987bde..225cf342 100644
--- a/racevisionGame/src/main/java/mock/model/collider/Collision.java
+++ b/racevisionGame/src/main/java/mock/model/collider/Collision.java
@@ -1,6 +1,7 @@
package mock.model.collider;
import shared.model.Bearing;
+import shared.model.Boat;
/**
* Data structure for holding collision details for ray casting and event handling.
@@ -14,13 +15,19 @@ public class Collision {
* Distance from boat centre to target centre
*/
private double distance;
+ /**
+ * Boat involved in the collision
+ */
+ private Boat boat;
/**
* Constructor for Collision structure
+ * @param boat involved in collision
* @param bearing from boat heading to target
* @param distance from boat centre to target centre
*/
- public Collision(Bearing bearing, double distance) {
+ public Collision(Boat boat, Bearing bearing, double distance) {
+ this.boat = boat;
this.bearing = bearing;
this.distance = distance;
}
@@ -32,4 +39,8 @@ public class Collision {
public double getDistance() {
return distance;
}
+
+ public Boat getBoat() {
+ return boat;
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/ActiveObserverCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/ActiveObserverCommand.java
new file mode 100644
index 00000000..ccd69a20
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/ActiveObserverCommand.java
@@ -0,0 +1,27 @@
+package mock.model.commandFactory;
+
+import java.util.Observable;
+
+/**
+ * Used to track the current active observer command. This is to ensure two commands that do similar things do not overlap.
+ */
+public class ActiveObserverCommand {
+ private ObserverCommand currentVelocityCommand;
+ private ObserverCommand currentAngularCommand;
+
+ public ActiveObserverCommand() {
+
+ }
+
+ public void changeVelocityCommand(Observable o, ObserverCommand c) {
+ o.deleteObserver(currentVelocityCommand);
+ o.addObserver(c);
+ currentVelocityCommand = c;
+ }
+
+ public void changeAngularCommand(Observable o, ObserverCommand c) {
+ o.deleteObserver(currentAngularCommand);
+ o.addObserver(c);
+ currentAngularCommand = c;
+ }
+}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java
new file mode 100644
index 00000000..bb86433d
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/CollisionCommand.java
@@ -0,0 +1,45 @@
+package mock.model.commandFactory;
+
+import mock.model.MockBoat;
+import mock.model.MockRace;
+import shared.model.Azimuth;
+import shared.model.GPSCoordinate;
+
+import java.util.Observable;
+
+/**
+ * Command class for collisions
+ */
+public class CollisionCommand extends ObserverCommand {
+ private GPSCoordinate startingPosition;
+ private Azimuth azimuth;
+ private double distance;
+
+ /**
+ * Constructor for class
+ * @param race race context
+ * @param boat boat controlled by command
+ */
+ public CollisionCommand(MockRace race, MockBoat boat) {
+ super(race, boat);
+ race.addObserver(this);
+ }
+
+ @Override
+ public void execute() {
+ this.azimuth = Azimuth.fromDegrees(boat.getBearing().degrees() - 180d);
+ this.startingPosition = boat.getPosition();
+ this.distance = 30;
+ boat.setVelocityDefault(false);
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if(GPSCoordinate.calculateDistanceMeters(boat.getPosition(), startingPosition) < distance) {
+ boat.setPosition(GPSCoordinate.calculateNewPosition(boat.getPosition(), 2, azimuth));
+ } else {
+ race.deleteObserver(this);
+ boat.setVelocityDefault(true);
+ }
+ }
+}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/Command.java b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java
index e0486114..421d5b32 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/Command.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/Command.java
@@ -3,6 +3,8 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
+import java.util.Observer;
+
/**
* Allows RaceLogic to control MockRace state according to the Command pattern
*/
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/ObserverCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/ObserverCommand.java
new file mode 100644
index 00000000..987d24be
--- /dev/null
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/ObserverCommand.java
@@ -0,0 +1,20 @@
+package mock.model.commandFactory;
+
+import mock.model.MockBoat;
+import mock.model.MockRace;
+
+import java.util.Observer;
+
+/**
+ * Command that can observe the race
+ */
+public abstract class ObserverCommand implements Command, Observer {
+ MockRace race;
+ MockBoat boat;
+
+ public ObserverCommand(MockRace race, MockBoat boat) {
+ this.race = race;
+ this.boat = boat;
+ boat.setAutoVMG(false);
+ }
+}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java
index 7d620a42..ac4877c4 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/SailsCommand.java
@@ -2,20 +2,50 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
+import mock.model.NewPolars;
+import mock.model.VMG;
-public class SailsCommand implements Command {
- private MockRace race;
- private MockBoat boat;
+import java.util.Observable;
+
+public class SailsCommand extends ObserverCommand {
private boolean sailsOut;
+ private double goalVelocity;
- public SailsCommand(MockRace race, MockBoat boat, Boolean sailsOut) {
- this.race = race;
- this.boat = boat;
+ public SailsCommand(MockRace race, MockBoat boat, boolean sailsOut) {
+ super(race, boat);
+ race.addVelocityCommand(this);
this.sailsOut = sailsOut;
}
@Override
public void execute() {
this.boat.setSailsOut(this.sailsOut);
+ boat.setVelocityDefault(false);
+
+ if(sailsOut) {
+ // Accelerate to VMG speed
+ double polarSpeed = NewPolars.calculateSpeed(race.getWindDirection(), race.getWindSpeed(), boat.getBearing());
+ VMG vmg = new VMG(polarSpeed, boat.getBearing());
+ goalVelocity = vmg.getSpeed();
+ } else {
+ // Decelerate to 0
+ goalVelocity = 0;
+ }
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ double acceleration = 0.5;
+
+ if(sailsOut && boat.getCurrentSpeed() < goalVelocity) {
+ boat.setCurrentSpeed(Math.min(goalVelocity, boat.getCurrentSpeed() + acceleration));
+ } else if (!sailsOut && boat.getCurrentSpeed() > goalVelocity) {
+ // Apply deceleration to strictly 0 speed
+ boat.setCurrentSpeed(Math.max(0, boat.getCurrentSpeed() - acceleration));
+ } else {
+ // Release boat from SailsCommand control
+ if(sailsOut) boat.setVelocityDefault(true);
+ race.deleteObserver(this);
+ }
}
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java
index d0b0584b..e10ee74a 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/TackGybeCommand.java
@@ -4,12 +4,16 @@ import mock.model.MockBoat;
import mock.model.MockRace;
import shared.model.Bearing;
+import java.util.Observable;
+
/**
* Command class for tacking and gybing
*/
-public class TackGybeCommand implements Command {
- private MockRace race;
- private MockBoat boat;
+public class TackGybeCommand extends ObserverCommand {
+ private double goalRotation;
+ private double totalRotation = 0;
+ private int direction; // -1 for anticlockwise, 1 for clockwise
+ private double goalAngle;
/**
* Constructor for class
@@ -17,24 +21,32 @@ public class TackGybeCommand implements Command {
* @param boat mock boat to update
*/
public TackGybeCommand(MockRace race, MockBoat boat) {
- this.race = race;
- this.boat = boat;
+ super(race, boat);
+ race.addAngularCommand(this);
}
@Override
public void execute() {
-
- boat.setAutoVMG(false);
-
double boatAngle = boat.getBearing().degrees();
- double windAngle =race.getWindDirection().degrees();
+ double windAngle = race.getWindDirection().degrees();
double differenceAngle = calcDistance(boatAngle, windAngle);
double angleA = windAngle + differenceAngle;
double angleB = windAngle - differenceAngle;
- if(angleA % 360 == boatAngle){
- boat.setBearing(Bearing.fromDegrees(angleB));
+ if (angleA % 360 == boatAngle) {
+ goalAngle = angleB % 360;
} else {
- boat.setBearing(Bearing.fromDegrees(angleA));
+ goalAngle = angleA % 360;
+ }
+
+ goalRotation = goalAngle - boatAngle;
+ if (goalRotation < 0) {
+ goalRotation += 360;
+ }
+ if (goalRotation > 180) {
+ goalRotation = 360 - goalRotation;
+ direction = -1;
+ } else {
+ direction = 1;
}
}
@@ -49,5 +61,16 @@ public class TackGybeCommand implements Command {
return phi > 180 ? 360 - phi : phi;
}
+ @Override
+ public void update(Observable o, Object arg) {
+ double offset = 3.0;
+ if (totalRotation < goalRotation) {
+ boat.setBearing(Bearing.fromDegrees(boat.getBearing().degrees() + offset * direction));
+ totalRotation += offset;
+ } else {
+ boat.setBearing(Bearing.fromDegrees(goalAngle));
+ race.deleteObserver(this);
+ }
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java
index 39469cf8..812f833a 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/VMGCommand.java
@@ -2,13 +2,20 @@ package mock.model.commandFactory;
import mock.model.MockBoat;
import mock.model.MockRace;
+import mock.model.NewPolars;
+import mock.model.VMG;
+import shared.model.Bearing;
+
+import java.util.Observable;
/**
* Command class for autoVMG
*/
-public class VMGCommand implements Command {
- private MockRace race;
- private MockBoat boat;
+public class VMGCommand extends ObserverCommand {
+ private double goalAngle;
+ private double goalRotation;
+ private double totalRotation = 0;
+ private int direction;
/**
* Constructor for class
@@ -16,8 +23,8 @@ public class VMGCommand implements Command {
* @param boat mock boat to update
*/
public VMGCommand(MockRace race, MockBoat boat) {
- this.race = race;
- this.boat = boat;
+ super(race, boat);
+ race.addAngularCommand(this);
}
@Override
@@ -27,5 +34,37 @@ public class VMGCommand implements Command {
} else {
boat.setAutoVMG(true);
}
+ newOptimalVMG(boat);
+
+ goalRotation = goalAngle - boat.getBearing().degrees();
+ if (goalRotation < 0) {
+ goalRotation += 360;
+ }
+ if (goalRotation > 180) {
+ goalRotation = 360 - goalRotation;
+ direction = -1;
+ } else {
+ direction = 1;
+ }
+ }
+
+ private void newOptimalVMG(MockBoat boat) {
+ long tackPeriod = 1000;
+ if (boat.getTimeSinceTackChange() > tackPeriod) {
+ VMG newVMG = NewPolars.setBestVMG(race.getWindDirection(), race.getWindSpeed(), boat.getBearing());
+ goalAngle = newVMG.getBearing().degrees();
+ }
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ double offset = 3.0;
+ if (totalRotation < goalRotation) {
+ boat.setBearing(Bearing.fromDegrees(boat.getBearing().degrees() + offset * direction));
+ totalRotation += offset;
+ } else {
+ boat.setBearing(Bearing.fromDegrees(goalAngle));
+ race.deleteObserver(this);
+ }
}
}
diff --git a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java
index 530bf5bc..85eec091 100644
--- a/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java
+++ b/racevisionGame/src/main/java/mock/model/commandFactory/WindCommand.java
@@ -4,17 +4,23 @@ import mock.model.MockBoat;
import mock.model.MockRace;
import shared.model.Bearing;
+import java.util.Observable;
+
/**
- * Created by connortaylorbrown on 4/08/17.
+ * Command class for upwind and downwind controls
*/
-public class WindCommand implements Command {
- private MockRace race;
- private MockBoat boat;
+public class WindCommand extends ObserverCommand {
private int direction;
+ /**
+ * Constructor for class
+ * @param race race context
+ * @param boat boat controlled by command
+ * @param upwind if true, downwind if false
+ */
public WindCommand(MockRace race, MockBoat boat, boolean upwind) {
- this.race = race;
- this.boat = boat;
+ super(race, boat);
+ race.addAngularCommand(this);
this.direction = upwind? -1 : 1;
}
@@ -34,4 +40,9 @@ public class WindCommand implements Command {
boat.setBearing(Bearing.fromDegrees(heading + offset));
}
+
+ @Override
+ public void update(Observable o, Object arg) {
+ race.deleteObserver(this);
+ }
}
diff --git a/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java
index 4ceff627..3cbf14f2 100644
--- a/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java
+++ b/racevisionGame/src/main/java/mock/model/wind/ShiftingWindGenerator.java
@@ -105,7 +105,6 @@ public class ShiftingWindGenerator implements WindGenerator {
if (shiftedSoFar >= 180){
shiftAnticlockwise = Math.random() > 0.5;
shiftedSoFar = 0;
-// System.out.println("Swapping");
}
timeOfLastShift = System.currentTimeMillis();
diff --git a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java
index 9b835f82..75024013 100644
--- a/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java
+++ b/racevisionGame/src/main/java/mock/xml/RaceXMLCreator.java
@@ -1,34 +1,23 @@
package mock.xml;
-import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import shared.dataInput.RaceXMLReader;
import shared.enums.XMLFileType;
import shared.exceptions.InvalidRaceDataException;
import shared.exceptions.XMLReaderException;
-import shared.model.*;
-import shared.xml.Race.*;
+import shared.model.CompoundMark;
+import shared.model.Constants;
+import shared.model.GPSCoordinate;
+import shared.xml.Race.XMLCompoundMark;
+import shared.xml.Race.XMLLimit;
+import shared.xml.Race.XMLMark;
+import shared.xml.Race.XMLRace;
import shared.xml.XMLUtilities;
-import javax.xml.XMLConstants;
-import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.Unmarshaller;
-import javax.xml.bind.util.JAXBSource;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Source;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import javax.xml.validation.Validator;
-import java.io.File;
import java.io.IOException;
-import java.io.StringWriter;
-import java.math.BigInteger;
-import java.net.URL;
+import java.io.InputStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -64,33 +53,54 @@ public class RaceXMLCreator {
/**
* Rotates the race in a specified direction.
- * @param s xml file name
+ * @param s xml file name or contents.
+ * @param fileType Whether s is a file name or contents.
* @param degrees degrees to rotate
+ * @param tutorial Whether we wish to run the tutorial - this changes the race start time.
* @return the new xml file as a string
* @throws XMLReaderException if the xml is not readable
* @throws InvalidRaceDataException if the race is invalid
- * @throws JAXBException if the Race class cannot be parsed into a xml.
- * @throws IOException if the schema file cannot be found
- * @throws SAXException error in schema file
- * @throws ParserConfigurationException error in parsing the schema file
*/
- public static String alterRaceToWind(String s, double degrees) throws XMLReaderException, InvalidRaceDataException, JAXBException, IOException, SAXException, ParserConfigurationException {
- RaceXMLReader reader = new RaceXMLReader(s, XMLFileType.ResourcePath);
+ public static String alterRaceToWind(String s, XMLFileType fileType, double degrees, boolean tutorial) throws XMLReaderException, InvalidRaceDataException {
- XMLRace race = XMLUtilities.xmlToClass(
- RaceXMLCreator.class.getClassLoader().getResourceAsStream(s),
- RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
- XMLRace.class);
+ RaceXMLReader reader = new RaceXMLReader(s, fileType);
- setRaceXMLAtCurrentTimeToNow(race);
+ try {
- double raceOriginalBearing = getLineAngle(getLeewardGate(reader).getMark1Position(), getWindwardGate(reader).getMark1Position());
+ XMLRace race = XMLUtilities.xmlToClass(
+ s,
+ RaceXMLCreator.class.getClassLoader().getResource("mock/mockXML/schema/raceSchema.xsd"),
+ XMLRace.class);
- double degreesToRotate = degrees - raceOriginalBearing;
+ if(tutorial){
+ setRaceXMLAtCurrentTimeToNow(race, 1000l, 5000l);
+ } else {
+ setRaceXMLAtCurrentTimeToNow(race);
+ }
+
+
+ CompoundMark leewardGate = getLeewardGate(reader);
+ CompoundMark windwardGate = getWindwardGate(reader);
+
+ double raceOriginalBearing = 0;
+
+ /*if (leewardGate != null && windwardGate != null) {
+ raceOriginalBearing = getLineAngle(
+ leewardGate.getMark1Position(),
+ windwardGate.getMark1Position() );
+ }*/
+
+ double degreesToRotate = degrees - raceOriginalBearing;
- alterRaceRotation(race, degreesToRotate);
+ if (degrees >= 0) {
+ alterRaceRotation(race, degreesToRotate);
+ }
+
+ return XMLUtilities.classToXML(race);
- return XMLUtilities.classToXML(race);
+ } catch (ParserConfigurationException | IOException | SAXException | JAXBException e) {
+ throw new InvalidRaceDataException("Could not parse or marshall race data file.", e);
+ }
}
/**
@@ -180,17 +190,12 @@ public class RaceXMLCreator {
}
- /**
- * Sets the xml description of the race to show the race was created now, and starts in 4 minutes
- * @param raceXML The race.xml contents.
- */
- public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
+
+ public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML, long racePrestartTime, long racePreparatoryTime){
//The start time is current time + 4 minutes. prestart is 3 minutes, and we add another minute.
- long millisecondsToAdd = Constants.RacePreStartTime + 1 * 60 * 1000;
+ long millisecondsToAdd = racePrestartTime + racePreparatoryTime;
long secondsToAdd = millisecondsToAdd / 1000;
- //Scale the time using our time scalar.
- secondsToAdd = secondsToAdd / Constants.RaceTimeScale;
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
ZonedDateTime creationTime = ZonedDateTime.now();
@@ -198,4 +203,13 @@ public class RaceXMLCreator {
raceXML.getRaceStartTime().setTime(dateFormat.format(creationTime.plusSeconds(secondsToAdd)));
}
+ /**
+ * Sets the xml description of the race to show the race was created now, and starts in 4 minutes
+ * @param raceXML The race.xml contents.
+ */
+ public static void setRaceXMLAtCurrentTimeToNow(XMLRace raceXML) {
+ setRaceXMLAtCurrentTimeToNow(raceXML, Constants.RacePreStartTime, Constants.RacePreparatoryTime);
+
+ }
+
}
diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HostGameMessageDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HostGameMessageDecoder.java
new file mode 100644
index 00000000..33d3a334
--- /dev/null
+++ b/racevisionGame/src/main/java/network/MessageDecoders/HostGameMessageDecoder.java
@@ -0,0 +1,64 @@
+package network.MessageDecoders;
+
+import network.Exceptions.InvalidMessageException;
+import network.Messages.AC35Data;
+import network.Messages.CourseWinds;
+import network.Messages.Enums.RaceStatusEnum;
+import network.Messages.HostGame;
+import network.Messages.RaceStatus;
+
+import java.util.Arrays;
+
+import static network.Utils.ByteConverter.bytesToInt;
+import static network.Utils.ByteConverter.bytesToLong;
+
+public class HostGameMessageDecoder implements MessageDecoder {
+
+ /**
+ * The encoded message.
+ */
+ private byte[] encodedMessage;
+
+ /**
+ * The decoded message.
+ */
+ private HostGame message;
+
+ /**
+ * Constructor
+ */
+ public HostGameMessageDecoder() {
+ }
+
+ @Override
+ public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
+ this.encodedMessage = encodedMessage;
+
+ try{
+ byte ipPart1 = encodedMessage[0];
+ byte ipPart2 = encodedMessage[1];
+ byte ipPart3 = encodedMessage[2];
+ byte ipPart4 = encodedMessage[3];
+ String ipString = bytesToLong(ipPart1) + "." + bytesToLong(ipPart2) + "." + bytesToLong(ipPart3) + "." + bytesToLong(ipPart4);
+// System.out.println(ipString);
+ int port = bytesToInt(Arrays.copyOfRange(encodedMessage, 4, 8));
+ byte map = encodedMessage[8];
+ byte speed = encodedMessage[9];
+ byte status = encodedMessage[10];
+ byte requiredNumPlayers = encodedMessage[11];
+ byte currentNumPlayers = encodedMessage[12];
+
+
+ message = new HostGame(ipString, port, map,
+ speed, RaceStatusEnum.fromByte(status),
+ requiredNumPlayers, currentNumPlayers);
+
+ return message;
+
+ } catch (Exception e) {
+ throw new InvalidMessageException("Could not decode Host game message.", e);
+ }
+ }
+
+
+}
diff --git a/racevisionGame/src/main/java/network/MessageDecoders/HostedGamesRequestDecoder.java b/racevisionGame/src/main/java/network/MessageDecoders/HostedGamesRequestDecoder.java
new file mode 100644
index 00000000..34e53748
--- /dev/null
+++ b/racevisionGame/src/main/java/network/MessageDecoders/HostedGamesRequestDecoder.java
@@ -0,0 +1,35 @@
+package network.MessageDecoders;
+
+import network.Exceptions.InvalidMessageException;
+import network.Messages.AC35Data;
+import network.Messages.HostGame;
+import network.Messages.HostGamesRequest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static network.Utils.ByteConverter.bytesToInt;
+
+public class HostedGamesRequestDecoder implements MessageDecoder{
+ @Override
+ public AC35Data decode(byte[] encodedMessage) throws InvalidMessageException {
+ try{
+ int numberOfGames = bytesToInt(Arrays.copyOfRange(encodedMessage, 0, 4));
+
+ HostGameMessageDecoder lineDecoder = new HostGameMessageDecoder();
+ List knownGames = new ArrayList<>();
+ int byteIndex = 4;
+ for (int i = 0; i < numberOfGames; i++){
+ knownGames.add((HostGame) lineDecoder.decode(Arrays.copyOfRange(encodedMessage, byteIndex, byteIndex+13)));
+ byteIndex += 13;
+ }
+
+ return new HostGamesRequest(knownGames);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new InvalidMessageException("Could not decode Host game message.", e);
+ }
+ }
+}
diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HostGameMessageEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HostGameMessageEncoder.java
new file mode 100644
index 00000000..8fbe30d2
--- /dev/null
+++ b/racevisionGame/src/main/java/network/MessageEncoders/HostGameMessageEncoder.java
@@ -0,0 +1,52 @@
+package network.MessageEncoders;
+
+import network.Exceptions.InvalidMessageException;
+import network.Messages.AC35Data;
+import network.Messages.HostGame;
+
+import java.nio.ByteBuffer;
+
+import static network.Utils.ByteConverter.intToBytes;
+
+
+public class HostGameMessageEncoder implements MessageEncoder{
+
+ /**
+ * Constructor
+ */
+ public HostGameMessageEncoder() {
+ }
+
+ @Override
+ public byte[] encode(AC35Data message) throws InvalidMessageException {
+ try{
+ //Downcast
+ HostGame hostGame = (HostGame) message;
+
+ ByteBuffer hostGameMessage = ByteBuffer.allocate(13);
+
+ ByteBuffer ipBytes = ByteBuffer.allocate(4);
+ String ip = hostGame.getIp();
+ String[] ipValues = ip.split("\\.");
+ for(String value:ipValues){
+ ipBytes.put(intToBytes(Integer.parseInt(value), 1)[0]);
+ }
+ byte raceStatus = hostGame.getStatus().getValue();
+
+ hostGameMessage.put(ipBytes.array());
+ hostGameMessage.put(intToBytes(hostGame.getPort()));
+ hostGameMessage.put(hostGame.getMap());
+ hostGameMessage.put(hostGame.getSpeed());
+ hostGameMessage.put(raceStatus);
+ hostGameMessage.put(hostGame.getRequiredNumPlayers());
+ hostGameMessage.put(hostGame.getCurrentNumPlayers());
+
+
+// System.out.println(hostGameMessage.array()[4]);
+ return hostGameMessage.array();
+
+ } catch (Exception e) {
+ throw new InvalidMessageException("Could not encode Host game message.", e);
+ }
+ }
+}
diff --git a/racevisionGame/src/main/java/network/MessageEncoders/HostedGamesRequestEncoder.java b/racevisionGame/src/main/java/network/MessageEncoders/HostedGamesRequestEncoder.java
new file mode 100644
index 00000000..80942a31
--- /dev/null
+++ b/racevisionGame/src/main/java/network/MessageEncoders/HostedGamesRequestEncoder.java
@@ -0,0 +1,43 @@
+package network.MessageEncoders;
+
+import network.Exceptions.InvalidMessageException;
+import network.Messages.AC35Data;
+import network.Messages.HostGame;
+import network.Messages.HostGamesRequest;
+
+import java.nio.ByteBuffer;
+
+import static network.Utils.ByteConverter.intToBytes;
+
+public class HostedGamesRequestEncoder implements MessageEncoder{
+
+ /**
+ * Constructor
+ */
+ public HostedGamesRequestEncoder() {
+ }
+
+ @Override
+ public byte[] encode(AC35Data message) throws InvalidMessageException {
+ try{
+ //Downcast
+ HostGamesRequest hostGamesRequest = (HostGamesRequest) message;
+
+ int numGames = hostGamesRequest.getKnownGames().size();
+
+ ByteBuffer hostedGamesRequestMessage = ByteBuffer.allocate(4+13*numGames);
+
+ hostedGamesRequestMessage.put(intToBytes(numGames));
+
+ HostGameMessageEncoder lineEncoder = new HostGameMessageEncoder();
+ for (HostGame line: hostGamesRequest.getKnownGames()) {
+ hostedGamesRequestMessage.put(lineEncoder.encode(line));
+ }
+
+ return hostedGamesRequestMessage.array();
+
+ }catch(Exception e){
+ throw new InvalidMessageException("Could not encode Host game message.", e);
+ }
+ }
+}
diff --git a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java
index aed5d70a..0941fd08 100644
--- a/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java
+++ b/racevisionGame/src/main/java/network/Messages/Enums/MessageType.java
@@ -39,6 +39,10 @@ public enum MessageType {
*/
ASSIGN_PLAYER_BOAT(121),
+ HOST_GAME(108),
+
+ HOSTED_GAMES_REQUEST(109),
+
NOTAMESSAGE(0);
diff --git a/racevisionGame/src/main/java/network/Messages/HostGame.java b/racevisionGame/src/main/java/network/Messages/HostGame.java
new file mode 100644
index 00000000..7fd5cb81
--- /dev/null
+++ b/racevisionGame/src/main/java/network/Messages/HostGame.java
@@ -0,0 +1,89 @@
+package network.Messages;
+
+
+import network.Messages.Enums.MessageType;
+import network.Messages.Enums.RaceStatusEnum;
+import network.Utils.ByteConverter;
+
+import java.nio.ByteBuffer;
+
+import static network.Utils.ByteConverter.intToBytes;
+
+public class HostGame extends AC35Data {
+
+ private String ip;
+ private int port;
+ private byte map;
+ private byte speed;
+ private RaceStatusEnum status;
+ private byte requiredNumPlayers;
+ private byte currentNumPlayers;
+
+ public HostGame(String ip, int port, byte map, byte speed,
+ RaceStatusEnum status, byte requiredNumPlayers,
+ byte currentNumPlayers) {
+ super(MessageType.HOST_GAME);
+ this.ip = ip;
+ this.port = port;
+ this.map = map;
+ this.speed = speed;
+ this.status = status;
+ this.requiredNumPlayers = requiredNumPlayers;
+ this.currentNumPlayers = currentNumPlayers;
+
+ }
+
+ /**
+ * @return the ip of host
+ */
+ public String getIp() {
+ return ip;
+ }
+
+ /**
+ * @return the port of host
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * @return the map index
+ */
+ public byte getMap() {
+ return map;
+ }
+
+ /**
+ * @return the speed value of game
+ */
+ public byte getSpeed() {
+ return speed;
+ }
+
+ /**
+ * @return the status of race
+ */
+ public RaceStatusEnum getStatus() {
+ return status;
+ }
+
+ /**
+ * @return required number of players
+ */
+ public byte getRequiredNumPlayers() {
+ return requiredNumPlayers;
+ }
+
+ /**
+ * @return current number of players
+ */
+ public byte getCurrentNumPlayers() {
+ return currentNumPlayers;
+ }
+
+ public void setIp(String ip) {
+ this.ip = ip;
+ }
+}
+
diff --git a/racevisionGame/src/main/java/network/Messages/HostGamesRequest.java b/racevisionGame/src/main/java/network/Messages/HostGamesRequest.java
new file mode 100644
index 00000000..080b4e3c
--- /dev/null
+++ b/racevisionGame/src/main/java/network/Messages/HostGamesRequest.java
@@ -0,0 +1,23 @@
+package network.Messages;
+
+import network.Messages.Enums.MessageType;
+
+import java.util.List;
+
+public class HostGamesRequest extends AC35Data{
+
+ private List knownGames;
+
+ /**
+ * Constructor
+ * @param knownGames games known by sender
+ */
+ public HostGamesRequest(List knownGames) {
+ super(MessageType.HOSTED_GAMES_REQUEST);
+ this.knownGames = knownGames;
+ }
+
+ public List getKnownGames() {
+ return knownGames;
+ }
+}
diff --git a/racevisionGame/src/main/java/network/Messages/LatestMessages.java b/racevisionGame/src/main/java/network/Messages/LatestMessages.java
index c16d722f..86f02d91 100644
--- a/racevisionGame/src/main/java/network/Messages/LatestMessages.java
+++ b/racevisionGame/src/main/java/network/Messages/LatestMessages.java
@@ -1,5 +1,6 @@
package network.Messages;
+import mock.model.RaceServer;
import network.Messages.Enums.XMLMessageType;
import java.util.*;
@@ -33,9 +34,6 @@ public class LatestMessages extends Observable {
private XMLMessage regattaXMLMessage;
-
-
-
/**
* Ctor.
*/
@@ -45,6 +43,7 @@ public class LatestMessages extends Observable {
/**
* Returns a copy of the race snapshot.
+ *
* @return Copy of the race snapshot.
*/
public List getSnapshot() {
@@ -54,6 +53,7 @@ public class LatestMessages extends Observable {
/**
* Sets the snapshot of the race.
+ *
* @param snapshot New snapshot of race.
*/
public void setSnapshot(List snapshot) {
@@ -61,12 +61,9 @@ public class LatestMessages extends Observable {
}
-
-
-
-
/**
* Returns the latest race xml message.
+ *
* @return The latest race xml message.
*/
public XMLMessage getRaceXMLMessage() {
@@ -75,6 +72,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest race xml message to a specified race XML message.
+ *
* @param raceXMLMessage The new race XML message to use.
*/
public void setRaceXMLMessage(XMLMessage raceXMLMessage) {
@@ -87,6 +85,7 @@ public class LatestMessages extends Observable {
/**
* Returns the latest boat xml message.
+ *
* @return The latest boat xml message.
*/
public XMLMessage getBoatXMLMessage() {
@@ -95,6 +94,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest boat xml message to a specified boat XML message.
+ *
* @param boatXMLMessage The new boat XML message to use.
*/
public void setBoatXMLMessage(XMLMessage boatXMLMessage) {
@@ -107,6 +107,7 @@ public class LatestMessages extends Observable {
/**
* Returns the latest regatta xml message.
+ *
* @return The latest regatta xml message.
*/
public XMLMessage getRegattaXMLMessage() {
@@ -115,6 +116,7 @@ public class LatestMessages extends Observable {
/**
* Sets the latest regatta xml message to a specified regatta XML message.
+ *
* @param regattaXMLMessage The new regatta XML message to use.
*/
public void setRegattaXMLMessage(XMLMessage regattaXMLMessage) {
@@ -126,6 +128,7 @@ public class LatestMessages extends Observable {
/**
* Checks the type of xml message, and places it in this LatestMessages object.
+ *
* @param xmlMessage The new xml message to use.
*/
public void setXMLMessage(XMLMessage xmlMessage) {
@@ -145,17 +148,20 @@ public class LatestMessages extends Observable {
/**
* Returns whether or not there is an xml message for each message type.
+ *
* @return True if race, boat, and regatta have an xml message, false otherwise.
*/
public boolean hasAllXMLMessages() {
- if (this.regattaXMLMessage == null || this.boatXMLMessage == null || this.raceXMLMessage == null) {
+ if (this.regattaXMLMessage == null || this.boatXMLMessage == null ||
+ this.raceXMLMessage == null) {
return false;
} else {
+ RaceServer.staticUpdateXML();
return true;
}
}
-}
+}
\ No newline at end of file
diff --git a/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java b/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java
index 8022c374..1277e62b 100644
--- a/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java
+++ b/racevisionGame/src/main/java/network/PacketDump/AC35DumpReader.java
@@ -41,7 +41,6 @@ public class AC35DumpReader {
messLen[1] = dump[pointer + 13];
messLen[0] = dump[pointer + 14];
int messageLength = ByteBuffer.wrap(messLen).getShort();
- //System.out.println(messageLength);
packets.add(new AC35Packet(Arrays.copyOfRange(dump, pointer, pointer + messageLength + 19)));
diff --git a/racevisionGame/src/main/java/shared/model/Boat.java b/racevisionGame/src/main/java/shared/model/Boat.java
index 1e4c0f09..bd3eea5e 100644
--- a/racevisionGame/src/main/java/shared/model/Boat.java
+++ b/racevisionGame/src/main/java/shared/model/Boat.java
@@ -403,22 +403,20 @@ public class Boat extends Collider {
public boolean isSailsOut() {
return sailsOut;
}
- public void bounce(double repulsionRadius) {
- Azimuth reverseAzimuth = Azimuth.fromDegrees(getBearing().degrees() - 180d);
- setPosition(GPSCoordinate.calculateNewPosition(getPosition(), 2 * repulsionRadius, reverseAzimuth));
- }
@Override
public boolean rayCast(Boat boat) {
if(boat != this) {
- return rayCast(boat, 15);
+ return rayCast(boat, 100);
} else return false;
}
@Override
- public void onCollisionEnter(Boat collider, Collision e) {
+ public void onCollisionEnter(Collision e) {
if(e.getBearing().degrees() > 270 || e.getBearing().degrees() < 90) {
- collider.bounce(15);
+ // Notify observers of collision
+ this.setChanged();
+ notifyObservers(e);
}
}
}
diff --git a/racevisionGame/src/main/java/shared/model/CompoundMark.java b/racevisionGame/src/main/java/shared/model/CompoundMark.java
index 4fa2599c..57d9a32b 100644
--- a/racevisionGame/src/main/java/shared/model/CompoundMark.java
+++ b/racevisionGame/src/main/java/shared/model/CompoundMark.java
@@ -187,7 +187,7 @@ public class CompoundMark extends XMLCompoundMark{
}
//finds the mark furthest west and east
- if(this.getMark1Position().getLongitude() > this.getMark2Position().getLongitude()){
+ if(this.getMark1Position().getLongitude() < this.getMark2Position().getLongitude()){
westMostMark = this.mark1;
eastMostMark = this.mark2;
}else{
diff --git a/racevisionGame/src/main/java/shared/model/Constants.java b/racevisionGame/src/main/java/shared/model/Constants.java
index 7a5e2820..c482bafd 100644
--- a/racevisionGame/src/main/java/shared/model/Constants.java
+++ b/racevisionGame/src/main/java/shared/model/Constants.java
@@ -5,7 +5,6 @@ package shared.model;
* Created by Erika on 19-Mar-17.
*/
public class Constants {
-
/**
* Multiply by this factor to convert nautical miles to meters.
*
@@ -15,8 +14,6 @@ public class Constants {
*/
public static final int NMToMetersConversion = 1852;
-
-
/**
* Multiply by this factor to convert Knots to millimeters per second.
*
@@ -26,33 +23,24 @@ public class Constants {
*/
public static final double KnotsToMMPerSecond = 514.444;
-
-
/**
* The scale factor of the race.
* Frame periods are multiplied by this to get the amount of time a single frame represents.
* E.g., frame period = 20ms, scale = 5, frame represents 20 * 5 = 100ms, and so boats are simulated for 100ms, even though only 20ms actually occurred.
*/
- public static final int RaceTimeScale = 2;//10;
+ public static final int RaceTimeScale = 10;
/**
- * The race pre-start time, in milliseconds. 3 minutes (30 seconds for development).
+ * The race pre-start time, in milliseconds. 30 seconds.
+ * Official time is 3 minutes.
*/
-// public static final long RacePreStartTime = 30 * 1000;
- public static final long RacePreStartTime = 1000;
-
+ public static final long RacePreStartTime = 30 * 1000;
/**
- * The race preparatory time, in milliseconds. 1 minute.
+ * The race preparatory time, in milliseconds. 10 seconds.
+ * Official time is 1 minute.
*/
-// public static final long RacePreparatoryTime = 60 * 1000;
- public static final long RacePreparatoryTime = 1 * 60 * 1000;
-
-
-
-
-
-
+ public static final long RacePreparatoryTime = 10 * 1000;
/**
* The number of milliseconds in one hour.
*
@@ -70,7 +58,4 @@ public class Constants {
* Divide by this factor to convert hours to seconds.
*/
public static long OneHourSeconds = 1 * 60 * 60;
-
-
-
}
diff --git a/racevisionGame/src/main/java/shared/model/Mark.java b/racevisionGame/src/main/java/shared/model/Mark.java
index b4025b8e..23778cff 100644
--- a/racevisionGame/src/main/java/shared/model/Mark.java
+++ b/racevisionGame/src/main/java/shared/model/Mark.java
@@ -29,6 +29,11 @@ public class Mark extends Collider{
*/
private GPSCoordinate position;
+ /**
+ * Repulsion radius of the mark
+ */
+ private double repulsionRadius = 50;
+
/**
* Constructs a mark with a given source ID, name, and position.
* @param sourceID The source ID of the mark.
@@ -92,11 +97,12 @@ public class Mark extends Collider{
@Override
public boolean rayCast(Boat boat) {
- return rayCast(boat, 15);
+ return rayCast(boat, repulsionRadius);
}
@Override
- public void onCollisionEnter(Boat collider, Collision e) {
- collider.bounce(15);
+ public void onCollisionEnter(Collision e) {
+ this.setChanged();
+ notifyObservers(e);
}
}
diff --git a/racevisionGame/src/main/java/shared/model/MarkRoundingData.java b/racevisionGame/src/main/java/shared/model/MarkRoundingData.java
new file mode 100644
index 00000000..c9fb36a2
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/model/MarkRoundingData.java
@@ -0,0 +1,118 @@
+package shared.model;
+
+
+
+/**
+ * Contains data related to mark rounding for a specific leg.
+ */
+public class MarkRoundingData {
+
+ /**
+ * The leg this relates to.
+ */
+ private Leg leg;
+
+ /**
+ * The mark that should be rounded.
+ */
+ private Mark markToRound;
+
+ /**
+ * The bearing of the leg.
+ */
+ private Bearing legBearing;
+
+ /**
+ * The bearing of the next leg.
+ */
+ private Bearing nextLegBearing;
+
+ /**
+ * The location of the first rounding check point.
+ */
+ private GPSCoordinate roundCheck1;
+
+ /**
+ * The location of the second rounding check point.
+ */
+ private GPSCoordinate roundCheck2;
+
+ /**
+ * A halfway point between mark to round and roundCheck1.
+ */
+ private GPSCoordinate roundCheck1Halfway;
+
+ /**
+ * A halfway point between mark to round and roundCheck2.
+ */
+ private GPSCoordinate roundCheck2Halfway;
+
+
+ public MarkRoundingData() {
+ }
+
+
+ public Leg getLeg() {
+ return leg;
+ }
+
+ public void setLeg(Leg leg) {
+ this.leg = leg;
+ }
+
+ public Mark getMarkToRound() {
+ return markToRound;
+ }
+
+ public void setMarkToRound(Mark markToRound) {
+ this.markToRound = markToRound;
+ }
+
+ public Bearing getLegBearing() {
+ return legBearing;
+ }
+
+ public void setLegBearing(Bearing legBearing) {
+ this.legBearing = legBearing;
+ }
+
+ public Bearing getNextLegBearing() {
+ return nextLegBearing;
+ }
+
+ public void setNextLegBearing(Bearing nextLegBearing) {
+ this.nextLegBearing = nextLegBearing;
+ }
+
+ public GPSCoordinate getRoundCheck1() {
+ return roundCheck1;
+ }
+
+ public void setRoundCheck1(GPSCoordinate roundCheck1) {
+ this.roundCheck1 = roundCheck1;
+ }
+
+ public GPSCoordinate getRoundCheck2() {
+ return roundCheck2;
+ }
+
+ public void setRoundCheck2(GPSCoordinate roundCheck2) {
+ this.roundCheck2 = roundCheck2;
+ }
+
+ public GPSCoordinate getRoundCheck1Halfway() {
+ return roundCheck1Halfway;
+ }
+
+ public void setRoundCheck1Halfway(GPSCoordinate roundCheck1Halfway) {
+ this.roundCheck1Halfway = roundCheck1Halfway;
+ }
+
+ public GPSCoordinate getRoundCheck2Halfway() {
+ return roundCheck2Halfway;
+ }
+
+ public void setRoundCheck2Halfway(GPSCoordinate roundCheck2Halfway) {
+ this.roundCheck2Halfway = roundCheck2Halfway;
+ }
+}
diff --git a/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java b/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java
new file mode 100644
index 00000000..de11c170
--- /dev/null
+++ b/racevisionGame/src/main/java/shared/model/MarkRoundingSequence.java
@@ -0,0 +1,210 @@
+package shared.model;
+
+import java.util.*;
+
+import static shared.enums.RoundingType.*;
+
+/**
+ * This class contains a sequence of points that describe the mark rounding order for a course.
+ */
+public class MarkRoundingSequence {
+
+
+ /**
+ * Legs in the race.
+ */
+ private List legs;
+
+ /**
+ * For each leg, mark rounding information.
+ */
+ private Map roundingPoints;
+
+
+
+ public MarkRoundingSequence(List legs) {
+ this.legs = legs;
+ generateRoundingPoints();
+ }
+
+
+ /**
+ * Returns the rounding points for a given leg.
+ * @param leg Leg to check.
+ * @return Rounding points for leg.
+ */
+ public MarkRoundingData getRoundingData(Leg leg) {
+ return roundingPoints.get(leg);
+ }
+
+
+ /**
+ * Generates the rounding points for all legs in the race.
+ */
+ private void generateRoundingPoints() {
+ this.roundingPoints = new HashMap<>(this.legs.size());
+
+ for (int i = 0; i < this.legs.size(); i++) {
+ Leg currentLeg = this.legs.get(i);
+
+ Optional nextLeg = Optional.empty();
+ if (i < legs.size() - 1) {
+ nextLeg = Optional.of(this.legs.get(i + 1));
+ }
+
+ generateRoundingPoint(currentLeg, nextLeg);
+ }
+
+ }
+
+
+ /**
+ * Generates the rounding points for a specific leg.
+ * @param currentLeg The leg to generate rounding points for.
+ * @param nextLeg The following leg, used to help generate rounding points. Final leg of race doesn't have a following leg.
+ */
+ private void generateRoundingPoint(Leg currentLeg, Optional nextLeg) {
+
+ Bearing bearingToAddFirstPoint = calculateBearingToAdd(currentLeg);
+
+ GPSCoordinate startCoord = currentLeg.getStartCompoundMark().getAverageGPSCoordinate();
+ GPSCoordinate endCoord = currentLeg.getEndCompoundMark().getAverageGPSCoordinate();
+ Bearing legBearing = GPSCoordinate.calculateBearing(startCoord, endCoord);
+ Bearing nextBearing = legBearing;
+
+ Mark markToRound = currentLeg.getEndCompoundMark().getMarkForRounding(legBearing);
+
+ GPSCoordinate roundCheck1;
+ if (currentLeg.getEndCompoundMark().getMark2() == null) {
+ //End is a single mark.
+ roundCheck1 = calculateRoundingCheckPoint(
+ currentLeg,
+ markToRound,
+ legBearing,
+ bearingToAddFirstPoint);
+ } else {
+ //End is a gate.
+ if (markToRound == currentLeg.getEndCompoundMark().getMark1()) {
+ roundCheck1 = currentLeg.getEndCompoundMark().getMark2().getPosition();
+ } else {
+ roundCheck1 = currentLeg.getEndCompoundMark().getMark1().getPosition();
+ }
+ }
+
+ //TODO the halfway points currently haven't been done properly.
+
+ GPSCoordinate roundCheck1Halfway = calculateRoundingCheckPoint(
+ currentLeg,
+ markToRound,
+ legBearing,
+ bearingToAddFirstPoint);
+
+
+ GPSCoordinate roundCheck2 = roundCheck1;
+ GPSCoordinate roundCheck2Halfway = roundCheck1Halfway;
+ if (nextLeg.isPresent()) {
+
+ Bearing bearingToAddSecondPoint = bearingToAddFirstPoint;//calculateBearingToAdd(nextLeg.get());
+
+ GPSCoordinate startCoord2 = nextLeg.get().getStartCompoundMark().getAverageGPSCoordinate();
+ GPSCoordinate endCoord2 = nextLeg.get().getEndCompoundMark().getAverageGPSCoordinate();
+ nextBearing = GPSCoordinate.calculateBearing(startCoord2, endCoord2);
+
+ roundCheck2 = calculateRoundingCheckPoint(
+ currentLeg,
+ markToRound,
+ nextBearing,
+ bearingToAddSecondPoint);
+
+ roundCheck2Halfway = calculateRoundingCheckPoint(
+ currentLeg,
+ markToRound,
+ nextBearing,
+ bearingToAddSecondPoint);
+ }
+
+
+ MarkRoundingData roundingData = new MarkRoundingData();
+ roundingData.setLeg(currentLeg);
+
+ roundingData.setLegBearing(legBearing);
+ roundingData.setNextLegBearing(nextBearing);
+
+ roundingData.setMarkToRound(markToRound);
+
+ roundingData.setRoundCheck1(roundCheck1);
+ roundingData.setRoundCheck1Halfway(roundCheck1Halfway);
+
+ roundingData.setRoundCheck2(roundCheck2);
+ roundingData.setRoundCheck2Halfway(roundCheck2Halfway);
+
+
+ this.roundingPoints.put(currentLeg, roundingData);
+
+
+ //Rounding points:
+
+ //each mark/gate has a specific mark to round. Call this ROUNDINGMARK
+ // with a mark, it is the mark
+ // with a gate, it depends if it is a starboard or port gate.
+ // it is the mark that allows the boat to enter between both marks of the gate, whilst obeying the starboard/port requirement.
+
+ //let the bearing between start of leg and end of leg be called LEGBEARING
+
+ //the first rounding point is ROUNDINGDISTANCE units away from the ROUNDINGMARK, on an angle perpendicular to LEGBEARING.
+ // the angle means that the rounding mark is at the center of a gate, for gates.
+
+ //the second rounding point is the same as the first, except LEGBEARING is the bearing between end of current leg, and start of next leg.
+ }
+
+
+
+ /**
+ * Calculates the location of the rounding check point, which together with the mark to round, forms a line that the boat must cross to round the mark.
+ * @param leg Leg of race to check.
+ * @param markToRound Mark at end of leg to round.
+ * @param legBearing The bearing of the nearest leg. For the first rounding point this is the leg's bearing, for the second rounding point it is the next leg's bearing.
+ * @param bearingToAdd The bearing to add to the leg bearing to get a perpendicular bearing.
+ * @return The location of the rounding point, which together with the mark to round forms a line the boat must cross.
+ */
+ private GPSCoordinate calculateRoundingCheckPoint(Leg leg, Mark markToRound, Bearing legBearing, Bearing bearingToAdd) {
+
+
+ double roundingDistanceMeters = leg.getEndCompoundMark().getRoundingDistance();
+
+
+ //We project from rounding mark to get the second point which forms the line the boat must cross.
+ /*
+ c2
+ |
+ |
+ r------c1
+ b
+ */
+ GPSCoordinate roundCheck = GPSCoordinate.calculateNewPosition(
+ markToRound.getPosition(),
+ roundingDistanceMeters,
+ Azimuth.fromDegrees(legBearing.degrees() + bearingToAdd.degrees()) );
+
+ return roundCheck;
+ }
+
+
+ /**
+ * Calculates the bearing that must be added to a leg's bearing to calculate a perpendicular bearing, used for finding rounding points.
+ * @param leg Leg to check.
+ * @return Bearing to add. Will be either +90 or -90.
+ */
+ private Bearing calculateBearingToAdd(Leg leg) {
+
+ if (leg.getEndCompoundMark().getRoundingType() == Port ||
+ leg.getEndCompoundMark().getRoundingType() == SP) {
+ return Bearing.fromDegrees(90);
+ } else {
+ return Bearing.fromDegrees(-90);
+ }
+
+ }
+
+
+}
diff --git a/racevisionGame/src/main/java/shared/model/RaceClock.java b/racevisionGame/src/main/java/shared/model/RaceClock.java
index 4c6532a9..3ce61245 100644
--- a/racevisionGame/src/main/java/shared/model/RaceClock.java
+++ b/racevisionGame/src/main/java/shared/model/RaceClock.java
@@ -3,6 +3,7 @@ package shared.model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.Nullable;
+import visualiser.Controllers.RaceStartController;
import visualiser.model.ResizableRaceCanvas;
import java.time.Duration;
@@ -15,8 +16,8 @@ import java.util.Date;
* This class is used to implement a clock which keeps track of and
* displays times relevant to a race. This is displayed on the
* {@link ResizableRaceCanvas} via the
- * {@link visualiser.Controllers.RaceController} and the
- * {@link visualiser.Controllers.StartController}.
+ * {@link visualiser.Controllers.RaceViewController} and the
+ * {@link RaceStartController}.
*/
public class RaceClock {
diff --git a/racevisionGame/src/main/java/shared/model/RaceState.java b/racevisionGame/src/main/java/shared/model/RaceState.java
index 48361da2..b4a845e2 100644
--- a/racevisionGame/src/main/java/shared/model/RaceState.java
+++ b/racevisionGame/src/main/java/shared/model/RaceState.java
@@ -13,6 +13,7 @@ import shared.dataInput.RegattaDataSource;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Observable;
/**
@@ -20,7 +21,7 @@ import java.util.List;
* 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 {
+public abstract class RaceState extends Observable{
@@ -46,6 +47,12 @@ public abstract class RaceState {
private ObservableList legs;
+ /**
+ * The sequence of rounding points for each leg/mark.
+ */
+ private MarkRoundingSequence markRoundingSequence;
+
+
/**
* The clock which tracks the race's start time, current time, and elapsed duration.
@@ -102,10 +109,15 @@ public abstract class RaceState {
*/
protected void useLegsList(List legs) {
this.legs.setAll(legs);
+
+ //We create this before adding the extra finish leg, as it doesn't contain compound marks.
+ this.markRoundingSequence = new MarkRoundingSequence(getLegs());
+
//We add a "dummy" leg at the end of the race.
if (getLegs().size() > 0) {
getLegs().add(new Leg("Finish", getLegs().size()));
}
+
}
@@ -367,6 +379,11 @@ public abstract class RaceState {
}
-
-
+ /**
+ * Returns the rounding sequences for each leg.
+ * @return Rounding sequence for each leg.
+ */
+ public MarkRoundingSequence getMarkRoundingSequence() {
+ return markRoundingSequence;
+ }
}
diff --git a/racevisionGame/src/main/java/shared/xml/Race/XMLParticipants.java b/racevisionGame/src/main/java/shared/xml/Race/XMLParticipants.java
index 331cc861..a7fd3a8f 100644
--- a/racevisionGame/src/main/java/shared/xml/Race/XMLParticipants.java
+++ b/racevisionGame/src/main/java/shared/xml/Race/XMLParticipants.java
@@ -50,7 +50,7 @@ import javax.xml.bind.annotation.XmlType;
})
public class XMLParticipants {
- @XmlElement(name = "Yacht", required = true)
+ @XmlElement(name = "Yacht", required = false)
protected List yacht;
/**
diff --git a/racevisionGame/src/main/java/shared/xml/XMLUtilities.java b/racevisionGame/src/main/java/shared/xml/XMLUtilities.java
index 0a50f77d..0b789235 100644
--- a/racevisionGame/src/main/java/shared/xml/XMLUtilities.java
+++ b/racevisionGame/src/main/java/shared/xml/XMLUtilities.java
@@ -49,7 +49,7 @@ public class XMLUtilities {
return xmlToClass(document, schemaURL, c);
}
- public static Object xmlToClass(String xml, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
+ public static T xmlToClass(String xml, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = parser.parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8"))));
@@ -63,10 +63,10 @@ public class XMLUtilities {
* @param c The XML class to convert to.
* @param The XML class to convert to.
* @return The XML class object.
- * @throws ParserConfigurationException
- * @throws IOException
- * @throws SAXException
- * @throws JAXBException
+ * @throws ParserConfigurationException Thrown if input cannot be converted to class.
+ * @throws IOException Thrown if input cannot be converted to class.
+ * @throws SAXException Thrown if input cannot be converted to class.
+ * @throws JAXBException Thrown if input cannot be converted to class.
*/
public static T xmlToClass(InputStream i, URL schemaURL, Class c) throws ParserConfigurationException, IOException, SAXException, JAXBException {
DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
diff --git a/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java b/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java
index c0361b52..e3d309fc 100644
--- a/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java
+++ b/racevisionGame/src/main/java/shared/xml/boats/BoatConfig.java
@@ -184,7 +184,7 @@ public class BoatConfig {
* Objects of the following type(s) are allowed in the list
* {@link BoatConfig.Boats.Boat }
*
- *
+ * @return List of Boat entries.
*/
public List getBoat() {
if (boat == null) {
@@ -327,7 +327,7 @@ public class BoatConfig {
/**
* Gets the value of the sourceID property.
- *
+ * @return source id.
*/
public int getSourceID() {
return sourceID;
@@ -335,7 +335,7 @@ public class BoatConfig {
/**
* Sets the value of the sourceID property.
- *
+ * @param value new source id.
*/
public void setSourceID(int value) {
this.sourceID = value;
@@ -494,7 +494,7 @@ public class BoatConfig {
/**
* Gets the value of the y property.
- *
+ * @return Y value.
*/
public double getY() {
return y;
@@ -502,7 +502,7 @@ public class BoatConfig {
/**
* Sets the value of the y property.
- *
+ * @param value new y value.
*/
public void setY(double value) {
this.y = value;
@@ -510,7 +510,7 @@ public class BoatConfig {
/**
* Gets the value of the z property.
- *
+ * @return z value.
*/
public double getZ() {
return z;
@@ -518,7 +518,7 @@ public class BoatConfig {
/**
* Sets the value of the z property.
- *
+ * @param value new z value.
*/
public void setZ(double value) {
this.z = value;
diff --git a/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java
index 0319de9a..ed3dbfb2 100644
--- a/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java
+++ b/racevisionGame/src/main/java/shared/xml/boats/ObjectFactory.java
@@ -38,7 +38,7 @@ public class ObjectFactory {
/**
* Create an instance of {@link BoatConfig }
- *
+ * @return BoatConfig.
*/
public BoatConfig createBoatConfig() {
return new BoatConfig();
@@ -46,7 +46,7 @@ public class ObjectFactory {
/**
* Create an instance of {@link BoatConfig.Boats }
- *
+ * @return Boats.
*/
public BoatConfig.Boats createBoatConfigBoats() {
return new BoatConfig.Boats();
@@ -54,7 +54,7 @@ public class ObjectFactory {
/**
* Create an instance of {@link BoatConfig.Boats.Boat }
- *
+ * @return Boat.
*/
public BoatConfig.Boats.Boat createBoatConfigBoatsBoat() {
return new BoatConfig.Boats.Boat();
@@ -62,7 +62,7 @@ public class ObjectFactory {
/**
* Create an instance of {@link BoatConfig.Boats.Boat.GPSposition }
- *
+ * @return GPSposition.
*/
public BoatConfig.Boats.Boat.GPSposition createBoatConfigBoatsBoatGPSposition() {
return new BoatConfig.Boats.Boat.GPSposition();
diff --git a/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java
index 7fc72202..23c875e0 100644
--- a/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java
+++ b/racevisionGame/src/main/java/shared/xml/regatta/ObjectFactory.java
@@ -38,7 +38,7 @@ public class ObjectFactory {
/**
* Create an instance of {@link RegattaConfig }
- *
+ * @return RegattaConfig.
*/
public RegattaConfig createRegattaConfig() {
return new RegattaConfig();
diff --git a/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java
index d8cc1613..eebee2ae 100644
--- a/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java
+++ b/racevisionGame/src/main/java/shared/xml/regatta/RegattaConfig.java
@@ -74,7 +74,7 @@ public class RegattaConfig {
/**
* Gets the value of the regattaID property.
- *
+ * @return regatta id.
*/
public int getRegattaID() {
return regattaID;
@@ -82,7 +82,7 @@ public class RegattaConfig {
/**
* Sets the value of the regattaID property.
- *
+ * @param value new regatta id.
*/
public void setRegattaID(int value) {
this.regattaID = value;
@@ -138,7 +138,7 @@ public class RegattaConfig {
/**
* Gets the value of the centralLatitude property.
- *
+ * @return central latitude.
*/
public double getCentralLatitude() {
return centralLatitude;
@@ -146,7 +146,7 @@ public class RegattaConfig {
/**
* Sets the value of the centralLatitude property.
- *
+ * @param value new central latitude.
*/
public void setCentralLatitude(double value) {
this.centralLatitude = value;
@@ -154,7 +154,7 @@ public class RegattaConfig {
/**
* Gets the value of the centralLongitude property.
- *
+ * @return central longitude.
*/
public double getCentralLongitude() {
return centralLongitude;
@@ -162,7 +162,7 @@ public class RegattaConfig {
/**
* Sets the value of the centralLongitude property.
- *
+ * @param value new central longitude.
*/
public void setCentralLongitude(double value) {
this.centralLongitude = value;
@@ -170,7 +170,7 @@ public class RegattaConfig {
/**
* Gets the value of the centralAltitude property.
- *
+ * @return central altitude.
*/
public double getCentralAltitude() {
return centralAltitude;
@@ -178,7 +178,7 @@ public class RegattaConfig {
/**
* Sets the value of the centralAltitude property.
- *
+ * @param value new central altitude.
*/
public void setCentralAltitude(double value) {
this.centralAltitude = value;
@@ -186,7 +186,7 @@ public class RegattaConfig {
/**
* Gets the value of the utcOffset property.
- *
+ * @return utc offset.
*/
public double getUtcOffset() {
return utcOffset;
@@ -194,7 +194,7 @@ public class RegattaConfig {
/**
* Sets the value of the utcOffset property.
- *
+ * @param value new utc offset.
*/
public void setUtcOffset(double value) {
this.utcOffset = value;
@@ -202,7 +202,7 @@ public class RegattaConfig {
/**
* Gets the value of the magneticVariation property.
- *
+ * @return magnetic variation.
*/
public double getMagneticVariation() {
return magneticVariation;
@@ -210,7 +210,7 @@ public class RegattaConfig {
/**
* Sets the value of the magneticVariation property.
- *
+ * @param value new magnetic variation.
*/
public void setMagneticVariation(double value) {
this.magneticVariation = value;
diff --git a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatCollisionCommand.java b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatCollisionCommand.java
index c3046d18..48d19d09 100644
--- a/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatCollisionCommand.java
+++ b/racevisionGame/src/main/java/visualiser/Commands/VisualiserRaceCommands/BoatCollisionCommand.java
@@ -3,6 +3,8 @@ package visualiser.Commands.VisualiserRaceCommands;
import javafx.scene.media.AudioClip;
import mock.model.commandFactory.Command;
import network.Messages.YachtEvent;
+import shared.exceptions.BoatNotFoundException;
+import visualiser.model.VisualiserBoat;
import visualiser.model.VisualiserRaceState;
/**
@@ -34,6 +36,11 @@ public class BoatCollisionCommand implements Command {
sound.play();
}
- //System.out.println("Collision command executed!");
+ try {
+ VisualiserBoat boat = visualiserRace.getBoat(yachtEvent.getSourceID());
+ boat.setHasCollided(true);
+ } catch (BoatNotFoundException e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java
index eb5d001d..739b2f6e 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/ArrowController.java
@@ -1,65 +1,31 @@
package visualiser.Controllers;
-
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
-import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
-import javafx.scene.shape.Circle;
import shared.model.Bearing;
import shared.model.Wind;
/**
- * Controller for the arrow.fxml view.
+ * Controller for the wind direction arrow on the race screen.
*/
public class ArrowController {
-
-
- @FXML
- private Pane compass;
-
- @FXML
- private StackPane arrowStackPane;
-
- @FXML
- private ImageView arrowImage;
-
- @FXML
- private Circle circle;
-
- @FXML
- private Label northLabel;
-
- @FXML
- private Label windLabel;
-
- @FXML
- private Label speedLabel;
-
-
- /**
- * This is the property our arrow control binds to.
- */
- private Property wind;
-
-
- /**
- * Constructor.
- */
- public ArrowController() {
- }
-
+ private @FXML StackPane arrowStackPane;
+ private @FXML ImageView arrowImage;
+ private @FXML Label speedLabel;
+ private final static Integer MIN_KNOTS = 2; // knots for min_height
+ private final static Integer MAX_KNOTS = 30; // knots for max_height
+ private final static Integer MIN_HEIGHT = 25; // min arrow height
+ private final static Integer MAX_HEIGHT = 75; // max arrow height
/**
* Sets which wind property the arrow control should bind to.
* @param wind The wind property to bind to.
*/
public void setWindProperty(Property wind) {
- this.wind = wind;
-
wind.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
Platform.runLater(() -> updateWind(newValue));
@@ -67,7 +33,6 @@ public class ArrowController {
});
}
-
/**
* Updates the control to use the new wind value.
* This updates the arrow direction (due to bearing), arrow length (due to speed), and label (due to speed).
@@ -78,7 +43,6 @@ public class ArrowController {
updateWindSpeed(wind.getWindSpeed());
}
-
/**
* Updates the control to account for the new wind speed.
* This changes the length (height) of the wind arrow, and updates the speed label.
@@ -94,29 +58,22 @@ public class ArrowController {
* @param speedKnots Wind speed, in knots.
*/
private void updateWindArrowLength(double speedKnots) {
-
- //At 2 knots, the arrow reaches its minimum height, and at 30 knots it reaches its maximum height.
- double minKnots = 2;
- double maxKnots = 30;
- double deltaKnots = maxKnots - minKnots;
-
- double minHeight = 25;
- double maxHeight = 75;
- double deltaHeight = maxHeight - minHeight;
+ double deltaKnots = MAX_KNOTS - MIN_KNOTS;
+ double deltaHeight = MAX_HEIGHT - MIN_HEIGHT;
//Clamp speed.
- if (speedKnots > maxKnots) {
- speedKnots = maxKnots;
- } else if (speedKnots < minKnots) {
- speedKnots = minKnots;
+ if (speedKnots > MAX_KNOTS) {
+ speedKnots = MAX_KNOTS;
+ } else if (speedKnots < MIN_KNOTS) {
+ speedKnots = MIN_KNOTS;
}
//How far between the knots bounds is the current speed?
- double currentDeltaKnots = speedKnots - minKnots;
+ double currentDeltaKnots = speedKnots - MIN_KNOTS;
double currentKnotsScalar = currentDeltaKnots / deltaKnots;
//Thus, how far between the pixel height bounds should the arrow height be?
- double newHeight = minHeight + (currentKnotsScalar * deltaHeight);
+ double newHeight = MIN_HEIGHT + (currentKnotsScalar * deltaHeight);
arrowImage.setFitHeight(newHeight);
}
@@ -129,7 +86,6 @@ public class ArrowController {
speedLabel.setText(String.format("%.1fkn", speedKnots));
}
-
/**
* Updates the control to account for a new wind bearing.
* This rotates the arrow according to the bearing.
@@ -140,7 +96,4 @@ public class ArrowController {
arrowStackPane.setRotate(bearing.degrees());
}
-
-
-
-}
+}
\ No newline at end of file
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java b/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java
deleted file mode 100644
index 28692488..00000000
--- a/racevisionGame/src/main/java/visualiser/Controllers/ConnectionController.java
+++ /dev/null
@@ -1,147 +0,0 @@
-package visualiser.Controllers;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.*;
-import javafx.scene.layout.AnchorPane;
-import mock.app.Event;
-import org.xml.sax.SAXException;
-import shared.exceptions.InvalidBoatDataException;
-import shared.exceptions.InvalidRaceDataException;
-import shared.exceptions.InvalidRegattaDataException;
-import shared.exceptions.XMLReaderException;
-import visualiser.model.RaceConnection;
-
-import javax.xml.bind.JAXBException;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.soap.Text;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ResourceBundle;
-
-//TODO it appears this view/controller was replaced by Lobby.fxml. Remove?
-/**
- * Controls the connection that the VIsualiser can connect to.
- */
-public class ConnectionController extends Controller {
- @FXML
- private AnchorPane connectionWrapper;
- @FXML
- private TableView connectionTable;
- @FXML
- private TableColumn hostnameColumn;
- @FXML
- private TableColumn statusColumn;
- @FXML
- private Button connectButton;
-
- @FXML
- private TextField urlField;
- @FXML
- private TextField portField;
-
-
- /*Title Screen fxml items*/
- @FXML
- private Button hostGameTitleBtn;
- @FXML
- private Button connectGameBtn;
- @FXML
- private RadioButton nightRadioBtn;
- @FXML
- private RadioButton dayRadioButton;
-
- /*Lobby fxml items*/
- @FXML
- private TableView lobbyTable;
- @FXML
- private TableColumn gameNameColumn;
- @FXML
- private TableColumn hostNameColumn;
- @FXML
- private TableColumn playerCountColumn;
- @FXML
- private TextField playerNameField;
- @FXML
- private Button joinGameBtn;
-
- /*Host game fxml items*/
- @FXML
- private TextField gameNameField;
- @FXML
- private TextField hostNameField;
- @FXML
- private TextField hostGameBtn;
-
-
-
-
-
-
-
-
- private ObservableList connections;
-
-
-
- @Override
- public void initialize(URL location, ResourceBundle resources) {
- // TODO - replace with config file
- connections = FXCollections.observableArrayList();
-
- connectionTable.setItems(connections);
- hostnameColumn.setCellValueFactory(cellData -> cellData.getValue().hostnameProperty());
- statusColumn.setCellValueFactory(cellData -> cellData.getValue().statusProperty());
-
- connectionTable.getSelectionModel().selectedItemProperty().addListener((obs, prev, curr) -> {
- if (curr != null && curr.check()) connectButton.setDisable(false);
- else connectButton.setDisable(true);
- });
- connectButton.setDisable(true);
- }
-
- /**
- * Sets current status of all connections.
- */
- public void checkConnections() {
- for(RaceConnection connection: connections) {
- connection.check();
- }
- }
-
- public AnchorPane startWrapper(){
- return connectionWrapper;
- }
-
- /**
- * Connects to host currently selected in table. Button enabled only if host is ready.
- */
- public void connectSocket() {
- try{
- RaceConnection connection = connectionTable.getSelectionModel().getSelectedItem();
- Socket socket = new Socket(connection.getHostname(), connection.getPort());
- socket.setKeepAlive(true);
- connectionWrapper.setVisible(false);
- //parent.enterLobby(socket);
- } catch (IOException e) { /* Never reached */ }
- }
-
- /**
- * adds a new connection
- */
- public void addConnection(){
- String hostName = urlField.getText();
- String portString = portField.getText();
- try{
- int port = Integer.parseInt(portString);
- connections.add(new RaceConnection(hostName, port, null));
- }catch(NumberFormatException e){
- System.err.println("Port number entered is not a number");
- }
- }
-
-
-}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java
index 220b7816..6d14b32d 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/Controller.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/Controller.java
@@ -1,32 +1,108 @@
package visualiser.Controllers;
-import javafx.fxml.Initializable;
-
-import java.net.URL;
-import java.util.ResourceBundle;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import visualiser.app.App;
+import java.io.IOException;
/**
- * Controller parent for app controllers.
- * Created by fwy13 on 15/03/2017.
+ * Abstract controller class to give each subclass the functionality to load
+ * a new scene into the existing stage, or create a new popup window.
*/
-public abstract class Controller implements Initializable {
- protected MainController parent;
+public abstract class Controller {
+ private Stage stage = App.getStage();
+
+ /**
+ * Loads the title screen again when app is already running.
+ * @throws IOException if a problem with the title.fxml
+ */
+ protected void loadTitleScreen() throws IOException {
+ FXMLLoader loader = new FXMLLoader(getClass().getResource("/visualiser/scenes/title.fxml"));
+ Parent root = loader.load();
+ stage.setResizable(false);
+ Scene scene = new Scene(root);
+ addCssStyle(scene);
+ stage.setScene(scene);
+ stage.show();
+ }
+
+ /**
+ * Used to load a new scene in the currently open stage.
+ * @param fxmlUrl the URL of the FXML file to be loaded
+ * @return the controller of the new scene
+ * @throws IOException if there is an issue with the fxmlUrl
+ */
+ protected Controller loadScene(String fxmlUrl) throws IOException {
+ // load the correct fxml file
+ FXMLLoader loader = new FXMLLoader();
+ loader.setLocation(getClass().getResource
+ ("/visualiser/scenes/"+fxmlUrl));
+ Parent root = loader.load();
+
+ // reuse previous stage and it's window size
+ Stage stage = App.getStage();
+ Double stageHeight = stage.getHeight();
+ Double stageWidth = stage.getWidth();
+
+ // set new scene into existing window
+ Scene scene = new Scene(root, stageWidth, stageHeight);
+ addCssStyle(scene);
+ stage.setScene(scene);
+ stage.setResizable(true);
+ stage.show();
+ stage.setHeight(stageHeight);
+ stage.setWidth(stageWidth);
+ stage.sizeToScene();
+
+ // return controller for the loaded fxml scene
+ return loader.getController();
+ }
/**
- * Sets the parent of the application
- *
- * @param parent controller
+ * Used to load a scene in a new separate popup stage.
+ * @param fxmlUrl the URL of the FXML file to be loaded
+ * @param title title for the new window
+ * @param modality modality settings for popup window
+ * @return the controller of the new scene
+ * @throws IOException if there is an issue with the fxmlUrl
*/
- public void setParent(MainController parent) {
- this.parent = parent;
+ protected Controller loadPopupScene(String fxmlUrl, String title, Modality
+ modality) throws IOException {
+ // load the correct fxml scene
+ FXMLLoader loader = new FXMLLoader();
+ loader.setLocation(getClass().getResource(
+ "/visualiser/scenes/" + fxmlUrl));
+ Parent root = loader.load();
+
+ // create a new 'pop-up' window
+ Stage stage = new Stage();
+ stage.initModality(modality);
+ stage.setTitle(title);
+ stage.centerOnScreen();
+ stage.getIcons().add(new Image(getClass().getClassLoader().getResourceAsStream("images/SailIcon.png")));
+ Scene scene = new Scene(root);
+ addCssStyle(scene);
+ stage.setScene(scene);
+ stage.show();
+
+ // return controller for the loaded fxml scene
+ return loader.getController();
}
/**
- * Initialisation class that is run on start up.
- *
- * @param location resources location
- * @param resources resources bundle
+ * Adds the relevant CSS styling to the scene being loaded.
+ * @param scene new scene to be loaded and displayed
*/
- @Override
- public abstract void initialize(URL location, ResourceBundle resources);
+ private void addCssStyle(Scene scene){
+ if (App.dayMode) {
+ scene.getStylesheets().add("/css/dayMode.css");
+ } else {
+ scene.getStylesheets().add("/css/nightMode.css");
+ }
+ }
+
}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java b/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java
deleted file mode 100644
index 6de6dcdf..00000000
--- a/racevisionGame/src/main/java/visualiser/Controllers/FinishController.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package visualiser.Controllers;
-
-
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.Label;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableView;
-import javafx.scene.layout.AnchorPane;
-import visualiser.model.VisualiserBoat;
-
-import java.net.URL;
-import java.util.ResourceBundle;
-
-
-/**
- * Finish Screen for when the race finishes.
- */
-public class FinishController extends Controller {
-
- @FXML
- AnchorPane finishWrapper;
-
- @FXML
- TableView boatInfoTable;
-
- @FXML
- TableColumn boatRankColumn;
-
- @FXML
- TableColumn boatNameColumn;
-
- @FXML
- Label raceWinnerLabel;
-
-
- /**
- * The boats to display on the table.
- */
- private ObservableList boats;
-
-
- /**
- * Ctor.
- */
- public FinishController() {
- }
-
-
- @Override
- public void initialize(URL location, ResourceBundle resources){
- }
-
-
-
- /**
- * Sets up the finish table
- * @param boats Boats to display
- */
- private void setFinishTable(ObservableList boats) {
- this.boats = boats;
-
- //Set contents.
- boatInfoTable.setItems(boats);
-
- //Name.
- boatNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
-
- //Rank/position.
- boatRankColumn.setCellValueFactory(cellData -> cellData.getValue().placingProperty());
-
-
- //Winner label.
- if (boats.size() > 0) {
- raceWinnerLabel.setText("Winner: " + boatNameColumn.getCellObservableValue(0).getValue());
- raceWinnerLabel.setWrapText(true);
- }
-
- }
-
-
-
- /**
- * Display the table
- * @param boats boats to display on the table.
- */
- public void enterFinish(ObservableList boats){
- finishWrapper.setVisible(true);
- setFinishTable(boats);
- }
-
-}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
index 4f07647c..e69de29b 100644
--- a/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
+++ b/racevisionGame/src/main/java/visualiser/Controllers/HostController.java
@@ -1,148 +0,0 @@
-package visualiser.Controllers;
-
-import javafx.fxml.FXML;
-import javafx.scene.control.Button;
-import javafx.scene.control.TextField;
-import javafx.scene.image.Image;
-import javafx.scene.image.ImageView;
-import javafx.scene.layout.AnchorPane;
-import javafx.scene.layout.GridPane;
-import javafx.stage.Stage;
-import javafx.scene.media.AudioClip;
-import mock.app.Event;
-import org.xml.sax.SAXException;
-import mock.exceptions.EventConstructionException;
-import shared.exceptions.InvalidBoatDataException;
-import shared.exceptions.InvalidRaceDataException;
-import shared.exceptions.InvalidRegattaDataException;
-import shared.exceptions.XMLReaderException;
-
-import javax.xml.bind.JAXBException;
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.ResourceBundle;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Controller for Hosting a game.
- */
-public class HostController extends Controller {
-
-
- @FXML
- TextField gameNameField;
-
- @FXML
- TextField hostNameField;
-
- @FXML
- AnchorPane hostWrapper;
-
- @FXML
- Button previousButton;
-
- @FXML
- Button nextButton;
-
- @FXML
- ImageView mapImage;
-
- private Event game;
-
- private ArrayList listOfMaps;
- private int currentMapIndex = 0;
-
-
- @Override
- public void initialize(URL location, ResourceBundle resources){
- Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png"));
- Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png"));
- Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png"));
- Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png"));
-
- listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap));
- mapImage.setImage(listOfMaps.get(currentMapIndex));
- }
-
- /**
- * Hosts a game
- * @throws IOException if socket cannot be connected to
- */
- public void hostGamePressed() throws IOException{
- try {
- this.game = new Event(false, currentMapIndex);
- connectSocket("localhost", 4942);
- } catch (EventConstructionException e) {
- Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
- throw new RuntimeException(e);
- }
- }
-
- public void endEvent() throws IOException {
- game.endEvent();
- }
-
- /**
- * Connect to a socket
- * @param address address of the server
- * @param port port that the server is run off
- */
- public void connectSocket(String address, int port) {
- try{
- Socket socket = new Socket(address, port);
- hostWrapper.setVisible(false);
- parent.enterLobby(socket, true);
- } catch (IOException e) { /* Never reached */ }
- }
-
- public AnchorPane startWrapper(){
- return hostWrapper;
- }
-
- /**
- * Hosts a game.
- */
- public void hostGame(){
- mapImage.fitWidthProperty().bind(((Stage) mapImage.getScene().getWindow()).widthProperty().multiply(0.6));
- hostWrapper.setVisible(true);
- }
-
- /**
- * Menu button pressed. Prompt alert then return to menu
- */
- public void menuBtnPressed(){
- AudioClip sound = new AudioClip(this.getClass().getResource("/visualiser/sounds/buttonpress.wav").toExternalForm());
- sound.play();
- hostWrapper.setVisible(false);
- parent.enterTitle();
- }
-
- public void nextImage(){
- increaseIndex();
- mapImage.setImage(listOfMaps.get(currentMapIndex));
- }
-
- public void previousImage(){
- decreaseIndex();
- mapImage.setImage(listOfMaps.get(currentMapIndex));
- }
- private void increaseIndex(){
- currentMapIndex = (currentMapIndex + 1)%listOfMaps.size();
- }
-
- private void decreaseIndex(){
- currentMapIndex = ((((currentMapIndex - 1)%listOfMaps.size())+listOfMaps.size())%listOfMaps.size());
- }
-
- public void setGameType(int gameType){
- this.currentMapIndex = gameType;
- }
-
- public int getGameType(){ return this.currentMapIndex; }
-
-}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java
new file mode 100644
index 00000000..2e267031
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/Controllers/HostGameController.java
@@ -0,0 +1,143 @@
+package visualiser.Controllers;
+
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import mock.app.Event;
+import mock.exceptions.EventConstructionException;
+import visualiser.app.App;
+import visualiser.app.MatchBrowserSingleton;
+import visualiser.network.MatchBrowserInterface;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Controller for Hosting a game.
+ */
+public class HostGameController extends Controller {
+ private @FXML ImageView mapImage;
+ private ArrayList listOfMaps;
+ private int currentMapIndex = 0;
+ private DatagramSocket udpSocket;
+ private MatchBrowserInterface matchBrowserInterface;
+
+ public void initialize() {
+ loadMaps();
+ this.udpSocket = MatchBrowserSingleton.getInstance().getUdpSocket();
+ this.matchBrowserInterface = MatchBrowserSingleton.getInstance().getMatchBrowserInterface();
+ }
+
+
+
+ /**
+ * Loads in the list of playable maps to be selected from.
+ */
+ private void loadMaps(){
+ // image preview of maps
+ Image ac35Map = new Image(getClass().getClassLoader().getResourceAsStream("images/AC35_Racecourse_MAP.png"));
+ Image oMap = new Image(getClass().getClassLoader().getResourceAsStream("images/oMapLayout.png"));
+ Image iMap = new Image(getClass().getClassLoader().getResourceAsStream("images/iMapLayout.png"));
+ Image mMap = new Image(getClass().getClassLoader().getResourceAsStream("images/mMapLayout.png"));
+
+ listOfMaps = new ArrayList(Arrays.asList(ac35Map, oMap, iMap, mMap));
+ mapImage.setImage(listOfMaps.get(currentMapIndex));
+ Platform.runLater(() -> {
+ mapImage.fitWidthProperty()
+ .bind(mapImage.getScene().getWindow().widthProperty().multiply(0.6));
+ });
+ }
+
+ /**
+ * Hosts a game
+ */
+ public void hostGamePressed() {
+ try {
+ App.game = new Event(false, currentMapIndex);
+ App.gameType = currentMapIndex;
+ connectSocket("localhost", 4942);
+ alertMatchBrowser();
+
+ } catch (EventConstructionException e) {
+ Logger.getGlobal().log(Level.SEVERE, "Could not create Event.", e);
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Sends info to the match browser so clients can see it
+ */
+ public void alertMatchBrowser(){
+ try{
+ matchBrowserInterface.startSendingHostData(App.game.getHostedGameData(), udpSocket);
+ }catch (IOException e){
+ System.err.println("failed to send out hosted game info");
+ }
+ }
+
+ /**
+ * Connect to a socket
+ * @param address address of the server
+ * @param port port that the server is run off
+ * @throws IOException socket error
+ */
+ public void connectSocket(String address, int port) throws IOException {
+ Socket socket = new Socket(address, port);
+ InGameLobbyController iglc = (InGameLobbyController)loadScene("gameLobby.fxml");
+ iglc.enterGameLobby(socket, true);
+ }
+
+ /**
+ * Menu button pressed. Prompt alert then return to menu
+ * @throws IOException socket error
+ */
+ public void menuBtnPressed() throws Exception {
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle("Quitting race");
+ alert.setContentText("Do you wish to quit the race?");
+ alert.setHeaderText("You are about to quit the race");
+ Optional result = alert.showAndWait();
+ if(result.get() == ButtonType.OK){
+ loadTitleScreen();
+ }
+ }
+
+ /**
+ * Method called when the 'next' arrow button is pressed. It is used to
+ * change the currently displayed map preview to the next in the list.
+ */
+ public void nextImage(){
+ // increase index
+ currentMapIndex = (currentMapIndex + 1) % listOfMaps.size();
+ // update map preview
+ mapImage.setImage(listOfMaps.get(currentMapIndex));
+ }
+
+ /**
+ * Method called when the 'previous' arrow button is pressed. It is used to
+ * change the currently displayed map preview to the previous in the list.
+ */
+ public void previousImage(){
+ // decrease index
+ currentMapIndex = ((((currentMapIndex - 1) % listOfMaps.size()) +
+ listOfMaps.size()) % listOfMaps.size());
+ // update map preview
+ mapImage.setImage(listOfMaps.get(currentMapIndex));
+ }
+
+ public void setCurrentMapIndex(Integer index){
+ this.currentMapIndex = index;
+ }
+
+}
diff --git a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java
new file mode 100644
index 00000000..0953abeb
--- /dev/null
+++ b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java
@@ -0,0 +1,365 @@
+package visualiser.Controllers;
+
+import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
+import javafx.animation.AnimationTimer;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Label;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.GridPane;
+import javafx.scene.shape.MeshView;
+import mock.app.Event;
+import network.Messages.Enums.RaceStatusEnum;
+import network.Messages.Enums.RequestToJoinEnum;
+import visualiser.app.App;
+import visualiser.gameController.ControllerClient;
+import visualiser.layout.SeaSurface;
+import visualiser.layout.Subject3D;
+import visualiser.layout.View3D;
+import visualiser.model.VisualiserBoat;
+import visualiser.model.VisualiserRaceEvent;
+import visualiser.model.VisualiserRaceState;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URL;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Controller for Hosting a game.
+ */
+public class InGameLobbyController extends Controller {
+ @FXML
+ private ImageView imageView;
+
+ @FXML
+ GridPane playerContainer;
+
+
+ @FXML
+ private Label playerLabel;
+
+ @FXML
+ private Label playerLabel2;
+
+ @FXML
+ private Label playerLabel3;
+
+ @FXML
+ private Label playerLabel4;
+
+ @FXML
+ private Label playerLabel5;
+
+ @FXML
+ private Label playerLabel6;
+
+ @FXML
+ private Label countdownLabel;
+
+ @FXML
+ private AnchorPane countdownTenPane;
+
+ @FXML
+ private Label countdownTenText;
+
+ private Event game;
+
+ private View3D playerBoat;
+
+ private VisualiserRaceEvent visualiserRaceEvent;
+
+ private boolean isHost;
+
+ private ControllerClient controllerClient;
+
+ private ArrayList